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

Fix client version params #778

Merged
merged 7 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@azure-tools/typespec-client-generator-core"
---

tie api version information to clients so we can have diff api version information per client
3 changes: 3 additions & 0 deletions packages/typespec-client-generator-core/src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,9 @@ export function createSdkContext<
diagnostics: diagnostics.diagnostics,
apiVersion: context.options["api-version"],
originalProgram: context.program,
__namespaceToApiVersionParameter: new Map(),
__namespaceToApiVersions: new Map(),
__namespaceToApiVersionClientDefaultValue: new Map(),
};
sdkContext.experimental_sdkPackage = getSdkPackage(sdkContext);
if (sdkContext.diagnostics) {
Expand Down
50 changes: 34 additions & 16 deletions packages/typespec-client-generator-core/src/http.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Diagnostic,
ModelProperty,
Operation,
Type,
createDiagnosticCollector,
ignoreDiagnostics,
Expand Down Expand Up @@ -41,6 +42,7 @@ import {
TCGCContext,
getAvailableApiVersions,
getDocHelper,
getLocationOfOperation,
isAcceptHeader,
isNullable,
isSubscriptionId,
Expand Down Expand Up @@ -106,7 +108,9 @@ function getSdkHttpParameters(
bodyParam: undefined,
};
retval.parameters = httpOperation.parameters.parameters
.map((x) => diagnostics.pipe(getSdkHttpParameter(context, x.param, x.type)))
.map((x) =>
diagnostics.pipe(getSdkHttpParameter(context, x.param, httpOperation.operation, x.type))
)
.filter(
(x): x is SdkHeaderParameter | SdkQueryParameter | SdkPathParameter =>
x.kind === "header" || x.kind === "query" || x.kind === "path"
Expand All @@ -122,7 +126,7 @@ function getSdkHttpParameters(
// if there's a param on the body, we can just rely on getSdkHttpParameter
if (tspBody.parameter) {
const getParamResponse = diagnostics.pipe(
getSdkHttpParameter(context, tspBody.parameter, "body")
getSdkHttpParameter(context, tspBody.parameter, httpOperation.operation, "body")
);
if (getParamResponse.kind !== "body") {
diagnostics.add(
Expand Down Expand Up @@ -155,7 +159,11 @@ function getSdkHttpParameters(
contentTypes: [],
defaultContentType: "application/json", // actual content type info is added later
isApiVersionParam: false,
apiVersions: getAvailableApiVersions(context, tspBody.type),
apiVersions: getAvailableApiVersions(
context,
tspBody.type,
httpOperation.operation.namespace
),
type,
optional: false,
nullable: isNullable(tspBody.type),
Expand All @@ -166,7 +174,7 @@ function getSdkHttpParameters(
retval.bodyParam.correspondingMethodParams = diagnostics.pipe(
getCorrespondingMethodParams(
context,
httpOperation.operation.name,
httpOperation.operation,
methodParameters,
retval.bodyParam
)
Expand Down Expand Up @@ -214,7 +222,7 @@ function getSdkHttpParameters(
}
for (const param of retval.parameters) {
param.correspondingMethodParams = diagnostics.pipe(
getCorrespondingMethodParams(context, httpOperation.operation.name, methodParameters, param)
getCorrespondingMethodParams(context, httpOperation.operation, methodParameters, param)
);
}
return diagnostics.wrap(retval);
Expand Down Expand Up @@ -287,10 +295,11 @@ function addContentTypeInfoToBodyParam(
export function getSdkHttpParameter(
context: TCGCContext,
type: ModelProperty,
operation?: Operation,
location?: "path" | "query" | "header" | "body"
): [SdkHttpParameter, readonly Diagnostic[]] {
const diagnostics = createDiagnosticCollector();
const base = diagnostics.pipe(getSdkModelPropertyTypeBase(context, type));
const base = diagnostics.pipe(getSdkModelPropertyTypeBase(context, type, operation));
const program = context.program;
const correspondingMethodParams: SdkParameter[] = []; // we set it later in the operation
if (isPathParam(context.program, type) || location === "path") {
Expand Down Expand Up @@ -419,7 +428,11 @@ function getSdkHttpResponseAndExceptions(
defaultContentType: contentTypes.includes("application/json")
? "application/json"
: contentTypes[0],
apiVersions: getAvailableApiVersions(context, httpOperation.operation),
apiVersions: getAvailableApiVersions(
context,
httpOperation.operation,
httpOperation.operation.namespace
),
nullable: body ? isNullable(body) : true,
};
if (response.statusCodes === "*" || (body && isErrorModel(context.program, body))) {
Expand All @@ -433,13 +446,16 @@ function getSdkHttpResponseAndExceptions(

export function getCorrespondingMethodParams(
context: TCGCContext,
methodName: string,
operation: Operation,
methodParameters: SdkParameter[],
serviceParam: SdkHttpParameter
): [SdkModelPropertyType[], readonly Diagnostic[]] {
const diagnostics = createDiagnosticCollector();

const operationLocation = getLocationOfOperation(operation);
if (serviceParam.isApiVersionParam) {
if (!context.__api_version_parameter) {
const existingApiVersion = context.__namespaceToApiVersionParameter.get(operationLocation);
if (!existingApiVersion) {
const apiVersionParam = methodParameters.find((x) => x.name.includes("apiVersion"));
if (!apiVersionParam) {
diagnostics.add(
Expand All @@ -448,22 +464,24 @@ export function getCorrespondingMethodParams(
target: serviceParam.__raw!,
format: {
paramName: "apiVersion",
methodName: methodName,
methodName: operation.name,
},
})
);
return diagnostics.wrap([]);
}
context.__api_version_parameter = {
const apiVersionParamUpdated: SdkParameter = {
...apiVersionParam,
name: "apiVersion",
nameInClient: "apiVersion",
isGeneratedName: apiVersionParam.name !== "apiVersion",
optional: false,
clientDefaultValue: context.__api_version_client_default_value,
clientDefaultValue:
context.__namespaceToApiVersionClientDefaultValue.get(operationLocation),
};
context.__namespaceToApiVersionParameter.set(operationLocation, apiVersionParamUpdated);
}
return diagnostics.wrap([context.__api_version_parameter]);
return diagnostics.wrap([context.__namespaceToApiVersionParameter.get(operationLocation)!]);
}
if (isSubscriptionId(context, serviceParam)) {
if (!context.__subscriptionIdParameter) {
Expand All @@ -475,7 +493,7 @@ export function getCorrespondingMethodParams(
target: serviceParam.__raw!,
format: {
paramName: "subscriptionId",
methodName: methodName,
methodName: operation.name,
},
})
);
Expand Down Expand Up @@ -526,7 +544,7 @@ export function getCorrespondingMethodParams(
target: serviceParam.__raw!,
format: {
paramName: serviceParam.name,
methodName: methodName,
methodName: operation.name,
},
})
);
Expand All @@ -547,7 +565,7 @@ export function getCorrespondingMethodParams(
target: serviceParam.__raw!,
format: {
paramName: serviceParam.name,
methodName: methodName,
methodName: operation.name,
},
})
);
Expand Down
67 changes: 49 additions & 18 deletions packages/typespec-client-generator-core/src/internal-utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getUnionAsEnum } from "@azure-tools/typespec-azure-core";
import {
Diagnostic,
Interface,
Model,
Namespace,
Operation,
Expand Down Expand Up @@ -97,7 +98,8 @@ export function getClientNamespaceStringHelper(
*/
export function updateWithApiVersionInformation(
context: TCGCContext,
type: { name: string }
type: { name: string },
namespace?: Namespace | Interface
): {
isApiVersionParam: boolean;
clientDefaultValue?: unknown;
Expand All @@ -106,24 +108,19 @@ export function updateWithApiVersionInformation(
const isApiVersionParam = isApiVersion(context, type);
return {
isApiVersionParam,
clientDefaultValue: isApiVersionParam ? context.__api_version_client_default_value : undefined,
clientDefaultValue:
isApiVersionParam && namespace
? context.__namespaceToApiVersionClientDefaultValue.get(namespace)
: undefined,
onClient: onClient(context, type),
};
}

/**
*
* @param context
* @param type
* @returns All api versions the type is available on
*/
export function getAvailableApiVersions(context: TCGCContext, type: Type): string[] {
const apiVersions =
context.__api_versions ||
getVersions(context.program, type)[1]
?.getVersions()
.map((x) => x.value);
if (!apiVersions) return [];
export function filterApiVersionsWithDecorators(
context: TCGCContext,
type: Type,
apiVersions: string[]
): string[] {
const addedOnVersions = getAddedOnVersions(context.program, type)?.map((x) => x.value) ?? [];
const removedOnVersions = getRemovedOnVersions(context.program, type)?.map((x) => x.value) ?? [];
let added: boolean = addedOnVersions.length ? false : true;
Expand Down Expand Up @@ -155,6 +152,32 @@ export function getAvailableApiVersions(context: TCGCContext, type: Type): strin
return retval;
}

/**
*
* @param context
* @param type
* @param client If it's associated with a client, meaning it's a param etc, we can see if it's available on that client
* @returns All api versions the type is available on
*/
export function getAvailableApiVersions(
context: TCGCContext,
type: Type,
namespace?: Namespace | Interface
): string[] {
let cachedApiVersions: string[] = [];
if (namespace) {
cachedApiVersions = context.__namespaceToApiVersions.get(namespace) || [];
}

const apiVersions =
cachedApiVersions ||
getVersions(context.program, type)[1]
?.getVersions()
.map((x) => x.value);
if (!apiVersions) return [];
return filterApiVersionsWithDecorators(context, type, apiVersions);
}

interface DocWrapper {
description?: string;
details?: string;
Expand Down Expand Up @@ -267,9 +290,9 @@ export interface TCGCContext {
generatedNames?: Map<Union | Model, string>;
httpOperationCache?: Map<Operation, HttpOperation>;
unionsMap?: Map<Union, SdkUnionType>;
__api_version_parameter?: SdkParameter;
__api_version_client_default_value?: string;
__api_versions?: string[];
__namespaceToApiVersionParameter: Map<Interface | Namespace, SdkParameter>;
__namespaceToApiVersions: Map<Interface | Namespace, string[]>;
__namespaceToApiVersionClientDefaultValue: Map<Interface | Namespace, string | undefined>;
knownScalars?: Record<string, SdkBuiltInKinds>;
diagnostics: readonly Diagnostic[];
__subscriptionIdParameter?: SdkParameter;
Expand All @@ -285,6 +308,9 @@ export function createTCGCContext(program: Program): TCGCContext {
emitterName: "__TCGC_INTERNAL__",
diagnostics: [],
originalProgram: program,
__namespaceToApiVersionParameter: new Map(),
__namespaceToApiVersions: new Map(),
__namespaceToApiVersionClientDefaultValue: new Map(),
};
}

Expand Down Expand Up @@ -372,3 +398,8 @@ export function isSubscriptionId(context: TCGCContext, parameter: { name: string
export function onClient(context: TCGCContext, parameter: { name: string }): boolean {
return isSubscriptionId(context, parameter) || isApiVersion(context, parameter);
}

export function getLocationOfOperation(operation: Operation): Namespace | Interface {
// have to check interface first, because interfaces are more granular than namespaces
return (operation.interface || operation.namespace)!;
}
Loading
Loading