From ff61631d6d90311fcc6fa0731417e776be4ac4fd Mon Sep 17 00:00:00 2001 From: Weidong Xu Date: Thu, 15 Oct 2026 11:38:15 +0800 Subject: [PATCH 1/7] feat(http-client-java): support Azure continuation-token paging Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4bbe3fb2-95f1-4311-8050-62ba7dba4860 --- ...tp-client-java-azure-continuation-token.md | 11 + packages/http-client-java/README.md | 7 + .../emitter/src/code-model-builder.ts | 96 ++++---- .../emitter/test/code-model.test.ts | 52 +++++ .../test/fixtures/continuation-token.tsp | 40 ++++ .../ClientMethodParameterProcessor.java | 2 +- .../mapper/ProxyMethodParameterProcessor.java | 1 + .../core/template/ClientMethodTemplate.java | 94 +++++++- .../template/ClientMethodTemplateBase.java | 16 ++ .../ConvenienceMethodTemplateBase.java | 47 ++-- .../ConvenienceSyncMethodTemplate.java | 2 + ...viceSyncClientWrapAsyncClientTemplate.java | 2 + .../template/WrapperClientMethodTemplate.java | 10 +- .../generator/core/util/MethodUtil.java | 14 ++ .../ContinuationTokenPagingTests.java | 62 ++++++ ...aginationContinuationTokenAsyncClient.java | 42 +--- ...ivenPaginationContinuationTokenClient.java | 42 +--- .../pageable/XmlPaginationAsyncClient.java | 49 ---- .../payload/pageable/XmlPaginationClient.java | 31 --- ...rivenPaginationContinuationTokensImpl.java | 209 +++++++++++------- .../implementation/XmlPaginationsImpl.java | 45 ++-- .../java/payload/pageable/PageableTests.java | 139 +++++++++++- 22 files changed, 671 insertions(+), 342 deletions(-) create mode 100644 .chronus/changes/http-client-java-azure-continuation-token.md create mode 100644 packages/http-client-java/emitter/test/fixtures/continuation-token.tsp create mode 100644 packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ContinuationTokenPagingTests.java diff --git a/.chronus/changes/http-client-java-azure-continuation-token.md b/.chronus/changes/http-client-java-azure-continuation-token.md new file mode 100644 index 00000000000..1196fe24b9b --- /dev/null +++ b/.chronus/changes/http-client-java-azure-continuation-token.md @@ -0,0 +1,11 @@ +--- +changeKind: feature +packages: + - "@typespec/http-client-java" +--- + +Support continuation-token pagination in Azure Java clients. Tokens are hidden from client method signatures; resume through the paging API: + +```java +client.list().streamByPage(token); +``` diff --git a/packages/http-client-java/README.md b/packages/http-client-java/README.md index 67da2e2f5fd..3552854e93e 100644 --- a/packages/http-client-java/README.md +++ b/packages/http-client-java/README.md @@ -43,6 +43,13 @@ options: option: value ``` +### Pagination + +Azure clients support both `@nextLink` and `@continuationToken` pagination. Continuation-token +parameters are hidden from client method signatures. To resume from a token, use +`streamByPage(token)` on the returned `PagedIterable`, or `byPage(token)` on `PagedFlux`. +The service must omit the response token when there are no more pages; `has_more` is not interpreted. + ## Emitter options ### `emitter-output-dir` 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 ac0abb3e57e..48b0879a002 100644 --- a/packages/http-client-java/emitter/src/code-model-builder.ts +++ b/packages/http-client-java/emitter/src/code-model-builder.ts @@ -1304,63 +1304,61 @@ export class CodeModelBuilder { let continuationTokenParameter: Parameter | undefined; let continuationTokenResponseProperty: Property[] | undefined; let continuationTokenResponseHeader: HttpHeader | undefined; - if (!this.isAzureV1()) { - // parameter would either be query or header parameter, so taking the last segment would be enough - const continuationTokenParameterSegment = - sdkMethod.pagingMetadata.continuationTokenParameterSegments?.at(-1); - // response could be response header, where the last segment would do; or it be json path in the response body, where we use "findResponsePropertySegments" to find them - const continuationTokenResponseSegment = - sdkMethod.pagingMetadata.continuationTokenResponseSegments?.at(-1); - if (continuationTokenParameterSegment && op.parameters) { - // for now, continuationToken is either request query or header parameter - const parameter = getHttpOperationParameter(sdkMethod, continuationTokenParameterSegment); - if (parameter) { - for (const param of op.parameters) { - if (param.protocol.http?.in === parameter.kind) { - if ( - parameter.kind === "header" && - param.language.default.serializedName?.toLowerCase() === - parameter.serializedName.toLowerCase() - ) { - continuationTokenParameter = param; - break; - } else if ( - parameter.kind === "query" && - param.language.default.serializedName === parameter.serializedName - ) { - continuationTokenParameter = param; - break; - } + // parameter would either be query or header parameter, so taking the last segment would be enough + const continuationTokenParameterSegment = + sdkMethod.pagingMetadata.continuationTokenParameterSegments?.at(-1); + // response could be response header, where the last segment would do; or it be json path in the response body, where we use "findResponsePropertySegments" to find them + const continuationTokenResponseSegment = + sdkMethod.pagingMetadata.continuationTokenResponseSegments?.at(-1); + if (continuationTokenParameterSegment && op.parameters) { + // for now, continuationToken is either request query or header parameter + const parameter = getHttpOperationParameter(sdkMethod, continuationTokenParameterSegment); + if (parameter) { + for (const param of op.parameters) { + if (param.protocol.http?.in === parameter.kind) { + if ( + parameter.kind === "header" && + param.language.default.serializedName?.toLowerCase() === + parameter.serializedName.toLowerCase() + ) { + continuationTokenParameter = param; + break; + } else if ( + parameter.kind === "query" && + param.language.default.serializedName === parameter.serializedName + ) { + continuationTokenParameter = param; + break; } } } } - if (continuationTokenResponseSegment && op.responses) { - if (continuationTokenResponseSegment?.kind === "responseheader") { - // continuationToken is response header - for (const response of op.responses) { - if (isSchemaResponse(response) && response.protocol.http) { - for (const header of response.protocol.http.headers ?? []) { - if ( - header.header.toLowerCase() === - continuationTokenResponseSegment.serializedName.toLowerCase() - ) { - continuationTokenResponseHeader = header; - break; - } + } + if (continuationTokenResponseSegment && op.responses) { + if (continuationTokenResponseSegment?.kind === "responseheader") { + // continuationToken is response header + for (const response of op.responses) { + if (isSchemaResponse(response) && response.protocol.http) { + for (const header of response.protocol.http.headers ?? []) { + if ( + header.header.toLowerCase() === + continuationTokenResponseSegment.serializedName.toLowerCase() + ) { + continuationTokenResponseHeader = header; + break; } } - if (continuationTokenResponseHeader) { - break; - } } - } else if (continuationTokenResponseSegment?.kind === "property") { - // continuationToken is response body property - continuationTokenResponseProperty = findResponsePropertySegments( - op, - sdkMethod.pagingMetadata.continuationTokenResponseSegments, - ); + if (continuationTokenResponseHeader) { + break; + } } + } else if (continuationTokenResponseSegment?.kind === "property") { + // continuationToken is response body property + continuationTokenResponseProperty = findResponsePropertySegments( + op, + sdkMethod.pagingMetadata.continuationTokenResponseSegments, + ); } } 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 97db49dc1bd..ab87ee650c6 100644 --- a/packages/http-client-java/emitter/test/code-model.test.ts +++ b/packages/http-client-java/emitter/test/code-model.test.ts @@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url"; import { assert, describe, expect, it } from "vitest"; import { parse, stringify } from "yaml"; import { CodeModelBuilder } from "../src/code-model-builder.js"; +import type { PageableContinuationToken } from "../src/common/client.js"; import { addSubClient, createClient } from "../src/common/client.js"; import { addGlobalParameter, createCodeModel } from "../src/common/code-model.js"; import { @@ -40,6 +41,57 @@ import { createResponseErrorSchema } from "../src/external-schemas.js"; import { cloneOperationParameter } from "../src/operation-utils.js"; describe("code model characterization", () => { + it.each(["azure", "standard"] as const)( + "preserves continuation-token metadata for %s", + async (flavor) => { + const program = await compile( + NodeHost, + fileURLToPath(new URL("./fixtures/continuation-token.tsp", import.meta.url)), + { noEmit: true }, + ); + expectDiagnosticEmpty(program.diagnostics); + const model = await new CodeModelBuilder(program, { + program, + emitterOutputDir: "/code-model-test-output", + options: { flavor, "dev-options": {} }, + perf: { + startTimer: () => ({ end: () => 0 }), + time: (_label, callback) => callback(), + timeAsync: (_label, callback) => callback(), + report: () => {}, + }, + }).build(); + expectDiagnosticEmpty(program.diagnostics); + const operations = model.clients.flatMap((client) => + client.operationGroups.flatMap((group) => group.operations ?? []), + ); + expect(operations).toHaveLength(4); + for (const operation of operations) { + const name = operation.language.default.name; + const pageable = operation.extensions?.["x-ms-pageable"] as + { continuationToken?: PageableContinuationToken; nextLinkName?: string } | undefined; + assert.isDefined(pageable?.continuationToken); + const token = pageable.continuationToken; + expect(token.parameter).toBe( + operation.parameters?.find((p) => p.language.default.serializedName === "token"), + ); + expect(token.parameter.protocol.http?.in).toBe( + name.startsWith("query") ? "query" : "header", + ); + expect(pageable.nextLinkName).toBeUndefined(); + if (name.endsWith("Header")) { + expect(token.responseHeader?.header).toBe("next-token"); + expect(token.responseProperty).toBeUndefined(); + } else { + expect(token.responseProperty?.map((p) => p.serializedName)).toEqual( + name === "queryBody" ? ["next", "token"] : ["nextToken"], + ); + expect(token.responseHeader).toBeUndefined(); + } + } + }, + ); + it("preserves metadata defaults and supplied schema references", () => { const schema = createStringSchema("string", "A string"); const property = createProperty("value", "A value", schema, { diff --git a/packages/http-client-java/emitter/test/fixtures/continuation-token.tsp b/packages/http-client-java/emitter/test/fixtures/continuation-token.tsp new file mode 100644 index 00000000000..34934da1552 --- /dev/null +++ b/packages/http-client-java/emitter/test/fixtures/continuation-token.tsp @@ -0,0 +1,40 @@ +import "@typespec/http"; + +using TypeSpec.Http; + +@service +namespace ContinuationToken; + +model Item { + id: string; +} + +@list +@route("/query-body") +op queryBody(@query @continuationToken token?: string): { + @pageItems items: Item[]; + next?: { + @continuationToken token?: string; + }; +}; + +@list +@route("/header-body") +op headerBody(@header @continuationToken token?: string): { + @pageItems items: Item[]; + @continuationToken nextToken?: string; +}; + +@list +@route("/query-header") +op queryHeader(@query @continuationToken token?: string): { + @pageItems items: Item[]; + @header("next-token") @continuationToken nextToken?: string; +}; + +@list +@route("/header-header") +op headerHeader(@header @continuationToken token?: string): { + @pageItems items: Item[]; + @header("next-token") @continuationToken nextToken?: string; +}; diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ClientMethodParameterProcessor.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ClientMethodParameterProcessor.java index f200492b990..57b09640447 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ClientMethodParameterProcessor.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ClientMethodParameterProcessor.java @@ -75,7 +75,7 @@ private static List getCodeModelParameters(Request request, boolean i // Required path, body, header and query parameters are allowed codeModelParameters = request.getParameters().stream().filter(p -> { RequestParameterLocation location = p.getProtocol().getHttp().getIn(); - return p.isRequired() + return (p.isRequired() || MethodUtil.isContinuationTokenParameter(p)) && (location == RequestParameterLocation.PATH || location == RequestParameterLocation.BODY || location == RequestParameterLocation.HEADER diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ProxyMethodParameterProcessor.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ProxyMethodParameterProcessor.java index c4857f91b92..abcfda6b6e0 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ProxyMethodParameterProcessor.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ProxyMethodParameterProcessor.java @@ -63,6 +63,7 @@ Result process(Request request, String contentType) { // The protocol APIs (by Data Plane Generator) will put required path, body, query, header parameters // to method signature. if (parameter.isRequired() + || MethodUtil.isContinuationTokenParameter(parameter) || proxyMethodParameter.isConstant() || proxyMethodParameter.isFromClient() || isApiVersionParameter(parameter)) { diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java index ac07ae179e3..f89fcf3cfc8 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java @@ -171,6 +171,11 @@ protected static void addOptionalVariables(JavaBlock function, ClientMethod clie // Parameter is required and will be part of the method signature. continue; } + if (clientMethod.isPageStreamingType() + && clientMethod.getMethodPageDetails().getContinuationToken() != null + && parameter == clientMethod.getMethodPageDetails().getContinuationToken().getClientMethodParameter()) { + continue; + } final String defaultValue = parameterDefaultValueExpression(parameter); function.line("final %s %s = %s;", parameter.getClientType(), parameter.getName(), defaultValue == null ? "null" : defaultValue); @@ -859,6 +864,10 @@ protected void generateProtocolPagingSync(ClientMethod clientMethod, JavaType ty protected void generateProtocolPagingPlainSync(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { + if (settings.isAzureV1() && hasContinuationToken(clientMethod)) { + generateContinuationTokenPaging(clientMethod, typeBlock, settings, true); + return; + } addServiceMethodAnnotation(typeBlock, ReturnType.COLLECTION); if (clientMethod.getMethodPageDetails().nonNullNextLink()) { writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { @@ -1051,6 +1060,8 @@ protected void pagedSinglePageResponseConversion(ProxyMethod restAPIMethod, Clie } else { function.line(nextLinkLine(clientMethod)); } + } else if (clientMethod.getMethodPageDetails().getContinuationToken() != null) { + function.line(continuationTokenExpression(clientMethod, settings) + ","); } else { function.line("null,"); } @@ -1066,12 +1077,21 @@ protected void generatePagingSync(ClientMethod clientMethod, JavaType typeBlock, addServiceMethodAnnotation(typeBlock, ReturnType.COLLECTION); writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { addOptionalVariables(function, clientMethod); + String arguments = clientMethod.getMethodParameters() + .stream() + .filter(p -> !clientMethod.getMethodPageDetails().shouldHideParameter(p)) + .map(ClientMethodParameter::getName) + .collect(Collectors.joining(", ")); function.methodReturn(String.format("new PagedIterable<>(%s(%s))", - MethodNamer.getSimpleAsyncMethodName(clientMethod.getName()), clientMethod.getArgumentList())); + MethodNamer.getSimpleAsyncMethodName(clientMethod.getName()), arguments)); }); } protected void generatePagingPlainSync(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { + if (settings.isAzureV1() && hasContinuationToken(clientMethod)) { + generateContinuationTokenPaging(clientMethod, typeBlock, settings, true); + return; + } addServiceMethodAnnotation(typeBlock, ReturnType.COLLECTION); if (clientMethod.getMethodPageDetails().nonNullNextLink()) { writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { @@ -1116,6 +1136,10 @@ protected void generatePagingPlainSync(ClientMethod clientMethod, JavaType typeB } protected void generatePagingAsync(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { + if (settings.isAzureV1() && hasContinuationToken(clientMethod)) { + generateContinuationTokenPaging(clientMethod, typeBlock, settings, false); + return; + } addServiceMethodAnnotation(typeBlock, ReturnType.COLLECTION); if (clientMethod.getMethodPageDetails().nonNullNextLink()) { writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { @@ -1161,6 +1185,60 @@ private static void addServiceMethodAnnotation(JavaType typeBlock, ReturnType re typeBlock.annotation("ServiceMethod(returns = ReturnType." + returnType.name() + ")"); } + private static boolean hasContinuationToken(ClientMethod clientMethod) { + return !clientMethod.getMethodPageDetails().nonNullNextLink() + && clientMethod.getMethodPageDetails().getContinuationToken() != null; + } + + private void generateContinuationTokenPaging(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings, + boolean sync) { + addServiceMethodAnnotation(typeBlock, ReturnType.COLLECTION); + writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { + addOptionalVariables(function, clientMethod); + ClientMethodParameter tokenParameter + = clientMethod.getMethodPageDetails().getContinuationToken().getClientMethodParameter(); + String firstArguments = clientMethod.getMethodParameters() + .stream() + .map(p -> p == tokenParameter ? "null" : p.getName()) + .collect(Collectors.joining(", ")); + String methodName = sync + ? clientMethod.getProxyMethod().getPagingSinglePageMethodName() + : clientMethod.getProxyMethod().getPagingAsyncSinglePageMethodName(); + String firstPage = getPagingSinglePageExpression(clientMethod, methodName, firstArguments, settings); + String nextPage = getPagingSinglePageExpression(clientMethod, methodName, clientMethod.getArgumentList(), + settings, tokenParameter.getName()); + function.line("return new %s<>(", sync ? "PagedIterable" : "PagedFlux"); + function.indent(() -> { + function.line(firstPage + ","); + function.line(nextPage + ");"); + }); + }); + } + + private static String continuationTokenExpression(ClientMethod clientMethod, JavaSettings settings) { + MethodPageDetails.ContinuationToken token = clientMethod.getMethodPageDetails().getContinuationToken(); + if (token.getResponseHeaderSerializedName() != null) { + return "res.getHeaders().getValue(HttpHeaderName.fromString(" + + ClassType.STRING.defaultValueExpression(token.getResponseHeaderSerializedName()) + "))"; + } + List properties = token.getResponsePropertyReference(); + if (CoreUtils.isNullOrEmpty(properties)) { + throw new IllegalStateException("Continuation-token paging requires a response token property or header."); + } + if (isXmlPagingResponse(clientMethod, settings)) { + return "getXmlNextLink(res.getValue(), " + xmlPropertyPath(properties) + ")"; + } else if (settings.isDataPlaneClient()) { + return "getNextLink(res.getValue(), " + serializedPropertyPath(properties) + ")"; + } + String expression = "res.getValue()"; + StringBuilder nullChecks = new StringBuilder(expression + " == null"); + for (ModelPropertySegment property : properties) { + expression += "." + CodeNamer.getModelNamer().modelPropertyGetterName(property.getProperty()) + "()"; + nullChecks.append(" || ").append(expression).append(" == null"); + } + return nullChecks + " ? null : " + expression; + } + protected void addQueryParameterReInjectionLogic(MethodPageDetails.NextLinkReInjection nextLinkReInjection, JavaBlock javaBlock) { javaBlock.line("if (requestOptions != null) {"); @@ -1516,6 +1594,8 @@ protected void generatePagedAsyncSinglePage(ClientMethod clientMethod, JavaType } else { function.line(nextLinkLine(clientMethod)); } + } else if (clientMethod.getMethodPageDetails().getContinuationToken() != null) { + function.line(continuationTokenExpression(clientMethod, settings) + ","); } else { function.line("null,"); } @@ -1842,13 +1922,20 @@ protected void generateProtocolLongRunningBeginAsync(ClientMethod clientMethod, private String getPagingSinglePageExpression(ClientMethod clientMethod, String methodName, String argumentLine, JavaSettings settings) { + return getPagingSinglePageExpression(clientMethod, methodName, argumentLine, settings, null); + } + + private String getPagingSinglePageExpression(ClientMethod clientMethod, String methodName, String argumentLine, + JavaSettings settings, String continuationParameter) { if (settings.isDataPlaneClient() && settings.isPageSizeEnabled()) { Optional maxPageSizeSerializedName = MethodUtil.serializedNameOfMaxPageSizeParameter(clientMethod.getProxyMethod()); if (maxPageSizeSerializedName.isPresent()) { argumentLine = argumentLine.replace("requestOptions", "requestOptionsLocal"); StringBuilder expression = new StringBuilder(); - expression.append("(pageSize) -> {"); + expression.append(continuationParameter == null + ? "(pageSize) -> {" + : "(" + continuationParameter + ", pageSize) -> {"); expression.append( "RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions;") .append("if (pageSize != null) {") @@ -1867,7 +1954,8 @@ private String getPagingSinglePageExpression(ClientMethod clientMethod, String m } if (settings.isAzureV1()) { - return String.format("() -> %s(%s)", methodName, argumentLine); + return String.format("%s -> %s(%s)", continuationParameter == null ? "()" : continuationParameter, + methodName, argumentLine); } else { return String.format("pagingOptions -> { %s return %s(%s); }", getLogExceptionExpressionForPagingOptions(clientMethod), methodName, argumentLine); diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplateBase.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplateBase.java index 3dc7497a9f0..650ce00018a 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplateBase.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplateBase.java @@ -43,6 +43,7 @@ protected static void generateProtocolMethodJavadoc(ClientMethod clientMethod, J List queryParameters = clientMethod.getProxyMethod() .getAllParameters() .stream() + .filter(p -> !isHiddenContinuationToken(clientMethod, p)) .filter(p -> RequestParameterLocation.QUERY.equals(p.getRequestParameterLocation()) // ignore if synthesized by modelerfour, i.e. api-version && p.getOrigin() == ParameterSynthesizedOrigin.NONE) @@ -55,6 +56,7 @@ protected static void generateProtocolMethodJavadoc(ClientMethod clientMethod, J List headerParameters = clientMethod.getProxyMethod() .getAllParameters() .stream() + .filter(p -> !isHiddenContinuationToken(clientMethod, p)) .filter(p -> RequestParameterLocation.HEADER.equals(p.getRequestParameterLocation())) // ignore if synthesized by modelerfour and is constant // we would want user to provide a correct "content-type" if it is not a constant @@ -125,6 +127,9 @@ protected static void generateProtocolMethodJavadoc(ClientMethod clientMethod, J } clientMethod.getParameters() + .stream() + .filter( + p -> !clientMethod.isPageStreamingType() || !clientMethod.getMethodPageDetails().shouldHideParameter(p)) .forEach(p -> commentBlock.param(p.getName(), MethodUtil.methodParameterDescriptionOrDefault(p))); if (clientMethod.getProxyMethod() != null) { generateJavadocExceptions(clientMethod, commentBlock, false); @@ -138,6 +143,17 @@ protected static void generateProtocolMethodJavadoc(ClientMethod clientMethod, J } } + private static boolean isHiddenContinuationToken(ClientMethod clientMethod, ProxyMethodParameter parameter) { + if (!clientMethod.isPageStreamingType() || clientMethod.getMethodPageDetails().getContinuationToken() == null) { + return false; + } + ProxyMethodParameter token = clientMethod.getMethodPageDetails().getContinuationToken().getRequestParameter(); + return token.getRequestParameterLocation() == parameter.getRequestParameterLocation() + && (token.getRequestParameterLocation() == RequestParameterLocation.HEADER + ? token.getRequestParameterName().equalsIgnoreCase(parameter.getRequestParameterName()) + : token.getRequestParameterName().equals(parameter.getRequestParameterName())); + } + protected static void generateJavadocExceptions(ClientMethod clientMethod, JavaJavadocComment commentBlock, boolean useFullClassName) { ProxyMethod restAPIMethod = clientMethod.getProxyMethod(); 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 e72c75ea5b3..1af99ed6801 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 @@ -430,27 +430,32 @@ protected void writeMethodImplementation(ClientMethod protocolMethod, ClientMeth } // invocation with protocol method parameters and RequestOptions - String invocationExpression = protocolMethod.getMethodInputParameters().stream().map(p -> { - String parameterName = p.getName(); - String expression = parameterExpressionsMap.get(parameterName); - IType parameterRawType = p.getRawType(); - if (isJsonMergePatchOperation - && ClientModelUtil.isClientModel(parameterRawType) - && RequestParameterLocation.BODY.equals(p.getRequestParameterLocation()) - && ClientModelUtil.isJsonMergePatchModel( - ClientModelUtil.getClientModel(((ClassType) parameterRawType).getName()), - JavaSettings.getInstance())) { - ClientModel clientModel = ClientModelUtil.getClientModel(((ClassType) parameterRawType).getName()); - // If it is polymorphic model, we need to enable json merge patch through root parent model - ClientModel rootParentModel = ClientModelUtil.getRootParent(clientModel); - IType rootParentModelType = rootParentModel.getType(); - - return writeParameterConversionExpressionWithJsonMergePatchEnabled(methodBlock, - rootParentModelType.toString(), parameterName, expression); - } else { - return expression == null ? parameterName : expression; - } - }).collect(Collectors.joining(", ")); + String invocationExpression = protocolMethod.getMethodInputParameters() + .stream() + .filter(p -> !protocolMethod.isPageStreamingType() + || !protocolMethod.getMethodPageDetails().shouldHideParameter(p)) + .map(p -> { + String parameterName = p.getName(); + String expression = parameterExpressionsMap.get(parameterName); + IType parameterRawType = p.getRawType(); + if (isJsonMergePatchOperation + && ClientModelUtil.isClientModel(parameterRawType) + && RequestParameterLocation.BODY.equals(p.getRequestParameterLocation()) + && ClientModelUtil.isJsonMergePatchModel( + ClientModelUtil.getClientModel(((ClassType) parameterRawType).getName()), + JavaSettings.getInstance())) { + ClientModel clientModel = ClientModelUtil.getClientModel(((ClassType) parameterRawType).getName()); + // If it is polymorphic model, we need to enable json merge patch through root parent model + ClientModel rootParentModel = ClientModelUtil.getRootParent(clientModel); + IType rootParentModelType = rootParentModel.getType(); + + return writeParameterConversionExpressionWithJsonMergePatchEnabled(methodBlock, + rootParentModelType.toString(), parameterName, expression); + } else { + return expression == null ? parameterName : expression; + } + }) + .collect(Collectors.joining(", ")); // write the invocation of protocol method, and related type conversion writeInvocationAndConversion(convenienceMethod, protocolMethod, invocationExpression, methodBlock, 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 d06633dbdbf..5d2bad78102 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 @@ -193,6 +193,8 @@ protected void writeThrowException(ClientMethodType methodType, String exception private static String getMethodInvokeViaAsyncClient(ClientMethod convenienceMethod) { List parameterNames = convenienceMethod.getMethodInputParameters() .stream() + .filter(p -> !convenienceMethod.isPageStreamingType() + || !convenienceMethod.getMethodPageDetails().shouldHideParameter(p)) .map(ClientMethodParameter::getName) .collect(Collectors.toList()); diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ServiceSyncClientWrapAsyncClientTemplate.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ServiceSyncClientWrapAsyncClientTemplate.java index 85a1c3069c8..6a13a6c1171 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ServiceSyncClientWrapAsyncClientTemplate.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ServiceSyncClientWrapAsyncClientTemplate.java @@ -68,6 +68,8 @@ private String clientReference() { protected void writeMethodInvocation(ClientMethod clientMethod, JavaBlock function, boolean shouldReturn) { List parameterNames = clientMethod.getMethodInputParameters() .stream() + .filter(p -> !clientMethod.isPageStreamingType() + || !clientMethod.getMethodPageDetails().shouldHideParameter(p)) .map(ClientMethodParameter::getName) .collect(Collectors.toList()); diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/WrapperClientMethodTemplate.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/WrapperClientMethodTemplate.java index 37a162525c3..f6cba187c1f 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/WrapperClientMethodTemplate.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/WrapperClientMethodTemplate.java @@ -104,7 +104,11 @@ public void write(ClientMethod clientMethod, JavaType typeBlock) { protected void writeMethodInvocation(ClientMethod clientMethod, JavaBlock function, boolean shouldReturn) { List parameters = clientMethod.getMethodInputParameters(); function.line((shouldReturn ? "return " : "") + "this.serviceClient.%1$s(%2$s);", clientMethod.getName(), - parameters.stream().map(ClientMethodParameter::getName).collect(Collectors.joining(", "))); + parameters.stream() + .filter(p -> !clientMethod.isPageStreamingType() + || !clientMethod.getMethodPageDetails().shouldHideParameter(p)) + .map(ClientMethodParameter::getName) + .collect(Collectors.joining(", "))); } protected void generateJavadoc(ClientMethod clientMethod, JavaType typeBlock, ProxyMethod restAPIMethod) { @@ -112,6 +116,10 @@ protected void generateJavadoc(ClientMethod clientMethod, JavaType typeBlock, Pr comment.description(clientMethod.getDescription()); List methodParameters = clientMethod.getMethodInputParameters(); for (ClientMethodParameter parameter : methodParameters) { + if (clientMethod.isPageStreamingType() + && clientMethod.getMethodPageDetails().shouldHideParameter(parameter)) { + continue; + } comment.param(parameter.getName(), parameter.getDescription()); } if (clientMethod.hasParameterDeclaration()) { diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/MethodUtil.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/MethodUtil.java index 0329008a3a4..0863ed2018f 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/MethodUtil.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/MethodUtil.java @@ -57,6 +57,20 @@ public class MethodUtil { public static final String CONTENT_TYPE_APPLICATION_JSON_ERROR_WEIGHT = "application/json;q=0.9"; + /** + * Checks whether a parameter carries a paging continuation token. + * + * @param parameter the operation parameter. + * @return whether the parameter is the paging continuation token. + */ + public static boolean isContinuationTokenParameter(Parameter parameter) { + Operation operation = parameter.getOperation(); + return operation != null + && operation.isPageable() + && operation.getExtensions().getXmsPageable().getContinuationToken() != null + && operation.getExtensions().getXmsPageable().getContinuationToken().getParameter() == parameter; + } + private static final Set REPEATABILITY_REQUEST_HTTP_METHODS = EnumSet.of(HttpMethod.PUT, HttpMethod.PATCH, HttpMethod.DELETE, HttpMethod.POST); diff --git a/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ContinuationTokenPagingTests.java b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ContinuationTokenPagingTests.java new file mode 100644 index 00000000000..884472c8107 --- /dev/null +++ b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ContinuationTokenPagingTests.java @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.typespec.http.client.generator.core.template; + +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.ClientMethodParameter; +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.MethodPageDetails; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ReturnValue; +import com.microsoft.typespec.http.client.generator.core.model.javamodel.JavaClass; +import com.microsoft.typespec.http.client.generator.core.model.javamodel.JavaFileContents; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class ContinuationTokenPagingTests { + @Test + public void hidesTokenInWrapperDelegation() { + JavaFileContents contents = new JavaFileContents(); + WrapperClientMethodTemplate.getInstance().write(pagingMethod(), new JavaClass(contents)); + String output = contents.toString(); + Assertions.assertTrue(output.contains("list(String filter)")); + Assertions.assertTrue(output.contains("return this.serviceClient.list(filter);")); + Assertions.assertFalse(output.contains("@param token")); + } + + @Test + public void hidesTokenInSyncOverAsyncDelegation() { + JavaFileContents contents = new JavaFileContents(); + ServiceSyncClientWrapAsyncClientTemplate.getInstance().writeMethod(pagingMethod(), new JavaClass(contents)); + String output = contents.toString(); + Assertions.assertTrue(output.contains("list(String filter)")); + Assertions.assertTrue(output.contains("return new PagedIterable<>(this.client.list(filter));")); + Assertions.assertFalse(output.contains("@param token")); + } + + private static ClientMethod pagingMethod() { + ClientMethodParameter token = new ClientMethodParameter.Builder().name("token") + .annotations(List.of()) + .description("The continuation token.") + .wireType(ClassType.STRING) + .build(); + ClientMethodParameter filter = new ClientMethodParameter.Builder().name("filter") + .annotations(List.of()) + .description("The filter.") + .wireType(ClassType.STRING) + .required(true) + .build(); + MethodPageDetails details = new MethodPageDetails(List.of(), null, null, null, + new MethodPageDetails.ContinuationToken(null, token, "next-token", null), null, null); + return new ClientMethod.Builder().name("list") + .description("List items.") + .parameters(List.of(filter, token)) + .type(ClientMethodType.PagingSync) + .methodPageDetails(details) + .returnValue(new ReturnValue("The items.", GenericType.pagedIterable(ClassType.STRING))) + .build(); + } +} diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/ServerDrivenPaginationContinuationTokenAsyncClient.java b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/ServerDrivenPaginationContinuationTokenAsyncClient.java index 975886ae606..5f4f6345552 100644 --- a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/ServerDrivenPaginationContinuationTokenAsyncClient.java +++ b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/ServerDrivenPaginationContinuationTokenAsyncClient.java @@ -47,7 +47,6 @@ public final class ServerDrivenPaginationContinuationTokenAsyncClient { * * * - * * *
Query Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
barStringNoThe bar parameter
* You can add these to a request with {@link RequestOptions#addQueryParam} @@ -95,7 +94,6 @@ public PagedFlux requestQueryResponseBody(RequestOptions requestOpti * * * - * * *
Header Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
fooStringNoThe foo parameter
* You can add these to a request with {@link RequestOptions#addHeader} @@ -129,7 +127,6 @@ public PagedFlux requestHeaderResponseBody(RequestOptions requestOpt * * * - * * *
Query Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
barStringNoThe bar parameter
* You can add these to a request with {@link RequestOptions#addQueryParam} @@ -177,7 +174,6 @@ public PagedFlux requestQueryResponseHeader(RequestOptions requestOp * * * - * * *
Header Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
fooStringNoThe foo parameter
* You can add these to a request with {@link RequestOptions#addHeader} @@ -211,7 +207,6 @@ public PagedFlux requestHeaderResponseHeader(RequestOptions requestO * * * - * * *
Query Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
barStringNoThe bar parameter
* You can add these to a request with {@link RequestOptions#addQueryParam} @@ -259,7 +254,6 @@ public PagedFlux requestQueryNestedResponseBody(RequestOptions reque * * * - * * *
Header Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
fooStringNoThe foo parameter
* You can add these to a request with {@link RequestOptions#addHeader} @@ -290,7 +284,6 @@ public PagedFlux requestHeaderNestedResponseBody(RequestOptions requ /** * The requestQueryResponseBody operation. * - * @param token The token parameter. * @param foo The foo parameter. * @param bar The bar parameter. * @throws IllegalArgumentException thrown if parameters fail the validation. @@ -303,12 +296,9 @@ public PagedFlux requestHeaderNestedResponseBody(RequestOptions requ */ @Generated @ServiceMethod(returns = ReturnType.COLLECTION) - public PagedFlux requestQueryResponseBody(String token, String foo, String bar) { + public PagedFlux requestQueryResponseBody(String foo, String bar) { // Generated convenience method for requestQueryResponseBody RequestOptions requestOptions = new RequestOptions(); - if (token != null) { - requestOptions.addQueryParam("token", token, false); - } if (foo != null) { requestOptions.setHeader(HttpHeaderName.fromString("foo"), foo); } @@ -377,7 +367,6 @@ public PagedFlux requestQueryResponseBody() { /** * The requestHeaderResponseBody operation. * - * @param token The token parameter. * @param foo The foo parameter. * @param bar The bar parameter. * @throws IllegalArgumentException thrown if parameters fail the validation. @@ -390,12 +379,9 @@ public PagedFlux requestQueryResponseBody() { */ @Generated @ServiceMethod(returns = ReturnType.COLLECTION) - public PagedFlux requestHeaderResponseBody(String token, String foo, String bar) { + public PagedFlux requestHeaderResponseBody(String foo, String bar) { // Generated convenience method for requestHeaderResponseBody RequestOptions requestOptions = new RequestOptions(); - if (token != null) { - requestOptions.setHeader(HttpHeaderName.fromString("token"), token); - } if (foo != null) { requestOptions.setHeader(HttpHeaderName.fromString("foo"), foo); } @@ -464,7 +450,6 @@ public PagedFlux requestHeaderResponseBody() { /** * The requestQueryResponseHeader operation. * - * @param token The token parameter. * @param foo The foo parameter. * @param bar The bar parameter. * @throws IllegalArgumentException thrown if parameters fail the validation. @@ -477,12 +462,9 @@ public PagedFlux requestHeaderResponseBody() { */ @Generated @ServiceMethod(returns = ReturnType.COLLECTION) - public PagedFlux requestQueryResponseHeader(String token, String foo, String bar) { + public PagedFlux requestQueryResponseHeader(String foo, String bar) { // Generated convenience method for requestQueryResponseHeader RequestOptions requestOptions = new RequestOptions(); - if (token != null) { - requestOptions.addQueryParam("token", token, false); - } if (foo != null) { requestOptions.setHeader(HttpHeaderName.fromString("foo"), foo); } @@ -551,7 +533,6 @@ public PagedFlux requestQueryResponseHeader() { /** * The requestHeaderResponseHeader operation. * - * @param token The token parameter. * @param foo The foo parameter. * @param bar The bar parameter. * @throws IllegalArgumentException thrown if parameters fail the validation. @@ -564,12 +545,9 @@ public PagedFlux requestQueryResponseHeader() { */ @Generated @ServiceMethod(returns = ReturnType.COLLECTION) - public PagedFlux requestHeaderResponseHeader(String token, String foo, String bar) { + public PagedFlux requestHeaderResponseHeader(String foo, String bar) { // Generated convenience method for requestHeaderResponseHeader RequestOptions requestOptions = new RequestOptions(); - if (token != null) { - requestOptions.setHeader(HttpHeaderName.fromString("token"), token); - } if (foo != null) { requestOptions.setHeader(HttpHeaderName.fromString("foo"), foo); } @@ -638,7 +616,6 @@ public PagedFlux requestHeaderResponseHeader() { /** * The requestQueryNestedResponseBody operation. * - * @param token The token parameter. * @param foo The foo parameter. * @param bar The bar parameter. * @throws IllegalArgumentException thrown if parameters fail the validation. @@ -651,12 +628,9 @@ public PagedFlux requestHeaderResponseHeader() { */ @Generated @ServiceMethod(returns = ReturnType.COLLECTION) - public PagedFlux requestQueryNestedResponseBody(String token, String foo, String bar) { + public PagedFlux requestQueryNestedResponseBody(String foo, String bar) { // Generated convenience method for requestQueryNestedResponseBody RequestOptions requestOptions = new RequestOptions(); - if (token != null) { - requestOptions.addQueryParam("token", token, false); - } if (foo != null) { requestOptions.setHeader(HttpHeaderName.fromString("foo"), foo); } @@ -725,7 +699,6 @@ public PagedFlux requestQueryNestedResponseBody() { /** * The requestHeaderNestedResponseBody operation. * - * @param token The token parameter. * @param foo The foo parameter. * @param bar The bar parameter. * @throws IllegalArgumentException thrown if parameters fail the validation. @@ -738,12 +711,9 @@ public PagedFlux requestQueryNestedResponseBody() { */ @Generated @ServiceMethod(returns = ReturnType.COLLECTION) - public PagedFlux requestHeaderNestedResponseBody(String token, String foo, String bar) { + public PagedFlux requestHeaderNestedResponseBody(String foo, String bar) { // Generated convenience method for requestHeaderNestedResponseBody RequestOptions requestOptions = new RequestOptions(); - if (token != null) { - requestOptions.setHeader(HttpHeaderName.fromString("token"), token); - } if (foo != null) { requestOptions.setHeader(HttpHeaderName.fromString("foo"), foo); } diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/ServerDrivenPaginationContinuationTokenClient.java b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/ServerDrivenPaginationContinuationTokenClient.java index 545b4d29e28..8137c2bd88e 100644 --- a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/ServerDrivenPaginationContinuationTokenClient.java +++ b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/ServerDrivenPaginationContinuationTokenClient.java @@ -43,7 +43,6 @@ public final class ServerDrivenPaginationContinuationTokenClient { * * * - * * *
Query Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
barStringNoThe bar parameter
* You can add these to a request with {@link RequestOptions#addQueryParam} @@ -91,7 +90,6 @@ public PagedIterable requestQueryResponseBody(RequestOptions request * * * - * * *
Header Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
fooStringNoThe foo parameter
* You can add these to a request with {@link RequestOptions#addHeader} @@ -125,7 +123,6 @@ public PagedIterable requestHeaderResponseBody(RequestOptions reques * * * - * * *
Query Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
barStringNoThe bar parameter
* You can add these to a request with {@link RequestOptions#addQueryParam} @@ -173,7 +170,6 @@ public PagedIterable requestQueryResponseHeader(RequestOptions reque * * * - * * *
Header Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
fooStringNoThe foo parameter
* You can add these to a request with {@link RequestOptions#addHeader} @@ -207,7 +203,6 @@ public PagedIterable requestHeaderResponseHeader(RequestOptions requ * * * - * * *
Query Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
barStringNoThe bar parameter
* You can add these to a request with {@link RequestOptions#addQueryParam} @@ -255,7 +250,6 @@ public PagedIterable requestQueryNestedResponseBody(RequestOptions r * * * - * * *
Header Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
fooStringNoThe foo parameter
* You can add these to a request with {@link RequestOptions#addHeader} @@ -286,7 +280,6 @@ public PagedIterable requestHeaderNestedResponseBody(RequestOptions /** * The requestQueryResponseBody operation. * - * @param token The token parameter. * @param foo The foo parameter. * @param bar The bar parameter. * @throws IllegalArgumentException thrown if parameters fail the validation. @@ -299,12 +292,9 @@ public PagedIterable requestHeaderNestedResponseBody(RequestOptions */ @Generated @ServiceMethod(returns = ReturnType.COLLECTION) - public PagedIterable requestQueryResponseBody(String token, String foo, String bar) { + public PagedIterable requestQueryResponseBody(String foo, String bar) { // Generated convenience method for requestQueryResponseBody RequestOptions requestOptions = new RequestOptions(); - if (token != null) { - requestOptions.addQueryParam("token", token, false); - } if (foo != null) { requestOptions.setHeader(HttpHeaderName.fromString("foo"), foo); } @@ -337,7 +327,6 @@ public PagedIterable requestQueryResponseBody() { /** * The requestHeaderResponseBody operation. * - * @param token The token parameter. * @param foo The foo parameter. * @param bar The bar parameter. * @throws IllegalArgumentException thrown if parameters fail the validation. @@ -350,12 +339,9 @@ public PagedIterable requestQueryResponseBody() { */ @Generated @ServiceMethod(returns = ReturnType.COLLECTION) - public PagedIterable requestHeaderResponseBody(String token, String foo, String bar) { + public PagedIterable requestHeaderResponseBody(String foo, String bar) { // Generated convenience method for requestHeaderResponseBody RequestOptions requestOptions = new RequestOptions(); - if (token != null) { - requestOptions.setHeader(HttpHeaderName.fromString("token"), token); - } if (foo != null) { requestOptions.setHeader(HttpHeaderName.fromString("foo"), foo); } @@ -388,7 +374,6 @@ public PagedIterable requestHeaderResponseBody() { /** * The requestQueryResponseHeader operation. * - * @param token The token parameter. * @param foo The foo parameter. * @param bar The bar parameter. * @throws IllegalArgumentException thrown if parameters fail the validation. @@ -401,12 +386,9 @@ public PagedIterable requestHeaderResponseBody() { */ @Generated @ServiceMethod(returns = ReturnType.COLLECTION) - public PagedIterable requestQueryResponseHeader(String token, String foo, String bar) { + public PagedIterable requestQueryResponseHeader(String foo, String bar) { // Generated convenience method for requestQueryResponseHeader RequestOptions requestOptions = new RequestOptions(); - if (token != null) { - requestOptions.addQueryParam("token", token, false); - } if (foo != null) { requestOptions.setHeader(HttpHeaderName.fromString("foo"), foo); } @@ -439,7 +421,6 @@ public PagedIterable requestQueryResponseHeader() { /** * The requestHeaderResponseHeader operation. * - * @param token The token parameter. * @param foo The foo parameter. * @param bar The bar parameter. * @throws IllegalArgumentException thrown if parameters fail the validation. @@ -452,12 +433,9 @@ public PagedIterable requestQueryResponseHeader() { */ @Generated @ServiceMethod(returns = ReturnType.COLLECTION) - public PagedIterable requestHeaderResponseHeader(String token, String foo, String bar) { + public PagedIterable requestHeaderResponseHeader(String foo, String bar) { // Generated convenience method for requestHeaderResponseHeader RequestOptions requestOptions = new RequestOptions(); - if (token != null) { - requestOptions.setHeader(HttpHeaderName.fromString("token"), token); - } if (foo != null) { requestOptions.setHeader(HttpHeaderName.fromString("foo"), foo); } @@ -490,7 +468,6 @@ public PagedIterable requestHeaderResponseHeader() { /** * The requestQueryNestedResponseBody operation. * - * @param token The token parameter. * @param foo The foo parameter. * @param bar The bar parameter. * @throws IllegalArgumentException thrown if parameters fail the validation. @@ -503,12 +480,9 @@ public PagedIterable requestHeaderResponseHeader() { */ @Generated @ServiceMethod(returns = ReturnType.COLLECTION) - public PagedIterable requestQueryNestedResponseBody(String token, String foo, String bar) { + public PagedIterable requestQueryNestedResponseBody(String foo, String bar) { // Generated convenience method for requestQueryNestedResponseBody RequestOptions requestOptions = new RequestOptions(); - if (token != null) { - requestOptions.addQueryParam("token", token, false); - } if (foo != null) { requestOptions.setHeader(HttpHeaderName.fromString("foo"), foo); } @@ -541,7 +515,6 @@ public PagedIterable requestQueryNestedResponseBody() { /** * The requestHeaderNestedResponseBody operation. * - * @param token The token parameter. * @param foo The foo parameter. * @param bar The bar parameter. * @throws IllegalArgumentException thrown if parameters fail the validation. @@ -554,12 +527,9 @@ public PagedIterable requestQueryNestedResponseBody() { */ @Generated @ServiceMethod(returns = ReturnType.COLLECTION) - public PagedIterable requestHeaderNestedResponseBody(String token, String foo, String bar) { + public PagedIterable requestHeaderNestedResponseBody(String foo, String bar) { // Generated convenience method for requestHeaderNestedResponseBody RequestOptions requestOptions = new RequestOptions(); - if (token != null) { - requestOptions.setHeader(HttpHeaderName.fromString("token"), token); - } if (foo != null) { requestOptions.setHeader(HttpHeaderName.fromString("foo"), foo); } diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/XmlPaginationAsyncClient.java b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/XmlPaginationAsyncClient.java index 199b858542a..a94f6044b80 100644 --- a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/XmlPaginationAsyncClient.java +++ b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/XmlPaginationAsyncClient.java @@ -47,13 +47,6 @@ public final class XmlPaginationAsyncClient { /** * The listWithContinuation operation. - *

Query Parameters

- * - * - * - * - *
Query Parameters
NameTypeRequiredDescription
markerStringNoThe marker parameter
- * You can add these to a request with {@link RequestOptions#addQueryParam} *

Response Body Schema

* *
@@ -104,48 +97,6 @@ public PagedFlux listWithNextLink(RequestOptions requestOptions) {
         return this.serviceClient.listWithNextLinkAsync(requestOptions);
     }
 
-    /**
-     * The listWithContinuation operation.
-     * 
-     * @param marker The marker parameter.
-     * @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 the XML response for listing pets as paginated response with {@link PagedFlux}.
-     */
-    @Generated
-    @ServiceMethod(returns = ReturnType.COLLECTION)
-    public PagedFlux listWithContinuation(String marker) {
-        // Generated convenience method for listWithContinuation
-        RequestOptions requestOptions = new RequestOptions();
-        if (marker != null) {
-            requestOptions.addQueryParam("marker", marker, false);
-        }
-        PagedFlux pagedFluxResponse = listWithContinuation(requestOptions);
-        return PagedFlux.create(() -> (continuationTokenParam, pageSizeParam) -> {
-            Flux> flux;
-            if (pageSizeParam == null) {
-                flux = (continuationTokenParam == null)
-                    ? pagedFluxResponse.byPage().take(1)
-                    : pagedFluxResponse.byPage(continuationTokenParam).take(1);
-            } else {
-                flux = (continuationTokenParam == null)
-                    ? pagedFluxResponse.byPage(pageSizeParam).take(1)
-                    : pagedFluxResponse.byPage(continuationTokenParam, pageSizeParam).take(1);
-            }
-            return flux.map(pagedResponse -> new PagedResponseBase(pagedResponse.getRequest(),
-                pagedResponse.getStatusCode(), pagedResponse.getHeaders(),
-                pagedResponse.getValue()
-                    .stream()
-                    .map(protocolMethodData -> protocolMethodData.toObject(XmlPet.class, XML_SERIALIZER))
-                    .collect(Collectors.toList()),
-                pagedResponse.getContinuationToken(), null));
-        });
-    }
-
     /**
      * The listWithContinuation operation.
      * 
diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/XmlPaginationClient.java b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/XmlPaginationClient.java
index c1d5f6fd3d5..f11694e747a 100644
--- a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/XmlPaginationClient.java
+++ b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/XmlPaginationClient.java
@@ -43,13 +43,6 @@ public final class XmlPaginationClient {
 
     /**
      * The listWithContinuation operation.
-     * 

Query Parameters

- * - * - * - * - *
Query Parameters
NameTypeRequiredDescription
markerStringNoThe marker parameter
- * You can add these to a request with {@link RequestOptions#addQueryParam} *

Response Body Schema

* *
@@ -100,30 +93,6 @@ public PagedIterable listWithNextLink(RequestOptions requestOptions)
         return this.serviceClient.listWithNextLink(requestOptions);
     }
 
-    /**
-     * The listWithContinuation operation.
-     * 
-     * @param marker The marker parameter.
-     * @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 the XML response for listing pets as paginated response with {@link PagedIterable}.
-     */
-    @Generated
-    @ServiceMethod(returns = ReturnType.COLLECTION)
-    public PagedIterable listWithContinuation(String marker) {
-        // Generated convenience method for listWithContinuation
-        RequestOptions requestOptions = new RequestOptions();
-        if (marker != null) {
-            requestOptions.addQueryParam("marker", marker, false);
-        }
-        return serviceClient.listWithContinuation(requestOptions)
-            .mapPage(bodyItemValue -> bodyItemValue.toObject(XmlPet.class, XML_SERIALIZER));
-    }
-
     /**
      * The listWithContinuation operation.
      * 
diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/ServerDrivenPaginationContinuationTokensImpl.java b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/ServerDrivenPaginationContinuationTokensImpl.java
index 3594168cfdd..13fd03eea13 100644
--- a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/ServerDrivenPaginationContinuationTokensImpl.java
+++ b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/ServerDrivenPaginationContinuationTokensImpl.java
@@ -9,6 +9,7 @@
 import com.azure.core.annotation.HeaderParam;
 import com.azure.core.annotation.Host;
 import com.azure.core.annotation.HostParam;
+import com.azure.core.annotation.QueryParam;
 import com.azure.core.annotation.ReturnType;
 import com.azure.core.annotation.ServiceInterface;
 import com.azure.core.annotation.ServiceMethod;
@@ -17,6 +18,7 @@
 import com.azure.core.exception.HttpResponseException;
 import com.azure.core.exception.ResourceModifiedException;
 import com.azure.core.exception.ResourceNotFoundException;
+import com.azure.core.http.HttpHeaderName;
 import com.azure.core.http.rest.PagedFlux;
 import com.azure.core.http.rest.PagedIterable;
 import com.azure.core.http.rest.PagedResponse;
@@ -71,7 +73,8 @@ public interface ServerDrivenPaginationContinuationTokensService {
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Mono> requestQueryResponseBody(@HostParam("endpoint") String endpoint,
-            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
+            @QueryParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
+            Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-query-response-body")
         @ExpectedResponses({ 200 })
@@ -80,7 +83,8 @@ Mono> requestQueryResponseBody(@HostParam("endpoint") Strin
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Response requestQueryResponseBodySync(@HostParam("endpoint") String endpoint,
-            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
+            @QueryParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
+            Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-header-response-body")
         @ExpectedResponses({ 200 })
@@ -89,7 +93,8 @@ Response requestQueryResponseBodySync(@HostParam("endpoint") String
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Mono> requestHeaderResponseBody(@HostParam("endpoint") String endpoint,
-            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
+            @HeaderParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
+            Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-header-response-body")
         @ExpectedResponses({ 200 })
@@ -98,7 +103,8 @@ Mono> requestHeaderResponseBody(@HostParam("endpoint") Stri
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Response requestHeaderResponseBodySync(@HostParam("endpoint") String endpoint,
-            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
+            @HeaderParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
+            Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-query-response-header")
         @ExpectedResponses({ 200 })
@@ -107,7 +113,8 @@ Response requestHeaderResponseBodySync(@HostParam("endpoint") String
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Mono> requestQueryResponseHeader(@HostParam("endpoint") String endpoint,
-            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
+            @QueryParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
+            Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-query-response-header")
         @ExpectedResponses({ 200 })
@@ -116,7 +123,8 @@ Mono> requestQueryResponseHeader(@HostParam("endpoint") Str
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Response requestQueryResponseHeaderSync(@HostParam("endpoint") String endpoint,
-            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
+            @QueryParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
+            Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-header-response-header")
         @ExpectedResponses({ 200 })
@@ -125,7 +133,8 @@ Response requestQueryResponseHeaderSync(@HostParam("endpoint") Strin
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Mono> requestHeaderResponseHeader(@HostParam("endpoint") String endpoint,
-            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
+            @HeaderParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
+            Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-header-response-header")
         @ExpectedResponses({ 200 })
@@ -134,7 +143,8 @@ Mono> requestHeaderResponseHeader(@HostParam("endpoint") St
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Response requestHeaderResponseHeaderSync(@HostParam("endpoint") String endpoint,
-            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
+            @HeaderParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
+            Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-query-nested-response-body")
         @ExpectedResponses({ 200 })
@@ -143,7 +153,8 @@ Response requestHeaderResponseHeaderSync(@HostParam("endpoint") Stri
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Mono> requestQueryNestedResponseBody(@HostParam("endpoint") String endpoint,
-            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
+            @QueryParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
+            Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-query-nested-response-body")
         @ExpectedResponses({ 200 })
@@ -152,7 +163,8 @@ Mono> requestQueryNestedResponseBody(@HostParam("endpoint")
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Response requestQueryNestedResponseBodySync(@HostParam("endpoint") String endpoint,
-            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
+            @QueryParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
+            Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-header-nested-response-body")
         @ExpectedResponses({ 200 })
@@ -161,7 +173,8 @@ Response requestQueryNestedResponseBodySync(@HostParam("endpoint") S
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Mono> requestHeaderNestedResponseBody(@HostParam("endpoint") String endpoint,
-            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
+            @HeaderParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
+            Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-header-nested-response-body")
         @ExpectedResponses({ 200 })
@@ -170,7 +183,8 @@ Mono> requestHeaderNestedResponseBody(@HostParam("endpoint"
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Response requestHeaderNestedResponseBodySync(@HostParam("endpoint") String endpoint,
-            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
+            @HeaderParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
+            Context context);
     }
 
     /**
@@ -201,6 +215,7 @@ Response requestHeaderNestedResponseBodySync(@HostParam("endpoint")
      * }
      * 
* + * @param token The token 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. @@ -209,13 +224,14 @@ Response requestHeaderNestedResponseBodySync(@HostParam("endpoint") * @return the response body along with {@link PagedResponse} on successful completion of {@link Mono}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private Mono> requestQueryResponseBodySinglePageAsync(RequestOptions requestOptions) { + private Mono> requestQueryResponseBodySinglePageAsync(String token, + RequestOptions requestOptions) { final String accept = "application/json"; return FluxUtil - .withContext( - context -> service.requestQueryResponseBody(this.client.getEndpoint(), accept, requestOptions, context)) + .withContext(context -> service.requestQueryResponseBody(this.client.getEndpoint(), token, accept, + requestOptions, context)) .map(res -> new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), - getValues(res.getValue(), "pets"), null, null)); + getValues(res.getValue(), "pets"), getNextLink(res.getValue(), "nextToken"), null)); } /** @@ -224,7 +240,6 @@ private Mono> requestQueryResponseBodySinglePageAsync( * * * - * * *
Query Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
barStringNoThe bar parameter
* You can add these to a request with {@link RequestOptions#addQueryParam} @@ -255,7 +270,8 @@ private Mono> requestQueryResponseBodySinglePageAsync( */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedFlux requestQueryResponseBodyAsync(RequestOptions requestOptions) { - return new PagedFlux<>(() -> requestQueryResponseBodySinglePageAsync(requestOptions)); + return new PagedFlux<>(() -> requestQueryResponseBodySinglePageAsync(null, requestOptions), + token -> requestQueryResponseBodySinglePageAsync(token, requestOptions)); } /** @@ -286,6 +302,7 @@ public PagedFlux requestQueryResponseBodyAsync(RequestOptions reques * } *
* + * @param token The token 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. @@ -294,12 +311,12 @@ public PagedFlux requestQueryResponseBodyAsync(RequestOptions reques * @return the response body along with {@link PagedResponse}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private PagedResponse requestQueryResponseBodySinglePage(RequestOptions requestOptions) { + private PagedResponse requestQueryResponseBodySinglePage(String token, RequestOptions requestOptions) { final String accept = "application/json"; - Response res - = service.requestQueryResponseBodySync(this.client.getEndpoint(), accept, requestOptions, Context.NONE); + Response res = service.requestQueryResponseBodySync(this.client.getEndpoint(), token, accept, + requestOptions, Context.NONE); return new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), - getValues(res.getValue(), "pets"), null, null); + getValues(res.getValue(), "pets"), getNextLink(res.getValue(), "nextToken"), null); } /** @@ -308,7 +325,6 @@ private PagedResponse requestQueryResponseBodySinglePage(RequestOpti * * * - * * *
Query Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
barStringNoThe bar parameter
* You can add these to a request with {@link RequestOptions#addQueryParam} @@ -339,7 +355,8 @@ private PagedResponse requestQueryResponseBodySinglePage(RequestOpti */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedIterable requestQueryResponseBody(RequestOptions requestOptions) { - return new PagedIterable<>(() -> requestQueryResponseBodySinglePage(requestOptions)); + return new PagedIterable<>(() -> requestQueryResponseBodySinglePage(null, requestOptions), + token -> requestQueryResponseBodySinglePage(token, requestOptions)); } /** @@ -370,6 +387,7 @@ public PagedIterable requestQueryResponseBody(RequestOptions request * } * * + * @param token The token 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. @@ -378,12 +396,14 @@ public PagedIterable requestQueryResponseBody(RequestOptions request * @return the response body along with {@link PagedResponse} on successful completion of {@link Mono}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private Mono> requestHeaderResponseBodySinglePageAsync(RequestOptions requestOptions) { + private Mono> requestHeaderResponseBodySinglePageAsync(String token, + RequestOptions requestOptions) { final String accept = "application/json"; - return FluxUtil.withContext( - context -> service.requestHeaderResponseBody(this.client.getEndpoint(), accept, requestOptions, context)) + return FluxUtil + .withContext(context -> service.requestHeaderResponseBody(this.client.getEndpoint(), token, accept, + requestOptions, context)) .map(res -> new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), - getValues(res.getValue(), "pets"), null, null)); + getValues(res.getValue(), "pets"), getNextLink(res.getValue(), "nextToken"), null)); } /** @@ -399,7 +419,6 @@ private Mono> requestHeaderResponseBodySinglePageAsync * * * - * * *
Header Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
fooStringNoThe foo parameter
* You can add these to a request with {@link RequestOptions#addHeader} @@ -423,7 +442,8 @@ private Mono> requestHeaderResponseBodySinglePageAsync */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedFlux requestHeaderResponseBodyAsync(RequestOptions requestOptions) { - return new PagedFlux<>(() -> requestHeaderResponseBodySinglePageAsync(requestOptions)); + return new PagedFlux<>(() -> requestHeaderResponseBodySinglePageAsync(null, requestOptions), + token -> requestHeaderResponseBodySinglePageAsync(token, requestOptions)); } /** @@ -454,6 +474,7 @@ public PagedFlux requestHeaderResponseBodyAsync(RequestOptions reque * } * * + * @param token The token 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. @@ -462,12 +483,12 @@ public PagedFlux requestHeaderResponseBodyAsync(RequestOptions reque * @return the response body along with {@link PagedResponse}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private PagedResponse requestHeaderResponseBodySinglePage(RequestOptions requestOptions) { + private PagedResponse requestHeaderResponseBodySinglePage(String token, RequestOptions requestOptions) { final String accept = "application/json"; - Response res - = service.requestHeaderResponseBodySync(this.client.getEndpoint(), accept, requestOptions, Context.NONE); + Response res = service.requestHeaderResponseBodySync(this.client.getEndpoint(), token, accept, + requestOptions, Context.NONE); return new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), - getValues(res.getValue(), "pets"), null, null); + getValues(res.getValue(), "pets"), getNextLink(res.getValue(), "nextToken"), null); } /** @@ -483,7 +504,6 @@ private PagedResponse requestHeaderResponseBodySinglePage(RequestOpt * * * - * * *
Header Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
fooStringNoThe foo parameter
* You can add these to a request with {@link RequestOptions#addHeader} @@ -507,7 +527,8 @@ private PagedResponse requestHeaderResponseBodySinglePage(RequestOpt */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedIterable requestHeaderResponseBody(RequestOptions requestOptions) { - return new PagedIterable<>(() -> requestHeaderResponseBodySinglePage(requestOptions)); + return new PagedIterable<>(() -> requestHeaderResponseBodySinglePage(null, requestOptions), + token -> requestHeaderResponseBodySinglePage(token, requestOptions)); } /** @@ -538,6 +559,7 @@ public PagedIterable requestHeaderResponseBody(RequestOptions reques * } * * + * @param token The token 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. @@ -546,12 +568,15 @@ public PagedIterable requestHeaderResponseBody(RequestOptions reques * @return the response body along with {@link PagedResponse} on successful completion of {@link Mono}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private Mono> requestQueryResponseHeaderSinglePageAsync(RequestOptions requestOptions) { + private Mono> requestQueryResponseHeaderSinglePageAsync(String token, + RequestOptions requestOptions) { final String accept = "application/json"; - return FluxUtil.withContext( - context -> service.requestQueryResponseHeader(this.client.getEndpoint(), accept, requestOptions, context)) + return FluxUtil + .withContext(context -> service.requestQueryResponseHeader(this.client.getEndpoint(), token, accept, + requestOptions, context)) .map(res -> new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), - getValues(res.getValue(), "pets"), null, null)); + getValues(res.getValue(), "pets"), res.getHeaders().getValue(HttpHeaderName.fromString("next-token")), + null)); } /** @@ -560,7 +585,6 @@ private Mono> requestQueryResponseHeaderSinglePageAsyn * * * - * * *
Query Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
barStringNoThe bar parameter
* You can add these to a request with {@link RequestOptions#addQueryParam} @@ -591,7 +615,8 @@ private Mono> requestQueryResponseHeaderSinglePageAsyn */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedFlux requestQueryResponseHeaderAsync(RequestOptions requestOptions) { - return new PagedFlux<>(() -> requestQueryResponseHeaderSinglePageAsync(requestOptions)); + return new PagedFlux<>(() -> requestQueryResponseHeaderSinglePageAsync(null, requestOptions), + token -> requestQueryResponseHeaderSinglePageAsync(token, requestOptions)); } /** @@ -622,6 +647,7 @@ public PagedFlux requestQueryResponseHeaderAsync(RequestOptions requ * } * * + * @param token The token 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. @@ -630,12 +656,14 @@ public PagedFlux requestQueryResponseHeaderAsync(RequestOptions requ * @return the response body along with {@link PagedResponse}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private PagedResponse requestQueryResponseHeaderSinglePage(RequestOptions requestOptions) { + private PagedResponse requestQueryResponseHeaderSinglePage(String token, + RequestOptions requestOptions) { final String accept = "application/json"; - Response res - = service.requestQueryResponseHeaderSync(this.client.getEndpoint(), accept, requestOptions, Context.NONE); + Response res = service.requestQueryResponseHeaderSync(this.client.getEndpoint(), token, accept, + requestOptions, Context.NONE); return new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), - getValues(res.getValue(), "pets"), null, null); + getValues(res.getValue(), "pets"), res.getHeaders().getValue(HttpHeaderName.fromString("next-token")), + null); } /** @@ -644,7 +672,6 @@ private PagedResponse requestQueryResponseHeaderSinglePage(RequestOp * * * - * * *
Query Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
barStringNoThe bar parameter
* You can add these to a request with {@link RequestOptions#addQueryParam} @@ -675,7 +702,8 @@ private PagedResponse requestQueryResponseHeaderSinglePage(RequestOp */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedIterable requestQueryResponseHeader(RequestOptions requestOptions) { - return new PagedIterable<>(() -> requestQueryResponseHeaderSinglePage(requestOptions)); + return new PagedIterable<>(() -> requestQueryResponseHeaderSinglePage(null, requestOptions), + token -> requestQueryResponseHeaderSinglePage(token, requestOptions)); } /** @@ -706,6 +734,7 @@ public PagedIterable requestQueryResponseHeader(RequestOptions reque * } * * + * @param token The token 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. @@ -714,12 +743,15 @@ public PagedIterable requestQueryResponseHeader(RequestOptions reque * @return the response body along with {@link PagedResponse} on successful completion of {@link Mono}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private Mono> requestHeaderResponseHeaderSinglePageAsync(RequestOptions requestOptions) { + private Mono> requestHeaderResponseHeaderSinglePageAsync(String token, + RequestOptions requestOptions) { final String accept = "application/json"; - return FluxUtil.withContext( - context -> service.requestHeaderResponseHeader(this.client.getEndpoint(), accept, requestOptions, context)) + return FluxUtil + .withContext(context -> service.requestHeaderResponseHeader(this.client.getEndpoint(), token, accept, + requestOptions, context)) .map(res -> new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), - getValues(res.getValue(), "pets"), null, null)); + getValues(res.getValue(), "pets"), res.getHeaders().getValue(HttpHeaderName.fromString("next-token")), + null)); } /** @@ -735,7 +767,6 @@ private Mono> requestHeaderResponseHeaderSinglePageAsy * * * - * * *
Header Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
fooStringNoThe foo parameter
* You can add these to a request with {@link RequestOptions#addHeader} @@ -759,7 +790,8 @@ private Mono> requestHeaderResponseHeaderSinglePageAsy */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedFlux requestHeaderResponseHeaderAsync(RequestOptions requestOptions) { - return new PagedFlux<>(() -> requestHeaderResponseHeaderSinglePageAsync(requestOptions)); + return new PagedFlux<>(() -> requestHeaderResponseHeaderSinglePageAsync(null, requestOptions), + token -> requestHeaderResponseHeaderSinglePageAsync(token, requestOptions)); } /** @@ -790,6 +822,7 @@ public PagedFlux requestHeaderResponseHeaderAsync(RequestOptions req * } * * + * @param token The token 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. @@ -798,12 +831,14 @@ public PagedFlux requestHeaderResponseHeaderAsync(RequestOptions req * @return the response body along with {@link PagedResponse}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private PagedResponse requestHeaderResponseHeaderSinglePage(RequestOptions requestOptions) { + private PagedResponse requestHeaderResponseHeaderSinglePage(String token, + RequestOptions requestOptions) { final String accept = "application/json"; - Response res - = service.requestHeaderResponseHeaderSync(this.client.getEndpoint(), accept, requestOptions, Context.NONE); + Response res = service.requestHeaderResponseHeaderSync(this.client.getEndpoint(), token, accept, + requestOptions, Context.NONE); return new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), - getValues(res.getValue(), "pets"), null, null); + getValues(res.getValue(), "pets"), res.getHeaders().getValue(HttpHeaderName.fromString("next-token")), + null); } /** @@ -819,7 +854,6 @@ private PagedResponse requestHeaderResponseHeaderSinglePage(RequestO * * * - * * *
Header Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
fooStringNoThe foo parameter
* You can add these to a request with {@link RequestOptions#addHeader} @@ -843,7 +877,8 @@ private PagedResponse requestHeaderResponseHeaderSinglePage(RequestO */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedIterable requestHeaderResponseHeader(RequestOptions requestOptions) { - return new PagedIterable<>(() -> requestHeaderResponseHeaderSinglePage(requestOptions)); + return new PagedIterable<>(() -> requestHeaderResponseHeaderSinglePage(null, requestOptions), + token -> requestHeaderResponseHeaderSinglePage(token, requestOptions)); } /** @@ -874,6 +909,7 @@ public PagedIterable requestHeaderResponseHeader(RequestOptions requ * } * * + * @param token The token 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. @@ -882,14 +918,15 @@ public PagedIterable requestHeaderResponseHeader(RequestOptions requ * @return the response body along with {@link PagedResponse} on successful completion of {@link Mono}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private Mono> - requestQueryNestedResponseBodySinglePageAsync(RequestOptions requestOptions) { + private Mono> requestQueryNestedResponseBodySinglePageAsync(String token, + RequestOptions requestOptions) { final String accept = "application/json"; return FluxUtil - .withContext(context -> service.requestQueryNestedResponseBody(this.client.getEndpoint(), accept, + .withContext(context -> service.requestQueryNestedResponseBody(this.client.getEndpoint(), token, accept, requestOptions, context)) .map(res -> new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), - getValues(res.getValue(), "nestedItems", "pets"), null, null)); + getValues(res.getValue(), "nestedItems", "pets"), + getNextLink(res.getValue(), "nestedNext", "nextToken"), null)); } /** @@ -898,7 +935,6 @@ public PagedIterable requestHeaderResponseHeader(RequestOptions requ * * * - * * *
Query Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
barStringNoThe bar parameter
* You can add these to a request with {@link RequestOptions#addQueryParam} @@ -929,7 +965,8 @@ public PagedIterable requestHeaderResponseHeader(RequestOptions requ */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedFlux requestQueryNestedResponseBodyAsync(RequestOptions requestOptions) { - return new PagedFlux<>(() -> requestQueryNestedResponseBodySinglePageAsync(requestOptions)); + return new PagedFlux<>(() -> requestQueryNestedResponseBodySinglePageAsync(null, requestOptions), + token -> requestQueryNestedResponseBodySinglePageAsync(token, requestOptions)); } /** @@ -960,6 +997,7 @@ public PagedFlux requestQueryNestedResponseBodyAsync(RequestOptions * } * * + * @param token The token 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. @@ -968,12 +1006,14 @@ public PagedFlux requestQueryNestedResponseBodyAsync(RequestOptions * @return the response body along with {@link PagedResponse}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private PagedResponse requestQueryNestedResponseBodySinglePage(RequestOptions requestOptions) { + private PagedResponse requestQueryNestedResponseBodySinglePage(String token, + RequestOptions requestOptions) { final String accept = "application/json"; - Response res = service.requestQueryNestedResponseBodySync(this.client.getEndpoint(), accept, + Response res = service.requestQueryNestedResponseBodySync(this.client.getEndpoint(), token, accept, requestOptions, Context.NONE); return new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), - getValues(res.getValue(), "nestedItems", "pets"), null, null); + getValues(res.getValue(), "nestedItems", "pets"), getNextLink(res.getValue(), "nestedNext", "nextToken"), + null); } /** @@ -982,7 +1022,6 @@ private PagedResponse requestQueryNestedResponseBodySinglePage(Reque * * * - * * *
Query Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
barStringNoThe bar parameter
* You can add these to a request with {@link RequestOptions#addQueryParam} @@ -1013,7 +1052,8 @@ private PagedResponse requestQueryNestedResponseBodySinglePage(Reque */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedIterable requestQueryNestedResponseBody(RequestOptions requestOptions) { - return new PagedIterable<>(() -> requestQueryNestedResponseBodySinglePage(requestOptions)); + return new PagedIterable<>(() -> requestQueryNestedResponseBodySinglePage(null, requestOptions), + token -> requestQueryNestedResponseBodySinglePage(token, requestOptions)); } /** @@ -1044,6 +1084,7 @@ public PagedIterable requestQueryNestedResponseBody(RequestOptions r * } * * + * @param token The token 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. @@ -1052,14 +1093,15 @@ public PagedIterable requestQueryNestedResponseBody(RequestOptions r * @return the response body along with {@link PagedResponse} on successful completion of {@link Mono}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private Mono> - requestHeaderNestedResponseBodySinglePageAsync(RequestOptions requestOptions) { + private Mono> requestHeaderNestedResponseBodySinglePageAsync(String token, + RequestOptions requestOptions) { final String accept = "application/json"; return FluxUtil - .withContext(context -> service.requestHeaderNestedResponseBody(this.client.getEndpoint(), accept, + .withContext(context -> service.requestHeaderNestedResponseBody(this.client.getEndpoint(), token, accept, requestOptions, context)) .map(res -> new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), - getValues(res.getValue(), "nestedItems", "pets"), null, null)); + getValues(res.getValue(), "nestedItems", "pets"), + getNextLink(res.getValue(), "nestedNext", "nextToken"), null)); } /** @@ -1075,7 +1117,6 @@ public PagedIterable requestQueryNestedResponseBody(RequestOptions r * * * - * * *
Header Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
fooStringNoThe foo parameter
* You can add these to a request with {@link RequestOptions#addHeader} @@ -1099,7 +1140,8 @@ public PagedIterable requestQueryNestedResponseBody(RequestOptions r */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedFlux requestHeaderNestedResponseBodyAsync(RequestOptions requestOptions) { - return new PagedFlux<>(() -> requestHeaderNestedResponseBodySinglePageAsync(requestOptions)); + return new PagedFlux<>(() -> requestHeaderNestedResponseBodySinglePageAsync(null, requestOptions), + token -> requestHeaderNestedResponseBodySinglePageAsync(token, requestOptions)); } /** @@ -1130,6 +1172,7 @@ public PagedFlux requestHeaderNestedResponseBodyAsync(RequestOptions * } * * + * @param token The token 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. @@ -1138,12 +1181,14 @@ public PagedFlux requestHeaderNestedResponseBodyAsync(RequestOptions * @return the response body along with {@link PagedResponse}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private PagedResponse requestHeaderNestedResponseBodySinglePage(RequestOptions requestOptions) { + private PagedResponse requestHeaderNestedResponseBodySinglePage(String token, + RequestOptions requestOptions) { final String accept = "application/json"; - Response res = service.requestHeaderNestedResponseBodySync(this.client.getEndpoint(), accept, + Response res = service.requestHeaderNestedResponseBodySync(this.client.getEndpoint(), token, accept, requestOptions, Context.NONE); return new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), - getValues(res.getValue(), "nestedItems", "pets"), null, null); + getValues(res.getValue(), "nestedItems", "pets"), getNextLink(res.getValue(), "nestedNext", "nextToken"), + null); } /** @@ -1159,7 +1204,6 @@ private PagedResponse requestHeaderNestedResponseBodySinglePage(Requ * * * - * * *
Header Parameters
NameTypeRequiredDescription
tokenStringNoThe token parameter
fooStringNoThe foo parameter
* You can add these to a request with {@link RequestOptions#addHeader} @@ -1183,7 +1227,8 @@ private PagedResponse requestHeaderNestedResponseBodySinglePage(Requ */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedIterable requestHeaderNestedResponseBody(RequestOptions requestOptions) { - return new PagedIterable<>(() -> requestHeaderNestedResponseBodySinglePage(requestOptions)); + return new PagedIterable<>(() -> requestHeaderNestedResponseBodySinglePage(null, requestOptions), + token -> requestHeaderNestedResponseBodySinglePage(token, requestOptions)); } private List getValues(BinaryData binaryData, String... path) { diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/XmlPaginationsImpl.java b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/XmlPaginationsImpl.java index 8986bca9518..fb4e7408edc 100644 --- a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/XmlPaginationsImpl.java +++ b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/XmlPaginationsImpl.java @@ -10,6 +10,7 @@ import com.azure.core.annotation.Host; import com.azure.core.annotation.HostParam; import com.azure.core.annotation.PathParam; +import com.azure.core.annotation.QueryParam; import com.azure.core.annotation.ReturnType; import com.azure.core.annotation.ServiceInterface; import com.azure.core.annotation.ServiceMethod; @@ -72,7 +73,8 @@ public interface XmlPaginationsService { @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 }) @UnexpectedResponseExceptionType(HttpResponseException.class) Mono> listWithContinuation(@HostParam("endpoint") String endpoint, - @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context); + @QueryParam("marker") String marker, @HeaderParam("Accept") String accept, RequestOptions requestOptions, + Context context); @Get("/payload/pageable/xml/list-with-continuation") @ExpectedResponses({ 200 }) @@ -81,7 +83,8 @@ Mono> listWithContinuation(@HostParam("endpoint") String en @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 }) @UnexpectedResponseExceptionType(HttpResponseException.class) Response listWithContinuationSync(@HostParam("endpoint") String endpoint, - @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context); + @QueryParam("marker") String marker, @HeaderParam("Accept") String accept, RequestOptions requestOptions, + Context context); @Get("/payload/pageable/xml/list-with-next-link") @ExpectedResponses({ 200 }) @@ -142,6 +145,7 @@ Response listWithNextLinkNextSync(@PathParam(value = "nextLink", enc * } * * + * @param marker The marker 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. @@ -151,11 +155,11 @@ Response listWithNextLinkNextSync(@PathParam(value = "nextLink", enc * {@link Mono}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private Mono> listWithContinuationSinglePageAsync(RequestOptions requestOptions) { + private Mono> listWithContinuationSinglePageAsync(String marker, + RequestOptions requestOptions) { final String accept = "application/xml"; - return FluxUtil - .withContext( - context -> service.listWithContinuation(this.client.getEndpoint(), accept, requestOptions, context)) + return FluxUtil.withContext( + context -> service.listWithContinuation(this.client.getEndpoint(), marker, accept, requestOptions, context)) .map(res -> new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), getXmlValues(res.getValue(), reader -> { try { @@ -164,18 +168,11 @@ private Mono> listWithContinuationSinglePageAsync(Requ } catch (javax.xml.stream.XMLStreamException e) { throw new IllegalStateException(e); } - }, "Pets", "Pet"), null, null)); + }, "Pets", "Pet"), getXmlNextLink(res.getValue(), "NextMarker"), null)); } /** * The listWithContinuation operation. - *

Query Parameters

- * - * - * - * - *
Query Parameters
NameTypeRequiredDescription
markerStringNoThe marker parameter
- * You can add these to a request with {@link RequestOptions#addQueryParam} *

Response Body Schema

* *
@@ -196,7 +193,8 @@ private Mono> listWithContinuationSinglePageAsync(Requ
      */
     @ServiceMethod(returns = ReturnType.COLLECTION)
     public PagedFlux listWithContinuationAsync(RequestOptions requestOptions) {
-        return new PagedFlux<>(() -> listWithContinuationSinglePageAsync(requestOptions));
+        return new PagedFlux<>(() -> listWithContinuationSinglePageAsync(null, requestOptions),
+            marker -> listWithContinuationSinglePageAsync(marker, requestOptions));
     }
 
     /**
@@ -219,6 +217,7 @@ public PagedFlux listWithContinuationAsync(RequestOptions requestOpt
      * }
      * 
* + * @param marker The marker 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. @@ -227,10 +226,10 @@ public PagedFlux listWithContinuationAsync(RequestOptions requestOpt * @return the XML response for listing pets along with {@link PagedResponse}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private PagedResponse listWithContinuationSinglePage(RequestOptions requestOptions) { + private PagedResponse listWithContinuationSinglePage(String marker, RequestOptions requestOptions) { final String accept = "application/xml"; Response res - = service.listWithContinuationSync(this.client.getEndpoint(), accept, requestOptions, Context.NONE); + = service.listWithContinuationSync(this.client.getEndpoint(), marker, accept, requestOptions, Context.NONE); return new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), getXmlValues(res.getValue(), reader -> { try { @@ -238,18 +237,11 @@ private PagedResponse listWithContinuationSinglePage(RequestOptions } catch (javax.xml.stream.XMLStreamException e) { throw new IllegalStateException(e); } - }, "Pets", "Pet"), null, null); + }, "Pets", "Pet"), getXmlNextLink(res.getValue(), "NextMarker"), null); } /** * The listWithContinuation operation. - *

Query Parameters

- * - * - * - * - *
Query Parameters
NameTypeRequiredDescription
markerStringNoThe marker parameter
- * You can add these to a request with {@link RequestOptions#addQueryParam} *

Response Body Schema

* *
@@ -270,7 +262,8 @@ private PagedResponse listWithContinuationSinglePage(RequestOptions
      */
     @ServiceMethod(returns = ReturnType.COLLECTION)
     public PagedIterable listWithContinuation(RequestOptions requestOptions) {
-        return new PagedIterable<>(() -> listWithContinuationSinglePage(requestOptions));
+        return new PagedIterable<>(() -> listWithContinuationSinglePage(null, requestOptions),
+            marker -> listWithContinuationSinglePage(marker, requestOptions));
     }
 
     /**
diff --git a/packages/http-client-java/generator/http-client-generator-test/src/test/java/payload/pageable/PageableTests.java b/packages/http-client-java/generator/http-client-generator-test/src/test/java/payload/pageable/PageableTests.java
index 10fa38a72db..57a5b16c428 100644
--- a/packages/http-client-java/generator/http-client-generator-test/src/test/java/payload/pageable/PageableTests.java
+++ b/packages/http-client-java/generator/http-client-generator-test/src/test/java/payload/pageable/PageableTests.java
@@ -3,7 +3,15 @@
 
 package payload.pageable;
 
+import com.azure.core.http.HttpHeaders;
+import com.azure.core.http.HttpRequest;
 import com.azure.core.http.rest.PagedIterable;
+import com.azure.core.http.rest.RequestOptions;
+import com.azure.core.test.http.MockHttpResponse;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.junit.jupiter.api.Assertions;
@@ -11,6 +19,8 @@
 import payload.pageable.models.Pet;
 import payload.pageable.models.XmlPet;
 import payload.pageable.serverdrivenpagination.alternateinitialverb.models.Filter;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
 
 public class PageableTests {
 
@@ -54,13 +64,128 @@ public void testXmlListWithNextLink() {
             pagedIterable.stream().map(XmlPet::getId).collect(Collectors.toList()));
     }
 
-    /*
-     * Continuation-token scenarios are intentionally not covered here. Azure V1 currently emits a single-page
-     * PagedIterable for them because it does not propagate a response continuation token into the next request.
-     *
-     * XML continuation-token paging is also intentionally not covered because it has the same token propagation
-     * limitation.
-     */
+    @Test
+    public void testContinuationToken() {
+        ServerDrivenPaginationContinuationTokenClient client
+            = builder.buildServerDrivenPaginationContinuationTokenClient();
+        assertPetIds(client.requestQueryResponseBody("foo", "bar"));
+        assertPetIds(client.requestHeaderResponseBody("foo", "bar"));
+        assertPetIds(client.requestQueryResponseHeader("foo", "bar"));
+        assertPetIds(client.requestHeaderResponseHeader("foo", "bar"));
+        assertPetIds(client.requestQueryNestedResponseBody("foo", "bar"));
+        assertPetIds(client.requestHeaderNestedResponseBody("foo", "bar"));
+    }
+
+    @Test
+    public void testContinuationTokenAsync() {
+        ServerDrivenPaginationContinuationTokenAsyncClient client
+            = builder.buildServerDrivenPaginationContinuationTokenAsyncClient();
+        List> results = List.of(client.requestQueryResponseBody("foo", "bar"),
+            client.requestHeaderResponseBody("foo", "bar"), client.requestQueryResponseHeader("foo", "bar"),
+            client.requestHeaderResponseHeader("foo", "bar"), client.requestQueryNestedResponseBody("foo", "bar"),
+            client.requestHeaderNestedResponseBody("foo", "bar"));
+        for (Flux result : results) {
+            Assertions.assertEquals(List.of("1", "2", "3", "4"),
+                result.map(Pet::getId).collectList().block(Duration.ofSeconds(10)));
+        }
+    }
+
+    @Test
+    public void testResumeWithContinuationToken() {
+        ServerDrivenPaginationContinuationTokenClient client
+            = builder.buildServerDrivenPaginationContinuationTokenClient();
+        Assertions.assertEquals(List.of("3", "4"),
+            client.requestQueryResponseBody("foo", "bar")
+                .streamByPage("page2")
+                .flatMap(page -> page.getValue().stream())
+                .map(Pet::getId)
+                .collect(Collectors.toList()));
+        Assertions.assertEquals(List.of("3", "4"),
+            client.requestHeaderResponseHeader("foo", "bar")
+                .streamByPage("page2")
+                .flatMap(page -> page.getValue().stream())
+                .map(Pet::getId)
+                .collect(Collectors.toList()));
+        Assertions.assertEquals(List.of("3", "4"),
+            builder.buildServerDrivenPaginationContinuationTokenAsyncClient()
+                .requestQueryNestedResponseBody("foo", "bar")
+                .byPage("page2")
+                .flatMapIterable(page -> page.getValue())
+                .map(Pet::getId)
+                .collectList()
+                .block(Duration.ofSeconds(10)));
+    }
+
+    @Test
+    public void testProtocolContinuationTokenPreservesOptions() {
+        RequestOptions options = new RequestOptions().addHeader("foo", "foo").addQueryParam("bar", "bar");
+        ServerDrivenPaginationContinuationTokenClient client
+            = builder.buildServerDrivenPaginationContinuationTokenClient();
+        for (int i = 0; i < 2; i++) {
+            Assertions.assertEquals(4, client.requestQueryResponseBody(options).stream().count());
+            Assertions.assertEquals(4, client.requestHeaderResponseHeader(options).stream().count());
+        }
+    }
+
+    @Test
+    public void testContinuationTokenEncoding() {
+        String token = "opaque +/?&=%";
+        List requests = new ArrayList<>();
+        PageableClientBuilder mockBuilder = new PageableClientBuilder().httpClient(request -> {
+            requests.add(request);
+            Assertions.assertEquals(
+                "/payload/pageable/server-driven-pagination/continuationtoken/request-query-response-body",
+                request.getUrl().getPath());
+            Assertions.assertEquals("foo", request.getHeaders().getValue("foo"));
+            String query = request.getUrl().getQuery();
+            Assertions.assertTrue(query.contains("bar=bar"));
+            String body;
+            if (query.contains("token=")) {
+                List tokenParameters = List.of(query.split("&"))
+                    .stream()
+                    .filter(parameter -> parameter.startsWith("token="))
+                    .collect(Collectors.toList());
+                Assertions.assertEquals(1, tokenParameters.size());
+                Assertions.assertEquals(token,
+                    URLDecoder.decode(tokenParameters.get(0).substring("token=".length()), StandardCharsets.UTF_8));
+                body = "{\"pets\":[{\"id\":\"2\",\"name\":\"cat\"}]}";
+            } else {
+                body = "{\"pets\":[{\"id\":\"1\",\"name\":\"dog\"}],\"nextToken\":\"" + token + "\"}";
+            }
+            return Mono
+                .just(new MockHttpResponse(request, 200, new HttpHeaders(), body.getBytes(StandardCharsets.UTF_8)));
+        });
+        assertPetIds(
+            mockBuilder.buildServerDrivenPaginationContinuationTokenClient().requestQueryResponseBody("foo", "bar"),
+            "1", "2");
+        Assertions.assertEquals(2, requests.size());
+        requests.clear();
+        Assertions.assertEquals(List.of("1", "2"),
+            mockBuilder.buildServerDrivenPaginationContinuationTokenAsyncClient()
+                .requestQueryResponseBody("foo", "bar")
+                .map(Pet::getId)
+                .collectList()
+                .block(Duration.ofSeconds(10)));
+        Assertions.assertEquals(2, requests.size());
+    }
+
+    @Test
+    public void testXmlContinuationToken() {
+        Assertions.assertEquals(List.of("1", "2", "3", "4"),
+            builder.buildXmlPaginationClient()
+                .listWithContinuation()
+                .stream()
+                .map(XmlPet::getId)
+                .collect(Collectors.toList()));
+        Assertions.assertEquals(List.of("3", "4"),
+            builder.buildXmlPaginationAsyncClient()
+                .listWithContinuation()
+                .byPage("page2")
+                .flatMapIterable(page -> page.getValue())
+                .map(XmlPet::getId)
+                .collectList()
+                .block(Duration.ofSeconds(10)));
+    }
 
     private static void assertPetIds(PagedIterable pagedIterable) {
         assertPetIds(pagedIterable, "1", "2", "3", "4");

From 2f31b4215b21b588f073d044c2412621cd2740ba Mon Sep 17 00:00:00 2001
From: Weidong Xu 
Date: Thu, 15 Oct 2026 12:32:20 +0800
Subject: [PATCH 2/7] refactor(http-client-java): reuse RequestOptions for
 paging tokens

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4bbe3fb2-95f1-4311-8050-62ba7dba4860
---
 packages/http-client-java/README.md           |   1 +
 .../ClientMethodParameterProcessor.java       |   2 +-
 .../mapper/ProxyMethodParameterProcessor.java |   1 -
 .../core/template/ClientMethodTemplate.java   |  76 ++++-
 .../generator/core/util/MethodUtil.java       |  14 -
 .../ContinuationTokenPagingTests.java         |  46 +++
 ...rivenPaginationContinuationTokensImpl.java | 321 ++++++++++++------
 .../implementation/XmlPaginationsImpl.java    |  62 +++-
 .../java/payload/pageable/PageableTests.java  |  24 +-
 9 files changed, 382 insertions(+), 165 deletions(-)

diff --git a/packages/http-client-java/README.md b/packages/http-client-java/README.md
index 3552854e93e..f8bc8d3fb67 100644
--- a/packages/http-client-java/README.md
+++ b/packages/http-client-java/README.md
@@ -49,6 +49,7 @@ Azure clients support both `@nextLink` and `@continuationToken` pagination. Cont
 parameters are hidden from client method signatures. To resume from a token, use
 `streamByPage(token)` on the returned `PagedIterable`, or `byPage(token)` on `PagedFlux`.
 The service must omit the response token when there are no more pages; `has_more` is not interpreted.
+Token paging reuses and updates supplied `RequestOptions`; do not share them across concurrent paging operations.
 
 ## Emitter options
 
diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ClientMethodParameterProcessor.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ClientMethodParameterProcessor.java
index 57b09640447..f200492b990 100644
--- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ClientMethodParameterProcessor.java
+++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ClientMethodParameterProcessor.java
@@ -75,7 +75,7 @@ private static List getCodeModelParameters(Request request, boolean i
             // Required path, body, header and query parameters are allowed
             codeModelParameters = request.getParameters().stream().filter(p -> {
                 RequestParameterLocation location = p.getProtocol().getHttp().getIn();
-                return (p.isRequired() || MethodUtil.isContinuationTokenParameter(p))
+                return p.isRequired()
                     && (location == RequestParameterLocation.PATH
                         || location == RequestParameterLocation.BODY
                         || location == RequestParameterLocation.HEADER
diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ProxyMethodParameterProcessor.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ProxyMethodParameterProcessor.java
index abcfda6b6e0..c4857f91b92 100644
--- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ProxyMethodParameterProcessor.java
+++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/ProxyMethodParameterProcessor.java
@@ -63,7 +63,6 @@ Result process(Request request, String contentType) {
                 // The protocol APIs (by Data Plane Generator) will put required path, body, query, header parameters
                 // to method signature.
                 if (parameter.isRequired()
-                    || MethodUtil.isContinuationTokenParameter(parameter)
                     || proxyMethodParameter.isConstant()
                     || proxyMethodParameter.isFromClient()
                     || isApiVersionParameter(parameter)) {
diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java
index f89fcf3cfc8..8ceb6f2023f 100644
--- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java
+++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java
@@ -1205,8 +1205,8 @@ private void generateContinuationTokenPaging(ClientMethod clientMethod, JavaType
                 ? clientMethod.getProxyMethod().getPagingSinglePageMethodName()
                 : clientMethod.getProxyMethod().getPagingAsyncSinglePageMethodName();
             String firstPage = getPagingSinglePageExpression(clientMethod, methodName, firstArguments, settings);
-            String nextPage = getPagingSinglePageExpression(clientMethod, methodName, clientMethod.getArgumentList(),
-                settings, tokenParameter.getName());
+            String nextPage = getPagingSinglePageExpression(clientMethod, methodName, firstArguments, settings,
+                clientMethod.getMethodPageDetails().getContinuationToken().getRequestParameter().getName());
             function.line("return new %s<>(", sync ? "PagedIterable" : "PagedFlux");
             function.indent(() -> {
                 function.line(firstPage + ",");
@@ -1927,18 +1927,61 @@ private String getPagingSinglePageExpression(ClientMethod clientMethod, String m
 
     private String getPagingSinglePageExpression(ClientMethod clientMethod, String methodName, String argumentLine,
         JavaSettings settings, String continuationParameter) {
-        if (settings.isDataPlaneClient() && settings.isPageSizeEnabled()) {
-            Optional maxPageSizeSerializedName
-                = MethodUtil.serializedNameOfMaxPageSizeParameter(clientMethod.getProxyMethod());
+        Optional maxPageSizeSerializedName = settings.isDataPlaneClient() && settings.isPageSizeEnabled()
+            ? MethodUtil.serializedNameOfMaxPageSizeParameter(clientMethod.getProxyMethod())
+            : Optional.empty();
+        boolean continuationToken = settings.isAzureV1() && hasContinuationToken(clientMethod);
+        if (continuationToken || maxPageSizeSerializedName.isPresent()) {
+            argumentLine = argumentLine.replace("requestOptions", "requestOptionsLocal");
+            StringBuilder expression = new StringBuilder("(");
+            if (continuationParameter != null) {
+                expression.append(continuationParameter);
+            }
             if (maxPageSizeSerializedName.isPresent()) {
-                argumentLine = argumentLine.replace("requestOptions", "requestOptionsLocal");
-                StringBuilder expression = new StringBuilder();
-                expression.append(continuationParameter == null
-                    ? "(pageSize) -> {"
-                    : "(" + continuationParameter + ", pageSize) -> {");
-                expression.append(
-                    "RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions;")
-                    .append("if (pageSize != null) {")
+                expression.append(continuationParameter == null ? "pageSize" : ", pageSize");
+            }
+            expression.append(") -> {")
+                .append(
+                    "RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions;");
+            if (continuationToken) {
+                // Clear tokens from earlier callbacks before applying the current page's token.
+                ProxyMethodParameter token
+                    = clientMethod.getMethodPageDetails().getContinuationToken().getRequestParameter();
+                String name = ClassType.STRING.defaultValueExpression(token.getRequestParameterName());
+                if (token.getRequestParameterLocation() == RequestParameterLocation.QUERY) {
+                    expression.append("requestOptionsLocal.addRequestCallback(requestLocal -> {")
+                        .append("UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl());")
+                        .append("urlBuilder.setQueryParameter(")
+                        .append(name)
+                        .append(", null);")
+                        .append("requestLocal.setUrl(urlBuilder.toString());")
+                        .append("});");
+                    if (continuationParameter != null) {
+                        expression.append("requestOptionsLocal.addQueryParam(")
+                            .append(name)
+                            .append(", ")
+                            .append(continuationParameter)
+                            .append(", ")
+                            .append(token.getAlreadyEncoded())
+                            .append(");");
+                    }
+                } else {
+                    expression
+                        .append(
+                            "requestOptionsLocal.addRequestCallback(requestLocal -> requestLocal.getHeaders().remove(")
+                        .append(name)
+                        .append("));");
+                    if (continuationParameter != null) {
+                        expression.append("requestOptionsLocal.setHeader(")
+                            .append(name)
+                            .append(", ")
+                            .append(continuationParameter)
+                            .append(");");
+                    }
+                }
+            }
+            if (maxPageSizeSerializedName.isPresent()) {
+                expression.append("if (pageSize != null) {")
                     .append("  requestOptionsLocal.addRequestCallback(requestLocal -> {")
                     .append("    UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl());")
                     .append("    urlBuilder.setQueryParameter(\"")
@@ -1946,11 +1989,10 @@ private String getPagingSinglePageExpression(ClientMethod clientMethod, String m
                     .append("\", String.valueOf(pageSize));")
                     .append("    requestLocal.setUrl(urlBuilder.toString());")
                     .append("  });")
-                    .append("}")
-                    .append(String.format("return %s(%s);", methodName, argumentLine));
-                expression.append("}");
-                return expression.toString();
+                    .append("}");
             }
+            expression.append(String.format("return %s(%s);", methodName, argumentLine)).append("}");
+            return expression.toString();
         }
 
         if (settings.isAzureV1()) {
diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/MethodUtil.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/MethodUtil.java
index 0863ed2018f..0329008a3a4 100644
--- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/MethodUtil.java
+++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/MethodUtil.java
@@ -57,20 +57,6 @@ public class MethodUtil {
 
     public static final String CONTENT_TYPE_APPLICATION_JSON_ERROR_WEIGHT = "application/json;q=0.9";
 
-    /**
-     * Checks whether a parameter carries a paging continuation token.
-     *
-     * @param parameter the operation parameter.
-     * @return whether the parameter is the paging continuation token.
-     */
-    public static boolean isContinuationTokenParameter(Parameter parameter) {
-        Operation operation = parameter.getOperation();
-        return operation != null
-            && operation.isPageable()
-            && operation.getExtensions().getXmsPageable().getContinuationToken() != null
-            && operation.getExtensions().getXmsPageable().getContinuationToken().getParameter() == parameter;
-    }
-
     private static final Set REPEATABILITY_REQUEST_HTTP_METHODS
         = EnumSet.of(HttpMethod.PUT, HttpMethod.PATCH, HttpMethod.DELETE, HttpMethod.POST);
 
diff --git a/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ContinuationTokenPagingTests.java b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ContinuationTokenPagingTests.java
index 884472c8107..f2a54f751ed 100644
--- a/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ContinuationTokenPagingTests.java
+++ b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ContinuationTokenPagingTests.java
@@ -3,12 +3,16 @@
 
 package com.microsoft.typespec.http.client.generator.core.template;
 
+import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.RequestParameterLocation;
+import com.microsoft.typespec.http.client.generator.core.extension.plugin.JavaSettings;
 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.ClientMethodParameter;
 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.MethodPageDetails;
+import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ProxyMethod;
+import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ProxyMethodParameter;
 import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ReturnValue;
 import com.microsoft.typespec.http.client.generator.core.model.javamodel.JavaClass;
 import com.microsoft.typespec.http.client.generator.core.model.javamodel.JavaFileContents;
@@ -17,6 +21,48 @@
 import org.junit.jupiter.api.Test;
 
 public class ContinuationTokenPagingTests {
+    @Test
+    public void passesQueryTokenThroughRequestOptions() {
+        String output = renderProtocolPaging(RequestParameterLocation.QUERY, false);
+        Assertions.assertTrue(output.contains("requestOptions == null ? new RequestOptions() : requestOptions"));
+        Assertions.assertTrue(output.contains("setQueryParameter(\"token\", null)"));
+        Assertions.assertTrue(output.contains("requestOptionsLocal.addQueryParam(\"token\", token, false)"));
+        Assertions.assertTrue(output.contains("listSinglePageAsync(requestOptionsLocal)"));
+        Assertions.assertFalse(output.contains("listSinglePageAsync(token,"));
+    }
+
+    @Test
+    public void passesHeaderTokenThroughRequestOptions() {
+        String output = renderProtocolPaging(RequestParameterLocation.HEADER, true);
+        Assertions.assertTrue(output.contains("requestOptions == null ? new RequestOptions() : requestOptions"));
+        Assertions.assertTrue(output.contains("getHeaders().remove(\"token\")"));
+        Assertions.assertTrue(output.contains("requestOptionsLocal.setHeader(\"token\", token)"));
+        Assertions.assertTrue(output.contains("listSinglePage(requestOptionsLocal)"));
+    }
+
+    private static String renderProtocolPaging(RequestParameterLocation location, boolean sync) {
+        ProxyMethodParameter token = new ProxyMethodParameter.Builder().name("token")
+            .requestParameterName("token")
+            .requestParameterLocation(location)
+            .wireType(ClassType.STRING)
+            .build();
+        ClientMethod method = pagingMethod().newBuilder()
+            .parameters(List.of(ClientMethodParameter.REQUEST_OPTIONS_PARAMETER))
+            .proxyMethod(new ProxyMethod.Builder().name("list").parameters(List.of()).build())
+            .methodPageDetails(new MethodPageDetails(List.of(), null, null, null,
+                new MethodPageDetails.ContinuationToken(token, null, "next-token", null), null, null))
+            .build();
+        JavaFileContents contents = new JavaFileContents();
+        if (sync) {
+            ClientMethodTemplate.getInstance()
+                .generateProtocolPagingPlainSync(method, new JavaClass(contents), JavaSettings.getInstance());
+        } else {
+            ClientMethodTemplate.getInstance()
+                .generatePagingAsync(method, new JavaClass(contents), JavaSettings.getInstance());
+        }
+        return contents.toString();
+    }
+
     @Test
     public void hidesTokenInWrapperDelegation() {
         JavaFileContents contents = new JavaFileContents();
diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/ServerDrivenPaginationContinuationTokensImpl.java b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/ServerDrivenPaginationContinuationTokensImpl.java
index 13fd03eea13..2ad7738a619 100644
--- a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/ServerDrivenPaginationContinuationTokensImpl.java
+++ b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/ServerDrivenPaginationContinuationTokensImpl.java
@@ -9,7 +9,6 @@
 import com.azure.core.annotation.HeaderParam;
 import com.azure.core.annotation.Host;
 import com.azure.core.annotation.HostParam;
-import com.azure.core.annotation.QueryParam;
 import com.azure.core.annotation.ReturnType;
 import com.azure.core.annotation.ServiceInterface;
 import com.azure.core.annotation.ServiceMethod;
@@ -29,6 +28,7 @@
 import com.azure.core.util.BinaryData;
 import com.azure.core.util.Context;
 import com.azure.core.util.FluxUtil;
+import com.azure.core.util.UrlBuilder;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -73,8 +73,7 @@ public interface ServerDrivenPaginationContinuationTokensService {
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Mono> requestQueryResponseBody(@HostParam("endpoint") String endpoint,
-            @QueryParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
-            Context context);
+            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-query-response-body")
         @ExpectedResponses({ 200 })
@@ -83,8 +82,7 @@ Mono> requestQueryResponseBody(@HostParam("endpoint") Strin
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Response requestQueryResponseBodySync(@HostParam("endpoint") String endpoint,
-            @QueryParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
-            Context context);
+            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-header-response-body")
         @ExpectedResponses({ 200 })
@@ -93,8 +91,7 @@ Response requestQueryResponseBodySync(@HostParam("endpoint") String
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Mono> requestHeaderResponseBody(@HostParam("endpoint") String endpoint,
-            @HeaderParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
-            Context context);
+            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-header-response-body")
         @ExpectedResponses({ 200 })
@@ -103,8 +100,7 @@ Mono> requestHeaderResponseBody(@HostParam("endpoint") Stri
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Response requestHeaderResponseBodySync(@HostParam("endpoint") String endpoint,
-            @HeaderParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
-            Context context);
+            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-query-response-header")
         @ExpectedResponses({ 200 })
@@ -113,8 +109,7 @@ Response requestHeaderResponseBodySync(@HostParam("endpoint") String
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Mono> requestQueryResponseHeader(@HostParam("endpoint") String endpoint,
-            @QueryParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
-            Context context);
+            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-query-response-header")
         @ExpectedResponses({ 200 })
@@ -123,8 +118,7 @@ Mono> requestQueryResponseHeader(@HostParam("endpoint") Str
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Response requestQueryResponseHeaderSync(@HostParam("endpoint") String endpoint,
-            @QueryParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
-            Context context);
+            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-header-response-header")
         @ExpectedResponses({ 200 })
@@ -133,8 +127,7 @@ Response requestQueryResponseHeaderSync(@HostParam("endpoint") Strin
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Mono> requestHeaderResponseHeader(@HostParam("endpoint") String endpoint,
-            @HeaderParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
-            Context context);
+            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-header-response-header")
         @ExpectedResponses({ 200 })
@@ -143,8 +136,7 @@ Mono> requestHeaderResponseHeader(@HostParam("endpoint") St
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Response requestHeaderResponseHeaderSync(@HostParam("endpoint") String endpoint,
-            @HeaderParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
-            Context context);
+            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-query-nested-response-body")
         @ExpectedResponses({ 200 })
@@ -153,8 +145,7 @@ Response requestHeaderResponseHeaderSync(@HostParam("endpoint") Stri
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Mono> requestQueryNestedResponseBody(@HostParam("endpoint") String endpoint,
-            @QueryParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
-            Context context);
+            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-query-nested-response-body")
         @ExpectedResponses({ 200 })
@@ -163,8 +154,7 @@ Mono> requestQueryNestedResponseBody(@HostParam("endpoint")
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Response requestQueryNestedResponseBodySync(@HostParam("endpoint") String endpoint,
-            @QueryParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
-            Context context);
+            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-header-nested-response-body")
         @ExpectedResponses({ 200 })
@@ -173,8 +163,7 @@ Response requestQueryNestedResponseBodySync(@HostParam("endpoint") S
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Mono> requestHeaderNestedResponseBody(@HostParam("endpoint") String endpoint,
-            @HeaderParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
-            Context context);
+            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
 
         @Get("/payload/pageable/server-driven-pagination/continuationtoken/request-header-nested-response-body")
         @ExpectedResponses({ 200 })
@@ -183,8 +172,7 @@ Mono> requestHeaderNestedResponseBody(@HostParam("endpoint"
         @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
         @UnexpectedResponseExceptionType(HttpResponseException.class)
         Response requestHeaderNestedResponseBodySync(@HostParam("endpoint") String endpoint,
-            @HeaderParam("token") String token, @HeaderParam("Accept") String accept, RequestOptions requestOptions,
-            Context context);
+            @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context);
     }
 
     /**
@@ -215,7 +203,6 @@ Response requestHeaderNestedResponseBodySync(@HostParam("endpoint")
      * }
      * 
* - * @param token The token 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. @@ -224,12 +211,11 @@ Response requestHeaderNestedResponseBodySync(@HostParam("endpoint") * @return the response body along with {@link PagedResponse} on successful completion of {@link Mono}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private Mono> requestQueryResponseBodySinglePageAsync(String token, - RequestOptions requestOptions) { + private Mono> requestQueryResponseBodySinglePageAsync(RequestOptions requestOptions) { final String accept = "application/json"; return FluxUtil - .withContext(context -> service.requestQueryResponseBody(this.client.getEndpoint(), token, accept, - requestOptions, context)) + .withContext( + context -> service.requestQueryResponseBody(this.client.getEndpoint(), accept, requestOptions, context)) .map(res -> new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), getValues(res.getValue(), "pets"), getNextLink(res.getValue(), "nextToken"), null)); } @@ -270,8 +256,24 @@ private Mono> requestQueryResponseBodySinglePageAsync( */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedFlux requestQueryResponseBodyAsync(RequestOptions requestOptions) { - return new PagedFlux<>(() -> requestQueryResponseBodySinglePageAsync(null, requestOptions), - token -> requestQueryResponseBodySinglePageAsync(token, requestOptions)); + return new PagedFlux<>(() -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> { + UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl()); + urlBuilder.setQueryParameter("token", null); + requestLocal.setUrl(urlBuilder.toString()); + }); + return requestQueryResponseBodySinglePageAsync(requestOptionsLocal); + }, (token) -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> { + UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl()); + urlBuilder.setQueryParameter("token", null); + requestLocal.setUrl(urlBuilder.toString()); + }); + requestOptionsLocal.addQueryParam("token", token, false); + return requestQueryResponseBodySinglePageAsync(requestOptionsLocal); + }); } /** @@ -302,7 +304,6 @@ public PagedFlux requestQueryResponseBodyAsync(RequestOptions reques * } * * - * @param token The token 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. @@ -311,10 +312,10 @@ public PagedFlux requestQueryResponseBodyAsync(RequestOptions reques * @return the response body along with {@link PagedResponse}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private PagedResponse requestQueryResponseBodySinglePage(String token, RequestOptions requestOptions) { + private PagedResponse requestQueryResponseBodySinglePage(RequestOptions requestOptions) { final String accept = "application/json"; - Response res = service.requestQueryResponseBodySync(this.client.getEndpoint(), token, accept, - requestOptions, Context.NONE); + Response res + = service.requestQueryResponseBodySync(this.client.getEndpoint(), accept, requestOptions, Context.NONE); return new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), getValues(res.getValue(), "pets"), getNextLink(res.getValue(), "nextToken"), null); } @@ -355,8 +356,24 @@ private PagedResponse requestQueryResponseBodySinglePage(String toke */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedIterable requestQueryResponseBody(RequestOptions requestOptions) { - return new PagedIterable<>(() -> requestQueryResponseBodySinglePage(null, requestOptions), - token -> requestQueryResponseBodySinglePage(token, requestOptions)); + return new PagedIterable<>(() -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> { + UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl()); + urlBuilder.setQueryParameter("token", null); + requestLocal.setUrl(urlBuilder.toString()); + }); + return requestQueryResponseBodySinglePage(requestOptionsLocal); + }, (token) -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> { + UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl()); + urlBuilder.setQueryParameter("token", null); + requestLocal.setUrl(urlBuilder.toString()); + }); + requestOptionsLocal.addQueryParam("token", token, false); + return requestQueryResponseBodySinglePage(requestOptionsLocal); + }); } /** @@ -387,7 +404,6 @@ public PagedIterable requestQueryResponseBody(RequestOptions request * } * * - * @param token The token 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. @@ -396,12 +412,10 @@ public PagedIterable requestQueryResponseBody(RequestOptions request * @return the response body along with {@link PagedResponse} on successful completion of {@link Mono}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private Mono> requestHeaderResponseBodySinglePageAsync(String token, - RequestOptions requestOptions) { + private Mono> requestHeaderResponseBodySinglePageAsync(RequestOptions requestOptions) { final String accept = "application/json"; - return FluxUtil - .withContext(context -> service.requestHeaderResponseBody(this.client.getEndpoint(), token, accept, - requestOptions, context)) + return FluxUtil.withContext( + context -> service.requestHeaderResponseBody(this.client.getEndpoint(), accept, requestOptions, context)) .map(res -> new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), getValues(res.getValue(), "pets"), getNextLink(res.getValue(), "nextToken"), null)); } @@ -442,8 +456,16 @@ private Mono> requestHeaderResponseBodySinglePageAsync */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedFlux requestHeaderResponseBodyAsync(RequestOptions requestOptions) { - return new PagedFlux<>(() -> requestHeaderResponseBodySinglePageAsync(null, requestOptions), - token -> requestHeaderResponseBodySinglePageAsync(token, requestOptions)); + return new PagedFlux<>(() -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> requestLocal.getHeaders().remove("token")); + return requestHeaderResponseBodySinglePageAsync(requestOptionsLocal); + }, (token) -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> requestLocal.getHeaders().remove("token")); + requestOptionsLocal.setHeader("token", token); + return requestHeaderResponseBodySinglePageAsync(requestOptionsLocal); + }); } /** @@ -474,7 +496,6 @@ public PagedFlux requestHeaderResponseBodyAsync(RequestOptions reque * } * * - * @param token The token 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. @@ -483,10 +504,10 @@ public PagedFlux requestHeaderResponseBodyAsync(RequestOptions reque * @return the response body along with {@link PagedResponse}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private PagedResponse requestHeaderResponseBodySinglePage(String token, RequestOptions requestOptions) { + private PagedResponse requestHeaderResponseBodySinglePage(RequestOptions requestOptions) { final String accept = "application/json"; - Response res = service.requestHeaderResponseBodySync(this.client.getEndpoint(), token, accept, - requestOptions, Context.NONE); + Response res + = service.requestHeaderResponseBodySync(this.client.getEndpoint(), accept, requestOptions, Context.NONE); return new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), getValues(res.getValue(), "pets"), getNextLink(res.getValue(), "nextToken"), null); } @@ -527,8 +548,16 @@ private PagedResponse requestHeaderResponseBodySinglePage(String tok */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedIterable requestHeaderResponseBody(RequestOptions requestOptions) { - return new PagedIterable<>(() -> requestHeaderResponseBodySinglePage(null, requestOptions), - token -> requestHeaderResponseBodySinglePage(token, requestOptions)); + return new PagedIterable<>(() -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> requestLocal.getHeaders().remove("token")); + return requestHeaderResponseBodySinglePage(requestOptionsLocal); + }, (token) -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> requestLocal.getHeaders().remove("token")); + requestOptionsLocal.setHeader("token", token); + return requestHeaderResponseBodySinglePage(requestOptionsLocal); + }); } /** @@ -559,7 +588,6 @@ public PagedIterable requestHeaderResponseBody(RequestOptions reques * } * * - * @param token The token 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. @@ -568,12 +596,10 @@ public PagedIterable requestHeaderResponseBody(RequestOptions reques * @return the response body along with {@link PagedResponse} on successful completion of {@link Mono}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private Mono> requestQueryResponseHeaderSinglePageAsync(String token, - RequestOptions requestOptions) { + private Mono> requestQueryResponseHeaderSinglePageAsync(RequestOptions requestOptions) { final String accept = "application/json"; - return FluxUtil - .withContext(context -> service.requestQueryResponseHeader(this.client.getEndpoint(), token, accept, - requestOptions, context)) + return FluxUtil.withContext( + context -> service.requestQueryResponseHeader(this.client.getEndpoint(), accept, requestOptions, context)) .map(res -> new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), getValues(res.getValue(), "pets"), res.getHeaders().getValue(HttpHeaderName.fromString("next-token")), null)); @@ -615,8 +641,24 @@ private Mono> requestQueryResponseHeaderSinglePageAsyn */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedFlux requestQueryResponseHeaderAsync(RequestOptions requestOptions) { - return new PagedFlux<>(() -> requestQueryResponseHeaderSinglePageAsync(null, requestOptions), - token -> requestQueryResponseHeaderSinglePageAsync(token, requestOptions)); + return new PagedFlux<>(() -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> { + UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl()); + urlBuilder.setQueryParameter("token", null); + requestLocal.setUrl(urlBuilder.toString()); + }); + return requestQueryResponseHeaderSinglePageAsync(requestOptionsLocal); + }, (token) -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> { + UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl()); + urlBuilder.setQueryParameter("token", null); + requestLocal.setUrl(urlBuilder.toString()); + }); + requestOptionsLocal.addQueryParam("token", token, false); + return requestQueryResponseHeaderSinglePageAsync(requestOptionsLocal); + }); } /** @@ -647,7 +689,6 @@ public PagedFlux requestQueryResponseHeaderAsync(RequestOptions requ * } * * - * @param token The token 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. @@ -656,11 +697,10 @@ public PagedFlux requestQueryResponseHeaderAsync(RequestOptions requ * @return the response body along with {@link PagedResponse}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private PagedResponse requestQueryResponseHeaderSinglePage(String token, - RequestOptions requestOptions) { + private PagedResponse requestQueryResponseHeaderSinglePage(RequestOptions requestOptions) { final String accept = "application/json"; - Response res = service.requestQueryResponseHeaderSync(this.client.getEndpoint(), token, accept, - requestOptions, Context.NONE); + Response res + = service.requestQueryResponseHeaderSync(this.client.getEndpoint(), accept, requestOptions, Context.NONE); return new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), getValues(res.getValue(), "pets"), res.getHeaders().getValue(HttpHeaderName.fromString("next-token")), null); @@ -702,8 +742,24 @@ private PagedResponse requestQueryResponseHeaderSinglePage(String to */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedIterable requestQueryResponseHeader(RequestOptions requestOptions) { - return new PagedIterable<>(() -> requestQueryResponseHeaderSinglePage(null, requestOptions), - token -> requestQueryResponseHeaderSinglePage(token, requestOptions)); + return new PagedIterable<>(() -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> { + UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl()); + urlBuilder.setQueryParameter("token", null); + requestLocal.setUrl(urlBuilder.toString()); + }); + return requestQueryResponseHeaderSinglePage(requestOptionsLocal); + }, (token) -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> { + UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl()); + urlBuilder.setQueryParameter("token", null); + requestLocal.setUrl(urlBuilder.toString()); + }); + requestOptionsLocal.addQueryParam("token", token, false); + return requestQueryResponseHeaderSinglePage(requestOptionsLocal); + }); } /** @@ -734,7 +790,6 @@ public PagedIterable requestQueryResponseHeader(RequestOptions reque * } * * - * @param token The token 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. @@ -743,12 +798,10 @@ public PagedIterable requestQueryResponseHeader(RequestOptions reque * @return the response body along with {@link PagedResponse} on successful completion of {@link Mono}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private Mono> requestHeaderResponseHeaderSinglePageAsync(String token, - RequestOptions requestOptions) { + private Mono> requestHeaderResponseHeaderSinglePageAsync(RequestOptions requestOptions) { final String accept = "application/json"; - return FluxUtil - .withContext(context -> service.requestHeaderResponseHeader(this.client.getEndpoint(), token, accept, - requestOptions, context)) + return FluxUtil.withContext( + context -> service.requestHeaderResponseHeader(this.client.getEndpoint(), accept, requestOptions, context)) .map(res -> new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), getValues(res.getValue(), "pets"), res.getHeaders().getValue(HttpHeaderName.fromString("next-token")), null)); @@ -790,8 +843,16 @@ private Mono> requestHeaderResponseHeaderSinglePageAsy */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedFlux requestHeaderResponseHeaderAsync(RequestOptions requestOptions) { - return new PagedFlux<>(() -> requestHeaderResponseHeaderSinglePageAsync(null, requestOptions), - token -> requestHeaderResponseHeaderSinglePageAsync(token, requestOptions)); + return new PagedFlux<>(() -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> requestLocal.getHeaders().remove("token")); + return requestHeaderResponseHeaderSinglePageAsync(requestOptionsLocal); + }, (token) -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> requestLocal.getHeaders().remove("token")); + requestOptionsLocal.setHeader("token", token); + return requestHeaderResponseHeaderSinglePageAsync(requestOptionsLocal); + }); } /** @@ -822,7 +883,6 @@ public PagedFlux requestHeaderResponseHeaderAsync(RequestOptions req * } * * - * @param token The token 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. @@ -831,11 +891,10 @@ public PagedFlux requestHeaderResponseHeaderAsync(RequestOptions req * @return the response body along with {@link PagedResponse}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private PagedResponse requestHeaderResponseHeaderSinglePage(String token, - RequestOptions requestOptions) { + private PagedResponse requestHeaderResponseHeaderSinglePage(RequestOptions requestOptions) { final String accept = "application/json"; - Response res = service.requestHeaderResponseHeaderSync(this.client.getEndpoint(), token, accept, - requestOptions, Context.NONE); + Response res + = service.requestHeaderResponseHeaderSync(this.client.getEndpoint(), accept, requestOptions, Context.NONE); return new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), getValues(res.getValue(), "pets"), res.getHeaders().getValue(HttpHeaderName.fromString("next-token")), null); @@ -877,8 +936,16 @@ private PagedResponse requestHeaderResponseHeaderSinglePage(String t */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedIterable requestHeaderResponseHeader(RequestOptions requestOptions) { - return new PagedIterable<>(() -> requestHeaderResponseHeaderSinglePage(null, requestOptions), - token -> requestHeaderResponseHeaderSinglePage(token, requestOptions)); + return new PagedIterable<>(() -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> requestLocal.getHeaders().remove("token")); + return requestHeaderResponseHeaderSinglePage(requestOptionsLocal); + }, (token) -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> requestLocal.getHeaders().remove("token")); + requestOptionsLocal.setHeader("token", token); + return requestHeaderResponseHeaderSinglePage(requestOptionsLocal); + }); } /** @@ -909,7 +976,6 @@ public PagedIterable requestHeaderResponseHeader(RequestOptions requ * } * * - * @param token The token 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. @@ -918,11 +984,11 @@ public PagedIterable requestHeaderResponseHeader(RequestOptions requ * @return the response body along with {@link PagedResponse} on successful completion of {@link Mono}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private Mono> requestQueryNestedResponseBodySinglePageAsync(String token, - RequestOptions requestOptions) { + private Mono> + requestQueryNestedResponseBodySinglePageAsync(RequestOptions requestOptions) { final String accept = "application/json"; return FluxUtil - .withContext(context -> service.requestQueryNestedResponseBody(this.client.getEndpoint(), token, accept, + .withContext(context -> service.requestQueryNestedResponseBody(this.client.getEndpoint(), accept, requestOptions, context)) .map(res -> new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), getValues(res.getValue(), "nestedItems", "pets"), @@ -965,8 +1031,24 @@ private Mono> requestQueryNestedResponseBodySinglePage */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedFlux requestQueryNestedResponseBodyAsync(RequestOptions requestOptions) { - return new PagedFlux<>(() -> requestQueryNestedResponseBodySinglePageAsync(null, requestOptions), - token -> requestQueryNestedResponseBodySinglePageAsync(token, requestOptions)); + return new PagedFlux<>(() -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> { + UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl()); + urlBuilder.setQueryParameter("token", null); + requestLocal.setUrl(urlBuilder.toString()); + }); + return requestQueryNestedResponseBodySinglePageAsync(requestOptionsLocal); + }, (token) -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> { + UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl()); + urlBuilder.setQueryParameter("token", null); + requestLocal.setUrl(urlBuilder.toString()); + }); + requestOptionsLocal.addQueryParam("token", token, false); + return requestQueryNestedResponseBodySinglePageAsync(requestOptionsLocal); + }); } /** @@ -997,7 +1079,6 @@ public PagedFlux requestQueryNestedResponseBodyAsync(RequestOptions * } * * - * @param token The token 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. @@ -1006,10 +1087,9 @@ public PagedFlux requestQueryNestedResponseBodyAsync(RequestOptions * @return the response body along with {@link PagedResponse}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private PagedResponse requestQueryNestedResponseBodySinglePage(String token, - RequestOptions requestOptions) { + private PagedResponse requestQueryNestedResponseBodySinglePage(RequestOptions requestOptions) { final String accept = "application/json"; - Response res = service.requestQueryNestedResponseBodySync(this.client.getEndpoint(), token, accept, + Response res = service.requestQueryNestedResponseBodySync(this.client.getEndpoint(), accept, requestOptions, Context.NONE); return new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), getValues(res.getValue(), "nestedItems", "pets"), getNextLink(res.getValue(), "nestedNext", "nextToken"), @@ -1052,8 +1132,24 @@ private PagedResponse requestQueryNestedResponseBodySinglePage(Strin */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedIterable requestQueryNestedResponseBody(RequestOptions requestOptions) { - return new PagedIterable<>(() -> requestQueryNestedResponseBodySinglePage(null, requestOptions), - token -> requestQueryNestedResponseBodySinglePage(token, requestOptions)); + return new PagedIterable<>(() -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> { + UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl()); + urlBuilder.setQueryParameter("token", null); + requestLocal.setUrl(urlBuilder.toString()); + }); + return requestQueryNestedResponseBodySinglePage(requestOptionsLocal); + }, (token) -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> { + UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl()); + urlBuilder.setQueryParameter("token", null); + requestLocal.setUrl(urlBuilder.toString()); + }); + requestOptionsLocal.addQueryParam("token", token, false); + return requestQueryNestedResponseBodySinglePage(requestOptionsLocal); + }); } /** @@ -1084,7 +1180,6 @@ public PagedIterable requestQueryNestedResponseBody(RequestOptions r * } * * - * @param token The token 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. @@ -1093,11 +1188,11 @@ public PagedIterable requestQueryNestedResponseBody(RequestOptions r * @return the response body along with {@link PagedResponse} on successful completion of {@link Mono}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private Mono> requestHeaderNestedResponseBodySinglePageAsync(String token, - RequestOptions requestOptions) { + private Mono> + requestHeaderNestedResponseBodySinglePageAsync(RequestOptions requestOptions) { final String accept = "application/json"; return FluxUtil - .withContext(context -> service.requestHeaderNestedResponseBody(this.client.getEndpoint(), token, accept, + .withContext(context -> service.requestHeaderNestedResponseBody(this.client.getEndpoint(), accept, requestOptions, context)) .map(res -> new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), getValues(res.getValue(), "nestedItems", "pets"), @@ -1140,8 +1235,16 @@ private Mono> requestHeaderNestedResponseBodySinglePag */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedFlux requestHeaderNestedResponseBodyAsync(RequestOptions requestOptions) { - return new PagedFlux<>(() -> requestHeaderNestedResponseBodySinglePageAsync(null, requestOptions), - token -> requestHeaderNestedResponseBodySinglePageAsync(token, requestOptions)); + return new PagedFlux<>(() -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> requestLocal.getHeaders().remove("token")); + return requestHeaderNestedResponseBodySinglePageAsync(requestOptionsLocal); + }, (token) -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> requestLocal.getHeaders().remove("token")); + requestOptionsLocal.setHeader("token", token); + return requestHeaderNestedResponseBodySinglePageAsync(requestOptionsLocal); + }); } /** @@ -1172,7 +1275,6 @@ public PagedFlux requestHeaderNestedResponseBodyAsync(RequestOptions * } * * - * @param token The token 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. @@ -1181,10 +1283,9 @@ public PagedFlux requestHeaderNestedResponseBodyAsync(RequestOptions * @return the response body along with {@link PagedResponse}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private PagedResponse requestHeaderNestedResponseBodySinglePage(String token, - RequestOptions requestOptions) { + private PagedResponse requestHeaderNestedResponseBodySinglePage(RequestOptions requestOptions) { final String accept = "application/json"; - Response res = service.requestHeaderNestedResponseBodySync(this.client.getEndpoint(), token, accept, + Response res = service.requestHeaderNestedResponseBodySync(this.client.getEndpoint(), accept, requestOptions, Context.NONE); return new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), getValues(res.getValue(), "nestedItems", "pets"), getNextLink(res.getValue(), "nestedNext", "nextToken"), @@ -1227,8 +1328,16 @@ private PagedResponse requestHeaderNestedResponseBodySinglePage(Stri */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedIterable requestHeaderNestedResponseBody(RequestOptions requestOptions) { - return new PagedIterable<>(() -> requestHeaderNestedResponseBodySinglePage(null, requestOptions), - token -> requestHeaderNestedResponseBodySinglePage(token, requestOptions)); + return new PagedIterable<>(() -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> requestLocal.getHeaders().remove("token")); + return requestHeaderNestedResponseBodySinglePage(requestOptionsLocal); + }, (token) -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> requestLocal.getHeaders().remove("token")); + requestOptionsLocal.setHeader("token", token); + return requestHeaderNestedResponseBodySinglePage(requestOptionsLocal); + }); } private List getValues(BinaryData binaryData, String... path) { diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/XmlPaginationsImpl.java b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/XmlPaginationsImpl.java index fb4e7408edc..f6642bede15 100644 --- a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/XmlPaginationsImpl.java +++ b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/XmlPaginationsImpl.java @@ -10,7 +10,6 @@ import com.azure.core.annotation.Host; import com.azure.core.annotation.HostParam; import com.azure.core.annotation.PathParam; -import com.azure.core.annotation.QueryParam; import com.azure.core.annotation.ReturnType; import com.azure.core.annotation.ServiceInterface; import com.azure.core.annotation.ServiceMethod; @@ -29,6 +28,7 @@ import com.azure.core.util.BinaryData; import com.azure.core.util.Context; import com.azure.core.util.FluxUtil; +import com.azure.core.util.UrlBuilder; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -73,8 +73,7 @@ public interface XmlPaginationsService { @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 }) @UnexpectedResponseExceptionType(HttpResponseException.class) Mono> listWithContinuation(@HostParam("endpoint") String endpoint, - @QueryParam("marker") String marker, @HeaderParam("Accept") String accept, RequestOptions requestOptions, - Context context); + @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context); @Get("/payload/pageable/xml/list-with-continuation") @ExpectedResponses({ 200 }) @@ -83,8 +82,7 @@ Mono> listWithContinuation(@HostParam("endpoint") String en @UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 }) @UnexpectedResponseExceptionType(HttpResponseException.class) Response listWithContinuationSync(@HostParam("endpoint") String endpoint, - @QueryParam("marker") String marker, @HeaderParam("Accept") String accept, RequestOptions requestOptions, - Context context); + @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context); @Get("/payload/pageable/xml/list-with-next-link") @ExpectedResponses({ 200 }) @@ -145,7 +143,6 @@ Response listWithNextLinkNextSync(@PathParam(value = "nextLink", enc * } * * - * @param marker The marker 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. @@ -155,11 +152,11 @@ Response listWithNextLinkNextSync(@PathParam(value = "nextLink", enc * {@link Mono}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private Mono> listWithContinuationSinglePageAsync(String marker, - RequestOptions requestOptions) { + private Mono> listWithContinuationSinglePageAsync(RequestOptions requestOptions) { final String accept = "application/xml"; - return FluxUtil.withContext( - context -> service.listWithContinuation(this.client.getEndpoint(), marker, accept, requestOptions, context)) + return FluxUtil + .withContext( + context -> service.listWithContinuation(this.client.getEndpoint(), accept, requestOptions, context)) .map(res -> new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), getXmlValues(res.getValue(), reader -> { try { @@ -193,8 +190,24 @@ private Mono> listWithContinuationSinglePageAsync(Stri */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedFlux listWithContinuationAsync(RequestOptions requestOptions) { - return new PagedFlux<>(() -> listWithContinuationSinglePageAsync(null, requestOptions), - marker -> listWithContinuationSinglePageAsync(marker, requestOptions)); + return new PagedFlux<>(() -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> { + UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl()); + urlBuilder.setQueryParameter("marker", null); + requestLocal.setUrl(urlBuilder.toString()); + }); + return listWithContinuationSinglePageAsync(requestOptionsLocal); + }, (marker) -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> { + UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl()); + urlBuilder.setQueryParameter("marker", null); + requestLocal.setUrl(urlBuilder.toString()); + }); + requestOptionsLocal.addQueryParam("marker", marker, false); + return listWithContinuationSinglePageAsync(requestOptionsLocal); + }); } /** @@ -217,7 +230,6 @@ public PagedFlux listWithContinuationAsync(RequestOptions requestOpt * } * * - * @param marker The marker 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. @@ -226,10 +238,10 @@ public PagedFlux listWithContinuationAsync(RequestOptions requestOpt * @return the XML response for listing pets along with {@link PagedResponse}. */ @ServiceMethod(returns = ReturnType.SINGLE) - private PagedResponse listWithContinuationSinglePage(String marker, RequestOptions requestOptions) { + private PagedResponse listWithContinuationSinglePage(RequestOptions requestOptions) { final String accept = "application/xml"; Response res - = service.listWithContinuationSync(this.client.getEndpoint(), marker, accept, requestOptions, Context.NONE); + = service.listWithContinuationSync(this.client.getEndpoint(), accept, requestOptions, Context.NONE); return new PagedResponseBase<>(res.getRequest(), res.getStatusCode(), res.getHeaders(), getXmlValues(res.getValue(), reader -> { try { @@ -262,8 +274,24 @@ private PagedResponse listWithContinuationSinglePage(String marker, */ @ServiceMethod(returns = ReturnType.COLLECTION) public PagedIterable listWithContinuation(RequestOptions requestOptions) { - return new PagedIterable<>(() -> listWithContinuationSinglePage(null, requestOptions), - marker -> listWithContinuationSinglePage(marker, requestOptions)); + return new PagedIterable<>(() -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> { + UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl()); + urlBuilder.setQueryParameter("marker", null); + requestLocal.setUrl(urlBuilder.toString()); + }); + return listWithContinuationSinglePage(requestOptionsLocal); + }, (marker) -> { + RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions; + requestOptionsLocal.addRequestCallback(requestLocal -> { + UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl()); + urlBuilder.setQueryParameter("marker", null); + requestLocal.setUrl(urlBuilder.toString()); + }); + requestOptionsLocal.addQueryParam("marker", marker, false); + return listWithContinuationSinglePage(requestOptionsLocal); + }); } /** diff --git a/packages/http-client-java/generator/http-client-generator-test/src/test/java/payload/pageable/PageableTests.java b/packages/http-client-java/generator/http-client-generator-test/src/test/java/payload/pageable/PageableTests.java index 57a5b16c428..6517c7e90af 100644 --- a/packages/http-client-java/generator/http-client-generator-test/src/test/java/payload/pageable/PageableTests.java +++ b/packages/http-client-java/generator/http-client-generator-test/src/test/java/payload/pageable/PageableTests.java @@ -146,27 +146,33 @@ public void testContinuationTokenEncoding() { .filter(parameter -> parameter.startsWith("token=")) .collect(Collectors.toList()); Assertions.assertEquals(1, tokenParameters.size()); - Assertions.assertEquals(token, - URLDecoder.decode(tokenParameters.get(0).substring("token=".length()), StandardCharsets.UTF_8)); - body = "{\"pets\":[{\"id\":\"2\",\"name\":\"cat\"}]}"; + String actualToken + = URLDecoder.decode(tokenParameters.get(0).substring("token=".length()), StandardCharsets.UTF_8); + if (token.equals(actualToken)) { + body = "{\"pets\":[{\"id\":\"2\",\"name\":\"cat\"}],\"nextToken\":\"" + token + "next\"}"; + } else { + Assertions.assertEquals(token + "next", actualToken); + body = "{\"pets\":[{\"id\":\"3\",\"name\":\"bird\"}]}"; + } } else { body = "{\"pets\":[{\"id\":\"1\",\"name\":\"dog\"}],\"nextToken\":\"" + token + "\"}"; } return Mono .just(new MockHttpResponse(request, 200, new HttpHeaders(), body.getBytes(StandardCharsets.UTF_8))); }); - assertPetIds( - mockBuilder.buildServerDrivenPaginationContinuationTokenClient().requestQueryResponseBody("foo", "bar"), - "1", "2"); - Assertions.assertEquals(2, requests.size()); + PagedIterable result + = mockBuilder.buildServerDrivenPaginationContinuationTokenClient().requestQueryResponseBody("foo", "bar"); + assertPetIds(result, "1", "2", "3"); + assertPetIds(result, "1", "2", "3"); + Assertions.assertEquals(6, requests.size()); requests.clear(); - Assertions.assertEquals(List.of("1", "2"), + Assertions.assertEquals(List.of("1", "2", "3"), mockBuilder.buildServerDrivenPaginationContinuationTokenAsyncClient() .requestQueryResponseBody("foo", "bar") .map(Pet::getId) .collectList() .block(Duration.ofSeconds(10))); - Assertions.assertEquals(2, requests.size()); + Assertions.assertEquals(3, requests.size()); } @Test From 32715e3224678c6df224d74baeaf80cbf74bad89 Mon Sep 17 00:00:00 2001 From: Weidong Xu Date: Thu, 15 Oct 2026 12:51:30 +0800 Subject: [PATCH 3/7] test(http-client-java): simplify continuation-token paging tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4bbe3fb2-95f1-4311-8050-62ba7dba4860 --- .../http-client-java.instructions.md | 1 + packages/http-client-java/README.md | 8 --- .../java/payload/pageable/PageableTests.java | 55 ------------------- 3 files changed, 1 insertion(+), 63 deletions(-) diff --git a/.github/instructions/http-client-java.instructions.md b/.github/instructions/http-client-java.instructions.md index 98d1338a792..1e4c64acd43 100644 --- a/.github/instructions/http-client-java.instructions.md +++ b/.github/instructions/http-client-java.instructions.md @@ -5,6 +5,7 @@ applyTo: "packages/http-client-java/**/*" - The directories mentioned in this instruction are relative to `/packages/http-client-java`. For example, the root is the folder `/packages/http-client-java`; the directory `generator/http-client-generator-test` refers to `/packages/http-client-java/generator/http-client-generator-test`. - Always use absolute paths when changing directories or running scripts. - Whenever calling `npm`, use `--registry=https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/`. +- `README.md` is generated by `tspd`; do not edit it directly. Update `.tspd/docs/` or emitter option documentation, then run `npm run regen-docs`. # Update Node.js packages for latest dependencies diff --git a/packages/http-client-java/README.md b/packages/http-client-java/README.md index f8bc8d3fb67..67da2e2f5fd 100644 --- a/packages/http-client-java/README.md +++ b/packages/http-client-java/README.md @@ -43,14 +43,6 @@ options: option: value ``` -### Pagination - -Azure clients support both `@nextLink` and `@continuationToken` pagination. Continuation-token -parameters are hidden from client method signatures. To resume from a token, use -`streamByPage(token)` on the returned `PagedIterable`, or `byPage(token)` on `PagedFlux`. -The service must omit the response token when there are no more pages; `has_more` is not interpreted. -Token paging reuses and updates supplied `RequestOptions`; do not share them across concurrent paging operations. - ## Emitter options ### `emitter-output-dir` diff --git a/packages/http-client-java/generator/http-client-generator-test/src/test/java/payload/pageable/PageableTests.java b/packages/http-client-java/generator/http-client-generator-test/src/test/java/payload/pageable/PageableTests.java index 6517c7e90af..5d6f0a471c4 100644 --- a/packages/http-client-java/generator/http-client-generator-test/src/test/java/payload/pageable/PageableTests.java +++ b/packages/http-client-java/generator/http-client-generator-test/src/test/java/payload/pageable/PageableTests.java @@ -3,15 +3,9 @@ package payload.pageable; -import com.azure.core.http.HttpHeaders; -import com.azure.core.http.HttpRequest; import com.azure.core.http.rest.PagedIterable; import com.azure.core.http.rest.RequestOptions; -import com.azure.core.test.http.MockHttpResponse; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.junit.jupiter.api.Assertions; @@ -20,7 +14,6 @@ import payload.pageable.models.XmlPet; import payload.pageable.serverdrivenpagination.alternateinitialverb.models.Filter; import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; public class PageableTests { @@ -127,54 +120,6 @@ public void testProtocolContinuationTokenPreservesOptions() { } } - @Test - public void testContinuationTokenEncoding() { - String token = "opaque +/?&=%"; - List requests = new ArrayList<>(); - PageableClientBuilder mockBuilder = new PageableClientBuilder().httpClient(request -> { - requests.add(request); - Assertions.assertEquals( - "/payload/pageable/server-driven-pagination/continuationtoken/request-query-response-body", - request.getUrl().getPath()); - Assertions.assertEquals("foo", request.getHeaders().getValue("foo")); - String query = request.getUrl().getQuery(); - Assertions.assertTrue(query.contains("bar=bar")); - String body; - if (query.contains("token=")) { - List tokenParameters = List.of(query.split("&")) - .stream() - .filter(parameter -> parameter.startsWith("token=")) - .collect(Collectors.toList()); - Assertions.assertEquals(1, tokenParameters.size()); - String actualToken - = URLDecoder.decode(tokenParameters.get(0).substring("token=".length()), StandardCharsets.UTF_8); - if (token.equals(actualToken)) { - body = "{\"pets\":[{\"id\":\"2\",\"name\":\"cat\"}],\"nextToken\":\"" + token + "next\"}"; - } else { - Assertions.assertEquals(token + "next", actualToken); - body = "{\"pets\":[{\"id\":\"3\",\"name\":\"bird\"}]}"; - } - } else { - body = "{\"pets\":[{\"id\":\"1\",\"name\":\"dog\"}],\"nextToken\":\"" + token + "\"}"; - } - return Mono - .just(new MockHttpResponse(request, 200, new HttpHeaders(), body.getBytes(StandardCharsets.UTF_8))); - }); - PagedIterable result - = mockBuilder.buildServerDrivenPaginationContinuationTokenClient().requestQueryResponseBody("foo", "bar"); - assertPetIds(result, "1", "2", "3"); - assertPetIds(result, "1", "2", "3"); - Assertions.assertEquals(6, requests.size()); - requests.clear(); - Assertions.assertEquals(List.of("1", "2", "3"), - mockBuilder.buildServerDrivenPaginationContinuationTokenAsyncClient() - .requestQueryResponseBody("foo", "bar") - .map(Pet::getId) - .collectList() - .block(Duration.ofSeconds(10))); - Assertions.assertEquals(3, requests.size()); - } - @Test public void testXmlContinuationToken() { Assertions.assertEquals(List.of("1", "2", "3", "4"), From a0f8f18ff1334c00cd8a66ca5c5ae770bce78d9a Mon Sep 17 00:00:00 2001 From: Weidong Xu Date: Thu, 15 Oct 2026 13:23:20 +0800 Subject: [PATCH 4/7] fix(http-client-java): correct continuation-token paging generation Avoid token lambda name collisions and preserve typed token forwarding for Context-based clients. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4bbe3fb2-95f1-4311-8050-62ba7dba4860 --- .../core/template/ClientMethodTemplate.java | 33 +++++++- .../ContinuationTokenPagingTests.java | 78 +++++++++++++++++-- 2 files changed, 102 insertions(+), 9 deletions(-) diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java index 8ceb6f2023f..ea726892a84 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java @@ -38,6 +38,7 @@ import io.clientcore.core.annotations.ReturnType; import io.clientcore.core.http.models.HttpHeaderName; import io.clientcore.core.utils.CoreUtils; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; @@ -1190,6 +1191,24 @@ private static boolean hasContinuationToken(ClientMethod clientMethod) { && clientMethod.getMethodPageDetails().getContinuationToken() != null; } + private static String continuationTokenParameterName(ClientMethod clientMethod) { + MethodPageDetails.ContinuationToken token = clientMethod.getMethodPageDetails().getContinuationToken(); + Set usedNames + = new HashSet<>(List.of("requestOptions", "requestOptionsLocal", "requestLocal", "urlBuilder", "pageSize")); + clientMethod.getParameters() + .stream() + .filter(parameter -> parameter != token.getClientMethodParameter()) + .map(ClientMethodParameter::getName) + .forEach(usedNames::add); + String baseName = token.getRequestParameter().getName(); + String name = baseName; + int suffix = 1; + while (usedNames.contains(name)) { + name = baseName + suffix++; + } + return name; + } + private void generateContinuationTokenPaging(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings, boolean sync) { addServiceMethodAnnotation(typeBlock, ReturnType.COLLECTION); @@ -1205,8 +1224,15 @@ private void generateContinuationTokenPaging(ClientMethod clientMethod, JavaType ? clientMethod.getProxyMethod().getPagingSinglePageMethodName() : clientMethod.getProxyMethod().getPagingAsyncSinglePageMethodName(); String firstPage = getPagingSinglePageExpression(clientMethod, methodName, firstArguments, settings); - String nextPage = getPagingSinglePageExpression(clientMethod, methodName, firstArguments, settings, - clientMethod.getMethodPageDetails().getContinuationToken().getRequestParameter().getName()); + String tokenName = continuationTokenParameterName(clientMethod); + String nextArguments = settings.isDataPlaneClient() + ? firstArguments + : clientMethod.getMethodParameters() + .stream() + .map(p -> p == tokenParameter ? tokenName : p.getName()) + .collect(Collectors.joining(", ")); + String nextPage + = getPagingSinglePageExpression(clientMethod, methodName, nextArguments, settings, tokenName); function.line("return new %s<>(", sync ? "PagedIterable" : "PagedFlux"); function.indent(() -> { function.line(firstPage + ","); @@ -1930,7 +1956,8 @@ private String getPagingSinglePageExpression(ClientMethod clientMethod, String m Optional maxPageSizeSerializedName = settings.isDataPlaneClient() && settings.isPageSizeEnabled() ? MethodUtil.serializedNameOfMaxPageSizeParameter(clientMethod.getProxyMethod()) : Optional.empty(); - boolean continuationToken = settings.isAzureV1() && hasContinuationToken(clientMethod); + boolean continuationToken + = settings.isAzureV1() && settings.isDataPlaneClient() && hasContinuationToken(clientMethod); if (continuationToken || maxPageSizeSerializedName.isPresent()) { argumentLine = argumentLine.replace("requestOptions", "requestOptionsLocal"); StringBuilder expression = new StringBuilder("("); diff --git a/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ContinuationTokenPagingTests.java b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ContinuationTokenPagingTests.java index f2a54f751ed..00dc8ee445d 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ContinuationTokenPagingTests.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ContinuationTokenPagingTests.java @@ -3,6 +3,7 @@ package com.microsoft.typespec.http.client.generator.core.template; +import com.microsoft.typespec.http.client.generator.core.MockUnitJavagen; import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.RequestParameterLocation; import com.microsoft.typespec.http.client.generator.core.extension.plugin.JavaSettings; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClassType; @@ -16,11 +17,44 @@ import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ReturnValue; import com.microsoft.typespec.http.client.generator.core.model.javamodel.JavaClass; import com.microsoft.typespec.http.client.generator.core.model.javamodel.JavaFileContents; +import io.clientcore.core.utils.IOExceptionCheckedFunction; import java.util.List; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +@Isolated public class ContinuationTokenPagingTests { + @AfterEach + public void restoreSettings() { + new MockUnitJavagen(); + JavaSettings.clear(); + } + + @ParameterizedTest + @ValueSource(strings = { "requestOptionsLocal", "requestLocal", "urlBuilder", "pageSize" }) + public void avoidsTokenLambdaNameCollisions(String tokenName) { + for (boolean pageSize : List.of(false, true)) { + String output = renderPaging(RequestParameterLocation.QUERY, false, true, tokenName, pageSize); + Assertions.assertTrue(output.contains("(" + tokenName + "1" + (pageSize ? ", pageSize" : "") + ") ->"), + output); + } + } + + @Test + public void passesTypedTokenDirectlyWithContext() { + for (boolean sync : List.of(false, true)) { + String output = renderPaging(RequestParameterLocation.QUERY, sync, false, "token", false); + String singlePage = sync ? "listSinglePage" : "listSinglePageAsync"; + Assertions.assertTrue(output.contains(singlePage + "(null, context)"), output); + Assertions.assertTrue(output.contains(singlePage + "(token, context)"), output); + Assertions.assertFalse(output.contains("requestOptions"), output); + } + } + @Test public void passesQueryTokenThroughRequestOptions() { String output = renderProtocolPaging(RequestParameterLocation.QUERY, false); @@ -41,21 +75,53 @@ public void passesHeaderTokenThroughRequestOptions() { } private static String renderProtocolPaging(RequestParameterLocation location, boolean sync) { - ProxyMethodParameter token = new ProxyMethodParameter.Builder().name("token") - .requestParameterName("token") + return renderPaging(location, sync, true, "token", false); + } + + private static String renderPaging(RequestParameterLocation location, boolean sync, boolean dataPlane, + String tokenName, boolean pageSize) { + new MockUnitJavagen() { + @Override + @SuppressWarnings("unchecked") + public T getValue(String key, IOExceptionCheckedFunction converter) { + if ("data-plane".equals(key)) { + return (T) Boolean.valueOf(dataPlane); + } + if ("enable-page-size".equals(key)) { + return (T) Boolean.valueOf(pageSize); + } + return super.getValue(key, converter); + } + }; + JavaSettings.clear(); + ProxyMethodParameter token = new ProxyMethodParameter.Builder().name(tokenName) + .requestParameterName(tokenName) .requestParameterLocation(location) .wireType(ClassType.STRING) .build(); + ClientMethodParameter typedToken + = new ClientMethodParameter.Builder().name(tokenName).wireType(ClassType.STRING).build(); + ProxyMethodParameter maxPageSize = new ProxyMethodParameter.Builder().name("maxPageSize") + .requestParameterName("maxpagesize") + .requestParameterLocation(RequestParameterLocation.QUERY) + .wireType(ClassType.INTEGER) + .build(); ClientMethod method = pagingMethod().newBuilder() - .parameters(List.of(ClientMethodParameter.REQUEST_OPTIONS_PARAMETER)) - .proxyMethod(new ProxyMethod.Builder().name("list").parameters(List.of()).build()) + .parameters(dataPlane + ? List.of(ClientMethodParameter.REQUEST_OPTIONS_PARAMETER) + : List.of(typedToken, ClientMethodParameter.CONTEXT_PARAMETER)) + .proxyMethod(new ProxyMethod.Builder().name("list") + .parameters(List.of()) + .allParameters(pageSize ? List.of(maxPageSize) : List.of()) + .build()) .methodPageDetails(new MethodPageDetails(List.of(), null, null, null, - new MethodPageDetails.ContinuationToken(token, null, "next-token", null), null, null)) + new MethodPageDetails.ContinuationToken(token, dataPlane ? null : typedToken, "next-token", null), null, + null)) .build(); JavaFileContents contents = new JavaFileContents(); if (sync) { ClientMethodTemplate.getInstance() - .generateProtocolPagingPlainSync(method, new JavaClass(contents), JavaSettings.getInstance()); + .generatePagingPlainSync(method, new JavaClass(contents), JavaSettings.getInstance()); } else { ClientMethodTemplate.getInstance() .generatePagingAsync(method, new JavaClass(contents), JavaSettings.getInstance()); From 0b75d42c61a523887ef92ddced5ce5b4d6d9eaa1 Mon Sep 17 00:00:00 2001 From: Weidong Xu Date: Thu, 15 Oct 2026 14:17:15 +0800 Subject: [PATCH 5/7] fix(http-client-java): make paging metadata and variables consistent Prefer next-link metadata over continuation tokens, replace exact paging arguments, and allocate paging callback locals against enclosing parameters. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4bbe3fb2-95f1-4311-8050-62ba7dba4860 --- .../generator/core/mapper/PagingMetadata.java | 5 +- .../core/template/ClientMethodTemplate.java | 182 +++++++++++------- .../ClientCoreClientMethodTemplate.java | 2 +- .../core/mapper/PagingMetadataTests.java | 124 ++++++++++++ .../ContinuationTokenPagingTests.java | 147 ++++++++++++-- 5 files changed, 381 insertions(+), 79 deletions(-) create mode 100644 packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/mapper/PagingMetadataTests.java diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/PagingMetadata.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/PagingMetadata.java index c843858b03a..0b658001e41 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/PagingMetadata.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/mapper/PagingMetadata.java @@ -89,8 +89,9 @@ static PagingMetadata create(Operation operation, ProxyMethod proxyMethod, nextMethods = List.of(); } - final MethodPageDetails.ContinuationToken continuationToken - = getContinuationToken(xmsPageable, responseType, parametersDetails); + final MethodPageDetails.ContinuationToken continuationToken = CoreUtils.isNullOrEmpty(nextLinkPropertyReference) + ? getContinuationToken(xmsPageable, responseType, parametersDetails) + : null; final ClientMethodParameter maxPageSizeParameter = getPageSizeClientMethodParameter(parametersDetails); MethodPageDetails.NextLinkReInjection nextLinkReInjection = null; diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java index ea726892a84..267041f6709 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java @@ -874,15 +874,17 @@ protected void generateProtocolPagingPlainSync(ClientMethod clientMethod, JavaTy writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { addOptionalVariables(function, clientMethod); - function.line("RequestOptions requestOptionsForNextPage = new RequestOptions();"); - function.line( - "requestOptionsForNextPage.setContext(requestOptions != null && requestOptions.getContext() != null ? requestOptions.getContext() : " - + TemplateUtil.getContextNone() + ");"); + String nextOptions + = availablePagingVariableName("requestOptionsForNextPage", pagingVariableNames(clientMethod)); + function.line("RequestOptions %s = new RequestOptions();", nextOptions); + function.line(nextOptions + + ".setContext(requestOptions != null && requestOptions.getContext() != null ? requestOptions.getContext() : " + + TemplateUtil.getContextNone() + ");"); MethodPageDetails.NextLinkReInjection nextLinkReInjection = clientMethod.getMethodPageDetails().getNextLinkReInjection(); if (nextLinkReInjection != null) { - addQueryParameterReInjectionLogic(nextLinkReInjection, function); + addQueryParameterReInjectionLogic(nextLinkReInjection, clientMethod, function); } function.line("return new PagedIterable<>("); @@ -1098,18 +1100,17 @@ protected void generatePagingPlainSync(ClientMethod clientMethod, JavaType typeB writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { addOptionalVariables(function, clientMethod); if (settings.isDataPlaneClient()) { - function.line("RequestOptions requestOptionsForNextPage = new RequestOptions();"); - function.line( - "requestOptionsForNextPage.setContext(requestOptions != null && requestOptions.getContext() != null ? requestOptions.getContext() : " - + TemplateUtil.getContextNone() + ");"); + String nextOptions + = availablePagingVariableName("requestOptionsForNextPage", pagingVariableNames(clientMethod)); + function.line("RequestOptions %s = new RequestOptions();", nextOptions); + function.line(nextOptions + + ".setContext(requestOptions != null && requestOptions.getContext() != null ? requestOptions.getContext() : " + + TemplateUtil.getContextNone() + ");"); } function.line("return new PagedIterable<>("); - String nextMethodArgs = clientMethod.getMethodPageDetails() - .getNextMethod() - .getArgumentList() - .replace("requestOptions", "requestOptionsForNextPage"); + String nextMethodArgs = clientMethod.getMethodPageDetails().getNextMethod().getArgumentList(); String firstPageArgs = clientMethod.getArgumentList(); function.indent(() -> { function.line("%s,", this.getPagingSinglePageExpression(clientMethod, @@ -1146,16 +1147,18 @@ protected void generatePagingAsync(ClientMethod clientMethod, JavaType typeBlock writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { addOptionalVariables(function, clientMethod); if (settings.isDataPlaneClient()) { - function.line("RequestOptions requestOptionsForNextPage = new RequestOptions();"); - function.line( - "requestOptionsForNextPage.setContext(requestOptions != null && requestOptions.getContext() != null ? requestOptions.getContext() : " - + TemplateUtil.getContextNone() + ");"); + String nextOptions + = availablePagingVariableName("requestOptionsForNextPage", pagingVariableNames(clientMethod)); + function.line("RequestOptions %s = new RequestOptions();", nextOptions); + function.line(nextOptions + + ".setContext(requestOptions != null && requestOptions.getContext() != null ? requestOptions.getContext() : " + + TemplateUtil.getContextNone() + ");"); } MethodPageDetails.NextLinkReInjection reinjectedParams = clientMethod.getMethodPageDetails().getNextLinkReInjection(); if (reinjectedParams != null) { - addQueryParameterReInjectionLogic(reinjectedParams, function); + addQueryParameterReInjectionLogic(reinjectedParams, clientMethod, function); } function.line("return new PagedFlux<>("); @@ -1201,14 +1204,39 @@ private static String continuationTokenParameterName(ClientMethod clientMethod) .map(ClientMethodParameter::getName) .forEach(usedNames::add); String baseName = token.getRequestParameter().getName(); + return availablePagingVariableName(baseName, usedNames); + } + + private static Set pagingVariableNames(ClientMethod clientMethod) { + return clientMethod.getParameters() + .stream() + .map(ClientMethodParameter::getName) + .collect(Collectors.toCollection(HashSet::new)); + } + + private static String availablePagingVariableName(String baseName, Set usedNames) { String name = baseName; int suffix = 1; - while (usedNames.contains(name)) { + while (!usedNames.add(name)) { name = baseName + suffix++; } return name; } + private static String replacePagingArgument(String arguments, String name, String replacement) { + return List.of(arguments.split(", ")) + .stream() + .map(argument -> argument.equals(name) ? replacement : argument) + .collect(Collectors.joining(", ")); + } + + private static String pagingQueryCallback(String options, String request, String builder, String name, + String value) { + return options + ".addRequestCallback(" + request + " -> {" + "UrlBuilder " + builder + " = UrlBuilder.parse(" + + request + ".getUrl());" + builder + ".setQueryParameter(" + ClassType.STRING.defaultValueExpression(name) + + ", " + value + ");" + request + ".setUrl(" + builder + ".toString());});"; + } + private void generateContinuationTokenPaging(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings, boolean sync) { addServiceMethodAnnotation(typeBlock, ReturnType.COLLECTION); @@ -1266,18 +1294,23 @@ private static String continuationTokenExpression(ClientMethod clientMethod, Jav } protected void addQueryParameterReInjectionLogic(MethodPageDetails.NextLinkReInjection nextLinkReInjection, - JavaBlock javaBlock) { + ClientMethod clientMethod, JavaBlock javaBlock) { + Set usedNames = pagingVariableNames(clientMethod); + String nextOptions = availablePagingVariableName("requestOptionsForNextPage", usedNames); + String httpRequest = availablePagingVariableName("httpRequest", usedNames); + String urlBuilder = availablePagingVariableName("urlBuilder", usedNames); + String queryParams = availablePagingVariableName("queryParams", usedNames); javaBlock.line("if (requestOptions != null) {"); javaBlock.indent(() -> { - javaBlock.line("requestOptions.addRequestCallback(httpRequest -> {"); + javaBlock.line("requestOptions.addRequestCallback(%s -> {", httpRequest); javaBlock.indent(() -> { - javaBlock.line("UrlBuilder urlBuilder = UrlBuilder.parse(httpRequest.getUrl().toString());"); - javaBlock.line("Map queryParams = urlBuilder.getQuery();"); + javaBlock.line("UrlBuilder %s = UrlBuilder.parse(%s.getUrl().toString());", urlBuilder, httpRequest); + javaBlock.line("Map %s = %s.getQuery();", queryParams, urlBuilder); for (String paramSerializedName : nextLinkReInjection.getQueryParameterSerializedNames()) { - javaBlock.line("if (queryParams.containsKey(\"" + paramSerializedName + "\")) {"); + String name = ClassType.STRING.defaultValueExpression(paramSerializedName); + javaBlock.line("if (%s.containsKey(%s)) {", queryParams, name); javaBlock.indent(() -> { - javaBlock.line("requestOptionsForNextPage.addQueryParam(\"" + paramSerializedName - + "\", queryParams.get(\"" + paramSerializedName + "\"));"); + javaBlock.line("%s.addQueryParam(%s, %s.get(%s));", nextOptions, name, queryParams, name); }); javaBlock.line("}"); } @@ -1959,32 +1992,40 @@ private String getPagingSinglePageExpression(ClientMethod clientMethod, String m boolean continuationToken = settings.isAzureV1() && settings.isDataPlaneClient() && hasContinuationToken(clientMethod); if (continuationToken || maxPageSizeSerializedName.isPresent()) { - argumentLine = argumentLine.replace("requestOptions", "requestOptionsLocal"); + Set usedNames = pagingVariableNames(clientMethod); + if (continuationParameter != null) { + usedNames.add(continuationParameter); + } + String requestOptionsLocal = availablePagingVariableName("requestOptionsLocal", usedNames); + String requestLocal = availablePagingVariableName("requestLocal", usedNames); + String urlBuilder = availablePagingVariableName("urlBuilder", usedNames); + String pageSize = availablePagingVariableName("pageSize", usedNames); + argumentLine = replacePagingArgument(argumentLine, "requestOptions", requestOptionsLocal); StringBuilder expression = new StringBuilder("("); if (continuationParameter != null) { expression.append(continuationParameter); } if (maxPageSizeSerializedName.isPresent()) { - expression.append(continuationParameter == null ? "pageSize" : ", pageSize"); + if (continuationParameter != null) { + expression.append(", "); + } + expression.append(pageSize); } expression.append(") -> {") - .append( - "RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions;"); + .append("RequestOptions ") + .append(requestOptionsLocal) + .append(" = requestOptions == null ? new RequestOptions() : requestOptions;"); if (continuationToken) { // Clear tokens from earlier callbacks before applying the current page's token. ProxyMethodParameter token = clientMethod.getMethodPageDetails().getContinuationToken().getRequestParameter(); String name = ClassType.STRING.defaultValueExpression(token.getRequestParameterName()); if (token.getRequestParameterLocation() == RequestParameterLocation.QUERY) { - expression.append("requestOptionsLocal.addRequestCallback(requestLocal -> {") - .append("UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl());") - .append("urlBuilder.setQueryParameter(") - .append(name) - .append(", null);") - .append("requestLocal.setUrl(urlBuilder.toString());") - .append("});"); + expression.append(pagingQueryCallback(requestOptionsLocal, requestLocal, urlBuilder, + token.getRequestParameterName(), "null")); if (continuationParameter != null) { - expression.append("requestOptionsLocal.addQueryParam(") + expression.append(requestOptionsLocal) + .append(".addQueryParam(") .append(name) .append(", ") .append(continuationParameter) @@ -1993,13 +2034,17 @@ private String getPagingSinglePageExpression(ClientMethod clientMethod, String m .append(");"); } } else { - expression - .append( - "requestOptionsLocal.addRequestCallback(requestLocal -> requestLocal.getHeaders().remove(") + expression.append(requestOptionsLocal) + .append(".addRequestCallback(") + .append(requestLocal) + .append(" -> ") + .append(requestLocal) + .append(".getHeaders().remove(") .append(name) .append("));"); if (continuationParameter != null) { - expression.append("requestOptionsLocal.setHeader(") + expression.append(requestOptionsLocal) + .append(".setHeader(") .append(name) .append(", ") .append(continuationParameter) @@ -2008,14 +2053,11 @@ private String getPagingSinglePageExpression(ClientMethod clientMethod, String m } } if (maxPageSizeSerializedName.isPresent()) { - expression.append("if (pageSize != null) {") - .append(" requestOptionsLocal.addRequestCallback(requestLocal -> {") - .append(" UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl());") - .append(" urlBuilder.setQueryParameter(\"") - .append(maxPageSizeSerializedName.get()) - .append("\", String.valueOf(pageSize));") - .append(" requestLocal.setUrl(urlBuilder.toString());") - .append(" });") + expression.append("if (") + .append(pageSize) + .append(" != null) {") + .append(pagingQueryCallback(requestOptionsLocal, requestLocal, urlBuilder, + maxPageSizeSerializedName.get(), "String.valueOf(" + pageSize + ")")) .append("}"); } expression.append(String.format("return %s(%s);", methodName, argumentLine)).append("}"); @@ -2033,23 +2075,33 @@ private String getPagingSinglePageExpression(ClientMethod clientMethod, String m private String getPagingNextPageExpression(ClientMethod clientMethod, String methodName, String argumentLine, JavaSettings settings) { + Set usedNames = pagingVariableNames(clientMethod); + String nextOptions = availablePagingVariableName("requestOptionsForNextPage", usedNames); + String nextLink = availablePagingVariableName("nextLink", usedNames); + argumentLine = replacePagingArgument(argumentLine, "nextLink", nextLink); if (settings.isDataPlaneClient() && settings.isPageSizeEnabled()) { Optional maxPageSizeSerializedName = MethodUtil.serializedNameOfMaxPageSizeParameter(clientMethod.getProxyMethod()); if (maxPageSizeSerializedName.isPresent()) { - argumentLine = argumentLine.replace("requestOptions", "requestOptionsLocal"); + String requestOptionsLocal = availablePagingVariableName("requestOptionsLocal", usedNames); + String requestLocal = availablePagingVariableName("requestLocal", usedNames); + String urlBuilder = availablePagingVariableName("urlBuilder", usedNames); + String pageSize = availablePagingVariableName("pageSize", usedNames); + argumentLine = replacePagingArgument(argumentLine, "requestOptions", requestOptionsLocal); StringBuilder expression = new StringBuilder(); - expression.append("(nextLink, pageSize) -> {"); - expression.append("RequestOptions requestOptionsLocal = new RequestOptions();") - .append("requestOptionsLocal.setContext(requestOptionsForNextPage.getContext());") - .append("if (pageSize != null) {") - .append(" requestOptionsLocal.addRequestCallback(requestLocal -> {") - .append(" UrlBuilder urlBuilder = UrlBuilder.parse(requestLocal.getUrl());") - .append(" urlBuilder.setQueryParameter(\"") - .append(maxPageSizeSerializedName.get()) - .append("\", String.valueOf(pageSize));") - .append(" requestLocal.setUrl(urlBuilder.toString());") - .append(" });") + expression.append("(").append(nextLink).append(", ").append(pageSize).append(") -> {"); + expression.append("RequestOptions ") + .append(requestOptionsLocal) + .append(" = new RequestOptions();") + .append(requestOptionsLocal) + .append(".setContext(") + .append(nextOptions) + .append(".getContext());") + .append("if (") + .append(pageSize) + .append(" != null) {") + .append(pagingQueryCallback(requestOptionsLocal, requestLocal, urlBuilder, + maxPageSizeSerializedName.get(), "String.valueOf(" + pageSize + ")")) .append("}") .append(String.format("return %s(%s);", methodName, argumentLine)); expression.append("}"); @@ -2058,13 +2110,13 @@ private String getPagingNextPageExpression(ClientMethod clientMethod, String met } if (settings.isDataPlaneClient()) { - argumentLine = argumentLine.replace("requestOptions", "requestOptionsForNextPage"); + argumentLine = replacePagingArgument(argumentLine, "requestOptions", nextOptions); } if (settings.isAzureV1()) { - return String.format("nextLink -> %s(%s)", methodName, argumentLine); + return String.format("%s -> %s(%s)", nextLink, methodName, argumentLine); } else { - return String.format("(pagingOptions, nextLink) -> { %s return %s(%s); }", + return String.format("(pagingOptions, %s) -> { %s return %s(%s); }", nextLink, getLogExceptionExpressionForPagingOptions(clientMethod), methodName, argumentLine); } } diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/clientcore/ClientCoreClientMethodTemplate.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/clientcore/ClientCoreClientMethodTemplate.java index f4dd1e6195c..d03a6480618 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/clientcore/ClientCoreClientMethodTemplate.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/clientcore/ClientCoreClientMethodTemplate.java @@ -1327,7 +1327,7 @@ protected String getLogExpression(String propertyName, String methodName) { @Override protected void addQueryParameterReInjectionLogic(MethodPageDetails.NextLinkReInjection nextLinkReInjection, - JavaBlock javaBlock) { + ClientMethod clientMethod, JavaBlock javaBlock) { } /** diff --git a/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/mapper/PagingMetadataTests.java b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/mapper/PagingMetadataTests.java new file mode 100644 index 00000000000..a08fd17b11f --- /dev/null +++ b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/mapper/PagingMetadataTests.java @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.typespec.http.client.generator.core.mapper; + +import com.microsoft.typespec.http.client.generator.core.MockUnitJavagen; +import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.Header; +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; +import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.Parameter; +import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.Protocol; +import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.Protocols; +import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.RequestParameterLocation; +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.extension.model.extensionmodel.PageableContinuationToken; +import com.microsoft.typespec.http.client.generator.core.extension.model.extensionmodel.XmsExtensions; +import com.microsoft.typespec.http.client.generator.core.extension.model.extensionmodel.XmsPageable; +import com.microsoft.typespec.http.client.generator.core.extension.plugin.JavaSettings; +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.ClientMethodParameter; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientMethodType; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientModel; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientModelProperty; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientModels; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.GenericType; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ListType; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.MethodPageDetails; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ProxyMethod; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ReturnValue; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; + +@Isolated +public class PagingMetadataTests { + @AfterEach + public void restoreSettings() { + ClientModels.getInstance().clear(); + new MockUnitJavagen(); + JavaSettings.clear(); + } + + @Test + public void prefersNextLinkOverContinuationToken() { + new MockUnitJavagen(); + JavaSettings.clear(); + ClientModel model = new ClientModel.Builder().name("PagingMetadataResponse") + .packageName("com.azure.test") + .properties(List.of( + new ClientModelProperty.Builder().name("items") + .serializedName("items") + .wireType(new ListType(ClassType.STRING)) + .clientType(new ListType(ClassType.STRING)) + .build(), + new ClientModelProperty.Builder().name("nextLink") + .serializedName("nextLink") + .wireType(ClassType.STRING) + .clientType(ClassType.STRING) + .build())) + .build(); + ClientModels.getInstance().addModel(model); + StringSchema schema = new StringSchema(); + schema.setType(Schema.AllSchemaTypes.STRING); + Language language = new Language(); + language.setName("token"); + language.setSerializedName("token"); + Languages languages = new Languages(); + languages.setJava(language); + languages.setDefault(language); + Protocol http = new Protocol(); + http.setIn(RequestParameterLocation.QUERY); + Protocols protocols = new Protocols(); + protocols.setHttp(http); + Parameter parameter = new Parameter(); + parameter.setSchema(schema); + parameter.setLanguage(languages); + parameter.setProtocol(protocols); + Header header = new Header(); + header.setHeader("next-token"); + PageableContinuationToken continuationToken = new PageableContinuationToken(); + continuationToken.setParameter(parameter); + continuationToken.setResponseHeader(header); + XmsPageable pageable = new XmsPageable(); + pageable.setItemName("items"); + pageable.setContinuationToken(continuationToken); + XmsExtensions extensions = new XmsExtensions(); + extensions.setXmsPageable(pageable); + Operation operation = new Operation(); + operation.setExtensions(extensions); + ClientMethodParameter token = new ClientMethodParameter.Builder().name("token") + .annotations(List.of()) + .wireType(ClassType.STRING) + .build(); + ClientMethodParametersDetails parameters = new ClientMethodParametersDetails( + List.of(new ParametersTuple(parameter, token)), List.of(), Map.of(), null); + ProxyMethod proxy = new ProxyMethod.Builder().name("list") + .responseBodyType(new ClassType.Builder().name(model.getName()).packageName(model.getPackage()).build()) + .build(); + for (boolean nextLink : List.of(false, true)) { + pageable.setNextLinkName(nextLink ? "nextLink" : null); + PagingMetadata metadata = PagingMetadata.create(operation, proxy, parameters, JavaSettings.getInstance()); + Assertions.assertNotNull(metadata); + for (boolean sync : List.of(false, true)) { + MethodPageDetails details = metadata.asMethodPageDetails(sync); + Assertions.assertEquals(nextLink, details.nonNullNextLink()); + Assertions.assertEquals(nextLink, details.getContinuationToken() == null); + ClientMethod method = new ClientMethod.Builder().name("list") + .parameters(List.of(token, ClientMethodParameter.CONTEXT_PARAMETER)) + .methodPageDetails(details) + .type(sync ? ClientMethodType.PagingSync : ClientMethodType.PagingAsync) + .returnValue(new ReturnValue("The items.", + sync ? GenericType.pagedIterable(ClassType.STRING) : GenericType.pagedFlux(ClassType.STRING))) + .build(); + Assertions.assertEquals(nextLink, method.getParametersDeclaration().contains("String token")); + } + } + } +} diff --git a/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ContinuationTokenPagingTests.java b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ContinuationTokenPagingTests.java index 00dc8ee445d..6d306d71c26 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ContinuationTokenPagingTests.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/ContinuationTokenPagingTests.java @@ -10,24 +10,37 @@ import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientMethod; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientMethodParameter; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientMethodType; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientModelProperty; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.GenericType; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.MethodPageDetails; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ModelPropertySegment; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ProxyMethod; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ProxyMethodParameter; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ReturnValue; import com.microsoft.typespec.http.client.generator.core.model.javamodel.JavaClass; import com.microsoft.typespec.http.client.generator.core.model.javamodel.JavaFileContents; import io.clientcore.core.utils.IOExceptionCheckedFunction; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; +import javax.tools.ToolProvider; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @Isolated public class ContinuationTokenPagingTests { + @TempDir + Path compilationDirectory; + @AfterEach public void restoreSettings() { new MockUnitJavagen(); @@ -36,22 +49,79 @@ public void restoreSettings() { @ParameterizedTest @ValueSource(strings = { "requestOptionsLocal", "requestLocal", "urlBuilder", "pageSize" }) - public void avoidsTokenLambdaNameCollisions(String tokenName) { + public void avoidsTokenLambdaNameCollisions(String tokenName) throws IOException { for (boolean pageSize : List.of(false, true)) { String output = renderPaging(RequestParameterLocation.QUERY, false, true, tokenName, pageSize); Assertions.assertTrue(output.contains("(" + tokenName + "1" + (pageSize ? ", pageSize" : "") + ") ->"), output); + assertCompiles(output, "RequestOptions requestOptions"); + } + } + + @ParameterizedTest + @ValueSource(strings = { "requestOptionsFilter", "requestOptionsLocal", "requestLocal", "urlBuilder", "pageSize" }) + public void preservesNonTokenParameterNames(String parameterName) throws IOException { + ClientMethodParameter parameter = new ClientMethodParameter.Builder().name(parameterName) + .annotations(List.of()) + .wireType(ClassType.STRING) + .required(true) + .build(); + for (boolean sync : List.of(false, true)) { + for (boolean pageSize : List.of(false, true)) { + for (RequestParameterLocation location : List.of(RequestParameterLocation.QUERY, + RequestParameterLocation.HEADER)) { + String output = renderPaging(location, sync, true, "token", pageSize, parameter); + assertCompiles(output, "String " + parameterName + ", RequestOptions requestOptions"); + } + } + } + } + + @ParameterizedTest + @ValueSource( + strings = { + "requestOptionsFilter", + "requestOptionsLocal", + "requestOptionsForNextPage", + "requestLocal", + "urlBuilder", + "pageSize", + "nextLink", + "httpRequest", + "queryParams" }) + public void preservesNextLinkParameterNames(String parameterName) throws IOException { + ClientMethodParameter parameter = new ClientMethodParameter.Builder().name(parameterName) + .annotations(List.of()) + .wireType(ClassType.STRING) + .required(true) + .build(); + for (boolean sync : List.of(false, true)) { + for (boolean pageSize : List.of(false, true)) { + String output + = renderPaging(RequestParameterLocation.QUERY, sync, true, "token", pageSize, true, parameter); + assertCompiles(output, "String " + parameterName + ", RequestOptions requestOptions"); + } + } + } + + @Test + public void keepsTypedTokenAsOrdinaryParameterWithNextLink() throws IOException { + for (boolean sync : List.of(false, true)) { + String output = renderPaging(RequestParameterLocation.QUERY, sync, false, "token", false, true); + Assertions.assertTrue(output.contains("list(String token, Context context)"), output); + assertCompiles(output, "String token, Context context"); } } @Test - public void passesTypedTokenDirectlyWithContext() { + public void passesTypedTokenDirectlyWithContext() throws IOException { for (boolean sync : List.of(false, true)) { String output = renderPaging(RequestParameterLocation.QUERY, sync, false, "token", false); String singlePage = sync ? "listSinglePage" : "listSinglePageAsync"; Assertions.assertTrue(output.contains(singlePage + "(null, context)"), output); Assertions.assertTrue(output.contains(singlePage + "(token, context)"), output); Assertions.assertFalse(output.contains("requestOptions"), output); + assertCompiles(output, "String token, Context context"); } } @@ -79,7 +149,12 @@ private static String renderProtocolPaging(RequestParameterLocation location, bo } private static String renderPaging(RequestParameterLocation location, boolean sync, boolean dataPlane, - String tokenName, boolean pageSize) { + String tokenName, boolean pageSize, ClientMethodParameter... additionalParameters) { + return renderPaging(location, sync, dataPlane, tokenName, pageSize, false, additionalParameters); + } + + private static String renderPaging(RequestParameterLocation location, boolean sync, boolean dataPlane, + String tokenName, boolean pageSize, boolean nextLink, ClientMethodParameter... additionalParameters) { new MockUnitJavagen() { @Override @SuppressWarnings("unchecked") @@ -99,24 +174,52 @@ public T getValue(String key, IOExceptionCheckedFunction converte .requestParameterLocation(location) .wireType(ClassType.STRING) .build(); - ClientMethodParameter typedToken - = new ClientMethodParameter.Builder().name(tokenName).wireType(ClassType.STRING).build(); + ClientMethodParameter typedToken = new ClientMethodParameter.Builder().name(tokenName) + .annotations(List.of()) + .wireType(ClassType.STRING) + .build(); ProxyMethodParameter maxPageSize = new ProxyMethodParameter.Builder().name("maxPageSize") .requestParameterName("maxpagesize") .requestParameterLocation(RequestParameterLocation.QUERY) .wireType(ClassType.INTEGER) .build(); + List parameters = new ArrayList<>(List.of(additionalParameters)); + parameters.addAll(dataPlane + ? List.of(ClientMethodParameter.REQUEST_OPTIONS_PARAMETER) + : List.of(typedToken, ClientMethodParameter.CONTEXT_PARAMETER)); + ClientMethod nextMethod = new ClientMethod.Builder().name("listNext") + .parameters(List.of( + new ClientMethodParameter.Builder().name("nextLink") + .annotations(List.of()) + .wireType(ClassType.STRING) + .required(true) + .build(), + dataPlane ? ClientMethodParameter.REQUEST_OPTIONS_PARAMETER : ClientMethodParameter.CONTEXT_PARAMETER)) + .proxyMethod(new ProxyMethod.Builder().name("listNext").build()) + .build(); ClientMethod method = pagingMethod().newBuilder() - .parameters(dataPlane - ? List.of(ClientMethodParameter.REQUEST_OPTIONS_PARAMETER) - : List.of(typedToken, ClientMethodParameter.CONTEXT_PARAMETER)) + .type(sync ? ClientMethodType.PagingSync : ClientMethodType.PagingAsync) + .returnValue(new ReturnValue("The items.", + sync ? GenericType.pagedIterable(ClassType.STRING) : GenericType.pagedFlux(ClassType.STRING))) + .parameters(parameters) .proxyMethod(new ProxyMethod.Builder().name("list") .parameters(List.of()) .allParameters(pageSize ? List.of(maxPageSize) : List.of()) .build()) - .methodPageDetails(new MethodPageDetails(List.of(), null, null, null, - new MethodPageDetails.ContinuationToken(token, dataPlane ? null : typedToken, "next-token", null), null, - null)) + .methodPageDetails(new MethodPageDetails(List.of(), + nextLink + ? List.of(new ModelPropertySegment(null, + new ClientModelProperty.Builder().name("nextLink") + .serializedName("nextLink") + .wireType(ClassType.STRING) + .clientType(ClassType.STRING) + .build())) + : null, + nextLink ? nextMethod : null, null, + nextLink + ? null + : new MethodPageDetails.ContinuationToken(token, dataPlane ? null : typedToken, "next-token", null), + null, nextLink && dataPlane ? new MethodPageDetails.NextLinkReInjection(List.of("filter")) : null)) .build(); JavaFileContents contents = new JavaFileContents(); if (sync) { @@ -129,6 +232,28 @@ public T getValue(String key, IOExceptionCheckedFunction converte return contents.toString(); } + private void assertCompiles(String method, String singlePageParameters) throws IOException { + String source = "import com.azure.core.annotation.*;\n" + "import com.azure.core.http.rest.*;\n" + + "import com.azure.core.util.*;\n" + "import reactor.core.publisher.Mono;\n" + "import java.util.Map;\n" + + "public class PagingCompilation {\n" + method + + "\nprivate Mono> listSinglePageAsync(" + singlePageParameters + + ") { return Mono.empty(); }\n" + "private PagedResponse listSinglePage(" + singlePageParameters + + ") { return null; }\n" + + "private Mono> listNextSinglePageAsync(String nextLink, RequestOptions options) { return Mono.empty(); }\n" + + "private PagedResponse listNextSinglePage(String nextLink, RequestOptions options) { return null; }\n" + + "private Mono> listNextSinglePageAsync(String nextLink, Context context) { return Mono.empty(); }\n" + + "private PagedResponse listNextSinglePage(String nextLink, Context context) { return null; }\n" + + "}\n"; + Path file = compilationDirectory.resolve("PagingCompilation.java"); + Files.writeString(file, source); + ByteArrayOutputStream diagnostics = new ByteArrayOutputStream(); + int result = ToolProvider.getSystemJavaCompiler() + .run(null, diagnostics, diagnostics, "--release", "11", "-classpath", + System.getProperty("surefire.test.class.path", System.getProperty("java.class.path")), "-d", + compilationDirectory.toString(), file.toString()); + Assertions.assertEquals(0, result, () -> diagnostics.toString(StandardCharsets.UTF_8) + "\n" + source); + } + @Test public void hidesTokenInWrapperDelegation() { JavaFileContents contents = new JavaFileContents(); From e9bd95e514f2e16d84ff29cde9b76b208b5c5ef4 Mon Sep 17 00:00:00 2001 From: Weidong Xu Date: Thu, 15 Oct 2026 15:14:50 +0800 Subject: [PATCH 6/7] fix(http-client-java): read XML attribute paging tokens Preserve XML attribute and namespace metadata when extracting paging tokens, including attributes on root and nested elements. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4bbe3fb2-95f1-4311-8050-62ba7dba4860 --- .../core/template/ClientMethodTemplate.java | 17 +- .../generator/core/util/TemplateUtil.java | 35 ++-- .../template/XmlContinuationTokenTests.java | 176 ++++++++++++++++++ 3 files changed, 209 insertions(+), 19 deletions(-) create mode 100644 packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/XmlContinuationTokenTests.java diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java index 267041f6709..d4af96d58f8 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java @@ -100,6 +100,13 @@ private static String xmlPropertyPath(List propertyReferen return xmlPropertyPath(propertyReference) + ", " + ClassType.STRING.defaultValueExpression(itemElementName); } + private static String xmlNextLinkExpression(List properties) { + ModelPropertySegment terminal = properties.get(properties.size() - 1); + return "getXmlNextLink(res.getValue(), " + terminal.getProperty().isXmlAttribute() + ", " + + ClassType.STRING.defaultValueExpression(terminal.getProperty().getXmlNamespace()) + ", " + + xmlPropertyPath(properties) + ")"; + } + private static String serializedPropertyPath(List propertyReference) { return propertyReference.stream() .map(segment -> ClassType.STRING.defaultValueExpression(segment.getProperty().getSerializedName())) @@ -1055,8 +1062,8 @@ protected void pagedSinglePageResponseConversion(ProxyMethod restAPIMethod, Clie } if (clientMethod.getMethodPageDetails().nonNullNextLink()) { if (isXmlPagingResponse(clientMethod, settings)) { - function.line("getXmlNextLink(res.getValue(), %s),", - xmlPropertyPath(clientMethod.getMethodPageDetails().getNextLinkPropertyReference())); + function.line("%s,", + xmlNextLinkExpression(clientMethod.getMethodPageDetails().getNextLinkPropertyReference())); } else if (settings.isDataPlaneClient()) { function.line("getNextLink(res.getValue(), %s),", serializedPropertyPath(clientMethod.getMethodPageDetails().getNextLinkPropertyReference())); @@ -1280,7 +1287,7 @@ private static String continuationTokenExpression(ClientMethod clientMethod, Jav throw new IllegalStateException("Continuation-token paging requires a response token property or header."); } if (isXmlPagingResponse(clientMethod, settings)) { - return "getXmlNextLink(res.getValue(), " + xmlPropertyPath(properties) + ")"; + return xmlNextLinkExpression(properties); } else if (settings.isDataPlaneClient()) { return "getNextLink(res.getValue(), " + serializedPropertyPath(properties) + ")"; } @@ -1645,8 +1652,8 @@ protected void generatePagedAsyncSinglePage(ClientMethod clientMethod, JavaType } if (clientMethod.getMethodPageDetails().nonNullNextLink()) { if (isXmlPagingResponse(clientMethod, settings)) { - function.line("getXmlNextLink(res.getValue(), %s),", - xmlPropertyPath(clientMethod.getMethodPageDetails().getNextLinkPropertyReference())); + function.line("%s,", xmlNextLinkExpression( + clientMethod.getMethodPageDetails().getNextLinkPropertyReference())); } else if (settings.isDataPlaneClient() && settings.isAzureV1()) { function.line("getNextLink(res.getValue(), %s),", serializedPropertyPath( clientMethod.getMethodPageDetails().getNextLinkPropertyReference())); diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/TemplateUtil.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/TemplateUtil.java index 93d77a9479f..4a7da4481d9 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/TemplateUtil.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/TemplateUtil.java @@ -311,20 +311,26 @@ private static void writeXmlPagingHelperMethods(JavaClass classBlock) { block.line("}"); block.line("return values;"); }); - classBlock.privateMethod("String getXmlNextLink(BinaryData binaryData, String... path)", block -> { - block.line( - "try (com.azure.xml.XmlReader reader = com.azure.xml.XmlReader.fromStream(binaryData.toStream())) {"); - block.indent(() -> { - block.line("reader.nextElement();"); - block.line("return getXmlNextLink(reader, path, 0);"); + classBlock.privateMethod( + "String getXmlNextLink(BinaryData binaryData, boolean attribute, String namespace, String... path)", + block -> { + block.line( + "try (com.azure.xml.XmlReader reader = com.azure.xml.XmlReader.fromStream(binaryData.toStream())) {"); + block.indent(() -> { + block.line("reader.nextElement();"); + block.line("return getXmlNextLink(reader, attribute, namespace, path, 0);"); + }); + block.line("} catch (javax.xml.stream.XMLStreamException e) {"); + block.indent( + () -> block.line("throw new IllegalStateException(\"Failed to read XML pageable response.\", e);")); + block.line("}"); }); - block.line("} catch (javax.xml.stream.XMLStreamException e) {"); - block.indent( - () -> block.line("throw new IllegalStateException(\"Failed to read XML pageable response.\", e);")); - block.line("}"); - }); - classBlock.privateMethod("String getXmlNextLink(com.azure.xml.XmlReader reader, String[] path, int pathIndex) " - + "throws javax.xml.stream.XMLStreamException", block -> { + classBlock.privateMethod( + "String getXmlNextLink(com.azure.xml.XmlReader reader, boolean attribute, String namespace, String[] path, int pathIndex) " + + "throws javax.xml.stream.XMLStreamException", + block -> { + block.ifBlock("attribute && pathIndex == path.length - 1", attributeBlock -> attributeBlock + .line("return reader.getStringAttribute(namespace, path[pathIndex]);")); block.line("while (reader.nextElement() != com.azure.xml.XmlToken.END_ELEMENT) {"); block.indent(() -> { block.line("if (!reader.elementNameMatches(path[pathIndex])) {"); @@ -332,7 +338,8 @@ private static void writeXmlPagingHelperMethods(JavaClass classBlock) { block.line("} else if (pathIndex == path.length - 1) {"); block.indent(() -> block.line("return reader.getStringElement();")); block.line("} else {"); - block.indent(() -> block.line("return getXmlNextLink(reader, path, pathIndex + 1);")); + block.indent( + () -> block.line("return getXmlNextLink(reader, attribute, namespace, path, pathIndex + 1);")); block.line("}"); }); block.line("}"); diff --git a/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/XmlContinuationTokenTests.java b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/XmlContinuationTokenTests.java new file mode 100644 index 00000000000..4d799636120 --- /dev/null +++ b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/XmlContinuationTokenTests.java @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.typespec.http.client.generator.core.template; + +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.rest.PagedResponse; +import com.azure.core.http.rest.Response; +import com.azure.core.http.rest.SimpleResponse; +import com.azure.core.util.BinaryData; +import com.microsoft.typespec.http.client.generator.core.MockUnitJavagen; +import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.RequestParameterLocation; +import com.microsoft.typespec.http.client.generator.core.extension.plugin.JavaSettings; +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.ClientMethodParameter; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientMethodType; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientModelProperty; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.GenericType; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ListType; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.MethodPageDetails; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ModelPropertySegment; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ProxyMethod; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ProxyMethodParameter; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ReturnValue; +import com.microsoft.typespec.http.client.generator.core.model.javamodel.JavaClass; +import com.microsoft.typespec.http.client.generator.core.model.javamodel.JavaFileContents; +import com.microsoft.typespec.http.client.generator.core.util.TemplateUtil; +import io.clientcore.core.utils.IOExceptionCheckedFunction; +import java.io.ByteArrayOutputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.tools.ToolProvider; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.parallel.Isolated; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +@Isolated +public class XmlContinuationTokenTests { + @TempDir + Path compilationDirectory; + + @AfterEach + public void restoreSettings() { + new MockUnitJavagen(); + JavaSettings.clear(); + } + + @ParameterizedTest + @CsvSource({ + "true,false,false", + "true,true,false", + "true,false,true", + "true,true,true", + "false,false,false", + "false,true,false" }) + public void readsXmlToken(boolean attribute, boolean nested, boolean namespaced) throws Exception { + new MockUnitJavagen() { + @Override + @SuppressWarnings("unchecked") + public T getValue(String key, IOExceptionCheckedFunction converter) { + return "data-plane".equals(key) ? (T) Boolean.TRUE : super.getValue(key, converter); + } + }; + JavaSettings.clear(); + ClassType xmlType = new ClassType.Builder().name("XmlPage").packageName("test").usedInXml(true).build(); + ClassType itemType = new ClassType.Builder().name("Item").packageName("test").build(); + List tokenPath = new ArrayList<>(); + if (nested) { + tokenPath.add(new ModelPropertySegment(null, + new ClientModelProperty.Builder().name("paging").xmlName("Paging").wireType(xmlType).build())); + } + tokenPath.add(new ModelPropertySegment(null, + new ClientModelProperty.Builder().name("nextMarker") + .xmlName("NextMarker") + .xmlAttribute(attribute) + .xmlNamespace(namespaced ? "urn:paging" : null) + .wireType(ClassType.STRING) + .build())); + ProxyMethod proxy = new ProxyMethod.Builder().name("list") + .parameters(List.of()) + .allParameters(List.of()) + .rawResponseBodyType(xmlType) + .returnType(GenericType.response(ClassType.BINARY_DATA)) + .responseExpectedStatusCodes(List.of(200)) + .build(); + ClientMethod method = new ClientMethod.Builder().name("list") + .description("List items.") + .type(ClientMethodType.PagingSync) + .parameters(List.of(ClientMethodParameter.REQUEST_OPTIONS_PARAMETER)) + .returnValue(new ReturnValue("The items.", GenericType.pagedIterable(ClassType.BINARY_DATA))) + .proxyMethod(proxy) + .validateExpressions(Map.of()) + .requiredNullableParameterExpressions(List.of()) + .methodPageDetails(new MethodPageDetails( + List.of(new ModelPropertySegment(null, + new ClientModelProperty.Builder().name("items") + .xmlName("Items") + .xmlListElementName("Item") + .clientType(new ListType(itemType)) + .build())), + null, null, null, + new MethodPageDetails.ContinuationToken(new ProxyMethodParameter.Builder().name("token") + .requestParameterName("token") + .requestParameterLocation(RequestParameterLocation.QUERY) + .build(), null, null, tokenPath), + null, null)) + .build(); + JavaFileContents contents = new JavaFileContents(); + JavaClass classBlock = new JavaClass(contents); + TemplateUtil.writeClientMethodsAndHelpers(classBlock, List.of(method)); + classBlock.publicMethod("PagedResponse read(Response res)", + block -> ClientMethodTemplate.getInstance() + .pagedSinglePageResponseConversion(proxy, method, JavaSettings.getInstance(), block)); + ClientMethod nextLinkMethod = method.newBuilder() + .methodPageDetails(new MethodPageDetails(method.getMethodPageDetails().getPageItemsPropertyReference(), + tokenPath, null, null, null, null, null)) + .build(); + classBlock.publicMethod("PagedResponse readNextLink(Response res)", + block -> ClientMethodTemplate.getInstance() + .pagedSinglePageResponseConversion(proxy, nextLinkMethod, JavaSettings.getInstance(), block)); + String source = "package test;\nimport com.azure.core.annotation.*;\nimport com.azure.core.http.rest.*;\n" + + "import com.azure.core.util.*;\nimport com.azure.core.util.serializer.*;\n" + + "import java.util.*;\nimport java.util.stream.Collectors;\n" + "public class XmlPagingCompilation {\n" + + contents + "\nprivate PagedResponse listSinglePage(RequestOptions options) { return null; }\n" + + "private PagedFlux listAsync(RequestOptions options) { return null; }\n}\n" + + "class Item { static Item fromXml(com.azure.xml.XmlReader reader, String root)" + + " throws javax.xml.stream.XMLStreamException { reader.skipElement(); return new Item(); } }\n"; + Path file = compilationDirectory.resolve("XmlPagingCompilation.java"); + Files.writeString(file, source); + Path provider = compilationDirectory.resolve("XmlSerializerProviders.java"); + Path serializer = compilationDirectory.resolve("XmlSerializer.java"); + Files.writeString(provider, + "package test;\n" + TemplateUtil.loadTextFromResource("XmlSerializerProviders.java")); + Files.writeString(serializer, "package test;\n" + TemplateUtil.loadTextFromResource("XmlSerializer.java")); + ByteArrayOutputStream diagnostics = new ByteArrayOutputStream(); + int result = ToolProvider.getSystemJavaCompiler() + .run(null, diagnostics, diagnostics, "--release", "11", "-classpath", + System.getProperty("surefire.test.class.path", System.getProperty("java.class.path")), "-d", + compilationDirectory.toString(), file.toString(), provider.toString(), serializer.toString()); + Assertions.assertEquals(0, result, () -> diagnostics.toString(StandardCharsets.UTF_8) + "\n" + source); + String marker = namespaced ? "p:NextMarker" : "NextMarker"; + String token = attribute ? " " + marker + "=\"page2\"" : "<" + marker + ">page2"; + String element = nested ? "Paging" : "PetListResult"; + String xml = attribute + ? "<" + element + token + ">" + : "<" + element + ">" + token + ""; + if (nested) { + xml = "" + xml + ""; + } + xml = xml.replaceFirst(" generated = loader.loadClass("test.XmlPagingCompilation"); + Object client = generated.getConstructor().newInstance(); + for (String methodName : List.of("read", "readNextLink")) { + var read = generated.getMethod(methodName, Response.class); + PagedResponse page = (PagedResponse) read.invoke(client, + new SimpleResponse<>(null, 200, new HttpHeaders(), BinaryData.fromString(xml))); + Assertions.assertEquals("page2", page.getContinuationToken()); + PagedResponse lastPage = (PagedResponse) read.invoke(client, + new SimpleResponse<>(null, 200, new HttpHeaders(), BinaryData.fromString(xml.replace(token, "")))); + Assertions.assertNull(lastPage.getContinuationToken()); + } + } + } +} From ee89be060339d3aaa3d78a6aa8ba9a8213c3ce2e Mon Sep 17 00:00:00 2001 From: Weidong Xu Date: Thu, 15 Oct 2026 16:39:05 +0800 Subject: [PATCH 7/7] fix(http-client-java): match XML paging element namespaces Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4bbe3fb2-95f1-4311-8050-62ba7dba4860 --- .../generator/core/util/TemplateUtil.java | 3 ++- .../template/XmlContinuationTokenTests.java | 11 +++++--- .../implementation/XmlPaginationsImpl.java | 27 ++++++++++--------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/TemplateUtil.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/TemplateUtil.java index 4a7da4481d9..c218215cbc3 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/TemplateUtil.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/TemplateUtil.java @@ -333,7 +333,8 @@ private static void writeXmlPagingHelperMethods(JavaClass classBlock) { .line("return reader.getStringAttribute(namespace, path[pathIndex]);")); block.line("while (reader.nextElement() != com.azure.xml.XmlToken.END_ELEMENT) {"); block.indent(() -> { - block.line("if (!reader.elementNameMatches(path[pathIndex])) {"); + block.line( + "if (!reader.elementNameMatches(pathIndex == path.length - 1 ? namespace : null, path[pathIndex])) {"); block.indent(() -> block.line("reader.skipElement();")); block.line("} else if (pathIndex == path.length - 1) {"); block.indent(() -> block.line("return reader.getStringElement();")); diff --git a/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/XmlContinuationTokenTests.java b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/XmlContinuationTokenTests.java index 4d799636120..a00ead4e5f0 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/XmlContinuationTokenTests.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/test/java/com/microsoft/typespec/http/client/generator/core/template/XmlContinuationTokenTests.java @@ -62,7 +62,9 @@ public void restoreSettings() { "true,false,true", "true,true,true", "false,false,false", - "false,true,false" }) + "false,true,false", + "false,false,true", + "false,true,true" }) public void readsXmlToken(boolean attribute, boolean nested, boolean namespaced) throws Exception { new MockUnitJavagen() { @Override @@ -150,14 +152,17 @@ public T getValue(String key, IOExceptionCheckedFunction converte Assertions.assertEquals(0, result, () -> diagnostics.toString(StandardCharsets.UTF_8) + "\n" + source); String marker = namespaced ? "p:NextMarker" : "NextMarker"; String token = attribute ? " " + marker + "=\"page2\"" : "<" + marker + ">page2"; + String decoys = namespaced && !attribute + ? "wrongNamespaceunqualified" + : ""; String element = nested ? "Paging" : "PetListResult"; String xml = attribute ? "<" + element + token + ">" - : "<" + element + ">" + token + ""; + : "<" + element + ">" + decoys + token + ""; if (nested) { xml = "" + xml + ""; } - xml = xml.replaceFirst(" generated = loader.loadClass("test.XmlPagingCompilation"); diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/XmlPaginationsImpl.java b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/XmlPaginationsImpl.java index f6642bede15..52310f9aa7a 100644 --- a/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/XmlPaginationsImpl.java +++ b/packages/http-client-java/generator/http-client-generator-test/src/main/java/payload/pageable/implementation/XmlPaginationsImpl.java @@ -165,7 +165,7 @@ private Mono> listWithContinuationSinglePageAsync(Requ } catch (javax.xml.stream.XMLStreamException e) { throw new IllegalStateException(e); } - }, "Pets", "Pet"), getXmlNextLink(res.getValue(), "NextMarker"), null)); + }, "Pets", "Pet"), getXmlNextLink(res.getValue(), false, null, "NextMarker"), null)); } /** @@ -249,7 +249,7 @@ private PagedResponse listWithContinuationSinglePage(RequestOptions } catch (javax.xml.stream.XMLStreamException e) { throw new IllegalStateException(e); } - }, "Pets", "Pet"), getXmlNextLink(res.getValue(), "NextMarker"), null); + }, "Pets", "Pet"), getXmlNextLink(res.getValue(), false, null, "NextMarker"), null); } /** @@ -329,7 +329,7 @@ private Mono> listWithNextLinkSinglePageAsync(RequestO } catch (javax.xml.stream.XMLStreamException e) { throw new IllegalStateException(e); } - }, "Pets", "Pet"), getXmlNextLink(res.getValue(), "NextLink"), null)); + }, "Pets", "Pet"), getXmlNextLink(res.getValue(), false, null, "NextLink"), null)); } /** @@ -393,7 +393,7 @@ private PagedResponse listWithNextLinkSinglePage(RequestOptions requ } catch (javax.xml.stream.XMLStreamException e) { throw new IllegalStateException(e); } - }, "Pets", "Pet"), getXmlNextLink(res.getValue(), "NextLink"), null); + }, "Pets", "Pet"), getXmlNextLink(res.getValue(), false, null, "NextLink"), null); } /** @@ -462,7 +462,7 @@ private Mono> listWithNextLinkNextSinglePageAsync(Stri } catch (javax.xml.stream.XMLStreamException e) { throw new IllegalStateException(e); } - }, "Pets", "Pet"), getXmlNextLink(res.getValue(), "NextLink"), null)); + }, "Pets", "Pet"), getXmlNextLink(res.getValue(), false, null, "NextLink"), null)); } /** @@ -498,7 +498,7 @@ private PagedResponse listWithNextLinkNextSinglePage(String nextLink } catch (javax.xml.stream.XMLStreamException e) { throw new IllegalStateException(e); } - }, "Pets", "Pet"), getXmlNextLink(res.getValue(), "NextLink"), null); + }, "Pets", "Pet"), getXmlNextLink(res.getValue(), false, null, "NextLink"), null); } private List getValues(BinaryData binaryData, String... path) { @@ -555,24 +555,27 @@ private List getXmlValues(com.azure.xml.XmlReader reader, return values; } - private String getXmlNextLink(BinaryData binaryData, String... path) { + private String getXmlNextLink(BinaryData binaryData, boolean attribute, String namespace, String... path) { try (com.azure.xml.XmlReader reader = com.azure.xml.XmlReader.fromStream(binaryData.toStream())) { reader.nextElement(); - return getXmlNextLink(reader, path, 0); + return getXmlNextLink(reader, attribute, namespace, path, 0); } catch (javax.xml.stream.XMLStreamException e) { throw new IllegalStateException("Failed to read XML pageable response.", e); } } - private String getXmlNextLink(com.azure.xml.XmlReader reader, String[] path, int pathIndex) - throws javax.xml.stream.XMLStreamException { + private String getXmlNextLink(com.azure.xml.XmlReader reader, boolean attribute, String namespace, String[] path, + int pathIndex) throws javax.xml.stream.XMLStreamException { + if (attribute && pathIndex == path.length - 1) { + return reader.getStringAttribute(namespace, path[pathIndex]); + } while (reader.nextElement() != com.azure.xml.XmlToken.END_ELEMENT) { - if (!reader.elementNameMatches(path[pathIndex])) { + if (!reader.elementNameMatches(pathIndex == path.length - 1 ? namespace : null, path[pathIndex])) { reader.skipElement(); } else if (pathIndex == path.length - 1) { return reader.getStringElement(); } else { - return getXmlNextLink(reader, path, pathIndex + 1); + return getXmlNextLink(reader, attribute, namespace, path, pathIndex + 1); } } return null;