Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,6 @@ public enum SchemaContext {
*/
PAGED("paged"),

/**
* The schema is used as an anonymous type.
*/
ANONYMOUS("anonymous"),

/**
* The schema is used internally.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,6 @@ public enum Usage {
*/
PAGED("paged"),

/**
* Anonymous model.
* <p>
* Codegen may choose to not generate class for it, or generate class in implementation package.
*/
ANONYMOUS("anonymous"),

/**
* External model.
* <p>
Expand Down Expand Up @@ -144,8 +137,6 @@ public static Usage fromSchemaContext(SchemaContext schemaContext) {
return PUBLIC;
case PAGED:
return PAGED;
case ANONYMOUS:
return ANONYMOUS;
case INTERNAL:
return INTERNAL;
case JSON_MERGE_PATCH:
Expand Down
4 changes: 4 additions & 0 deletions typespec-extension/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Release History

## 0.20.1 (2024-08-29)

Compatible with compiler 0.59.

## 0.20.0 (2024-08-26)

Compatible with compiler 0.59.
Expand Down
186 changes: 93 additions & 93 deletions typespec-extension/package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions typespec-extension/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@azure-tools/typespec-java",
"version": "0.20.0",
"version": "0.20.1",
"description": "TypeSpec library for emitting Java client from the TypeSpec REST protocol binding",
"keywords": [
"TypeSpec"
Expand Down Expand Up @@ -71,9 +71,9 @@
"@types/js-yaml": "~4.0.9",
"@types/lodash": "~4.17.7",
"@types/mocha": "~10.0.7",
"@types/node": "~22.4.2",
"@typescript-eslint/eslint-plugin": "~8.2.0",
"@typescript-eslint/parser": "~8.2.0",
"@types/node": "~22.5.1",
"@typescript-eslint/eslint-plugin": "~8.3.0",
"@typescript-eslint/parser": "~8.3.0",
"@typespec/compiler": "0.59.1",
"@typespec/http": "0.59.1",
"@typespec/openapi": "0.59.0",
Expand Down
249 changes: 132 additions & 117 deletions typespec-extension/src/code-model-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ import {
SdkServiceMethod,
SdkType,
SdkUnionType,
UsageFlags,
createSdkContext,
getAllModels,
getClientType,
Expand Down Expand Up @@ -107,10 +106,7 @@ import {
getHeaderFieldName,
getPathParamName,
getQueryParamName,
isBody,
isBodyRoot,
isHeader,
isMultipartBodyProperty,
isPathParam,
isQueryParam,
} from "@typespec/http";
Expand Down Expand Up @@ -455,6 +451,7 @@ export class CodeModelBuilder {
schema instanceof ConstantSchema
) {
const schemaUsage: SchemaContext[] | undefined = schema.usage;

// Public override Internal
if (schemaUsage?.includes(SchemaContext.Public)) {
const index = schemaUsage.indexOf(SchemaContext.Internal);
Expand All @@ -463,11 +460,17 @@ export class CodeModelBuilder {
}
}

// Internal on Anonymous
if (schemaUsage?.includes(SchemaContext.Anonymous)) {
const index = schemaUsage.indexOf(SchemaContext.Internal);
if (index < 0) {
schemaUsage.push(SchemaContext.Internal);
// Internal on PublicSpread, but Public takes precedence
if (schemaUsage?.includes(SchemaContext.PublicSpread)) {
// remove PublicSpread as it now served its purpose
schemaUsage.splice(schemaUsage.indexOf(SchemaContext.PublicSpread), 1);

// Public would override PublicSpread, hence do nothing if this schema is Public
if (!schemaUsage?.includes(SchemaContext.Public)) {
// set the model as Internal, so that it is not exposed to user
if (!schemaUsage.includes(SchemaContext.Internal)) {
schemaUsage.push(SchemaContext.Internal);
}
}
}
}
Expand Down Expand Up @@ -1302,119 +1305,138 @@ export class CodeModelBuilder {
});
op.addParameter(parameter);

const jsonMergePatch = operationIsJsonMergePatch(sdkHttpOperation);

const schemaIsPublicBeforeProcess =
schema instanceof ObjectSchema && (schema as SchemaUsage).usage?.includes(SchemaContext.Public);

this.trackSchemaUsage(schema, { usage: [SchemaContext.Input] });

if (op.convenienceApi) {
// model/schema does not need to be Public or Internal, if it is not to be used in convenience API
this.trackSchemaUsage(schema, { usage: [op.internalApi ? SchemaContext.Internal : SchemaContext.Public] });
}

if (operationIsJsonMergePatch(sdkHttpOperation)) {
if (jsonMergePatch) {
this.trackSchemaUsage(schema, { usage: [SchemaContext.JsonMergePatch] });
}
if (op.convenienceApi && operationIsMultipart(sdkHttpOperation)) {
this.trackSchemaUsage(schema, { serializationFormats: [KnownMediaType.Multipart] });
}

// Implicit body parameter would have usage flag: UsageFlags.Spread, for this case we need to do body parameter flatten
const bodyParameterFlatten = sdkType.kind === "model" && sdkType.usage & UsageFlags.Spread && !this.isArm();

if (schema instanceof ObjectSchema && bodyParameterFlatten) {
// flatten body parameter
const parameters = sdkHttpOperation.parameters;
const bodyParameter = sdkHttpOperation.bodyParam;
// name the schema for documentation
schema.language.default.name = pascalCase(op.language.default.name) + "Request";

if (!parameter.language.default.name) {
// name the parameter for documentation
parameter.language.default.name = "request";
}
if (op.convenienceApi) {
// Explicit body parameter @body or @bodyRoot would result to the existance of rawHttpOperation.parameters.body.property
// Implicit body parameter would result to rawHttpOperation.parameters.body.property be undefined
// see https://typespec.io/docs/libraries/http/cheat-sheet#data-types
const bodyParameterFlatten =
schema instanceof ObjectSchema &&
sdkType.kind === "model" &&
!rawHttpOperation.parameters.body?.property &&
!this.isArm();

if (schema instanceof ObjectSchema && bodyParameterFlatten) {
// flatten body parameter
const parameters = sdkHttpOperation.parameters;
const bodyParameter = sdkHttpOperation.bodyParam;

if (!parameter.language.default.name) {
// name the parameter for documentation
parameter.language.default.name = "request";
}

if (operationIsJsonMergePatch(sdkHttpOperation)) {
// skip model flatten, if "application/merge-patch+json"
schema.language.default.name = pascalCase(op.language.default.name) + "PatchRequest";
return;
}
Comment on lines -1334 to -1338
Copy link
Member Author

@weidongxu-microsoft weidongxu-microsoft Aug 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and

-      schema.language.default.name = pascalCase(op.language.default.name) + "Request";

These change does not relate to the issue.

But we should use name from TCGC in almost all cases.

This is the cause of the diff of generated code in this PR.

if (jsonMergePatch) {
// skip model flatten, if "application/merge-patch+json"
if (sdkType.isGeneratedName) {
schema.language.default.name = pascalCase(op.language.default.name) + "PatchRequest";
}
return;
}

this.trackSchemaUsage(schema, { usage: [SchemaContext.Anonymous] });
const schemaUsage = (schema as SchemaUsage).usage;
if (!schemaIsPublicBeforeProcess && schemaUsage?.includes(SchemaContext.Public)) {
// Public added in this op, change it to PublicSpread
// This means that if this op would originally add Public to this schema, it adds PublicSpread instead
schemaUsage?.splice(schemaUsage?.indexOf(SchemaContext.Public), 1);
this.trackSchemaUsage(schema, { usage: [SchemaContext.PublicSpread] });
}

if (op.convenienceApi && op.parameters) {
op.convenienceApi.requests = [];
const request = new Request({
protocol: op.requests![0].protocol,
});
request.parameters = [];
op.convenienceApi.requests.push(request);
if (op.convenienceApi && op.parameters) {
op.convenienceApi.requests = [];
const request = new Request({
protocol: op.requests![0].protocol,
});
request.parameters = [];
op.convenienceApi.requests.push(request);

// header/query/path params
for (const opParameter of parameters) {
this.addParameterOrBodyPropertyToCodeModelRequest(opParameter, op, request, schema, parameter);
}
// body param
if (bodyParameter) {
if (bodyParameter.type.kind === "model") {
for (const bodyProperty of bodyParameter.type.properties) {
if (bodyProperty.kind === "property") {
this.addParameterOrBodyPropertyToCodeModelRequest(bodyProperty, op, request, schema, parameter);
// header/query/path params
for (const opParameter of parameters) {
this.addParameterOrBodyPropertyToCodeModelRequest(opParameter, op, request, schema, parameter);
}
// body param
if (bodyParameter) {
if (bodyParameter.type.kind === "model") {
for (const bodyProperty of bodyParameter.type.properties) {
if (bodyProperty.kind === "property") {
this.addParameterOrBodyPropertyToCodeModelRequest(bodyProperty, op, request, schema, parameter);
}
}
}
}
}
request.signatureParameters = request.parameters;

if (request.signatureParameters.length > 6) {
// create an option bag
const name = op.language.default.name + "Options";
const namespace = getNamespace(rawHttpOperation.operation);
// option bag schema
const optionBagSchema = this.codeModel.schemas.add(
new GroupSchema(name, `Options for ${op.language.default.name} API`, {
language: {
default: {
namespace: namespace,
},
java: {
namespace: this.getJavaNamespace(namespace),
request.signatureParameters = request.parameters;

if (request.signatureParameters.length > 6) {
// create an option bag
const name = op.language.default.name + "Options";
const namespace = getNamespace(rawHttpOperation.operation);
// option bag schema
const optionBagSchema = this.codeModel.schemas.add(
new GroupSchema(name, `Options for ${op.language.default.name} API`, {
language: {
default: {
namespace: namespace,
},
java: {
namespace: this.getJavaNamespace(namespace),
},
},
},
}),
);
request.parameters.forEach((it) => {
optionBagSchema.add(
new GroupProperty(it.language.default.name, it.language.default.description, it.schema, {
originalParameter: [it],
summary: it.summary,
required: it.required,
nullable: it.nullable,
readOnly: false,
serializedName: it.language.default.serializedName,
}),
);
});

this.trackSchemaUsage(optionBagSchema, { usage: [SchemaContext.Input] });
if (op.convenienceApi) {
this.trackSchemaUsage(optionBagSchema, {
usage: [op.internalApi ? SchemaContext.Internal : SchemaContext.Public],
request.parameters.forEach((it) => {
optionBagSchema.add(
new GroupProperty(it.language.default.name, it.language.default.description, it.schema, {
originalParameter: [it],
summary: it.summary,
required: it.required,
nullable: it.nullable,
readOnly: false,
serializedName: it.language.default.serializedName,
}),
);
});
}

// option bag parameter
const optionBagParameter = new Parameter(
"options",
optionBagSchema.language.default.description,
optionBagSchema,
{
implementation: ImplementationLocation.Method,
required: true,
nullable: false,
},
);
this.trackSchemaUsage(optionBagSchema, { usage: [SchemaContext.Input] });
if (op.convenienceApi) {
this.trackSchemaUsage(optionBagSchema, {
usage: [op.internalApi ? SchemaContext.Internal : SchemaContext.Public],
});
}

// option bag parameter
const optionBagParameter = new Parameter(
"options",
optionBagSchema.language.default.description,
optionBagSchema,
{
implementation: ImplementationLocation.Method,
required: true,
nullable: false,
},
);

request.signatureParameters = [optionBagParameter];
request.parameters.forEach((it) => (it.groupedBy = optionBagParameter));
request.parameters.push(optionBagParameter);
request.signatureParameters = [optionBagParameter];
request.parameters.forEach((it) => (it.groupedBy = optionBagParameter));
request.parameters.push(optionBagParameter);
}
}
}
}
Expand Down Expand Up @@ -2229,24 +2251,6 @@ export class CodeModelBuilder {
}
}

private getParameterLocation(target: ModelProperty): ParameterLocation | "BodyProperty" {
if (isHeader(this.program, target)) {
return ParameterLocation.Header;
} else if (isQueryParam(this.program, target)) {
return ParameterLocation.Query;
} else if (isPathParam(this.program, target)) {
return ParameterLocation.Path;
} else if (
isBody(this.program, target) ||
isBodyRoot(this.program, target) ||
isMultipartBodyProperty(this.program, target)
) {
return ParameterLocation.Body;
} else {
return "BodyProperty";
}
}

private isReadOnly(target: SdkModelPropertyType): boolean {
const segment = target.__raw ? getSegment(this.program, target.__raw) !== undefined : false;
if (segment) {
Expand Down Expand Up @@ -2516,10 +2520,21 @@ export class CodeModelBuilder {
};

// Exclude context that not to be propagated
const updatedSchemaUsage = (schema as SchemaUsage).usage?.filter(
(it) => it !== SchemaContext.Paged && it !== SchemaContext.PublicSpread,
);
const indexSpread = (schema as SchemaUsage).usage?.indexOf(SchemaContext.PublicSpread);
if (
updatedSchemaUsage &&
indexSpread &&
indexSpread >= 0 &&
!(schema as SchemaUsage).usage?.includes(SchemaContext.Public)
) {
// Propagate Public, if schema is PublicSpread
updatedSchemaUsage.push(SchemaContext.Public);
}
const schemaUsage = {
usage: (schema as SchemaUsage).usage?.filter(
(it) => it !== SchemaContext.Paged && it !== SchemaContext.Anonymous,
),
usage: updatedSchemaUsage,
serializationFormats: (schema as SchemaUsage).serializationFormats?.filter(
(it) => it !== KnownMediaType.Multipart,
),
Expand Down
Loading