diff --git a/.chronus/changes/http-client-java-response-replacement.md b/.chronus/changes/http-client-java-response-replacement.md new file mode 100644 index 00000000000..4d755b77e0f --- /dev/null +++ b/.chronus/changes/http-client-java-response-replacement.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/http-client-java" +--- + +Honor void and raw-byte response replacements in generated Java clients, preserving types used by typed response headers. Fix raw-byte conversion for text responses. diff --git a/packages/http-client-java/emitter/src/code-model-builder.ts b/packages/http-client-java/emitter/src/code-model-builder.ts index 34669a899b1..27870ec3b07 100644 --- a/packages/http-client-java/emitter/src/code-model-builder.ts +++ b/packages/http-client-java/emitter/src/code-model-builder.ts @@ -1191,6 +1191,32 @@ export class CodeModelBuilder { lroMetadata = this.processLroMetadata(codeModelOperation, sdkMethod); } + // TCGC only supports response replacement with void or bytes. + // Infer replacement when no declared HTTP body type matches the method return type. + // A no-content variant alone must not classify a parameter-only override as a replacement. + let responseReplacement: "void" | "bytes" | undefined = + sdkMethod.isOverride && + sdkMethod.kind === "basic" && + (sdkMethod.response.type === undefined || sdkMethod.response.type.kind === "bytes") && + httpOperation.responses.some((response) => response.type !== sdkMethod.response.type) && + !httpOperation.responses.some( + (response) => response.type !== undefined && response.type === sdkMethod.response.type, + ) + ? sdkMethod.response.type === undefined + ? "void" + : "bytes" + : undefined; + if ( + responseReplacement === "bytes" && + httpOperation.responses.every((response) => response.type === undefined) + ) { + reportDiagnostic(this.program, { + code: "bytes-response-replacement-without-body", + format: { operationName }, + target: sdkMethod.__raw ?? httpOperation.__raw.operation, + }); + responseReplacement = undefined; + } // responses for (const response of sdkMethod.operation.responses) { this.processResponse( @@ -1199,6 +1225,7 @@ export class CodeModelBuilder { response, lroMetadata.longRunning, false, + responseReplacement, ); } @@ -2381,12 +2408,39 @@ export class CodeModelBuilder { sdkResponse: SdkHttpResponse | SdkHttpErrorResponse, longRunning: boolean, isErrorResponse: boolean, + responseReplacement?: "void" | "bytes", ) { // TODO: what to do if more than 1 response? // It happens when the response type is Union, on one status code. // let response: Response; const headers: Array = []; + // Azure V1 replaces only the convenience return type, preserving the protocol response. + // Azure V2/ClientCore replace the operation response so the proxy handles void/raw bytes. + const replaceConvenienceResponse = + !isErrorResponse && responseReplacement !== undefined && this.isAzureV1(); + const replaceResponseBodyType = + !isErrorResponse && responseReplacement !== undefined && !this.isAzureV1(); + let bodyType: SdkType | undefined = sdkResponse.type; + if (!isErrorResponse && responseReplacement !== undefined) { + switch (responseReplacement) { + case "void": + if (replaceConvenienceResponse && op.convenienceApi) { + op.convenienceApi.response = createResponse(); + } else if (replaceResponseBodyType) { + bodyType = undefined; + } + break; + case "bytes": + if (replaceConvenienceResponse && op.convenienceApi) { + op.convenienceApi.response = createBinaryResponse(); + } + break; + default: + fail(`Unsupported response replacement: ${responseReplacement satisfies never}`); + } + } + if (sdkResponse.headers) { for (const header of sdkResponse.headers) { const schema = this.processSchema(header.type, header.name); @@ -2423,10 +2477,9 @@ export class CodeModelBuilder { } } - const bodyType: SdkType | undefined = sdkResponse.type; - let trackConvenienceApi: boolean = Boolean(op.convenienceApi); + let trackResponseBody: boolean = Boolean(op.convenienceApi) && !replaceConvenienceResponse; const trackResponseHeadersAsModel = - trackConvenienceApi && + Boolean(op.convenienceApi) && (op.convenienceApi?.responseHeadersAsModel === true || (this.isAzureV1() && this.options["max-overload"] === "model")); @@ -2441,7 +2494,10 @@ export class CodeModelBuilder { } let responseBodyIsFile: boolean = false; - if ( + if (replaceResponseBodyType && responseReplacement === "bytes" && bodyType) { + // Raw bytes bypass JSON/base64 decoding even for application/json responses. + responseBodyIsFile = true; + } else if ( bodyType && bodyType.kind === "model" && bodyType.serializationOptions.binary && @@ -2481,7 +2537,7 @@ export class CodeModelBuilder { if (longRunning) { // LRO uses the LroMetadata for poll/final result, not the response of activation request // hence the schema below is not tracked for convenience API - trackConvenienceApi = false; + trackResponseBody = false; } // schema (usually JSON) @@ -2544,7 +2600,7 @@ export class CodeModelBuilder { if (isSchemaResponse(response)) { this.trackSchemaUsage(response.schema, { usage: [SchemaContext.Exception] }); - if (trackConvenienceApi) { + if (trackResponseBody) { let outputErrorModel = false; if (!this.isBranded()) { outputErrorModel = true; @@ -2572,7 +2628,7 @@ export class CodeModelBuilder { if (isSchemaResponse(response)) { this.trackSchemaUsage(response.schema, { usage: [SchemaContext.Output] }); - if (trackConvenienceApi) { + if (trackResponseBody) { this.trackSchemaUsage(response.schema, { usage: [op.internalApi ? SchemaContext.Internal : SchemaContext.Public], }); diff --git a/packages/http-client-java/emitter/src/common/operation.ts b/packages/http-client-java/emitter/src/common/operation.ts index e04a4208c4c..ec6d1d7277d 100644 --- a/packages/http-client-java/emitter/src/common/operation.ts +++ b/packages/http-client-java/emitter/src/common/operation.ts @@ -69,6 +69,9 @@ export interface Operation extends Aspect { export interface ConvenienceApi extends Metadata { requests?: Array; + /** Overrides the convenience return type without changing the protocol response. */ + response?: Response; + /** * Whether the convenience method returns the significant response headers as a strongly-typed model * (opt-in via the "responseHeadersAsModel" client option). Only applicable to data-plane operations diff --git a/packages/http-client-java/emitter/src/diagnostics/bytes-response-replacement-without-body.md b/packages/http-client-java/emitter/src/diagnostics/bytes-response-replacement-without-body.md new file mode 100644 index 00000000000..d481310dec7 --- /dev/null +++ b/packages/http-client-java/emitter/src/diagnostics/bytes-response-replacement-without-body.md @@ -0,0 +1,13 @@ +This warning is issued when `replaceResponseWithBytes` targets an operation with no declared HTTP response body. + +## Impact + +The emitter cannot fabricate response bytes. It ignores the response type override and preserves the original response type in all Java flavors. + +## How to Fix + +Remove the bytes replacement, or declare the HTTP response body if the service actually returns one. + +## Suppression + +Suppressing this warning does not enable the replacement; the original response type is still used. diff --git a/packages/http-client-java/emitter/src/lib.ts b/packages/http-client-java/emitter/src/lib.ts index 035e2355d95..4b4428de9a8 100644 --- a/packages/http-client-java/emitter/src/lib.ts +++ b/packages/http-client-java/emitter/src/lib.ts @@ -156,6 +156,13 @@ export const $lib = createTypeSpecLibrary({ "Spread JSON merge-patch payload is not supported. The reason is that a property in JSON merge-patch payload class can: set a value; not set so that value does not change; set to null to remove the value. A parameter on method cannot distinguish the latter 2 cases.", }, }, + "bytes-response-replacement-without-body": { + ...doc("bytes-response-replacement-without-body"), + severity: "warning", + messages: { + default: paramMessage`Cannot replace the response of operation '${"operationName"}' with bytes: the emitter cannot fabricate bytes when the HTTP response has no body. The response type override is ignored.`, + }, + }, "type-not-supported-on-text-plain": { ...doc("type-not-supported-on-text-plain"), severity: "warning", diff --git a/packages/http-client-java/emitter/test/code-model.test.ts b/packages/http-client-java/emitter/test/code-model.test.ts index c1cdeeb4977..00873eead10 100644 --- a/packages/http-client-java/emitter/test/code-model.test.ts +++ b/packages/http-client-java/emitter/test/code-model.test.ts @@ -1,4 +1,5 @@ import { NodeHost, compile } from "@typespec/compiler"; +import { expectDiagnosticEmpty, expectDiagnostics } from "@typespec/compiler/testing"; import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; import { parse, stringify } from "yaml"; @@ -33,6 +34,7 @@ import { createSecurity, createStringSchema, createVirtualParameter, + isSchemaResponse, } from "../src/common/schemas/model.js"; import { createResponseErrorSchema } from "../src/external-schemas.js"; import { cloneOperationParameter } from "../src/operation-utils.js"; @@ -285,6 +287,226 @@ describe("code model characterization", () => { }, ); + it.each(["standard", "azure", "azurev2"])( + "honors response replacements in %s clients", + async (flavor) => { + const program = await compile( + NodeHost, + fileURLToPath(new URL("./fixtures/response-replacement.tsp", import.meta.url)), + { noEmit: true }, + ); + expect(program.diagnostics).toEqual([]); + const model = await new CodeModelBuilder(program, { + program, + emitterOutputDir: "/code-model-test-output", + options: { "dev-options": {}, flavor }, + perf: { + startTimer: () => ({ end: () => 0 }), + time: (_label, callback) => callback(), + timeAsync: (_label, callback) => callback(), + report: () => {}, + }, + }).build(); + expectDiagnostics( + program.diagnostics.filter( + (diagnostic) => + diagnostic.code !== + "@azure-tools/typespec-client-generator-core/unsupported-generic-decorator-arg-type", + ), + [ + { + code: "@typespec/http-client-java/bytes-response-replacement-without-body", + severity: "warning", + message: + "Cannot replace the response of operation 'bodylessBytesResponse' with bytes: the emitter cannot fabricate bytes when the HTTP response has no body. The response type override is ignored.", + }, + ], + ); + const operations = model.clients.flatMap((client) => + client.operationGroups.flatMap((group) => group.operations ?? []), + ); + const voidOperation = operations.find((op) => op.language.default.name === "voidResponse")!; + const bytesOperation = operations.find((op) => op.language.default.name === "bytesResponse")!; + const widgetOperation = operations.find( + (op) => op.language.default.name === "widgetResponse", + )!; + for (const operation of [voidOperation, bytesOperation]) { + expect(operation.responses).toHaveLength(1); + expect(operation.responses![0].protocol.http).toMatchObject({ + statusCodes: ["200"], + headers: [expect.objectContaining({ header: "x-response-id" })], + }); + if (operation === voidOperation && flavor !== "azure") { + expect(operation.responses![0].protocol.http).not.toHaveProperty("mediaTypes"); + } else { + expect(operation.responses![0].protocol.http.mediaTypes).toEqual(["application/json"]); + } + expect(operation.exceptions).toHaveLength(1); + expect(operation.exceptions![0].protocol.http.statusCodes).toEqual(["400"]); + expect(isSchemaResponse(operation.exceptions![0])).toBe(true); + expect(operation.exceptions![0]).toMatchObject({ + schema: { + usage: expect.arrayContaining( + flavor === "standard" ? ["exception", "public"] : ["exception"], + ), + }, + }); + } + if (flavor === "azure") { + expect(voidOperation.convenienceApi).toHaveProperty("response"); + expect(voidOperation.convenienceApi).toMatchObject({ response: {} }); + expect(bytesOperation.convenienceApi).toMatchObject({ response: { binary: true } }); + expect(isSchemaResponse(voidOperation.responses![0])).toBe(true); + expect(isSchemaResponse(bytesOperation.responses![0])).toBe(true); + } else { + expect(isSchemaResponse(voidOperation.responses![0])).toBe(false); + expect(voidOperation.responses![0]).not.toHaveProperty("binary"); + expect(bytesOperation.responses![0]).toMatchObject({ binary: true }); + } + expect(widgetOperation.convenienceApi).not.toHaveProperty("response"); + expect(widgetOperation.responses![0]).toMatchObject({ + schema: { type: SchemaType.Object, language: { default: { name: "Widget" } } }, + }); + const encodedBytesOperation = operations.find( + (op) => op.language.default.name === "encodedBytesResponse", + )!; + expect( + flavor === "azure" + ? encodedBytesOperation.convenienceApi + : { response: encodedBytesOperation.responses![0] }, + ).toMatchObject({ response: { binary: true } }); + const parameterOverrideOperation = operations.find( + (op) => op.language.default.name === "parameterOverrideBytesResponse", + )!; + expect(parameterOverrideOperation.convenienceApi).not.toHaveProperty("response"); + expect(parameterOverrideOperation.responses![0]).toMatchObject({ + schema: { type: SchemaType.ByteArray }, + }); + const parameterOverrideOptionalOperation = operations.find( + (op) => op.language.default.name === "parameterOverrideOptionalBytesResponse", + )!; + expect(parameterOverrideOptionalOperation.convenienceApi).not.toHaveProperty("response"); + expect(parameterOverrideOptionalOperation.responses![0]).toMatchObject({ + schema: { type: SchemaType.ByteArray }, + }); + expect(parameterOverrideOptionalOperation.responses![1]).not.toHaveProperty("schema"); + expect(parameterOverrideOptionalOperation.responses![1]).not.toHaveProperty("binary"); + + const optionalBytesOperation = operations.find( + (op) => op.language.default.name === "optionalBytesResponse", + )!; + const optionalVoidOperation = operations.find( + (op) => op.language.default.name === "optionalVoidResponse", + )!; + if (flavor === "azure") { + expect(optionalBytesOperation.convenienceApi).toMatchObject({ response: { binary: true } }); + expect(optionalVoidOperation.convenienceApi).toMatchObject({ response: {} }); + expect(isSchemaResponse(optionalVoidOperation.responses![0])).toBe(true); + } else { + expect(optionalBytesOperation.responses![0]).toMatchObject({ binary: true }); + expect(optionalVoidOperation.responses![0]).not.toHaveProperty("schema"); + } + for (const name of ["bodylessBytesResponse", "suppressedBodylessBytesResponse"]) { + const operation = operations.find((op) => op.language.default.name === name)!; + expect(operation.convenienceApi).not.toHaveProperty("response"); + expect(operation.responses![0]).not.toHaveProperty("schema"); + expect(operation.responses![0]).not.toHaveProperty("binary"); + expect(operation.responses![0].protocol.http).toMatchObject({ + statusCodes: ["204"], + headers: [expect.objectContaining({ header: "x-response-id" })], + }); + } + const bodylessOperation = operations.find( + (op) => op.language.default.name === "bodylessBytesResponse", + )!; + expect(bodylessOperation.exceptions![0]).toMatchObject({ + schema: { usage: expect.arrayContaining(["exception"]) }, + }); + }, + ); + it.each([ + { internalApi: false, typedHeaders: false }, + { internalApi: false, typedHeaders: true }, + { internalApi: true, typedHeaders: false }, + { internalApi: true, typedHeaders: true }, + ])( + "preserves replacement header visibility (internal: $internalApi, typed: $typedHeaders)", + async ({ internalApi, typedHeaders }) => { + const program = await compile( + NodeHost, + fileURLToPath( + new URL( + `./fixtures/response-replacement-headers${internalApi ? "-internal" : ""}.tsp`, + import.meta.url, + ), + ), + { noEmit: true }, + ); + expectDiagnosticEmpty(program.diagnostics); + const model = await new CodeModelBuilder(program, { + program, + emitterOutputDir: "/code-model-test-output", + options: { + flavor: "azure", + ...(typedHeaders ? { "max-overload": "model" } : {}), + "dev-options": {}, + }, + perf: { + startTimer: () => ({ end: () => 0 }), + time: (_label, callback) => callback(), + timeAsync: (_label, callback) => callback(), + report: () => {}, + }, + }).build(); + expectDiagnosticEmpty( + program.diagnostics.filter( + (diagnostic) => + diagnostic.code !== + "@azure-tools/typespec-client-generator-core/unsupported-generic-decorator-arg-type", + ), + ); + const bodySchema = model.schemas.objects?.find( + (schema) => schema.language.default.name === "OriginalBody", + ); + const bodyEnum = [ + ...(model.schemas.choices ?? []), + ...(model.schemas.sealedChoices ?? []), + ].find((schema) => schema.language.default.name === "BodyState"); + for (const schema of [bodySchema, bodyEnum]) { + expect(schema).toBeDefined(); + for (const visibility of ["public", "internal"]) { + expect(schema).not.toMatchObject({ usage: expect.arrayContaining([visibility]) }); + } + } + + const operations = model.clients.flatMap((client) => + client.operationGroups.flatMap((group) => group.operations ?? []), + ); + expect(operations).toHaveLength(2); + for (const operation of operations) { + expect(operation.internalApi).toBe(internalApi); + expect(operation.responses).toHaveLength(1); + expect(operation.responses![0]).toMatchObject({ schema: bodySchema }); + const header = operation.responses![0].protocol.http.headers![0]; + expect(header).toMatchObject({ + header: "x-response-state", + schema: { language: { default: { name: "ResponseState" } } }, + }); + if (typedHeaders) { + expect(header.schema).toMatchObject({ + usage: expect.arrayContaining([internalApi ? "internal" : "public"]), + }); + } else { + for (const visibility of ["public", "internal"]) { + expect(header.schema).not.toMatchObject({ + usage: expect.arrayContaining([visibility]), + }); + } + } + } + }, + ); + it.each([ { scenario: "arm-api-version-override", enableSubclient: false }, { scenario: "arm-api-version-override", enableSubclient: true }, diff --git a/packages/http-client-java/emitter/test/fixtures/response-replacement-headers-internal.tsp b/packages/http-client-java/emitter/test/fixtures/response-replacement-headers-internal.tsp new file mode 100644 index 00000000000..bbf60f3c60e --- /dev/null +++ b/packages/http-client-java/emitter/test/fixtures/response-replacement-headers-internal.tsp @@ -0,0 +1,10 @@ +import "./response-replacement-headers.tsp"; + +@@Azure.ClientGenerator.Core.access( + ResponseReplacementHeaders.voidResponse, + Azure.ClientGenerator.Core.Access.internal +); +@@Azure.ClientGenerator.Core.access( + ResponseReplacementHeaders.bytesResponse, + Azure.ClientGenerator.Core.Access.internal +); diff --git a/packages/http-client-java/emitter/test/fixtures/response-replacement-headers.tsp b/packages/http-client-java/emitter/test/fixtures/response-replacement-headers.tsp new file mode 100644 index 00000000000..f33eb67bd01 --- /dev/null +++ b/packages/http-client-java/emitter/test/fixtures/response-replacement-headers.tsp @@ -0,0 +1,42 @@ +import "@typespec/http"; +import "@azure-tools/typespec-client-generator-core"; + +using TypeSpec.Http; +using Azure.ClientGenerator.Core; + +@service +namespace ResponseReplacementHeaders; + +enum ResponseState { + ready, + waiting, +} + +enum BodyState { + pending, + complete, +} + +model OriginalBody { + state: BodyState; +} + +model OriginalResponse { + @statusCode statusCode: 200; + @header("x-response-state") state: ResponseState; + @body body: OriginalBody; +} + +@get +@route("/void") +op voidResponse(): OriginalResponse; + +@get +@route("/bytes") +op bytesResponse(): OriginalResponse; + +#suppress "@azure-tools/typespec-client-generator-core/override-response-replacement" "Testing a void response replacement with typed headers." +@@override(voidResponse, replaceResponseWithVoid(voidResponse)); + +#suppress "@azure-tools/typespec-client-generator-core/override-response-replacement" "Testing a bytes response replacement with typed headers." +@@override(bytesResponse, replaceResponseWithBytes(bytesResponse)); diff --git a/packages/http-client-java/emitter/test/fixtures/response-replacement.tsp b/packages/http-client-java/emitter/test/fixtures/response-replacement.tsp new file mode 100644 index 00000000000..ed191a1e205 --- /dev/null +++ b/packages/http-client-java/emitter/test/fixtures/response-replacement.tsp @@ -0,0 +1,114 @@ +import "@typespec/http"; +import "@azure-tools/typespec-client-generator-core"; + +using TypeSpec.Http; +using Azure.ClientGenerator.Core; + +@service +namespace ResponseReplacement; + +model Widget { + name: string; +} + +model WidgetResponse { + @statusCode statusCode: 200; + @header("x-response-id") responseId: string; + @body body: Widget; +} + +model EncodedBytesResponse { + @header contentType: "application/json"; + @body body: bytes; +} + +model NoContentResponse { + @statusCode statusCode: 204; + @header("x-response-id") responseId: string; +} + +@error +model ErrorResponse { + @statusCode statusCode: 400; + message: string; +} + +@post +@route("/void") +op voidResponse(): WidgetResponse | ErrorResponse; + +@get +@route("/bytes") +op bytesResponse(): WidgetResponse | ErrorResponse; + +@get +@route("/widget") +op widgetResponse(): Widget; + +@get +@route("/encoded-bytes") +op encodedBytesResponse(): EncodedBytesResponse; + +@get +@route("/parameter-override") +op parameterOverrideBytesResponse( + @query first: string, + @query second: string, +): EncodedBytesResponse; + +@get +@route("/parameter-override-optional") +op parameterOverrideOptionalBytesResponse(@query first: string, @query second: string): + | EncodedBytesResponse + | NoContentResponse; + +@get +@route("/optional-bytes") +op optionalBytesResponse(): EncodedBytesResponse | NoContentResponse; + +@get +@route("/optional-void") +op optionalVoidResponse(): WidgetResponse | NoContentResponse; + +@get +@route("/bodyless-bytes") +op bodylessBytesResponse(): NoContentResponse | ErrorResponse; + +#suppress "@typespec/http-client-java/bytes-response-replacement-without-body" "Testing suppression of a bodyless replacement warning." +@get +@route("/suppressed-bodyless-bytes") +op suppressedBodylessBytesResponse(): NoContentResponse; + +#suppress "@azure-tools/typespec-client-generator-core/override-response-replacement" "Testing a void response replacement." +@@override(voidResponse, replaceResponseWithVoid(voidResponse)); + +#suppress "@azure-tools/typespec-client-generator-core/override-response-replacement" "Testing a raw bytes response replacement." +@@override(bytesResponse, replaceResponseWithBytes(bytesResponse)); + +#suppress "@azure-tools/typespec-client-generator-core/override-response-replacement" "Testing replacement of encoded bytes with raw bytes." +@@override(encodedBytesResponse, replaceResponseWithBytes(encodedBytesResponse)); + +@@override( + parameterOverrideBytesResponse, + reorderParameters(parameterOverrideBytesResponse, #["second", "first"]) +); + +@@override( + parameterOverrideOptionalBytesResponse, + reorderParameters(parameterOverrideOptionalBytesResponse, #["second", "first"]) +); + +#suppress "@azure-tools/typespec-client-generator-core/override-response-replacement" "Testing replacement of optional encoded bytes." +@@override(optionalBytesResponse, replaceResponseWithBytes(optionalBytesResponse)); + +#suppress "@azure-tools/typespec-client-generator-core/override-response-replacement" "Testing replacement of an optional response with void." +@@override(optionalVoidResponse, replaceResponseWithVoid(optionalVoidResponse)); + +#suppress "@azure-tools/typespec-client-generator-core/override-response-replacement" "Testing a bodyless bytes replacement." +@@override(bodylessBytesResponse, replaceResponseWithBytes(bodylessBytesResponse)); + +#suppress "@azure-tools/typespec-client-generator-core/override-response-replacement" "Testing suppression of a bodyless replacement warning." +@@override( + suppressedBodylessBytesResponse, + replaceResponseWithBytes(suppressedBodylessBytesResponse) +); diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/extension/model/codemodel/ConvenienceApi.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/extension/model/codemodel/ConvenienceApi.java index 70885056589..59b8d1bb80d 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/extension/model/codemodel/ConvenienceApi.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/extension/model/codemodel/ConvenienceApi.java @@ -10,6 +10,7 @@ */ public class ConvenienceApi extends Metadata { private List requests; + private Response response; private boolean responseHeadersAsModel; /** @@ -37,6 +38,24 @@ public void setRequests(List requests) { this.requests = requests; } + /** + * Gets the convenience response override, leaving the protocol response unchanged. + * + * @return the convenience response override. + */ + public Response getResponse() { + return response; + } + + /** + * Sets the convenience response override. + * + * @param response the convenience response override. + */ + public void setResponse(Response response) { + this.response = response; + } + /** * Gets whether the convenience method returns the significant response headers as a strongly-typed model. * diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ClientMethodsReturnDescription.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ClientMethodsReturnDescription.java index 04531f7745b..da297017e99 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ClientMethodsReturnDescription.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ClientMethodsReturnDescription.java @@ -6,6 +6,7 @@ import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.ObjectSchema; import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.Operation; import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.Property; +import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.Response; import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.Schema; import com.microsoft.typespec.http.client.generator.core.extension.plugin.JavaSettings; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClassType; @@ -389,6 +390,17 @@ private static ClientMethodsReturnDescription createForInputStream(Operation ope * @return the response body type. */ private static IType getResponseBodyType(Operation operation, boolean isProtocolMethod, JavaSettings settings) { + if (!isProtocolMethod + && operation.getConvenienceApi() != null + && operation.getConvenienceApi().getResponse() != null) { + Response response = operation.getConvenienceApi().getResponse(); + if (Boolean.TRUE.equals(response.getBinary())) { + return ClassType.BINARY_DATA; + } + return response.getSchema() == null + ? PrimitiveType.VOID + : Mappers.getSchemaMapper().map(response.getSchema()); + } if (!isProtocolMethod && operation.getConvenienceApi() != null && operation.getConvenienceApi().isResponseHeadersAsModel()) { diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ConvenienceAsyncMethodTemplate.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ConvenienceAsyncMethodTemplate.java index 09ab038422f..075a660eab5 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ConvenienceAsyncMethodTemplate.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ConvenienceAsyncMethodTemplate.java @@ -70,6 +70,14 @@ protected void writeInvocationAndConversion(ClientMethod convenienceMethod, Clie IType protocolResponseBodyType = getConvenienceResponseBodyType(protocolMethod); IType rawResponseBodyType = convenienceMethod.getProxyMethod().getRawResponseBodyType(); + if (convenienceMethod.getType() == ClientMethodType.SimpleAsync + && responseBodyType.asNullable() == ClassType.VOID + && protocolResponseBodyType == ClassType.BINARY_DATA) { + methodBlock + .methodReturn(String.format("%1$s(%2$s).then()", getMethodName(protocolMethod), invocationExpression)); + return; + } + if (convenienceMethod.getType() == ClientMethodType.SimpleAsync && isResponseHeadersAsModel(convenienceMethod)) { // The convenience method returns the strongly-typed response-headers model, built from the response @@ -116,7 +124,9 @@ && isResponseHeadersAsModel(convenienceMethod)) { GenericType responseBaseType = getResponseBaseType(convenienceMethod.getReturnValue().getType()); boolean responseBodyIsVoid = responseBodyType.asNullable() == ClassType.VOID; - if (responseBaseType == null && (responseBodyIsVoid || responseBodyType == ClassType.BINARY_DATA)) { + if (responseBaseType == null + && ((responseBodyIsVoid && protocolResponseBodyType.asNullable() == ClassType.VOID) + || responseBodyType == ClassType.BINARY_DATA)) { methodBlock.methodReturn(methodInvocation); return; } @@ -177,10 +187,16 @@ private String expressionConvertFromBinaryData(IType responseBodyType, IType raw private String expressionMapFromBinaryData(IType responseBodyType, IType rawType, Set mediaTypes, Set typeReferenceStaticClasses) { + if (responseBodyType == ClassType.BINARY_DATA) { + return null; + } SupportedMimeType mimeType = SupportedMimeType.getResponseKnownMimeType(mediaTypes); String serializerArgument = xmlSerializerArgument(mimeType, responseBodyType); switch (mimeType) { case TEXT: + if (responseBodyType == ArrayType.BYTE_ARRAY) { + return "protocolMethodData -> protocolMethodData.toBytes()"; + } String baseHandling = "protocolMethodData.toString()"; if (!rawType.isNullable()) { // Dealing with a primitive type that needs to be converted. @@ -204,9 +220,6 @@ private String expressionMapFromBinaryData(IType responseBodyType, IType rawType typeReferenceStaticClasses.add((GenericType) responseBodyType); return String.format("protocolMethodData -> protocolMethodData.toObject(%1$s%2$s)", TemplateUtil.getTypeReferenceCreation(responseBodyType), serializerArgument); - } else if (responseBodyType == ClassType.BINARY_DATA) { - // BinaryData, no need to do the map in expressionConvertFromBinaryData - return null; } else if (responseBodyType == ArrayType.BYTE_ARRAY) { // byte[] if (rawType == ClassType.BASE_64_URL) { diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ConvenienceMethodTemplateBase.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ConvenienceMethodTemplateBase.java index 9e23121ce21..9c8d03e9cfe 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ConvenienceMethodTemplateBase.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ConvenienceMethodTemplateBase.java @@ -228,10 +228,16 @@ protected static GenericType getResponseBaseType(IType type) { protected static String expressionConvertFromBinaryData(IType responseBodyType, IType rawType, String invocationExpression, Set mediaTypes, Set typeReferenceStaticClasses) { + if (responseBodyType == ClassType.BINARY_DATA) { + return invocationExpression; + } SupportedMimeType mimeType = SupportedMimeType.getResponseKnownMimeType(mediaTypes); String serializerArgument = xmlSerializerArgument(mimeType, responseBodyType); switch (mimeType) { case TEXT: + if (responseBodyType == ArrayType.BYTE_ARRAY) { + return invocationExpression + ".toBytes()"; + } String basicText = invocationExpression + ".toString()"; if (!rawType.isNullable()) { return wrapPrimitiveMimeTypeText(basicText, (PrimitiveType) rawType); @@ -251,8 +257,6 @@ protected static String expressionConvertFromBinaryData(IType responseBodyType, return String.format("%2$s.toObject(%1$s%3$s)", TemplateUtil.getTypeReferenceCreation(responseBodyType), invocationExpression, serializerArgument); - } else if (responseBodyType == ClassType.BINARY_DATA) { - return invocationExpression; } else if (responseBodyType == ArrayType.BYTE_ARRAY) { if (rawType == ClassType.BASE_64_URL) { return invocationExpression + ".toObject(" + ClassType.BASE_64_URL.getName() + ".class" diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ConvenienceSyncMethodTemplate.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ConvenienceSyncMethodTemplate.java index 447ff2196ee..d06633dbdbf 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ConvenienceSyncMethodTemplate.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ConvenienceSyncMethodTemplate.java @@ -86,6 +86,13 @@ protected void writeInvocationAndConversion(ClientMethod convenienceMethod, Clie IType protocolResponseBodyType = getConvenienceResponseBodyType(protocolMethod); IType rawResponseBodyType = convenienceMethod.getProxyMethod().getRawResponseBodyType(); + if (convenienceMethod.getType() == ClientMethodType.SimpleSync + && responseBodyType.asNullable() == ClassType.VOID + && protocolResponseBodyType == ClassType.BINARY_DATA) { + methodBlock.line("%1$s(%2$s);", getMethodName(protocolMethod), invocationExpression); + return; + } + if (convenienceMethod.getType() == ClientMethodType.SimpleSync && isResponseHeadersAsModel(convenienceMethod)) { // The convenience method returns the strongly-typed response-headers model, built from the response // headers of the protocol method (which returns Response). @@ -118,14 +125,17 @@ && isResponseHeadersAsModel(convenienceMethod)) { "new SimpleResponse<>(protocolMethodResponse, new %1$s(" + "protocolMethodResponse.getHeaders()))", responseBodyType)); } else if (convenienceMethod.getType() == ClientMethodType.SimpleSyncRestResponse - && !(responseBodyType.asNullable() == ClassType.VOID || responseBodyType == ClassType.BINARY_DATA)) { + && responseBodyType != ClassType.BINARY_DATA + && !(responseBodyType.asNullable() == ClassType.VOID + && protocolResponseBodyType.asNullable() == ClassType.VOID)) { // protocolMethodResponse = ... methodBlock.line(getProtocolMethodResponseStatement(protocolMethod, invocationExpression)); // e.g. protocolMethodResponse.getValue().toObject(...) - String expressConversion = "protocolMethodResponse.getValue()"; - if (protocolResponseBodyType == ClassType.BINARY_DATA) { + String expressConversion + = responseBodyType.asNullable() == ClassType.VOID ? "null" : "protocolMethodResponse.getValue()"; + if (protocolResponseBodyType == ClassType.BINARY_DATA && responseBodyType.asNullable() != ClassType.VOID) { expressConversion = expressionConvertFromBinaryData(responseBodyType, rawResponseBodyType, expressConversion, protocolMethod.getProxyMethod().getResponseContentTypes(), typeReferenceStaticClasses); diff --git a/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/mapper/ClientMethodMapperTests.java b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/mapper/ClientMethodMapperTests.java index 6f9e05fe38d..9f76c9814a6 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/mapper/ClientMethodMapperTests.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/mapper/ClientMethodMapperTests.java @@ -3,6 +3,7 @@ package com.microsoft.typespec.http.client.generator.core.mapper; +import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.ConvenienceApi; import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.Language; import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.Languages; import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.Operation; @@ -11,6 +12,7 @@ import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.Request; import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.Response; import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.Schema; +import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.StringSchema; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClassType; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientMethodParameter; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.GenericType; @@ -29,6 +31,39 @@ public class ClientMethodMapperTests { + @Test + public void testConvenienceResponseReplacement() { + Operation operation = operationWithNoDesc(); + operation.getRequests().get(0).getProtocol().getHttp().setMethod(HttpMethod.GET.name()); + StringSchema schema = new StringSchema(); + schema.setType(Schema.AllSchemaTypes.STRING); + operation.getResponses().get(0).setSchema(schema); + ConvenienceApi convenienceApi = new ConvenienceApi(); + operation.setConvenienceApi(convenienceApi); + Response response = new Response(); + convenienceApi.setResponse(response); + + Assertions.assertEquals(PrimitiveType.VOID, + ClientMethodsReturnDescription.create(operation, false, true).getSyncReturnType()); + Assertions.assertEquals(ClassType.BINARY_DATA, + ClientMethodsReturnDescription.create(operation, true, true).getSyncReturnType()); + + response.setBinary(true); + Assertions.assertEquals(ClassType.BINARY_DATA, + ClientMethodsReturnDescription.create(operation, false, true).getSyncReturnType()); + Assertions.assertEquals(ClassType.BINARY_DATA, + ClientMethodsReturnDescription.create(operation, true, true).getSyncReturnType()); + + response.setBinary(false); + response.setSchema(schema); + Assertions.assertEquals(ClassType.STRING, + ClientMethodsReturnDescription.create(operation, false, true).getSyncReturnType()); + + convenienceApi.setResponse(null); + Assertions.assertEquals(ClassType.STRING, + ClientMethodsReturnDescription.create(operation, false, true).getSyncReturnType()); + } + @Test public void testOverloadedSignatures() { List parameters = new ArrayList<>(); diff --git a/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ConvenienceMethodTemplateBaseTests.java b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ConvenienceMethodTemplateBaseTests.java index dc43e9f71ec..0baaafbc111 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ConvenienceMethodTemplateBaseTests.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ConvenienceMethodTemplateBaseTests.java @@ -6,10 +6,16 @@ import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ArrayType; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClassType; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientMethod; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientMethodType; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.GenericType; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.IType; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ProxyMethod; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ReturnValue; +import com.microsoft.typespec.http.client.generator.core.model.javamodel.JavaBlock; +import com.microsoft.typespec.http.client.generator.core.model.javamodel.JavaFileContents; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -66,6 +72,71 @@ public void keepsNonGenericTypeUnchanged() { Assertions.assertSame(returnType, result); } + @Test + public void binaryDataResponsesIgnoreContentType() { + for (String contentType : List.of("text/plain", "application/json", "application/xml", + "application/octet-stream")) { + Assertions.assertEquals("return readWithResponse(requestOptions).getValue();", + renderConversion(false, ClassType.BINARY_DATA, ClassType.STRING, contentType)); + Assertions.assertEquals("return readWithResponse(requestOptions).flatMap(FluxUtil::toMono);", + renderConversion(true, ClassType.BINARY_DATA, ClassType.STRING, contentType)); + } + } + + @Test + public void textBytesResponsesUseRawBytes() { + Assertions.assertEquals("return readWithResponse(requestOptions).getValue().toBytes();", + renderConversion(false, ArrayType.BYTE_ARRAY, ArrayType.BYTE_ARRAY, "text/plain")); + Assertions.assertEquals( + "return readWithResponse(requestOptions).flatMap(FluxUtil::toMono).map(protocolMethodData -> protocolMethodData.toBytes());", + renderConversion(true, ArrayType.BYTE_ARRAY, ArrayType.BYTE_ARRAY, "text/plain")); + } + + @Test + public void textStringsKeepTextConversion() { + Assertions.assertEquals("return readWithResponse(requestOptions).getValue().toString();", + renderConversion(false, ClassType.STRING, ClassType.STRING, "text/plain")); + Assertions.assertEquals( + "return readWithResponse(requestOptions).flatMap(FluxUtil::toMono).map(protocolMethodData -> protocolMethodData.toString());", + renderConversion(true, ClassType.STRING, ClassType.STRING, "text/plain")); + } + + @Test + public void jsonBytesKeepDeserialization() { + Assertions.assertEquals("return readWithResponse(requestOptions).getValue().toObject(byte[].class);", + renderConversion(false, ArrayType.BYTE_ARRAY, ArrayType.BYTE_ARRAY, "application/json")); + Assertions.assertEquals( + "return readWithResponse(requestOptions).flatMap(FluxUtil::toMono).map(protocolMethodData -> protocolMethodData.toObject(byte[].class));", + renderConversion(true, ArrayType.BYTE_ARRAY, ArrayType.BYTE_ARRAY, "application/json")); + } + + private static String renderConversion(boolean async, IType responseBodyType, IType rawType, String contentType) { + ProxyMethod proxyMethod = new ProxyMethod.Builder().name("read") + .responseBodyType(ClassType.BINARY_DATA) + .rawResponseBodyType(rawType) + .responseContentTypes(Set.of(contentType)) + .build(); + IType protocolReturnType = GenericType.response(ClassType.BINARY_DATA); + ClientMethod protocolMethod = new ClientMethod.Builder().name("readWithResponse") + .parameters(List.of()) + .proxyMethod(proxyMethod) + .type(async ? ClientMethodType.SimpleAsyncRestResponse : ClientMethodType.SimpleSyncRestResponse) + .returnValue(new ReturnValue("response", async ? GenericType.mono(protocolReturnType) : protocolReturnType)) + .build(); + ClientMethod convenienceMethod = new ClientMethod.Builder().name("read") + .parameters(List.of()) + .proxyMethod(proxyMethod) + .type(async ? ClientMethodType.SimpleAsync : ClientMethodType.SimpleSync) + .returnValue(new ReturnValue("response", async ? GenericType.mono(responseBodyType) : responseBodyType)) + .build(); + JavaFileContents contents = new JavaFileContents(); + ConvenienceMethodTemplateBase template + = async ? ConvenienceAsyncMethodTemplate.getInstance() : ConvenienceSyncMethodTemplate.getInstance(); + template.writeInvocationAndConversion(convenienceMethod, protocolMethod, "requestOptions", + new JavaBlock(contents), new HashSet<>()); + return contents.toString().trim(); + } + private static ClientMethod methodWithReturnType(IType returnType) { return new ClientMethod.Builder().name("test") .description("test") diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/ResponseReplacementAsyncClient.java b/packages/http-client-java/generator/http-client-generator-test/src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/ResponseReplacementAsyncClient.java index ec14fd2065a..01fc63ddd74 100644 --- a/packages/http-client-java/generator/http-client-generator-test/src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/ResponseReplacementAsyncClient.java +++ b/packages/http-client-java/generator/http-client-generator-test/src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/ResponseReplacementAsyncClient.java @@ -5,7 +5,6 @@ package _specs_.azure.clientgenerator.core.responsereplacement; import _specs_.azure.clientgenerator.core.responsereplacement.implementation.ResponseReplacementClientImpl; -import _specs_.azure.clientgenerator.core.responsereplacement.models.Widget; import com.azure.core.annotation.Generated; import com.azure.core.annotation.ReturnType; import com.azure.core.annotation.ServiceClient; @@ -96,15 +95,14 @@ public Mono> bytesResponseWithResponse(RequestOptions reque * @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 the response body on successful completion of {@link Mono}. + * @return A {@link Mono} that completes when a successful response is received. */ @Generated @ServiceMethod(returns = ReturnType.SINGLE) - public Mono voidResponse() { + public Mono voidResponse() { // Generated convenience method for voidResponseWithResponse RequestOptions requestOptions = new RequestOptions(); - return voidResponseWithResponse(requestOptions).flatMap(FluxUtil::toMono) - .map(protocolMethodData -> protocolMethodData.toObject(Widget.class)); + return voidResponseWithResponse(requestOptions).then(); } /** @@ -119,10 +117,9 @@ public Mono voidResponse() { */ @Generated @ServiceMethod(returns = ReturnType.SINGLE) - public Mono bytesResponse() { + public Mono bytesResponse() { // Generated convenience method for bytesResponseWithResponse RequestOptions requestOptions = new RequestOptions(); - return bytesResponseWithResponse(requestOptions).flatMap(FluxUtil::toMono) - .map(protocolMethodData -> protocolMethodData.toObject(Widget.class)); + return bytesResponseWithResponse(requestOptions).flatMap(FluxUtil::toMono); } } diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/ResponseReplacementClient.java b/packages/http-client-java/generator/http-client-generator-test/src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/ResponseReplacementClient.java index 695a85829fa..87004517b3f 100644 --- a/packages/http-client-java/generator/http-client-generator-test/src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/ResponseReplacementClient.java +++ b/packages/http-client-java/generator/http-client-generator-test/src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/ResponseReplacementClient.java @@ -5,7 +5,6 @@ package _specs_.azure.clientgenerator.core.responsereplacement; import _specs_.azure.clientgenerator.core.responsereplacement.implementation.ResponseReplacementClientImpl; -import _specs_.azure.clientgenerator.core.responsereplacement.models.Widget; import com.azure.core.annotation.Generated; import com.azure.core.annotation.ReturnType; import com.azure.core.annotation.ServiceClient; @@ -94,14 +93,13 @@ public Response bytesResponseWithResponse(RequestOptions requestOpti * @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 the response. */ @Generated @ServiceMethod(returns = ReturnType.SINGLE) - public Widget voidResponse() { + public void voidResponse() { // Generated convenience method for voidResponseWithResponse RequestOptions requestOptions = new RequestOptions(); - return voidResponseWithResponse(requestOptions).getValue().toObject(Widget.class); + voidResponseWithResponse(requestOptions); } /** @@ -116,9 +114,9 @@ public Widget voidResponse() { */ @Generated @ServiceMethod(returns = ReturnType.SINGLE) - public Widget bytesResponse() { + public BinaryData bytesResponse() { // Generated convenience method for bytesResponseWithResponse RequestOptions requestOptions = new RequestOptions(); - return bytesResponseWithResponse(requestOptions).getValue().toObject(Widget.class); + return bytesResponseWithResponse(requestOptions).getValue(); } } diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/models/Widget.java b/packages/http-client-java/generator/http-client-generator-test/src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/models/Widget.java deleted file mode 100644 index ed5c8de0443..00000000000 --- a/packages/http-client-java/generator/http-client-generator-test/src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/models/Widget.java +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) TypeSpec Code Generator. - -package _specs_.azure.clientgenerator.core.responsereplacement.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 Widget model. - */ -@Immutable -public final class Widget implements JsonSerializable { - /* - * The name property. - */ - @Generated - private final String name; - - /** - * Creates an instance of Widget class. - * - * @param name the name value to set. - */ - @Generated - private Widget(String name) { - this.name = name; - } - - /** - * Get the name property: The name property. - * - * @return the name value. - */ - @Generated - public String getName() { - return this.name; - } - - /** - * {@inheritDoc} - */ - @Generated - @Override - public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { - jsonWriter.writeStartObject(); - jsonWriter.writeStringField("name", this.name); - return jsonWriter.writeEndObject(); - } - - /** - * Reads an instance of Widget from the JsonReader. - * - * @param jsonReader The JsonReader being read. - * @return An instance of Widget 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. - * @throws IOException If an error occurs while reading the Widget. - */ - @Generated - public static Widget fromJson(JsonReader jsonReader) throws IOException { - return jsonReader.readObject(reader -> { - String name = null; - while (reader.nextToken() != JsonToken.END_OBJECT) { - String fieldName = reader.getFieldName(); - reader.nextToken(); - - if ("name".equals(fieldName)) { - name = reader.getString(); - } else { - reader.skipChildren(); - } - } - return new Widget(name); - }); - } -} diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/models/package-info.java b/packages/http-client-java/generator/http-client-generator-test/src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/models/package-info.java deleted file mode 100644 index 0ed2436b353..00000000000 --- a/packages/http-client-java/generator/http-client-generator-test/src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/models/package-info.java +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) TypeSpec Code Generator. - -/** - * - * Package containing the data models for ResponseReplacement. - * Test scenarios for client response replacement. - * - */ -package _specs_.azure.clientgenerator.core.responsereplacement.models; diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/resources/META-INF/_specs_-azure-clientgenerator-core-responsereplacement_metadata.json b/packages/http-client-java/generator/http-client-generator-test/src/main/resources/META-INF/_specs_-azure-clientgenerator-core-responsereplacement_metadata.json index eb86813e040..7c406fd933e 100644 --- a/packages/http-client-java/generator/http-client-generator-test/src/main/resources/META-INF/_specs_-azure-clientgenerator-core-responsereplacement_metadata.json +++ b/packages/http-client-java/generator/http-client-generator-test/src/main/resources/META-INF/_specs_-azure-clientgenerator-core-responsereplacement_metadata.json @@ -1 +1 @@ -{"flavor":"Azure","apiVersions":{},"crossLanguagePackageId":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement","crossLanguageVersion":"8ede01c1a1b9","crossLanguageDefinitions":{"_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementAsyncClient":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementAsyncClient.bytesResponse":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement.bytesResponse","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementAsyncClient.bytesResponseWithResponse":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement.bytesResponse","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementAsyncClient.voidResponse":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement.voidResponse","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementAsyncClient.voidResponseWithResponse":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement.voidResponse","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementClient":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementClient.bytesResponse":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement.bytesResponse","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementClient.bytesResponseWithResponse":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement.bytesResponse","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementClient.voidResponse":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement.voidResponse","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementClient.voidResponseWithResponse":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement.voidResponse","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementClientBuilder":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement","_specs_.azure.clientgenerator.core.responsereplacement.models.Widget":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement.Widget"},"generatedFiles":["src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/ResponseReplacementAsyncClient.java","src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/ResponseReplacementClient.java","src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/ResponseReplacementClientBuilder.java","src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/implementation/ResponseReplacementClientImpl.java","src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/implementation/package-info.java","src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/models/Widget.java","src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/models/package-info.java","src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/package-info.java","src/main/java/module-info.java"]} \ No newline at end of file +{"flavor":"Azure","apiVersions":{},"crossLanguagePackageId":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement","crossLanguageVersion":"8ede01c1a1b9","crossLanguageDefinitions":{"_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementAsyncClient":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementAsyncClient.bytesResponse":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement.bytesResponse","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementAsyncClient.bytesResponseWithResponse":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement.bytesResponse","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementAsyncClient.voidResponse":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement.voidResponse","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementAsyncClient.voidResponseWithResponse":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement.voidResponse","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementClient":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementClient.bytesResponse":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement.bytesResponse","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementClient.bytesResponseWithResponse":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement.bytesResponse","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementClient.voidResponse":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement.voidResponse","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementClient.voidResponseWithResponse":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement.voidResponse","_specs_.azure.clientgenerator.core.responsereplacement.ResponseReplacementClientBuilder":"_Specs_.Azure.ClientGenerator.Core.ResponseReplacement"},"generatedFiles":["src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/ResponseReplacementAsyncClient.java","src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/ResponseReplacementClient.java","src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/ResponseReplacementClientBuilder.java","src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/implementation/ResponseReplacementClientImpl.java","src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/implementation/package-info.java","src/main/java/_specs_/azure/clientgenerator/core/responsereplacement/package-info.java","src/main/java/module-info.java"]} \ No newline at end of file