Skip to content

Commit 8fca913

Browse files
tsp, handle spread behavior change from TCGC (#2934)
* use PublicSpread * bump to 0.20.1 --------- Co-authored-by: actions-user <[email protected]>
1 parent adb046e commit 8fca913

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+354
-349
lines changed

extension-base/src/main/java/com/azure/autorest/extension/base/model/codemodel/SchemaContext.java

-5
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,6 @@ public enum SchemaContext {
3535
*/
3636
PAGED("paged"),
3737

38-
/**
39-
* The schema is used as an anonymous type.
40-
*/
41-
ANONYMOUS("anonymous"),
42-
4338
/**
4439
* The schema is used internally.
4540
*/

javagen/src/main/java/com/azure/autorest/model/clientmodel/ImplementationDetails.java

-9
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,6 @@ public enum Usage {
4949
*/
5050
PAGED("paged"),
5151

52-
/**
53-
* Anonymous model.
54-
* <p>
55-
* Codegen may choose to not generate class for it, or generate class in implementation package.
56-
*/
57-
ANONYMOUS("anonymous"),
58-
5952
/**
6053
* External model.
6154
* <p>
@@ -144,8 +137,6 @@ public static Usage fromSchemaContext(SchemaContext schemaContext) {
144137
return PUBLIC;
145138
case PAGED:
146139
return PAGED;
147-
case ANONYMOUS:
148-
return ANONYMOUS;
149140
case INTERNAL:
150141
return INTERNAL;
151142
case JSON_MERGE_PATCH:

typespec-extension/changelog.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Release History
22

3+
## 0.20.1 (2024-08-29)
4+
5+
Compatible with compiler 0.59.
6+
37
## 0.20.0 (2024-08-26)
48

59
Compatible with compiler 0.59.

typespec-extension/package-lock.json

+93-93
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

typespec-extension/package.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@azure-tools/typespec-java",
3-
"version": "0.20.0",
3+
"version": "0.20.1",
44
"description": "TypeSpec library for emitting Java client from the TypeSpec REST protocol binding",
55
"keywords": [
66
"TypeSpec"
@@ -71,9 +71,9 @@
7171
"@types/js-yaml": "~4.0.9",
7272
"@types/lodash": "~4.17.7",
7373
"@types/mocha": "~10.0.7",
74-
"@types/node": "~22.4.2",
75-
"@typescript-eslint/eslint-plugin": "~8.2.0",
76-
"@typescript-eslint/parser": "~8.2.0",
74+
"@types/node": "~22.5.1",
75+
"@typescript-eslint/eslint-plugin": "~8.3.0",
76+
"@typescript-eslint/parser": "~8.3.0",
7777
"@typespec/compiler": "0.59.1",
7878
"@typespec/http": "0.59.1",
7979
"@typespec/openapi": "0.59.0",

typespec-extension/src/code-model-builder.ts

+132-117
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ import {
6767
SdkServiceMethod,
6868
SdkType,
6969
SdkUnionType,
70-
UsageFlags,
7170
createSdkContext,
7271
getAllModels,
7372
getClientType,
@@ -107,10 +106,7 @@ import {
107106
getHeaderFieldName,
108107
getPathParamName,
109108
getQueryParamName,
110-
isBody,
111-
isBodyRoot,
112109
isHeader,
113-
isMultipartBodyProperty,
114110
isPathParam,
115111
isQueryParam,
116112
} from "@typespec/http";
@@ -455,6 +451,7 @@ export class CodeModelBuilder {
455451
schema instanceof ConstantSchema
456452
) {
457453
const schemaUsage: SchemaContext[] | undefined = schema.usage;
454+
458455
// Public override Internal
459456
if (schemaUsage?.includes(SchemaContext.Public)) {
460457
const index = schemaUsage.indexOf(SchemaContext.Internal);
@@ -463,11 +460,17 @@ export class CodeModelBuilder {
463460
}
464461
}
465462

466-
// Internal on Anonymous
467-
if (schemaUsage?.includes(SchemaContext.Anonymous)) {
468-
const index = schemaUsage.indexOf(SchemaContext.Internal);
469-
if (index < 0) {
470-
schemaUsage.push(SchemaContext.Internal);
463+
// Internal on PublicSpread, but Public takes precedence
464+
if (schemaUsage?.includes(SchemaContext.PublicSpread)) {
465+
// remove PublicSpread as it now served its purpose
466+
schemaUsage.splice(schemaUsage.indexOf(SchemaContext.PublicSpread), 1);
467+
468+
// Public would override PublicSpread, hence do nothing if this schema is Public
469+
if (!schemaUsage?.includes(SchemaContext.Public)) {
470+
// set the model as Internal, so that it is not exposed to user
471+
if (!schemaUsage.includes(SchemaContext.Internal)) {
472+
schemaUsage.push(SchemaContext.Internal);
473+
}
471474
}
472475
}
473476
}
@@ -1302,119 +1305,138 @@ export class CodeModelBuilder {
13021305
});
13031306
op.addParameter(parameter);
13041307

1308+
const jsonMergePatch = operationIsJsonMergePatch(sdkHttpOperation);
1309+
1310+
const schemaIsPublicBeforeProcess =
1311+
schema instanceof ObjectSchema && (schema as SchemaUsage).usage?.includes(SchemaContext.Public);
1312+
13051313
this.trackSchemaUsage(schema, { usage: [SchemaContext.Input] });
13061314

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

1312-
if (operationIsJsonMergePatch(sdkHttpOperation)) {
1320+
if (jsonMergePatch) {
13131321
this.trackSchemaUsage(schema, { usage: [SchemaContext.JsonMergePatch] });
13141322
}
13151323
if (op.convenienceApi && operationIsMultipart(sdkHttpOperation)) {
13161324
this.trackSchemaUsage(schema, { serializationFormats: [KnownMediaType.Multipart] });
13171325
}
13181326

1319-
// Implicit body parameter would have usage flag: UsageFlags.Spread, for this case we need to do body parameter flatten
1320-
const bodyParameterFlatten = sdkType.kind === "model" && sdkType.usage & UsageFlags.Spread && !this.isArm();
1321-
1322-
if (schema instanceof ObjectSchema && bodyParameterFlatten) {
1323-
// flatten body parameter
1324-
const parameters = sdkHttpOperation.parameters;
1325-
const bodyParameter = sdkHttpOperation.bodyParam;
1326-
// name the schema for documentation
1327-
schema.language.default.name = pascalCase(op.language.default.name) + "Request";
1328-
1329-
if (!parameter.language.default.name) {
1330-
// name the parameter for documentation
1331-
parameter.language.default.name = "request";
1332-
}
1327+
if (op.convenienceApi) {
1328+
// Explicit body parameter @body or @bodyRoot would result to the existance of rawHttpOperation.parameters.body.property
1329+
// Implicit body parameter would result to rawHttpOperation.parameters.body.property be undefined
1330+
// see https://typespec.io/docs/libraries/http/cheat-sheet#data-types
1331+
const bodyParameterFlatten =
1332+
schema instanceof ObjectSchema &&
1333+
sdkType.kind === "model" &&
1334+
!rawHttpOperation.parameters.body?.property &&
1335+
!this.isArm();
1336+
1337+
if (schema instanceof ObjectSchema && bodyParameterFlatten) {
1338+
// flatten body parameter
1339+
const parameters = sdkHttpOperation.parameters;
1340+
const bodyParameter = sdkHttpOperation.bodyParam;
1341+
1342+
if (!parameter.language.default.name) {
1343+
// name the parameter for documentation
1344+
parameter.language.default.name = "request";
1345+
}
13331346

1334-
if (operationIsJsonMergePatch(sdkHttpOperation)) {
1335-
// skip model flatten, if "application/merge-patch+json"
1336-
schema.language.default.name = pascalCase(op.language.default.name) + "PatchRequest";
1337-
return;
1338-
}
1347+
if (jsonMergePatch) {
1348+
// skip model flatten, if "application/merge-patch+json"
1349+
if (sdkType.isGeneratedName) {
1350+
schema.language.default.name = pascalCase(op.language.default.name) + "PatchRequest";
1351+
}
1352+
return;
1353+
}
13391354

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

1342-
if (op.convenienceApi && op.parameters) {
1343-
op.convenienceApi.requests = [];
1344-
const request = new Request({
1345-
protocol: op.requests![0].protocol,
1346-
});
1347-
request.parameters = [];
1348-
op.convenienceApi.requests.push(request);
1363+
if (op.convenienceApi && op.parameters) {
1364+
op.convenienceApi.requests = [];
1365+
const request = new Request({
1366+
protocol: op.requests![0].protocol,
1367+
});
1368+
request.parameters = [];
1369+
op.convenienceApi.requests.push(request);
13491370

1350-
// header/query/path params
1351-
for (const opParameter of parameters) {
1352-
this.addParameterOrBodyPropertyToCodeModelRequest(opParameter, op, request, schema, parameter);
1353-
}
1354-
// body param
1355-
if (bodyParameter) {
1356-
if (bodyParameter.type.kind === "model") {
1357-
for (const bodyProperty of bodyParameter.type.properties) {
1358-
if (bodyProperty.kind === "property") {
1359-
this.addParameterOrBodyPropertyToCodeModelRequest(bodyProperty, op, request, schema, parameter);
1371+
// header/query/path params
1372+
for (const opParameter of parameters) {
1373+
this.addParameterOrBodyPropertyToCodeModelRequest(opParameter, op, request, schema, parameter);
1374+
}
1375+
// body param
1376+
if (bodyParameter) {
1377+
if (bodyParameter.type.kind === "model") {
1378+
for (const bodyProperty of bodyParameter.type.properties) {
1379+
if (bodyProperty.kind === "property") {
1380+
this.addParameterOrBodyPropertyToCodeModelRequest(bodyProperty, op, request, schema, parameter);
1381+
}
13601382
}
13611383
}
13621384
}
1363-
}
1364-
request.signatureParameters = request.parameters;
1365-
1366-
if (request.signatureParameters.length > 6) {
1367-
// create an option bag
1368-
const name = op.language.default.name + "Options";
1369-
const namespace = getNamespace(rawHttpOperation.operation);
1370-
// option bag schema
1371-
const optionBagSchema = this.codeModel.schemas.add(
1372-
new GroupSchema(name, `Options for ${op.language.default.name} API`, {
1373-
language: {
1374-
default: {
1375-
namespace: namespace,
1376-
},
1377-
java: {
1378-
namespace: this.getJavaNamespace(namespace),
1385+
request.signatureParameters = request.parameters;
1386+
1387+
if (request.signatureParameters.length > 6) {
1388+
// create an option bag
1389+
const name = op.language.default.name + "Options";
1390+
const namespace = getNamespace(rawHttpOperation.operation);
1391+
// option bag schema
1392+
const optionBagSchema = this.codeModel.schemas.add(
1393+
new GroupSchema(name, `Options for ${op.language.default.name} API`, {
1394+
language: {
1395+
default: {
1396+
namespace: namespace,
1397+
},
1398+
java: {
1399+
namespace: this.getJavaNamespace(namespace),
1400+
},
13791401
},
1380-
},
1381-
}),
1382-
);
1383-
request.parameters.forEach((it) => {
1384-
optionBagSchema.add(
1385-
new GroupProperty(it.language.default.name, it.language.default.description, it.schema, {
1386-
originalParameter: [it],
1387-
summary: it.summary,
1388-
required: it.required,
1389-
nullable: it.nullable,
1390-
readOnly: false,
1391-
serializedName: it.language.default.serializedName,
13921402
}),
13931403
);
1394-
});
1395-
1396-
this.trackSchemaUsage(optionBagSchema, { usage: [SchemaContext.Input] });
1397-
if (op.convenienceApi) {
1398-
this.trackSchemaUsage(optionBagSchema, {
1399-
usage: [op.internalApi ? SchemaContext.Internal : SchemaContext.Public],
1404+
request.parameters.forEach((it) => {
1405+
optionBagSchema.add(
1406+
new GroupProperty(it.language.default.name, it.language.default.description, it.schema, {
1407+
originalParameter: [it],
1408+
summary: it.summary,
1409+
required: it.required,
1410+
nullable: it.nullable,
1411+
readOnly: false,
1412+
serializedName: it.language.default.serializedName,
1413+
}),
1414+
);
14001415
});
1401-
}
14021416

1403-
// option bag parameter
1404-
const optionBagParameter = new Parameter(
1405-
"options",
1406-
optionBagSchema.language.default.description,
1407-
optionBagSchema,
1408-
{
1409-
implementation: ImplementationLocation.Method,
1410-
required: true,
1411-
nullable: false,
1412-
},
1413-
);
1417+
this.trackSchemaUsage(optionBagSchema, { usage: [SchemaContext.Input] });
1418+
if (op.convenienceApi) {
1419+
this.trackSchemaUsage(optionBagSchema, {
1420+
usage: [op.internalApi ? SchemaContext.Internal : SchemaContext.Public],
1421+
});
1422+
}
1423+
1424+
// option bag parameter
1425+
const optionBagParameter = new Parameter(
1426+
"options",
1427+
optionBagSchema.language.default.description,
1428+
optionBagSchema,
1429+
{
1430+
implementation: ImplementationLocation.Method,
1431+
required: true,
1432+
nullable: false,
1433+
},
1434+
);
14141435

1415-
request.signatureParameters = [optionBagParameter];
1416-
request.parameters.forEach((it) => (it.groupedBy = optionBagParameter));
1417-
request.parameters.push(optionBagParameter);
1436+
request.signatureParameters = [optionBagParameter];
1437+
request.parameters.forEach((it) => (it.groupedBy = optionBagParameter));
1438+
request.parameters.push(optionBagParameter);
1439+
}
14181440
}
14191441
}
14201442
}
@@ -2229,24 +2251,6 @@ export class CodeModelBuilder {
22292251
}
22302252
}
22312253

2232-
private getParameterLocation(target: ModelProperty): ParameterLocation | "BodyProperty" {
2233-
if (isHeader(this.program, target)) {
2234-
return ParameterLocation.Header;
2235-
} else if (isQueryParam(this.program, target)) {
2236-
return ParameterLocation.Query;
2237-
} else if (isPathParam(this.program, target)) {
2238-
return ParameterLocation.Path;
2239-
} else if (
2240-
isBody(this.program, target) ||
2241-
isBodyRoot(this.program, target) ||
2242-
isMultipartBodyProperty(this.program, target)
2243-
) {
2244-
return ParameterLocation.Body;
2245-
} else {
2246-
return "BodyProperty";
2247-
}
2248-
}
2249-
22502254
private isReadOnly(target: SdkModelPropertyType): boolean {
22512255
const segment = target.__raw ? getSegment(this.program, target.__raw) !== undefined : false;
22522256
if (segment) {
@@ -2516,10 +2520,21 @@ export class CodeModelBuilder {
25162520
};
25172521

25182522
// Exclude context that not to be propagated
2523+
const updatedSchemaUsage = (schema as SchemaUsage).usage?.filter(
2524+
(it) => it !== SchemaContext.Paged && it !== SchemaContext.PublicSpread,
2525+
);
2526+
const indexSpread = (schema as SchemaUsage).usage?.indexOf(SchemaContext.PublicSpread);
2527+
if (
2528+
updatedSchemaUsage &&
2529+
indexSpread &&
2530+
indexSpread >= 0 &&
2531+
!(schema as SchemaUsage).usage?.includes(SchemaContext.Public)
2532+
) {
2533+
// Propagate Public, if schema is PublicSpread
2534+
updatedSchemaUsage.push(SchemaContext.Public);
2535+
}
25192536
const schemaUsage = {
2520-
usage: (schema as SchemaUsage).usage?.filter(
2521-
(it) => it !== SchemaContext.Paged && it !== SchemaContext.Anonymous,
2522-
),
2537+
usage: updatedSchemaUsage,
25232538
serializationFormats: (schema as SchemaUsage).serializationFormats?.filter(
25242539
(it) => it !== KnownMediaType.Multipart,
25252540
),

0 commit comments

Comments
 (0)