Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

http-client-java, support (non-nested) continuationToken for unbranded #6143

Merged
Changes from 30 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
8f04696
fix interface
weidongxu-microsoft Feb 14, 2025
25e05d0
test code and initial interface of PageableContinuationToken
weidongxu-microsoft Feb 14, 2025
f7a0c45
handle PagedResponse
weidongxu-microsoft Feb 18, 2025
224703c
handle query/header
weidongxu-microsoft Feb 18, 2025
03d005c
hack for test
weidongxu-microsoft Feb 19, 2025
928ab36
Merge branch 'main' into http-client-java_continuation-token
weidongxu-microsoft Feb 20, 2025
32137b1
refactor to use ModelPropertySegment, instead of itemName/itemSeriali…
weidongxu-microsoft Feb 20, 2025
3785ba3
hide continuationToken parameter from API
weidongxu-microsoft Feb 20, 2025
d2c8831
add throw calls on not support items in PagingOptions
weidongxu-microsoft Feb 20, 2025
18f8ae0
Merge branch 'main' into http-client-java_continuation-token
weidongxu-microsoft Feb 25, 2025
feb1c68
use tcgc for continuationToken
weidongxu-microsoft Feb 25, 2025
a6cb649
Merge branch 'main' into http-client-java_continuation-token
weidongxu-microsoft Feb 25, 2025
c168edd
enable for unbranded
weidongxu-microsoft Feb 25, 2025
0cd5416
fix
weidongxu-microsoft Feb 25, 2025
9ce5472
bump http-specs and regen
weidongxu-microsoft Feb 25, 2025
905abfe
update test
weidongxu-microsoft Feb 25, 2025
d9924ae
test
weidongxu-microsoft Feb 25, 2025
c85e84d
fix
weidongxu-microsoft Feb 25, 2025
c6b7a2c
comment
weidongxu-microsoft Feb 25, 2025
59e1d38
add test for page2
weidongxu-microsoft Feb 25, 2025
c6d3eb3
revert Main
weidongxu-microsoft Feb 25, 2025
4aa4b0f
bump jar
weidongxu-microsoft Feb 25, 2025
6af2a02
bump version
weidongxu-microsoft Feb 25, 2025
9d81f37
test for throws if non-supported param is set
weidongxu-microsoft Feb 25, 2025
e15b44e
fix test package ref
weidongxu-microsoft Feb 25, 2025
df60efe
Merge branch 'main' into http-client-java_continuation-token
weidongxu-microsoft Feb 27, 2025
77b4d3c
0.1.12
weidongxu-microsoft Feb 27, 2025
cbf31a6
review feedback
weidongxu-microsoft Feb 28, 2025
87491a1
simplify paged op logic, do early return
weidongxu-microsoft Feb 28, 2025
77234d1
Merge branch 'main' into http-client-java_continuation-token
weidongxu-microsoft Feb 28, 2025
34f4cb6
Fix typo in comment: "mutally" to "mutually"
weidongxu-microsoft Mar 4, 2025
89b3638
Merge branch 'main' into http-client-java_continuation-token
weidongxu-microsoft Mar 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 144 additions & 37 deletions packages/http-client-java/emitter/src/code-model-builder.ts
Original file line number Diff line number Diff line change
@@ -60,7 +60,6 @@ import {
SdkHttpResponse,
SdkLroPagingServiceMethod,
SdkLroServiceMethod,
SdkMethod,
SdkModelPropertyType,
SdkModelType,
SdkPathParameter,
@@ -103,7 +102,11 @@ import { getSegment } from "@typespec/rest";
import { getAddedOnVersions } from "@typespec/versioning";
import { fail } from "assert";
import pkg from "lodash";
import { Client as CodeModelClient, EncodedSchema } from "./common/client.js";
import {
Client as CodeModelClient,
EncodedSchema,
PageableContinuationToken,
} from "./common/client.js";
import { CodeModel } from "./common/code-model.js";
import { LongRunningMetadata } from "./common/long-running-metadata.js";
import { Operation as CodeModelOperation, ConvenienceApi, Request } from "./common/operation.js";
@@ -981,7 +984,7 @@ export class CodeModelBuilder {
}

// check for paged
this.processRouteForPaged(codeModelOperation, sdkMethod.operation.responses, sdkMethod);
this.processRouteForPaged(codeModelOperation, sdkMethod);

// check for long-running operation
this.processRouteForLongRunning(codeModelOperation, lroMetadata);
@@ -993,49 +996,153 @@ export class CodeModelBuilder {

private processRouteForPaged(
op: CodeModelOperation,
responses: SdkHttpResponse[],
sdkMethod: SdkMethod<SdkHttpOperation>,
sdkMethod: SdkServiceMethod<SdkHttpOperation>,
) {
if (sdkMethod.kind === "paging" || sdkMethod.kind === "lropaging") {
for (const response of responses) {
const bodyType = response.type;
if (bodyType && bodyType.kind === "model") {
let itemSerializedName: string | undefined = undefined;
let nextLinkSerializedName: string | undefined = undefined;

const itemSegments = sdkMethod.response.resultSegments;
const nextLinkSegments = sdkMethod.pagingMetadata.nextLinkSegments;

// TODO: in future the property could be nested, so that the "itemSegments" or "nextLinkSegments" would contain more than 1 element
if (itemSegments) {
// "itemsSegments" should exist for "paging"/"lropaging"
const lastSegment = itemSegments[itemSegments.length - 1];
if (lastSegment.kind === "property") {
itemSerializedName = getPropertySerializedName(lastSegment);
if (sdkMethod.kind !== "paging" && sdkMethod.kind !== "lropaging") {
return;
}
// TCGC should already verified that there is 1 response, and response body is a model
const responses = sdkMethod.operation.responses;
if (responses.length === 0) {
return;
}
const response = responses[0];
const bodyType = response.type;
if (!bodyType || bodyType.kind !== "model") {
return;
}

op.responses?.forEach((r) => {
if (r instanceof SchemaResponse) {
this.trackSchemaUsage(r.schema, { usage: [SchemaContext.Paged] });
}
});

function getLastPropertySegment(
segments: SdkModelPropertyType[] | undefined,
): SdkBodyModelPropertyType | undefined {
if (segments) {
const lastSegment = segments[segments.length - 1];
if (lastSegment.kind === "property") {
return lastSegment;
}
}
return undefined;
}
function getLastSegment(
segments: SdkModelPropertyType[] | undefined,
): SdkModelPropertyType | undefined {
if (segments) {
return segments[segments.length - 1];
}
return undefined;
}
function getLastSegmentSerializedName(
segments: SdkModelPropertyType[] | undefined,
): string | undefined {
const lastSegment = getLastPropertySegment(segments);
return lastSegment ? getPropertySerializedName(lastSegment) : undefined;
}

// TODO: in future the property could be nested, so that the "itemSegments" or "nextLinkSegments" would contain more than 1 element
// item/result
// "itemsSegments" should exist for "paging"/"lropaging"
const itemSerializedName = getLastSegmentSerializedName(sdkMethod.response.resultSegments);
// nextLink
const nextLinkSerializedName = getLastSegmentSerializedName(
sdkMethod.pagingMetadata.nextLinkSegments,
);

// continuationToken
let continuationTokenParameter: Parameter | undefined;
let continuationTokenResponseProperty: Property[] | undefined;
let continuationTokenResponseHeader: HttpHeader | undefined;
if (!this.isBranded()) {
const continuationTokenParameterSegment = getLastSegment(
sdkMethod.pagingMetadata.continuationTokenParameterSegments,
);
const continuationTokenResponseSegment = getLastSegment(
sdkMethod.pagingMetadata.continuationTokenResponseSegments,
);
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 (param.language.default.serializedName === parameter.serializedName) {
continuationTokenParameter = param;
break;
}
}
}
if (nextLinkSegments) {
const lastSegment = nextLinkSegments[nextLinkSegments.length - 1];
if (lastSegment.kind === "property") {
nextLinkSerializedName = getPropertySerializedName(lastSegment);
}
}
if (continuationTokenResponseSegment && op.responses) {
if (continuationTokenResponseSegment?.kind === "responseheader") {
// continuationToken is response header
for (const response of op.responses) {
if (response instanceof SchemaResponse && response.protocol.http) {
for (const header of response.protocol.http.headers) {
if (
header.header.toLowerCase() ===
continuationTokenResponseSegment.serializedName.toLowerCase()
) {
continuationTokenResponseHeader = header;
break;
}
}
}
if (continuationTokenResponseHeader) {
break;
}
}

op.responses?.forEach((r) => {
if (r instanceof SchemaResponse) {
this.trackSchemaUsage(r.schema, { usage: [SchemaContext.Paged] });
} else if (continuationTokenResponseSegment?.kind === "property") {
// continuationToken is response body property
// TODO: the property could be nested
for (const response of op.responses) {
if (
response instanceof SchemaResponse &&
response.schema instanceof ObjectSchema &&
response.schema.properties
) {
for (const property of response.schema.properties) {
if (
property.serializedName ===
getPropertySerializedName(continuationTokenResponseSegment)
) {
continuationTokenResponseProperty = [property];
break;
}
}
}
});

op.extensions = op.extensions ?? {};
op.extensions["x-ms-pageable"] = {
itemName: itemSerializedName,
nextLinkName: nextLinkSerializedName,
};
break;
if (continuationTokenResponseProperty) {
break;
}
}
}
}
}

op.extensions = op.extensions ?? {};
op.extensions["x-ms-pageable"] = {
itemName: itemSerializedName,
nextLinkName: nextLinkSerializedName,
continuationToken: continuationTokenParameter
? new PageableContinuationToken(
continuationTokenParameter,
continuationTokenResponseProperty,
continuationTokenResponseHeader,
)
: undefined,
};
}

private processLroMetadata(
49 changes: 48 additions & 1 deletion packages/http-client-java/emitter/src/common/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
import { Aspect, Metadata, OperationGroup, Parameter, Security } from "@autorest/codemodel";
import {
Aspect,
HttpHeader,
Metadata,
OperationGroup,
Parameter,
Property,
Security,
} from "@autorest/codemodel";
import { DeepPartial } from "@azure-tools/codegen";

export interface Client extends Aspect {
@@ -12,9 +20,21 @@ export interface Client extends Aspect {

serviceVersion?: ServiceVersion; // apiVersions is in

/**
* Parent client of this client, if exists.
*/
parent?: Client;
/**
* Sub clients of this client, if exists.
*/
subClients: Array<Client>;
/**
* Whether the Builder class has a public method (e.g. "buildSubClient") to initiate this client.
*/
buildMethodPublic: boolean;
/**
* Whether the parent client has a public accessor method (e.g. "getSubClient") to initiate this client.
*/
parentAccessorPublic: boolean;
}

@@ -68,5 +88,32 @@ export class ServiceVersion extends Metadata {
}

export interface EncodedSchema {
/**
* The encoded type -- the type on wire.
* E.g., the type for SDK maybe "int32", but type on wire be "string".
*/
encode?: string;
}

export class PageableContinuationToken {
/**
* The parameter of the operation as continuationToken in API request.
*/
parameter: Parameter;
// responseProperty and responseHeader is mutally exclusive
/**
* The reference to response body property of the operation as continuationToken in API request.
* Array because the property may be at "links.nextToken".
*/
responseProperty?: Array<Property>;
/**
* The reference to response header of the operation as continuationToken in API request.
*/
responseHeader?: HttpHeader;

constructor(parameter: Parameter, responseProperty?: Property[], responseHeader?: HttpHeader) {
this.parameter = parameter;
this.responseProperty = responseProperty;
this.responseHeader = responseHeader;
}
}
Original file line number Diff line number Diff line change
@@ -12,8 +12,8 @@
"spector-stop": "tsp-spector server stop"
},
"dependencies": {
"@typespec/http-specs": "0.1.0-alpha.9",
"@typespec/http-client-java": "file:../../typespec-http-client-java-0.1.11.tgz",
"@typespec/http-specs": "0.1.0-alpha.10",
"@typespec/http-client-java": "file:../../typespec-http-client-java-0.1.12.tgz",
"@typespec/http-client-java-tests": "file:"
},
"overrides": {
Original file line number Diff line number Diff line change
@@ -29,7 +29,8 @@
/**
* A builder for creating a new instance of the PageableClient type.
*/
@ServiceClientBuilder(serviceClients = { PageableClient.class })
@ServiceClientBuilder(
serviceClients = { ServerDrivenPaginationClient.class, ServerDrivenPaginationContinuationTokenClient.class })
public final class PageableClientBuilder implements HttpTrait<PageableClientBuilder>, ProxyTrait<PageableClientBuilder>,
ConfigurationTrait<PageableClientBuilder>, EndpointTrait<PageableClientBuilder> {
@Metadata(generated = true)
@@ -229,13 +230,24 @@ private HttpPipeline createHttpPipeline() {
}

/**
* Builds an instance of PageableClient class.
* Builds an instance of ServerDrivenPaginationClient class.
*
* @return an instance of PageableClient.
* @return an instance of ServerDrivenPaginationClient.
*/
@Metadata(generated = true)
public PageableClient buildPageableClient() {
return new PageableClient(buildInnerClient().getServerDrivenPaginations());
public ServerDrivenPaginationClient buildServerDrivenPaginationClient() {
return new ServerDrivenPaginationClient(buildInnerClient().getServerDrivenPaginations());
}

/**
* Builds an instance of ServerDrivenPaginationContinuationTokenClient class.
*
* @return an instance of ServerDrivenPaginationContinuationTokenClient.
*/
@Metadata(generated = true)
public ServerDrivenPaginationContinuationTokenClient buildServerDrivenPaginationContinuationTokenClient() {
return new ServerDrivenPaginationContinuationTokenClient(
buildInnerClient().getServerDrivenPaginationContinuationTokens());
}

private static final ClientLogger LOGGER = new ClientLogger(PageableClientBuilder.class);
Original file line number Diff line number Diff line change
@@ -15,17 +15,17 @@
* Initializes a new instance of the synchronous PageableClient type.
*/
@ServiceClient(builder = PageableClientBuilder.class)
public final class PageableClient {
public final class ServerDrivenPaginationClient {
@Metadata(generated = true)
private final ServerDrivenPaginationsImpl serviceClient;

/**
* Initializes an instance of PageableClient class.
* Initializes an instance of ServerDrivenPaginationClient class.
*
* @param serviceClient the service client implementation.
*/
@Metadata(generated = true)
PageableClient(ServerDrivenPaginationsImpl serviceClient) {
ServerDrivenPaginationClient(ServerDrivenPaginationsImpl serviceClient) {
this.serviceClient = serviceClient;
}

Loading
Loading