From 88aa774eed027148b1aaee24ef855beb8c32128d Mon Sep 17 00:00:00 2001 From: Weidong Xu Date: Wed, 13 Dec 2023 18:18:09 +0800 Subject: [PATCH] tsp, fix projectedName json on spread/flatten (#2462) * fix projectedName on spread/flatten * handle projectedName(json) for discriminator property --- .../ci-typespec-java-dev-nightly.yaml | 1 + eng/pipelines/ci-typespec-java.yaml | 1 + typespec-extension/changelog.md | 6 +- typespec-extension/src/code-model-builder.ts | 50 ++++++++--- .../com/cadl/flatten/FlattenAsyncClient.java | 51 +++++++++++ .../java/com/cadl/flatten/FlattenClient.java | 49 +++++++++++ .../implementation/FlattenClientImpl.java | 74 ++++++++++++++++ .../com/cadl/naming/NamingAsyncClient.java | 4 +- .../naming/implementation/NamingOpsImpl.java | 8 +- .../com/cadl/naming/models/BinaryData.java | 15 ++-- .../com/cadl/naming/models/BytesData.java | 87 +++++++++++++++++++ .../java/com/cadl/naming/models/Data.java | 80 +++++++++++++++++ typespec-tests/tsp/flatten.tsp | 7 ++ typespec-tests/tsp/naming.tsp | 12 +++ 14 files changed, 423 insertions(+), 22 deletions(-) create mode 100644 typespec-tests/src/main/java/com/cadl/naming/models/BytesData.java create mode 100644 typespec-tests/src/main/java/com/cadl/naming/models/Data.java diff --git a/eng/pipelines/ci-typespec-java-dev-nightly.yaml b/eng/pipelines/ci-typespec-java-dev-nightly.yaml index 90e82fc65a..cce750b314 100644 --- a/eng/pipelines/ci-typespec-java-dev-nightly.yaml +++ b/eng/pipelines/ci-typespec-java-dev-nightly.yaml @@ -57,6 +57,7 @@ jobs: workingDirectory: ./typespec-extension - task: PowerShell@2 + retryCountOnTaskFailure: 1 displayName: 'Generate Code' inputs: pwsh: true diff --git a/eng/pipelines/ci-typespec-java.yaml b/eng/pipelines/ci-typespec-java.yaml index 3d7e07f5e1..ce5d4e9130 100644 --- a/eng/pipelines/ci-typespec-java.yaml +++ b/eng/pipelines/ci-typespec-java.yaml @@ -58,6 +58,7 @@ jobs: publishJUnitResults: false - task: PowerShell@2 + retryCountOnTaskFailure: 1 displayName: 'Generate Code' inputs: pwsh: true diff --git a/typespec-extension/changelog.md b/typespec-extension/changelog.md index f839d1fe57..0b9bbc0c20 100644 --- a/typespec-extension/changelog.md +++ b/typespec-extension/changelog.md @@ -1,11 +1,15 @@ # Release History -## 0.12.0 (Unreleased) +## 0.12.1 (Unreleased) Compatible with compiler 0.51. - Supported convenience API for "multipart/form-data". +## 0.12.0 (2023-12-13) + +Compatible with compiler 0.51. + ## 0.11.3 (2023-12-06) Compatible with compiler 0.50. diff --git a/typespec-extension/src/code-model-builder.ts b/typespec-extension/src/code-model-builder.ts index 5d02ece07a..25c4c7f023 100644 --- a/typespec-extension/src/code-model-builder.ts +++ b/typespec-extension/src/code-model-builder.ts @@ -361,7 +361,7 @@ export class CodeModelBuilder { const keyScheme = new KeySecurityScheme({ name: "authorization", }); - (keyScheme as any).prefix = schemeOrApiKeyPrefix; // TODO (weidxu): modify KeySecurityScheme, after design stable + (keyScheme as any).prefix = schemeOrApiKeyPrefix; // TODO: modify KeySecurityScheme, after design stable securitySchemes.push(keyScheme); } break; @@ -1279,8 +1279,9 @@ export class CodeModelBuilder { request.parameters = []; op.convenienceApi.requests.push(request); - for (const [key, _] of parameters.properties) { - const existParameter = op.parameters.find((it) => it.language.default.serializedName === key); + for (const [_, opParameter] of parameters.properties) { + const serializedName = this.getSerializedName(opParameter); + const existParameter = op.parameters.find((it) => it.language.default.serializedName === serializedName); if (existParameter) { // parameter if ( @@ -1291,7 +1292,7 @@ export class CodeModelBuilder { } } else { // property from anonymous model - const existBodyProperty = schema.properties?.find((it) => it.serializedName === key); + const existBodyProperty = schema.properties?.find((it) => it.serializedName === serializedName); if (existBodyProperty) { request.parameters.push( new VirtualParameter( @@ -1990,15 +1991,42 @@ export class CodeModelBuilder { // discriminator let discriminatorPropertyName: string | undefined = undefined; + type discriminatorTypeWithPropertyName = Partial & { propertyName: string }; const discriminator = getDiscriminator(this.program, type); if (discriminator) { discriminatorPropertyName = discriminator.propertyName; - objectSchema.discriminator = new Discriminator( - new Property(discriminatorPropertyName, discriminatorPropertyName, this.stringSchema, { - required: true, - serializedName: discriminatorPropertyName, - }), + // find the discriminator property from model + // the property is required for getting its serializedName + let discriminatorProperty = Array.from(type.properties.values()).find( + (it) => it.name === discriminatorPropertyName, ); + if (!discriminatorProperty) { + // try find the discriminator property from any of its derived models + for (const deriveModel of type.derivedModels) { + discriminatorProperty = Array.from(deriveModel.properties.values()).find( + (it) => it.name === discriminatorPropertyName, + ); + if (discriminatorProperty) { + // found + break; + } + } + } + if (discriminatorProperty) { + objectSchema.discriminator = new Discriminator(this.processModelProperty(discriminatorProperty)); + // as we do not expose the discriminator property, its schema is fine to be just a string (and we do not want to generate an enum that not used anywhere) + // TODO: support enum schema, if we expose the discriminator property + objectSchema.discriminator.property.schema = this.stringSchema; + } else { + // fallback to property name, if cannot find the discriminator property + objectSchema.discriminator = new Discriminator( + new Property(discriminatorPropertyName, discriminatorPropertyName, this.stringSchema, { + required: true, + serializedName: discriminatorPropertyName, + }), + ); + } + (objectSchema.discriminator as discriminatorTypeWithPropertyName).propertyName = discriminatorPropertyName; } // parent @@ -2042,7 +2070,9 @@ export class CodeModelBuilder { (it) => it instanceof ObjectSchema && it.discriminator, ); if (parentWithDiscriminator) { - discriminatorPropertyName = (parentWithDiscriminator as ObjectSchema).discriminator!.property.serializedName; + discriminatorPropertyName = ( + (parentWithDiscriminator as ObjectSchema).discriminator as discriminatorTypeWithPropertyName + ).propertyName; const discriminatorProperty = Array.from(type.properties.values()).find( (it) => it.name === discriminatorPropertyName && (it.type.kind === "String" || it.type.kind === "EnumMember"), diff --git a/typespec-tests/src/main/java/com/cadl/flatten/FlattenAsyncClient.java b/typespec-tests/src/main/java/com/cadl/flatten/FlattenAsyncClient.java index 09a3850358..8d24cc03d4 100644 --- a/typespec-tests/src/main/java/com/cadl/flatten/FlattenAsyncClient.java +++ b/typespec-tests/src/main/java/com/cadl/flatten/FlattenAsyncClient.java @@ -70,6 +70,33 @@ public Mono> sendWithResponse(String id, BinaryData request, Requ return this.serviceClient.sendWithResponseAsync(id, request, requestOptions); } + /** + * The sendProjectedName operation. + *

+ * Request Body Schema + *

+ *
{@code
+     * {
+     *     file_id: String (Required)
+     * }
+     * }
+ * + * @param id A sequence of textual characters. + * @param request The request parameter. + * @param requestOptions The options to configure the HTTP request before HTTP client sends it. + * @throws HttpResponseException thrown if the request is rejected by server. + * @throws ClientAuthenticationException thrown if the request is rejected by server on status code 401. + * @throws ResourceNotFoundException thrown if the request is rejected by server on status code 404. + * @throws ResourceModifiedException thrown if the request is rejected by server on status code 409. + * @return the {@link Response} on successful completion of {@link Mono}. + */ + @Generated + @ServiceMethod(returns = ReturnType.SINGLE) + public Mono> sendProjectedNameWithResponse(String id, BinaryData request, + RequestOptions requestOptions) { + return this.serviceClient.sendProjectedNameWithResponseAsync(id, request, requestOptions); + } + /** * The sendLong operation. *

@@ -172,6 +199,30 @@ public Mono send(String id, String input) { return sendWithResponse(id, request, requestOptions).flatMap(FluxUtil::toMono); } + /** + * The sendProjectedName operation. + * + * @param id A sequence of textual characters. + * @param fileIdentifier A sequence of textual characters. + * @throws IllegalArgumentException thrown if parameters fail the validation. + * @throws HttpResponseException thrown if the request is rejected by server. + * @throws ClientAuthenticationException thrown if the request is rejected by server on status code 401. + * @throws ResourceNotFoundException thrown if the request is rejected by server on status code 404. + * @throws ResourceModifiedException thrown if the request is rejected by server on status code 409. + * @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent. + * @return A {@link Mono} that completes when a successful response is received. + */ + @Generated + @ServiceMethod(returns = ReturnType.SINGLE) + public Mono sendProjectedName(String id, String fileIdentifier) { + // Generated convenience method for sendProjectedNameWithResponse + RequestOptions requestOptions = new RequestOptions(); + Map requestObj = new HashMap<>(); + requestObj.put("file_id", fileIdentifier); + BinaryData request = BinaryData.fromObject(requestObj); + return sendProjectedNameWithResponse(id, request, requestOptions).flatMap(FluxUtil::toMono); + } + /** * The sendLong operation. * diff --git a/typespec-tests/src/main/java/com/cadl/flatten/FlattenClient.java b/typespec-tests/src/main/java/com/cadl/flatten/FlattenClient.java index 187bd6d0a2..c6a2a0523b 100644 --- a/typespec-tests/src/main/java/com/cadl/flatten/FlattenClient.java +++ b/typespec-tests/src/main/java/com/cadl/flatten/FlattenClient.java @@ -68,6 +68,32 @@ public Response sendWithResponse(String id, BinaryData request, RequestOpt return this.serviceClient.sendWithResponse(id, request, requestOptions); } + /** + * The sendProjectedName operation. + *

+ * Request Body Schema + *

+ *
{@code
+     * {
+     *     file_id: String (Required)
+     * }
+     * }
+ * + * @param id A sequence of textual characters. + * @param request The request parameter. + * @param requestOptions The options to configure the HTTP request before HTTP client sends it. + * @throws HttpResponseException thrown if the request is rejected by server. + * @throws ClientAuthenticationException thrown if the request is rejected by server on status code 401. + * @throws ResourceNotFoundException thrown if the request is rejected by server on status code 404. + * @throws ResourceModifiedException thrown if the request is rejected by server on status code 409. + * @return the {@link Response}. + */ + @Generated + @ServiceMethod(returns = ReturnType.SINGLE) + public Response sendProjectedNameWithResponse(String id, BinaryData request, RequestOptions requestOptions) { + return this.serviceClient.sendProjectedNameWithResponse(id, request, requestOptions); + } + /** * The sendLong operation. *

@@ -168,6 +194,29 @@ public void send(String id, String input) { sendWithResponse(id, request, requestOptions).getValue(); } + /** + * The sendProjectedName operation. + * + * @param id A sequence of textual characters. + * @param fileIdentifier A sequence of textual characters. + * @throws IllegalArgumentException thrown if parameters fail the validation. + * @throws HttpResponseException thrown if the request is rejected by server. + * @throws ClientAuthenticationException thrown if the request is rejected by server on status code 401. + * @throws ResourceNotFoundException thrown if the request is rejected by server on status code 404. + * @throws ResourceModifiedException thrown if the request is rejected by server on status code 409. + * @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent. + */ + @Generated + @ServiceMethod(returns = ReturnType.SINGLE) + public void sendProjectedName(String id, String fileIdentifier) { + // Generated convenience method for sendProjectedNameWithResponse + RequestOptions requestOptions = new RequestOptions(); + Map requestObj = new HashMap<>(); + requestObj.put("file_id", fileIdentifier); + BinaryData request = BinaryData.fromObject(requestObj); + sendProjectedNameWithResponse(id, request, requestOptions).getValue(); + } + /** * The sendLong operation. * diff --git a/typespec-tests/src/main/java/com/cadl/flatten/implementation/FlattenClientImpl.java b/typespec-tests/src/main/java/com/cadl/flatten/implementation/FlattenClientImpl.java index 1c0417f535..5d8a6d536c 100644 --- a/typespec-tests/src/main/java/com/cadl/flatten/implementation/FlattenClientImpl.java +++ b/typespec-tests/src/main/java/com/cadl/flatten/implementation/FlattenClientImpl.java @@ -163,6 +163,26 @@ Response sendSync(@HostParam("endpoint") String endpoint, @QueryParam("id" @QueryParam("api-version") String apiVersion, @HeaderParam("accept") String accept, @BodyParam("application/json") BinaryData request, RequestOptions requestOptions, Context context); + @Post("/flatten/send-projected-name") + @ExpectedResponses({ 200 }) + @UnexpectedResponseExceptionType(value = ClientAuthenticationException.class, code = { 401 }) + @UnexpectedResponseExceptionType(value = ResourceNotFoundException.class, code = { 404 }) + @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 }) + @UnexpectedResponseExceptionType(HttpResponseException.class) + Mono> sendProjectedName(@HostParam("endpoint") String endpoint, @QueryParam("id") String id, + @HeaderParam("accept") String accept, @BodyParam("application/json") BinaryData request, + RequestOptions requestOptions, Context context); + + @Post("/flatten/send-projected-name") + @ExpectedResponses({ 200 }) + @UnexpectedResponseExceptionType(value = ClientAuthenticationException.class, code = { 401 }) + @UnexpectedResponseExceptionType(value = ResourceNotFoundException.class, code = { 404 }) + @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 }) + @UnexpectedResponseExceptionType(HttpResponseException.class) + Response sendProjectedNameSync(@HostParam("endpoint") String endpoint, @QueryParam("id") String id, + @HeaderParam("accept") String accept, @BodyParam("application/json") BinaryData request, + RequestOptions requestOptions, Context context); + @Post("/flatten/send-long") @ExpectedResponses({ 200 }) @UnexpectedResponseExceptionType(value = ClientAuthenticationException.class, code = { 401 }) @@ -244,6 +264,60 @@ public Response sendWithResponse(String id, BinaryData request, RequestOpt requestOptions, Context.NONE); } + /** + * The sendProjectedName operation. + *

+ * Request Body Schema + *

+ *
{@code
+     * {
+     *     file_id: String (Required)
+     * }
+     * }
+ * + * @param id A sequence of textual characters. + * @param request The request parameter. + * @param requestOptions The options to configure the HTTP request before HTTP client sends it. + * @throws HttpResponseException thrown if the request is rejected by server. + * @throws ClientAuthenticationException thrown if the request is rejected by server on status code 401. + * @throws ResourceNotFoundException thrown if the request is rejected by server on status code 404. + * @throws ResourceModifiedException thrown if the request is rejected by server on status code 409. + * @return the {@link Response} on successful completion of {@link Mono}. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public Mono> sendProjectedNameWithResponseAsync(String id, BinaryData request, + RequestOptions requestOptions) { + final String accept = "application/json"; + return FluxUtil.withContext( + context -> service.sendProjectedName(this.getEndpoint(), id, accept, request, requestOptions, context)); + } + + /** + * The sendProjectedName operation. + *

+ * Request Body Schema + *

+ *
{@code
+     * {
+     *     file_id: String (Required)
+     * }
+     * }
+ * + * @param id A sequence of textual characters. + * @param request The request parameter. + * @param requestOptions The options to configure the HTTP request before HTTP client sends it. + * @throws HttpResponseException thrown if the request is rejected by server. + * @throws ClientAuthenticationException thrown if the request is rejected by server on status code 401. + * @throws ResourceNotFoundException thrown if the request is rejected by server on status code 404. + * @throws ResourceModifiedException thrown if the request is rejected by server on status code 409. + * @return the {@link Response}. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public Response sendProjectedNameWithResponse(String id, BinaryData request, RequestOptions requestOptions) { + final String accept = "application/json"; + return service.sendProjectedNameSync(this.getEndpoint(), id, accept, request, requestOptions, Context.NONE); + } + /** * The sendLong operation. *

diff --git a/typespec-tests/src/main/java/com/cadl/naming/NamingAsyncClient.java b/typespec-tests/src/main/java/com/cadl/naming/NamingAsyncClient.java index de38d7c396..34ecdb4043 100644 --- a/typespec-tests/src/main/java/com/cadl/naming/NamingAsyncClient.java +++ b/typespec-tests/src/main/java/com/cadl/naming/NamingAsyncClient.java @@ -83,7 +83,9 @@ public final class NamingAsyncClient { * { * name: String (Required) * data (Required): { - * data: byte[] (Required) + * data (Required): { + * kind_id: String (Optional) + * } * } * type: String(Blob/File) (Required) * status: String(Running/Completed/Failed) (Required) diff --git a/typespec-tests/src/main/java/com/cadl/naming/implementation/NamingOpsImpl.java b/typespec-tests/src/main/java/com/cadl/naming/implementation/NamingOpsImpl.java index d80fd57480..b52b88af98 100644 --- a/typespec-tests/src/main/java/com/cadl/naming/implementation/NamingOpsImpl.java +++ b/typespec-tests/src/main/java/com/cadl/naming/implementation/NamingOpsImpl.java @@ -141,7 +141,9 @@ Response getAnonymouseSync(@HostParam("endpoint") String endpoint, * { * name: String (Required) * data (Required): { - * data: byte[] (Required) + * data (Required): { + * kind_id: String (Optional) + * } * } * type: String(Blob/File) (Required) * status: String(Running/Completed/Failed) (Required) @@ -211,7 +213,9 @@ public Mono> postWithResponseAsync(String name, BinaryData * { * name: String (Required) * data (Required): { - * data: byte[] (Required) + * data (Required): { + * kind_id: String (Optional) + * } * } * type: String(Blob/File) (Required) * status: String(Running/Completed/Failed) (Required) diff --git a/typespec-tests/src/main/java/com/cadl/naming/models/BinaryData.java b/typespec-tests/src/main/java/com/cadl/naming/models/BinaryData.java index 63fbf1c354..6e4e91bd69 100644 --- a/typespec-tests/src/main/java/com/cadl/naming/models/BinaryData.java +++ b/typespec-tests/src/main/java/com/cadl/naming/models/BinaryData.java @@ -6,7 +6,6 @@ import com.azure.core.annotation.Generated; import com.azure.core.annotation.Immutable; -import com.azure.core.util.CoreUtils; import com.azure.json.JsonReader; import com.azure.json.JsonSerializable; import com.azure.json.JsonToken; @@ -26,7 +25,7 @@ public final class BinaryData implements JsonSerializable { * description of data property */ @Generated - private final byte[] data; + private final Data data; /** * Creates an instance of BinaryData class. @@ -34,7 +33,7 @@ public final class BinaryData implements JsonSerializable { * @param data the data value to set. */ @Generated - private BinaryData(byte[] data) { + private BinaryData(Data data) { this.data = data; } @@ -46,14 +45,14 @@ private BinaryData(byte[] data) { * @return the data value. */ @Generated - public byte[] getData() { - return CoreUtils.clone(this.data); + public Data getData() { + return this.data; } @Override public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { jsonWriter.writeStartObject(); - jsonWriter.writeBinaryField("data", this.data); + jsonWriter.writeJsonField("data", this.data); return jsonWriter.writeEndObject(); } @@ -68,13 +67,13 @@ public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { */ public static BinaryData fromJson(JsonReader jsonReader) throws IOException { return jsonReader.readObject(reader -> { - byte[] data = null; + Data data = null; while (reader.nextToken() != JsonToken.END_OBJECT) { String fieldName = reader.getFieldName(); reader.nextToken(); if ("data".equals(fieldName)) { - data = reader.getBinary(); + data = Data.fromJson(reader); } else { reader.skipChildren(); } diff --git a/typespec-tests/src/main/java/com/cadl/naming/models/BytesData.java b/typespec-tests/src/main/java/com/cadl/naming/models/BytesData.java new file mode 100644 index 0000000000..aae190a6e1 --- /dev/null +++ b/typespec-tests/src/main/java/com/cadl/naming/models/BytesData.java @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.cadl.naming.models; + +import com.azure.core.annotation.Generated; +import com.azure.core.annotation.Immutable; +import com.azure.core.util.CoreUtils; +import com.azure.json.JsonReader; +import com.azure.json.JsonToken; +import com.azure.json.JsonWriter; +import java.io.IOException; + +/** + * The BytesData model. + */ +@Immutable +public final class BytesData extends Data { + /* + * The data property. + */ + @Generated + private final byte[] data; + + /** + * Creates an instance of BytesData class. + * + * @param data the data value to set. + */ + @Generated + private BytesData(byte[] data) { + this.data = data; + } + + /** + * Get the data property: The data property. + * + * @return the data value. + */ + @Generated + public byte[] getData() { + return CoreUtils.clone(this.data); + } + + @Override + public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { + jsonWriter.writeStartObject(); + jsonWriter.writeStringField("kind_id", "bytes"); + jsonWriter.writeBinaryField("data", this.data); + return jsonWriter.writeEndObject(); + } + + /** + * Reads an instance of BytesData from the JsonReader. + * + * @param jsonReader The JsonReader being read. + * @return An instance of BytesData if the JsonReader was pointing to an instance of it, or null if it was pointing + * to JSON null. + * @throws IllegalStateException If the deserialized JSON object was missing any required properties or the + * polymorphic discriminator. + * @throws IOException If an error occurs while reading the BytesData. + */ + public static BytesData fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + byte[] data = null; + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + + if ("kind_id".equals(fieldName)) { + String type = reader.getString(); + if (!"bytes".equals(type)) { + throw new IllegalStateException( + "'kind_id' was expected to be non-null and equal to 'bytes'. The found 'kind_id' was '" + + type + "'."); + } + } else if ("data".equals(fieldName)) { + data = reader.getBinary(); + } else { + reader.skipChildren(); + } + } + return new BytesData(data); + }); + } +} diff --git a/typespec-tests/src/main/java/com/cadl/naming/models/Data.java b/typespec-tests/src/main/java/com/cadl/naming/models/Data.java new file mode 100644 index 0000000000..f642bff9d7 --- /dev/null +++ b/typespec-tests/src/main/java/com/cadl/naming/models/Data.java @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.cadl.naming.models; + +import com.azure.core.annotation.Generated; +import com.azure.core.annotation.Immutable; +import com.azure.json.JsonReader; +import com.azure.json.JsonSerializable; +import com.azure.json.JsonToken; +import com.azure.json.JsonWriter; +import java.io.IOException; + +/** + * The Data model. + */ +@Immutable +public class Data implements JsonSerializable { + /** + * Creates an instance of Data class. + */ + @Generated + protected Data() { + } + + @Override + public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { + jsonWriter.writeStartObject(); + return jsonWriter.writeEndObject(); + } + + /** + * Reads an instance of Data from the JsonReader. + * + * @param jsonReader The JsonReader being read. + * @return An instance of Data if the JsonReader was pointing to an instance of it, or null if it was pointing to + * JSON null. + * @throws IllegalStateException If the deserialized JSON object was missing the polymorphic discriminator. + * @throws IOException If an error occurs while reading the Data. + */ + public static Data fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + String discriminatorValue = null; + JsonReader readerToUse = reader.bufferObject(); + + readerToUse.nextToken(); // Prepare for reading + while (readerToUse.nextToken() != JsonToken.END_OBJECT) { + String fieldName = readerToUse.getFieldName(); + readerToUse.nextToken(); + if ("kind_id".equals(fieldName)) { + discriminatorValue = readerToUse.getString(); + break; + } else { + readerToUse.skipChildren(); + } + } + // Use the discriminator value to determine which subtype should be deserialized. + if ("bytes".equals(discriminatorValue)) { + return BytesData.fromJson(readerToUse.reset()); + } else { + return fromJsonKnownDiscriminator(readerToUse.reset()); + } + }); + } + + static Data fromJsonKnownDiscriminator(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + Data deserializedData = new Data(); + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + + reader.skipChildren(); + } + + return deserializedData; + }); + } +} diff --git a/typespec-tests/tsp/flatten.tsp b/typespec-tests/tsp/flatten.tsp index bce93f57fe..631e30be60 100644 --- a/typespec-tests/tsp/flatten.tsp +++ b/typespec-tests/tsp/flatten.tsp @@ -59,6 +59,13 @@ interface FlattenOp { @post send(@query id: string, ...Request, ...ApiVersionParameter): OkResponse; + @route("send-projected-name") + @post + sendProjectedName( + @query id: string, + @projectedName("json", "file_id") @projectedName("client", "fileIdentifier") fileId: string, + ): OkResponse; + @route("send-long") @post sendLong( diff --git a/typespec-tests/tsp/naming.tsp b/typespec-tests/tsp/naming.tsp index f0b9e007e7..a14eb01382 100644 --- a/typespec-tests/tsp/naming.tsp +++ b/typespec-tests/tsp/naming.tsp @@ -41,6 +41,18 @@ model Response { model DataModel { @summary("summary of data property") @doc("description of data property") + data: Data; +} + +@discriminator("kind") +model Data { + @projectedName("json", "kind_id") + @projectedName("client", "type") + kind: string; +} + +model BytesData extends Data { + kind: "bytes"; data: bytes; }