diff --git a/packages/typespec-client-generator-core/CHANGELOG.md b/packages/typespec-client-generator-core/CHANGELOG.md index 76296b659f..e46dd18d3e 100644 --- a/packages/typespec-client-generator-core/CHANGELOG.md +++ b/packages/typespec-client-generator-core/CHANGELOG.md @@ -1,5 +1,23 @@ # Change Log - @azure-tools/typespec-client-generator-core +## 0.49.1 + +### Bug Fixes + +- [#2000](https://github.com/Azure/typespec-azure/pull/2000) ensure operation examples to be ordered +- [#2004](https://github.com/Azure/typespec-azure/pull/2004) get correct pageable info for azure pageable operation with inheritance return model +- [#2034](https://github.com/Azure/typespec-azure/pull/2034) refine cross language definition id logic + +### Features + +- [#2010](https://github.com/Azure/typespec-azure/pull/2010) add `getHttpOperationParameter` helper function +- [#1978](https://github.com/Azure/typespec-azure/pull/1978) Add `@apiVersion` decorator to override whether a parameter is an api version or not + +### Breaking Changes + +- [#2007](https://github.com/Azure/typespec-azure/pull/2007) fix typo of `crossLanguageDefinitionId` of method. + + ## 0.49.0 ### Bug Fixes diff --git a/packages/typespec-client-generator-core/README.md b/packages/typespec-client-generator-core/README.md index 2c465cf53d..558b5203bc 100644 --- a/packages/typespec-client-generator-core/README.md +++ b/packages/typespec-client-generator-core/README.md @@ -73,6 +73,7 @@ options: - [`@access`](#@access) - [`@alternateType`](#@alternatetype) +- [`@apiVersion`](#@apiversion) - [`@client`](#@client) - [`@clientInitialization`](#@clientinitialization) - [`@clientName`](#@clientname) @@ -113,10 +114,10 @@ otherwise a warning will be added to diagnostics list. ##### Parameters -| Name | Type | Description | -| ----- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| value | `EnumMember` | The access info you want to set for this model or operation. | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| value | `EnumMember` | The access info you want to set for this model or operation. | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | ##### Examples @@ -243,10 +244,10 @@ The source type you want to apply the alternate type to. Only scalar types are s ##### Parameters -| Name | Type | Description | -| --------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| alternate | `Scalar` | The alternate type you want applied to the target. Only scalar types are supported. | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| --------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| alternate | `Scalar` | The alternate type you want applied to the target. Only scalar types are supported. | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | ##### Examples @@ -266,6 +267,39 @@ scalar storageDateTime extends utcDataTime; op test(@param @alternateType(string) date: utcDateTime): void; ``` +#### `@apiVersion` + +Use to override default assumptions on whether a parameter is an api-version parameter or not. +By default, we do matches with the `api-version` or `apiversion` string in the parameter name. Since api versions are +a client parameter, we will also elevate this parameter up onto the client. + +```typespec +@Azure.ClientGenerator.Core.apiVersion(value?: valueof boolean, scope?: valueof string) +``` + +##### Target + +`ModelProperty` + +##### Parameters + +| Name | Type | Description | +| ----- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| value | `valueof boolean` | If true, we will treat this parameter as an api-version parameter. If false, we will not. Default is true. | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | + +##### Examples + +```typespec +namespace Contoso; + +op test( + @apiVersion + @header("x-ms-version") + version: string, +): void; +``` + #### `@client` Create a ClientGenerator.Core client out of a namespace or interface @@ -280,10 +314,10 @@ Create a ClientGenerator.Core client out of a namespace or interface ##### Parameters -| Name | Type | Description | -| ----- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| value | `Model` | Optional configuration for the service. | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| value | `Model` | Optional configuration for the service. | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | ##### Examples @@ -332,10 +366,10 @@ Client parameters you would like to add to the client. By default, we apply endp ##### Parameters -| Name | Type | Description | -| ------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| options | `Model` | | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| options | `Model` | | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | ##### Examples @@ -371,10 +405,10 @@ Changes the name of a method, parameter, property, or model generated in the cli ##### Parameters -| Name | Type | Description | -| ------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| rename | `valueof string` | The rename you want applied to the object | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| rename | `valueof string` | The rename you want applied to the object | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | ##### Examples @@ -406,10 +440,10 @@ By default, the client namespace for them will follow the TypeSpec namespace. ##### Parameters -| Name | Type | Description | -| ------ | ---------------- | ------------------------------------------------------------------------------------------------------------- | -| rename | `valueof string` | The rename you want applied to the object | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters | +| Name | Type | Description | +| ------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| rename | `valueof string` | The rename you want applied to the object | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | ##### Examples @@ -419,10 +453,10 @@ namespace Contoso; ``` ```typespec -@clientName("ContosoJava", "java") -@clientName("ContosoPython", "python") -@clientName("ContosoCSharp", "csharp") -@clientName("ContosoJavascript", "javascript") +@clientNamespace("ContosoJava", "java") +@clientNamespace("ContosoPython", "python") +@clientNamespace("ContosoCSharp", "csharp") +@clientNamespace("ContosoJavascript", "javascript") namespace Contoso; ``` @@ -440,10 +474,10 @@ Whether you want to generate an operation as a convenient operation. ##### Parameters -| Name | Type | Description | -| ----- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| value | `valueof boolean` | Whether to generate the operation as convenience method or not. | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| value | `valueof boolean` | Whether to generate the operation as convenience method or not. | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | ##### Examples @@ -468,9 +502,9 @@ Set whether a model property should be flattened or not. ##### Parameters -| Name | Type | Description | -| ----- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | ##### Examples @@ -496,9 +530,9 @@ Create a ClientGenerator.Core operation group out of a namespace or interface ##### Parameters -| Name | Type | Description | -| ----- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | ##### Examples @@ -522,10 +556,10 @@ Override the default client method generated by TCGC from your service definitio ##### Parameters -| Name | Type | Description | -| -------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| override | `Operation` | : The override method definition that specifies the exact client method you want | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| -------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| override | `Operation` | : The override method definition that specifies the exact client method you want | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | ##### Examples @@ -581,10 +615,10 @@ Alias the name of a client parameter to a different name. This permits you to ha ##### Parameters -| Name | Type | Description | -| ---------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| paramAlias | `valueof string` | | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ---------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| paramAlias | `valueof string` | | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | ##### Examples @@ -621,10 +655,10 @@ Whether you want to generate an operation as a protocol operation. ##### Parameters -| Name | Type | Description | -| ----- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| value | `valueof boolean` | Whether to generate the operation as protocol or not. | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| value | `valueof boolean` | Whether to generate the operation as protocol or not. | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | ##### Examples @@ -647,9 +681,9 @@ To define the client scope of an operation. ##### Parameters -| Name | Type | Description | -| ----- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | ##### Examples @@ -686,10 +720,10 @@ otherwise a warning will be added to diagnostics list. ##### Parameters -| Name | Type | Description | -| ----- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| value | `EnumMember \| Union` | The usage info you want to set for this model. | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| value | `EnumMember \| Union` | The usage info you want to set for this model. | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | ##### Examples @@ -757,9 +791,9 @@ Whether a model needs the custom JSON converter, this is only used for backward ##### Parameters -| Name | Type | Description | -| ----- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | ##### Examples diff --git a/packages/typespec-client-generator-core/generated-defs/Azure.ClientGenerator.Core.ts b/packages/typespec-client-generator-core/generated-defs/Azure.ClientGenerator.Core.ts index b7558caf6c..7ca1da2521 100644 --- a/packages/typespec-client-generator-core/generated-defs/Azure.ClientGenerator.Core.ts +++ b/packages/typespec-client-generator-core/generated-defs/Azure.ClientGenerator.Core.ts @@ -16,7 +16,7 @@ import type { * Changes the name of a method, parameter, property, or model generated in the client SDK * * @param rename The rename you want applied to the object - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * @example * ```typespec @@ -43,7 +43,7 @@ export type ClientNameDecorator = ( * Whether you want to generate an operation as a convenient operation. * * @param value Whether to generate the operation as convenience method or not. - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * @example * ```typespec @@ -62,7 +62,7 @@ export type ConvenientAPIDecorator = ( * Whether you want to generate an operation as a protocol operation. * * @param value Whether to generate the operation as protocol or not. - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * @example * ```typespec @@ -81,7 +81,7 @@ export type ProtocolAPIDecorator = ( * Create a ClientGenerator.Core client out of a namespace or interface * * @param value Optional configuration for the service. - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * @example Basic client setting * ```typespec @@ -114,7 +114,7 @@ export type ClientDecorator = ( /** * Create a ClientGenerator.Core operation group out of a namespace or interface * - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * @example * ```typespec @@ -146,7 +146,7 @@ export type OperationGroupDecorator = ( * otherwise a warning will be added to diagnostics list. * * @param value The usage info you want to set for this model. - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * @example Expand usage for model * ```typespec @@ -218,7 +218,7 @@ export type UsageDecorator = ( * otherwise a warning will be added to diagnostics list. * * @param value The access info you want to set for this model or operation. - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * @example Set access * ```typespec @@ -354,7 +354,7 @@ export type AccessDecorator = ( /** * Set whether a model property should be flattened or not. * - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * @example * ```typespec @@ -377,7 +377,7 @@ export type FlattenPropertyDecorator = ( * * @param original : The original service definition * @param override : The override method definition that specifies the exact client method you want - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * @example * ```typespec @@ -428,7 +428,7 @@ export type OverrideDecorator = ( /** * Whether a model needs the custom JSON converter, this is only used for backward compatibility for csharp. * - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * @example * ```typespec @@ -447,7 +447,7 @@ export type UseSystemTextJsonConverterDecorator = ( /** * Client parameters you would like to add to the client. By default, we apply endpoint, credential, and api-version parameters. If you add clientInitialization, we will append those to the default list of parameters. * - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * @example * ```typespec @@ -478,7 +478,7 @@ export type ClientInitializationDecorator = ( /** * Alias the name of a client parameter to a different name. This permits you to have a different name for the parameter in client initialization then on individual methods and still refer to the same parameter. * - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * @example * ```typespec @@ -512,7 +512,8 @@ export type ParamAliasDecorator = ( * By default, the client namespace for them will follow the TypeSpec namespace. * * @param rename The rename you want applied to the object - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. + * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * @example * ```typespec * @clientNamespace("ContosoClient") @@ -520,10 +521,10 @@ export type ParamAliasDecorator = ( * ``` * @example * ```typespec - * @clientName("ContosoJava", "java") - * @clientName("ContosoPython", "python") - * @clientName("ContosoCSharp", "csharp") - * @clientName("ContosoJavascript", "javascript") + * @clientNamespace("ContosoJava", "java") + * @clientNamespace("ContosoPython", "python") + * @clientNamespace("ContosoCSharp", "csharp") + * @clientNamespace("ContosoJavascript", "javascript") * namespace Contoso; * ``` */ @@ -539,7 +540,7 @@ export type ClientNamespaceDecorator = ( * * @param source The source type you want to apply the alternate type to. Only scalar types are supported. * @param alternate The alternate type you want applied to the target. Only scalar types are supported. - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * @example * ```typespec @@ -568,7 +569,7 @@ export type AlternateTypeDecorator = ( /** * To define the client scope of an operation. * - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * @example * ```typespec @@ -578,6 +579,32 @@ export type AlternateTypeDecorator = ( */ export type ScopeDecorator = (context: DecoratorContext, target: Operation, scope?: string) => void; +/** + * Use to override default assumptions on whether a parameter is an api-version parameter or not. + * By default, we do matches with the `api-version` or `apiversion` string in the parameter name. Since api versions are + * a client parameter, we will also elevate this parameter up onto the client. + * + * @param value If true, we will treat this parameter as an api-version parameter. If false, we will not. Default is true. + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. + * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". + * @example + * ```typespec + * namespace Contoso; + * + * op test( + * @apiVersion + * @header("x-ms-version") + * version: string + * ): void; + * ``` + */ +export type ApiVersionDecorator = ( + context: DecoratorContext, + target: ModelProperty, + value?: boolean, + scope?: string, +) => void; + export type AzureClientGeneratorCoreDecorators = { clientName: ClientNameDecorator; convenientAPI: ConvenientAPIDecorator; @@ -594,4 +621,5 @@ export type AzureClientGeneratorCoreDecorators = { clientNamespace: ClientNamespaceDecorator; alternateType: AlternateTypeDecorator; scope: ScopeDecorator; + apiVersion: ApiVersionDecorator; }; diff --git a/packages/typespec-client-generator-core/lib/decorators.tsp b/packages/typespec-client-generator-core/lib/decorators.tsp index f6c58bf0e4..198910496e 100644 --- a/packages/typespec-client-generator-core/lib/decorators.tsp +++ b/packages/typespec-client-generator-core/lib/decorators.tsp @@ -5,7 +5,7 @@ namespace Azure.ClientGenerator.Core; /** * Changes the name of a method, parameter, property, or model generated in the client SDK * @param rename The rename you want applied to the object - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * * @example @@ -28,7 +28,7 @@ extern dec clientName(target: unknown, rename: valueof string, scope?: valueof s /** * Whether you want to generate an operation as a convenient operation. * @param value Whether to generate the operation as convenience method or not. - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * * @example @@ -42,7 +42,7 @@ extern dec convenientAPI(target: Operation, value?: valueof boolean, scope?: val /** * Whether you want to generate an operation as a protocol operation. * @param value Whether to generate the operation as protocol or not. - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * * @example @@ -56,7 +56,7 @@ extern dec protocolAPI(target: Operation, value?: valueof boolean, scope?: value /** * Create a ClientGenerator.Core client out of a namespace or interface * @param value Optional configuration for the service. - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * * @example Basic client setting @@ -85,7 +85,7 @@ extern dec client(target: Namespace | Interface, value?: Model, scope?: valueof /** * Create a ClientGenerator.Core operation group out of a namespace or interface - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * * @example @@ -128,7 +128,7 @@ enum Usage { * and different override usage should not conflict with each other, * otherwise a warning will be added to diagnostics list. * @param value The usage info you want to set for this model. - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * * @example Expand usage for model @@ -215,7 +215,7 @@ enum Access { * and different override access should not conflict with each other, * otherwise a warning will be added to diagnostics list. * @param value The access info you want to set for this model or operation. - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * * @example Set access @@ -350,7 +350,7 @@ extern dec access( /** * Set whether a model property should be flattened or not. - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * * @example @@ -370,7 +370,7 @@ extern dec flattenProperty(target: ModelProperty, scope?: valueof string); * Override the default client method generated by TCGC from your service definition * @param original: The original service definition * @param override: The override method definition that specifies the exact client method you want - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * * @example @@ -417,7 +417,7 @@ extern dec override(original: Operation, override: Operation, scope?: valueof st /** * Whether a model needs the custom JSON converter, this is only used for backward compatibility for csharp. - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * * @example @@ -432,7 +432,7 @@ extern dec useSystemTextJsonConverter(target: Model, scope?: valueof string); /** * Client parameters you would like to add to the client. By default, we apply endpoint, credential, and api-version parameters. If you add clientInitialization, we will append those to the default list of parameters. - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * * @example @@ -462,7 +462,7 @@ extern dec clientInitialization( /** * Alias the name of a client parameter to a different name. This permits you to have a different name for the parameter in client initialization then on individual methods and still refer to the same parameter. - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * * @example @@ -491,7 +491,8 @@ extern dec paramAlias(original: ModelProperty, paramAlias: valueof string, scope * Changes the namespace of a client, model, enum or union generated in the client SDK. * By default, the client namespace for them will follow the TypeSpec namespace. * @param rename The rename you want applied to the object - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. + * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * * @example * ```typespec @@ -501,10 +502,10 @@ extern dec paramAlias(original: ModelProperty, paramAlias: valueof string, scope * * @example * ```typespec - * @clientName("ContosoJava", "java") - * @clientName("ContosoPython", "python") - * @clientName("ContosoCSharp", "csharp") - * @clientName("ContosoJavascript", "javascript") + * @clientNamespace("ContosoJava", "java") + * @clientNamespace("ContosoPython", "python") + * @clientNamespace("ContosoCSharp", "csharp") + * @clientNamespace("ContosoJavascript", "javascript") * namespace Contoso; * ``` */ @@ -519,7 +520,7 @@ extern dec clientNamespace( * * @param source The source type you want to apply the alternate type to. Only scalar types are supported. * @param alternate The alternate type you want applied to the target. Only scalar types are supported. - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * @example * ```typespec @@ -542,7 +543,7 @@ extern dec alternateType(source: ModelProperty | Scalar, alternate: Scalar, scop /** * To define the client scope of an operation. - * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". * * @example @@ -552,3 +553,24 @@ extern dec alternateType(source: ModelProperty | Scalar, alternate: Scalar, scop * ``` */ extern dec scope(target: Operation, scope?: valueof string); + +/** + * Use to override default assumptions on whether a parameter is an api-version parameter or not. + * By default, we do matches with the `api-version` or `apiversion` string in the parameter name. Since api versions are + * a client parameter, we will also elevate this parameter up onto the client. + * @param value If true, we will treat this parameter as an api-version parameter. If false, we will not. Default is true. + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters. + * You can use "!" to specify negation such as "!(java, python)" or "!java, !python". + * + * @example + * ```typespec + * namespace Contoso; + * + * op test( + * @apiVersion + * @header("x-ms-version") + * version: string + * ): void; + * ``` + */ +extern dec apiVersion(target: ModelProperty, value?: valueof boolean, scope?: valueof string); diff --git a/packages/typespec-client-generator-core/package.json b/packages/typespec-client-generator-core/package.json index 27b44ff963..d5f50ac6b4 100644 --- a/packages/typespec-client-generator-core/package.json +++ b/packages/typespec-client-generator-core/package.json @@ -1,6 +1,6 @@ { "name": "@azure-tools/typespec-client-generator-core", - "version": "0.49.0", + "version": "0.49.1", "author": "Microsoft Corporation", "description": "TypeSpec Data Plane Generation library", "homepage": "https://azure.github.io/typespec-azure", diff --git a/packages/typespec-client-generator-core/src/decorators.ts b/packages/typespec-client-generator-core/src/decorators.ts index 9316fe7e75..caecf9db66 100644 --- a/packages/typespec-client-generator-core/src/decorators.ts +++ b/packages/typespec-client-generator-core/src/decorators.ts @@ -31,6 +31,7 @@ import { buildVersionProjections, getVersions } from "@typespec/versioning"; import { AccessDecorator, AlternateTypeDecorator, + ApiVersionDecorator, ClientDecorator, ClientInitializationDecorator, ClientNameDecorator, @@ -1063,19 +1064,34 @@ export function getClientInitialization( const paramAliasKey = createStateSymbol("paramAlias"); -export const paramAliasDecorator: ParamAliasDecorator = ( +export const $paramAlias: ParamAliasDecorator = ( context: DecoratorContext, original: ModelProperty, paramAlias: string, scope?: LanguageScopes, ) => { - setScopedDecoratorData(context, paramAliasDecorator, paramAliasKey, original, paramAlias, scope); + setScopedDecoratorData(context, $paramAlias, paramAliasKey, original, paramAlias, scope); }; export function getParamAlias(context: TCGCContext, original: ModelProperty): string | undefined { return getScopedDecoratorData(context, paramAliasKey, original); } +const apiVersionKey = createStateSymbol("apiVersion"); + +export const $apiVersion: ApiVersionDecorator = ( + context: DecoratorContext, + target: ModelProperty, + value?: boolean, + scope?: LanguageScopes, +) => { + setScopedDecoratorData(context, $apiVersion, apiVersionKey, target, value ?? true, scope); +}; + +export function getIsApiVersion(context: TCGCContext, param: ModelProperty): boolean | undefined { + return getScopedDecoratorData(context, apiVersionKey, param); +} + export const $clientNamespace: ClientNamespaceDecorator = ( context: DecoratorContext, entity: Namespace | Interface | Model | Enum | Union, diff --git a/packages/typespec-client-generator-core/src/example.ts b/packages/typespec-client-generator-core/src/example.ts index 308419741e..f6b21bd2ef 100644 --- a/packages/typespec-client-generator-core/src/example.ts +++ b/packages/typespec-client-generator-core/src/example.ts @@ -252,6 +252,9 @@ function handleHttpOperationExamples( operation.examples.push(operationExample); } + // sort examples by file path + operation.examples.sort((a, b) => (a.filePath > b.filePath ? 1 : -1)); + return diagnostics.wrap(undefined); } diff --git a/packages/typespec-client-generator-core/src/http.ts b/packages/typespec-client-generator-core/src/http.ts index 3f0465d34b..031c5f28c7 100644 --- a/packages/typespec-client-generator-core/src/http.ts +++ b/packages/typespec-client-generator-core/src/http.ts @@ -523,6 +523,7 @@ export function getCorrespondingMethodParams( ): [SdkModelPropertyType[], readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); + // 1. To see if the service parameter is a client parameter. const operationLocation = getLocationOfOperation(operation)!; let clientParams = context.__clientToParameters.get(operationLocation); if (!clientParams) { @@ -535,8 +536,11 @@ export function getCorrespondingMethodParams( twoParamsEquivalent(context, x.__raw, serviceParam.__raw) || (x.__raw?.kind === "ModelProperty" && getParamAlias(context, x.__raw) === serviceParam.name), ); - if (correspondingClientParams.length > 0) return diagnostics.wrap(correspondingClientParams); + if (correspondingClientParams.length > 0) { + return diagnostics.wrap(correspondingClientParams); + } + // 2. To see if the service parameter is api version parameter that has been elevated to client. if (serviceParam.isApiVersionParam && serviceParam.onClient) { const existingApiVersion = clientParams?.find((x) => isApiVersion(context, x)); if (!existingApiVersion) { @@ -552,8 +556,10 @@ export function getCorrespondingMethodParams( ); return diagnostics.wrap([]); } - return diagnostics.wrap(clientParams.filter((x) => isApiVersion(context, x))); + return diagnostics.wrap(existingApiVersion ? [existingApiVersion] : []); } + + // 3. To see if the service parameter is subscription parameter that has been elevated to client (only for arm service). if (isSubscriptionId(context, serviceParam)) { const subId = clientParams.find((x) => isSubscriptionId(context, x)); if (!subId) { @@ -572,13 +578,13 @@ export function getCorrespondingMethodParams( return diagnostics.wrap(subId ? [subId] : []); } - // to see if the service parameter is a method parameter or a property of a method parameter + // 4. To see if the service parameter is a method parameter or a property of a method parameter. const directMapping = findMapping(methodParameters, serviceParam); if (directMapping) { return diagnostics.wrap([directMapping]); } - // to see if all the property of service parameter could be mapped to a method parameter or a property of a method parameter + // 5. To see if all the property of the service parameter could be mapped to a method parameter or a property of a method parameter. if (serviceParam.kind === "body" && serviceParam.type.kind === "model") { const retVal = []; for (const serviceParamProp of serviceParam.type.properties) { @@ -592,6 +598,7 @@ export function getCorrespondingMethodParams( } } + // If mapping could not be found, TCGC will report error since we can't generate the client code without this mapping. diagnostics.add( createDiagnostic({ code: "no-corresponding-method-param", @@ -605,6 +612,12 @@ export function getCorrespondingMethodParams( return diagnostics.wrap([]); } +/** + * Try to find the mapping of a service paramete or a property of a service parameter to a method parameter or a property of a method parameter. + * @param methodParameters + * @param serviceParam + * @returns + */ function findMapping( methodParameters: SdkModelPropertyType[], serviceParam: SdkHttpParameter | SdkModelPropertyType, @@ -613,7 +626,7 @@ function findMapping( const visited: Set = new Set(); while (queue.length > 0) { const methodParam = queue.shift()!; - // http operation parameter/body parameter/property of body parameter could either from an operation parameter directly or from a property of an operation parameter + // HTTP operation parameter/body parameter/property of body parameter could either from an operation parameter directly or from a property of an operation parameter. if ( methodParam.__raw && serviceParam.__raw && @@ -621,7 +634,7 @@ function findMapping( ) { return methodParam; } - // this following two hard code mapping is for the case that TCGC help to add content type and accept header is not exist + // Two following two hard coded mapping is for the case that TCGC help to add content type and accept header when not exists. if ( serviceParam.kind === "header" && serviceParam.serializedName === "Content-Type" && @@ -636,6 +649,7 @@ function findMapping( ) { return methodParam; } + // BFS to find the mapping. if (methodParam.type.kind === "model" && !visited.has(methodParam.type)) { visited.add(methodParam.type); let current: SdkModelType | undefined = methodParam.type; diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index bb21fc63af..0c9da7da4b 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -585,7 +585,7 @@ interface SdkMethodBase extends DecoratedType { apiVersions: string[]; doc?: string; summary?: string; - crossLanguageDefintionId: string; + crossLanguageDefinitionId: string; } interface SdkServiceMethodBase diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index ac72bf2001..6f3dcee01c 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -11,11 +11,9 @@ import { Model, ModelProperty, Operation, - Type, } from "@typespec/compiler"; import { getServers, HttpServer } from "@typespec/http"; import { resolveVersions } from "@typespec/versioning"; -import { camelCase } from "change-case"; import { getAccess, getClientInitialization, @@ -291,6 +289,9 @@ function getPropertyPathFromSegment( for (const segment of segments) { const property = current.properties.get(segment); if (!property) { + if (current.baseModel) { + return getPropertyPathFromSegment(context, current.baseModel, segments); + } return ""; } wireSegments.push(getLibraryName(context, property)); @@ -387,7 +388,7 @@ function getSdkMethodResponse( name: createGeneratedName(context, operation, "UnionResponse"), isGeneratedName: true, clientNamespace: client.clientNamespace, - crossLanguageDefinitionId: getCrossLanguageDefinitionId(context, operation), + crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, operation)}.UnionResponse`, decorators: [], }; } else if (responseTypes.size === 1) { @@ -484,7 +485,7 @@ function getSdkBasicServiceMethod operation: serviceOperation, response, apiVersions, - crossLanguageDefintionId: getCrossLanguageDefinitionId(context, operation), + crossLanguageDefinitionId: getCrossLanguageDefinitionId(context, operation), decorators: diagnostics.pipe(getTypeDecorators(context, operation)), generateConvenient: shouldGenerateConvenient(context, operation), generateProtocol: shouldGenerateProtocol(context, operation), @@ -565,33 +566,10 @@ function getSdkInitializationType( function getSdkMethodParameter( context: TCGCContext, - type: Type, + type: ModelProperty, operation: Operation, ): [SdkMethodParameter, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); - if (type.kind !== "ModelProperty") { - const libraryName = getLibraryName(context, type); - const name = camelCase(libraryName ?? "body"); - // call before creating property type, so we can pass apiVersions of param onto its type - const apiVersions = getAvailableApiVersions(context, type, operation); - const propertyType = diagnostics.pipe(getClientTypeWithDiagnostics(context, type, operation)); - return diagnostics.wrap({ - kind: "method", - doc: getDoc(context.program, type), - summary: getSummary(context.program, type), - apiVersions, - type: propertyType, - name, - isGeneratedName: Boolean(libraryName), - optional: false, - discriminator: false, - serializedName: name, - isApiVersionParam: false, - onClient: false, - crossLanguageDefinitionId: "anonymous", - decorators: diagnostics.pipe(getTypeDecorators(context, type)), - }); - } return diagnostics.wrap({ ...diagnostics.pipe(getSdkModelPropertyType(context, type, operation)), kind: "method", @@ -633,7 +611,7 @@ function getSdkMethods( access: "internal", response: operationGroupClient, apiVersions: getAvailableApiVersions(context, operationGroup.type, client.type), - crossLanguageDefintionId: getCrossLanguageDefinitionId(context, operationGroup.type), + crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, operationGroup.type)}.${name}`, decorators: [], }); } @@ -693,6 +671,7 @@ function getEndpointTypeFromSingleServer< sdkParam.clientDefaultValue = apiVersionInfo.clientDefaultValue; } sdkParam.apiVersions = getAvailableApiVersions(context, param, client.__raw.type); + sdkParam.crossLanguageDefinitionId = `${getCrossLanguageDefinitionId(context, client.__raw.service)}.${param.name}`; } else { diagnostics.add( createDiagnostic({ @@ -752,7 +731,7 @@ function getSdkEndpointParameter; diff --git a/packages/typespec-client-generator-core/src/public-utils.ts b/packages/typespec-client-generator-core/src/public-utils.ts index fd67a920fe..6ea5357d7d 100644 --- a/packages/typespec-client-generator-core/src/public-utils.ts +++ b/packages/typespec-client-generator-core/src/public-utils.ts @@ -25,13 +25,23 @@ import { pascalCase } from "change-case"; import pluralize from "pluralize"; import { getClientNameOverride, + getIsApiVersion, listClients, listOperationGroups, listOperationsInOperationGroup, } from "./decorators.js"; import { + SdkBodyModelPropertyType, + SdkBodyParameter, SdkClientType, + SdkCookieParameter, + SdkHeaderParameter, + SdkHttpOperation, SdkHttpOperationExample, + SdkModelPropertyType, + SdkPathParameter, + SdkQueryParameter, + SdkServiceMethod, SdkServiceOperation, SdkType, TCGCContext, @@ -66,6 +76,11 @@ export function getDefaultApiVersion( return undefined; } } + +function isModelProperty(type: any): type is ModelProperty { + return type && typeof type === "object" && "kind" in type && type.kind === "ModelProperty"; +} + /** * Return whether a parameter is the Api Version parameter of a client * @param program @@ -73,6 +88,12 @@ export function getDefaultApiVersion( * @returns */ export function isApiVersion(context: TCGCContext, type: { name: string }): boolean { + if (isModelProperty(type)) { + const override = getIsApiVersion(context, type); + if (override !== undefined) { + return override; + } + } return ( type.name.toLowerCase().includes("apiversion") || type.name.toLowerCase().includes("api-version") @@ -218,20 +239,27 @@ export function getCrossLanguageDefinitionId( appendNamespace: boolean = true, ): string { let retval = type.name || "anonymous"; - const namespace = type.kind === "ModelProperty" ? type.model?.namespace : type.namespace; + let namespace = type.kind === "ModelProperty" ? type.model?.namespace : type.namespace; switch (type.kind) { + // Enum and Scalar will always have a name case "Union": case "Model": - // Enum and Scalar will always have a name if (type.name) { break; } const contextPath = operation ? getContextPath(context, operation, type) : findContextPath(context, type); + const namingPart = contextPath.slice(findLastNonAnonymousNode(contextPath)); + if ( + namingPart[0]?.type?.kind === "Model" || + namingPart[0]?.type?.kind === "Union" || + namingPart[0]?.type?.kind === "Operation" + ) { + namespace = namingPart[0]?.type?.namespace; + } retval = - contextPath - .slice(findLastNonAnonymousModelNode(contextPath)) + namingPart .map((x) => x.type?.kind === "Model" || x.type?.kind === "Union" ? x.type.name || x.name @@ -243,7 +271,12 @@ export function getCrossLanguageDefinitionId( break; case "ModelProperty": if (type.model) { - retval = `${getCrossLanguageDefinitionId(context, type.model, undefined, false)}.${retval}`; + // operation parameter case + if (type.model === operation?.parameters) { + retval = `${getCrossLanguageDefinitionId(context, operation, undefined, false)}.${retval}`; + } else { + retval = `${getCrossLanguageDefinitionId(context, type.model, operation, false)}.${retval}`; + } } break; case "Operation": @@ -352,7 +385,7 @@ function findContextPath( interface ContextNode { name: string; - type?: Model | Union | TspLiteralType; + type: Model | Union | TspLiteralType | Operation; } /** @@ -376,7 +409,7 @@ function getContextPath( if (httpOperation.parameters.body) { visited.clear(); - result = [{ name: root.name }]; + result = [{ name: root.name, type: root }]; let bodyType: Type; if (isHttpBodySpread(httpOperation.parameters.body)) { bodyType = getHttpBodySpreadModel(httpOperation.parameters.body.type as Model); @@ -390,7 +423,7 @@ function getContextPath( for (const parameter of Object.values(httpOperation.parameters.parameters)) { visited.clear(); - result = [{ name: root.name }]; + result = [{ name: root.name, type: root }]; if ( dfsModelProperties(typeToFind, parameter.param.type, `Request${pascalCase(parameter.name)}`) ) { @@ -402,7 +435,7 @@ function getContextPath( for (const innerResponse of response.responses) { if (innerResponse.body?.type) { visited.clear(); - result = [{ name: root.name }]; + result = [{ name: root.name, type: root }]; if (dfsModelProperties(typeToFind, innerResponse.body.type, "Response", true)) { return result; } @@ -412,7 +445,7 @@ function getContextPath( if (headers) { for (const header of Object.values(headers)) { visited.clear(); - result = [{ name: root.name }]; + result = [{ name: root.name, type: root }]; if (dfsModelProperties(typeToFind, header.type, `Response${pascalCase(header.name)}`)) { return result; } @@ -554,15 +587,15 @@ function getContextPath( } } -function findLastNonAnonymousModelNode(contextPath: ContextNode[]): number { +function findLastNonAnonymousNode(contextPath: ContextNode[]): number { let lastNonAnonymousModelNodeIndex = contextPath.length - 1; while (lastNonAnonymousModelNodeIndex >= 0) { const currType = contextPath[lastNonAnonymousModelNodeIndex].type; if ( - !contextPath[lastNonAnonymousModelNodeIndex].type || - (currType?.kind === "Model" && currType.name) + (currType.kind === "Model" || currType.kind === "Union" || currType.kind === "Operation") && + currType.name ) { - // it's nonanonymous model node (if no type defined, it's the operation node) + // it's non anonymous node break; } else { --lastNonAnonymousModelNodeIndex; @@ -589,11 +622,11 @@ function buildNameFromContextPaths( return ""; } - // 1. find the last nonanonymous model node - const lastNonAnonymousModelNodeIndex = findLastNonAnonymousModelNode(contextPath); + // 1. find the last non-anonymous model node + const lastNonAnonymousNodeIndex = findLastNonAnonymousNode(contextPath); // 2. build name let createName: string = ""; - for (let j = lastNonAnonymousModelNodeIndex; j < contextPath.length; j++) { + for (let j = lastNonAnonymousNodeIndex; j < contextPath.length; j++) { const currContextPathType = contextPath[j]?.type; if ( currContextPathType?.kind === "String" || @@ -602,11 +635,11 @@ function buildNameFromContextPaths( ) { // constant type createName = `${createName}${pascalCase(contextPath[j].name)}`; - } else if (!currContextPathType?.name) { - // is anonymous model node + } else if (!currContextPathType?.name || currContextPathType.kind === "Operation") { + // is anonymous node or operation node createName = `${createName}${pascalCase(contextPath[j].name)}`; } else { - // is non-anonymous model, use type name + // is non-anonymous node, use type name createName = `${createName}${currContextPathType!.name!}`; } } @@ -686,3 +719,48 @@ export function isAzureCoreModel(t: SdkType): boolean { export function isPagedResultModel(context: TCGCContext, t: SdkType): boolean { return context.__pagedResultSet.has(t); } + +/** + * Find corresponding http parameter list for a client initialization parameter, a service method parameter or a property of a service method parameter. + * + * @param method + * @param param + * @returns + */ +export function getHttpOperationParameter( + method: SdkServiceMethod, + param: SdkModelPropertyType, +): + | SdkPathParameter + | SdkQueryParameter + | SdkHeaderParameter + | SdkCookieParameter + | SdkBodyParameter + | SdkBodyModelPropertyType + | undefined { + const operation = method.operation; + // BFS to find the corresponding http parameter. + // An http parameter will be mapped to a method/client parameter, several method/client parameters (body spread case), or one property of a method property (metadata on property case). + // So, when we try to find which http parameter a parameter or property corresponds to, we compare the `correspondingMethodParams` list directly. + // If a method parameter is spread case, then we need to find the cooresponding http body parameter's property. + for (const p of operation.parameters) { + for (const cp of p.correspondingMethodParams) { + if (cp === param) { + return p; + } + } + } + if (operation.bodyParam) { + for (const cp of operation.bodyParam.correspondingMethodParams) { + if (cp === param) { + if (operation.bodyParam.type.kind === "model" && operation.bodyParam.type !== param.type) { + return operation.bodyParam.type.properties.find( + (p) => p.kind === "property" && p.name === param.name, + ) as SdkBodyModelPropertyType | undefined; + } + return operation.bodyParam; + } + } + } + return undefined; +} diff --git a/packages/typespec-client-generator-core/src/tsp-index.ts b/packages/typespec-client-generator-core/src/tsp-index.ts index 808b916444..dc5b700f86 100644 --- a/packages/typespec-client-generator-core/src/tsp-index.ts +++ b/packages/typespec-client-generator-core/src/tsp-index.ts @@ -2,6 +2,7 @@ import { AzureClientGeneratorCoreDecorators } from "../generated-defs/Azure.Clie import { $access, $alternateType, + $apiVersion, $client, $clientInitialization, $clientName, @@ -10,11 +11,11 @@ import { $flattenProperty, $operationGroup, $override, + $paramAlias, $protocolAPI, $scope, $usage, $useSystemTextJsonConverter, - paramAliasDecorator, } from "./decorators.js"; export { $lib } from "./lib.js"; @@ -34,7 +35,8 @@ export const $decorators = { override: $override, useSystemTextJsonConverter: $useSystemTextJsonConverter, clientInitialization: $clientInitialization, - paramAlias: paramAliasDecorator, + paramAlias: $paramAlias, + apiVersion: $apiVersion, clientNamespace: $clientNamespace, alternateType: $alternateType, scope: $scope, diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index f6e3fb71b9..524d0dc0c9 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -1090,7 +1090,7 @@ function getSdkCredentialType( name: createGeneratedName(context, client.service, "CredentialUnion"), isGeneratedName: true, clientNamespace: getClientNamespace(context, client.service), - crossLanguageDefinitionId: getCrossLanguageDefinitionId(context, client.service), + crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, client.service)}.CredentialUnion`, decorators: [], access: "public", usage: UsageFlags.None, @@ -1105,11 +1105,10 @@ export function getSdkCredentialParameter( ): SdkCredentialParameter | undefined { const auth = getAuthentication(context.program, client.service); if (!auth) return undefined; - const name = "credential"; return { type: getSdkCredentialType(context, client, auth), kind: "credential", - name, + name: "credential", isGeneratedName: true, doc: "Credential used to authenticate requests to the service.", apiVersions: getAvailableApiVersions(context, client.service, client.type), diff --git a/packages/typespec-client-generator-core/test/decorators/api-version.test.ts b/packages/typespec-client-generator-core/test/decorators/api-version.test.ts new file mode 100644 index 0000000000..ca61cbfa2b --- /dev/null +++ b/packages/typespec-client-generator-core/test/decorators/api-version.test.ts @@ -0,0 +1,97 @@ +import { ok, strictEqual } from "assert"; +import { beforeEach, describe, it } from "vitest"; +import { createSdkTestRunner, SdkTestRunner } from "../test-host.js"; + +describe("@apiVersion", () => { + let runner: SdkTestRunner; + + beforeEach(async () => { + runner = await createSdkTestRunner({ emitterName: "@azure-tools/typespec-python" }); + }); + + describe("@apiVersion", () => { + it("override parameter to be api version", async () => { + await runner.compile(` + @service({}) + namespace MyService; + op get( + @apiVersion + @header("x-ms-version") + version: string + ): string; + `); + const sdkPackage = runner.context.sdkPackage; + // there will be no api version param on client, bc the service isn't versioned + const apiVersionClientParam = sdkPackage.clients[0].initialization.properties.find( + (x) => x.name === "version", + ); + ok(!apiVersionClientParam); + const method = sdkPackage.clients[0].methods[0]; + strictEqual(method.kind, "basic"); + const apiVersionParam = method.parameters.find((x) => x.name === "version"); + ok(apiVersionParam); + strictEqual(apiVersionParam.isApiVersionParam, true); + }); + + it("override api version param defaults to latest api version", async () => { + await runner.compile(` + @service({ + title: "Contoso Widget Manager", + }) + @versioned(Contoso.WidgetManager.Versions) + namespace Contoso.WidgetManager; + + enum Versions { + v1, + v2, + v3, + } + op get( + @apiVersion + @header("x-ms-version") + version: string + ): string; + `); + const sdkPackage = runner.context.sdkPackage; + + const apiVersionClientParam = sdkPackage.clients[0].initialization.properties.find( + (x) => x.name === "version", + ); + ok(apiVersionClientParam); + strictEqual(apiVersionClientParam?.clientDefaultValue, "v3"); + strictEqual(apiVersionClientParam.isApiVersionParam, true); + const method = sdkPackage.clients[0].methods[0]; + + // since the api version param is elevated to client, it should not be present in the method + strictEqual(method.kind, "basic"); + const apiVersionParam = method.parameters.find((x) => x.name === "version"); + ok(!apiVersionParam); + const apiVersionOpParam = method.operation.parameters.find((x) => x.name === "version"); + ok(apiVersionOpParam); + strictEqual(apiVersionOpParam.isApiVersionParam, true); + strictEqual(apiVersionOpParam.correspondingMethodParams[0], apiVersionClientParam); + }); + + it("override parameter to not be api version", async () => { + await runner.compile(` + @service({}) + namespace MyService; + op get( + @apiVersion(false) + @query "api-version": string + ): string; + `); + const sdkPackage = runner.context.sdkPackage; + const apiVersionClientParam = sdkPackage.clients[0].initialization.properties.find( + (x) => x.name === "api-version", + ); + // there will be no api version param on client, bc we overrode it to not be api version + ok(!apiVersionClientParam); + const method = sdkPackage.clients[0].methods[0]; + strictEqual(method.kind, "basic"); + const apiVersionParam = method.parameters.find((x) => x.name === "api-version"); + ok(apiVersionParam); + strictEqual(apiVersionParam.isApiVersionParam, false); + }); + }); +}); diff --git a/packages/typespec-client-generator-core/test/examples/load.test.ts b/packages/typespec-client-generator-core/test/examples/load.test.ts index c0472e8d0f..d7ab2e90f7 100644 --- a/packages/typespec-client-generator-core/test/examples/load.test.ts +++ b/packages/typespec-client-generator-core/test/examples/load.test.ts @@ -253,4 +253,25 @@ describe("typespec-client-generator-core: load examples", () => { ok(operation); strictEqual(operation.examples?.length, 1); }); + + it("ensure ordering for multiple examples", async () => { + await runner.host.addRealTypeSpecFile("./examples/a_b_c.json", `${__dirname}/load/a_b_c.json`); + await runner.host.addRealTypeSpecFile("./examples/a_b.json", `${__dirname}/load/a_b.json`); + await runner.host.addRealTypeSpecFile("./examples/a.json", `${__dirname}/load/a.json`); + await runner.compile(` + @service({}) + namespace TestClient { + op get(): string; + } + `); + + const operation = ( + runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod + ).operation; + ok(operation); + strictEqual(operation.examples?.length, 3); + strictEqual(operation.examples![0].filePath, "a.json"); + strictEqual(operation.examples![1].filePath, "a_b.json"); + strictEqual(operation.examples![2].filePath, "a_b_c.json"); + }); }); diff --git a/packages/typespec-client-generator-core/test/examples/load/a.json b/packages/typespec-client-generator-core/test/examples/load/a.json new file mode 100644 index 0000000000..bb89bafbd8 --- /dev/null +++ b/packages/typespec-client-generator-core/test/examples/load/a.json @@ -0,0 +1,11 @@ +{ + "operationId": "get", + "title": "a", + "parameters": {}, + "responses": { + "200": { + "description": "ARM operation completed successfully.", + "body": "test" + } + } +} diff --git a/packages/typespec-client-generator-core/test/examples/load/a_b.json b/packages/typespec-client-generator-core/test/examples/load/a_b.json new file mode 100644 index 0000000000..0ab8daffae --- /dev/null +++ b/packages/typespec-client-generator-core/test/examples/load/a_b.json @@ -0,0 +1,11 @@ +{ + "operationId": "get", + "title": "ab", + "parameters": {}, + "responses": { + "200": { + "description": "ARM operation completed successfully.", + "body": "test" + } + } +} diff --git a/packages/typespec-client-generator-core/test/examples/load/a_b_c.json b/packages/typespec-client-generator-core/test/examples/load/a_b_c.json new file mode 100644 index 0000000000..42665dc299 --- /dev/null +++ b/packages/typespec-client-generator-core/test/examples/load/a_b_c.json @@ -0,0 +1,11 @@ +{ + "operationId": "get", + "title": "abc", + "parameters": {}, + "responses": { + "200": { + "description": "ARM operation completed successfully.", + "body": "test" + } + } +} diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index e25a16d13b..95e37366ee 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -652,7 +652,7 @@ describe("typespec-client-generator-core: package", () => { strictEqual(getStatus.name, "getWidgetOperationStatus"); strictEqual(getStatus.kind, "basic"); strictEqual( - getStatus.crossLanguageDefintionId, + getStatus.crossLanguageDefinitionId, "Contoso.WidgetManager.Widgets.getWidgetOperationStatus", ); strictEqual(getStatus.parameters.length, 3); @@ -729,7 +729,7 @@ describe("typespec-client-generator-core: package", () => { strictEqual(createOrUpdate.kind, "lro"); strictEqual(createOrUpdate.parameters.length, 11); strictEqual( - createOrUpdate.crossLanguageDefintionId, + createOrUpdate.crossLanguageDefinitionId, "Contoso.WidgetManager.Widgets.createOrUpdateWidget", ); deepStrictEqual(createOrUpdate.parameters.map((x) => x.name).sort(), [ @@ -878,7 +878,7 @@ describe("typespec-client-generator-core: package", () => { strictEqual(listManufacturers.name, "listManufacturers"); strictEqual( - listManufacturers.crossLanguageDefintionId, + listManufacturers.crossLanguageDefinitionId, "Contoso.WidgetManager.Widgets.listManufacturers", ); strictEqual(listManufacturers.kind, "paging"); diff --git a/packages/typespec-client-generator-core/test/packages/client.test.ts b/packages/typespec-client-generator-core/test/packages/client.test.ts index 55d88b07e2..16e66400b4 100644 --- a/packages/typespec-client-generator-core/test/packages/client.test.ts +++ b/packages/typespec-client-generator-core/test/packages/client.test.ts @@ -682,14 +682,17 @@ describe("typespec-client-generator-core: client", () => { strictEqual(clientAccessor.name, "getMyOperationGroup"); strictEqual(clientAccessor.parameters.length, 0); strictEqual(clientAccessor.response, operationGroup); - strictEqual(clientAccessor.crossLanguageDefintionId, "TestService.MyOperationGroup"); + strictEqual( + clientAccessor.crossLanguageDefinitionId, + "TestService.MyOperationGroup.getMyOperationGroup", + ); strictEqual(operationGroup.initialization.properties.length, 1); strictEqual(operationGroup.initialization.access, "internal"); strictEqual(operationGroup.methods.length, 1); strictEqual(operationGroup.methods[0].name, "func"); strictEqual( - operationGroup.methods[0].crossLanguageDefintionId, + operationGroup.methods[0].crossLanguageDefinitionId, "TestService.MyOperationGroup.func", ); strictEqual(operationGroup.crossLanguageDefinitionId, "TestService.MyOperationGroup"); @@ -733,7 +736,7 @@ describe("typespec-client-generator-core: client", () => { const fooAccessor = mainClient.methods[0]; strictEqual(fooAccessor.kind, "clientaccessor"); - strictEqual(fooAccessor.crossLanguageDefintionId, "TestService.Foo"); + strictEqual(fooAccessor.crossLanguageDefinitionId, "TestService.Foo.getFoo"); strictEqual(fooAccessor.access, "internal"); strictEqual(fooAccessor.name, "getFoo"); strictEqual(fooAccessor.parameters.length, 0); @@ -743,7 +746,7 @@ describe("typespec-client-generator-core: client", () => { strictEqual(barAccessor.kind, "clientaccessor"); strictEqual(barAccessor.access, "internal"); strictEqual(barAccessor.name, "getBar"); - strictEqual(barAccessor.crossLanguageDefintionId, "TestService.Bar"); + strictEqual(barAccessor.crossLanguageDefinitionId, "TestService.Bar.getBar"); strictEqual(barAccessor.parameters.length, 0); strictEqual(barAccessor.response, barClient); @@ -754,7 +757,7 @@ describe("typespec-client-generator-core: client", () => { const fooBarAccessor = fooClient.methods[0]; strictEqual(fooBarAccessor.kind, "clientaccessor"); - strictEqual(fooBarAccessor.crossLanguageDefintionId, "TestService.Foo.Bar"); + strictEqual(fooBarAccessor.crossLanguageDefinitionId, "TestService.Foo.Bar.getBar"); strictEqual(fooBarAccessor.access, "internal"); strictEqual(fooBarAccessor.name, "getBar"); strictEqual(fooBarAccessor.parameters.length, 0); @@ -766,7 +769,7 @@ describe("typespec-client-generator-core: client", () => { strictEqual(fooBarClient.methods.length, 1); strictEqual(fooBarClient.methods[0].kind, "basic"); strictEqual(fooBarClient.methods[0].name, "one"); - strictEqual(fooBarClient.methods[0].crossLanguageDefintionId, "TestService.Foo.Bar.one"); + strictEqual(fooBarClient.methods[0].crossLanguageDefinitionId, "TestService.Foo.Bar.one"); strictEqual(barClient.initialization.properties.length, 1); strictEqual(barClient.initialization.access, "internal"); @@ -774,7 +777,7 @@ describe("typespec-client-generator-core: client", () => { strictEqual(barClient.methods.length, 1); strictEqual(barClient.methods[0].kind, "basic"); strictEqual(barClient.methods[0].name, "two"); - strictEqual(barClient.methods[0].crossLanguageDefintionId, "TestService.Bar.two"); + strictEqual(barClient.methods[0].crossLanguageDefinitionId, "TestService.Bar.two"); }); function getServiceNoDefaultApiVersion(op: string) { @@ -895,7 +898,7 @@ describe("typespec-client-generator-core: client", () => { strictEqual(withoutApiVersion.parameters.length, 0); strictEqual(withoutApiVersion.operation.parameters.length, 0); strictEqual( - withoutApiVersion.crossLanguageDefintionId, + withoutApiVersion.crossLanguageDefinitionId, "Server.Versions.Versioned.withoutApiVersion", ); }); @@ -934,7 +937,7 @@ describe("typespec-client-generator-core: client", () => { strictEqual(withApiVersion.name, "withQueryApiVersion"); strictEqual(withApiVersion.kind, "basic"); strictEqual( - withApiVersion.crossLanguageDefintionId, + withApiVersion.crossLanguageDefinitionId, "Server.Versions.Versioned.withQueryApiVersion", ); strictEqual(withApiVersion.parameters.length, 0); @@ -988,7 +991,7 @@ describe("typespec-client-generator-core: client", () => { strictEqual(withApiVersion.name, "withPathApiVersion"); strictEqual(withApiVersion.kind, "basic"); strictEqual( - withApiVersion.crossLanguageDefintionId, + withApiVersion.crossLanguageDefinitionId, "Server.Versions.Versioned.withPathApiVersion", ); strictEqual(withApiVersion.parameters.length, 0); diff --git a/packages/typespec-client-generator-core/test/packages/paged-operation.test.ts b/packages/typespec-client-generator-core/test/packages/paged-operation.test.ts index a8aede3811..6cabefc163 100644 --- a/packages/typespec-client-generator-core/test/packages/paged-operation.test.ts +++ b/packages/typespec-client-generator-core/test/packages/paged-operation.test.ts @@ -177,4 +177,35 @@ describe("typespec-client-generator-core: paged operation", () => { "b.d", ); }); + + it("azure page result with inheritance", async () => { + await runner.compileWithBuiltInService(` + op test(): ExtendedListTestResult; + @pagedResult + model ListTestResult { + @items + values: Test[]; + + @nextLink + nextLink: string; + } + + model ExtendedListTestResult extends ListTestResult { + message: string; + } + + model Test { + id: string; + } + `); + const sdkPackage = runner.context.sdkPackage; + const method = getServiceMethodOfClient(sdkPackage); + strictEqual(method.name, "test"); + strictEqual(method.kind, "paging"); + strictEqual(method.nextLinkPath, "nextLink"); + + const response = method.response; + strictEqual(response.kind, "method"); + strictEqual(response.resultPath, "values"); + }); }); diff --git a/packages/typespec-client-generator-core/test/packages/parameters.test.ts b/packages/typespec-client-generator-core/test/packages/parameters.test.ts index 02ff61aff8..56ed0bbb3f 100644 --- a/packages/typespec-client-generator-core/test/packages/parameters.test.ts +++ b/packages/typespec-client-generator-core/test/packages/parameters.test.ts @@ -31,7 +31,7 @@ describe("typespec-client-generator-core: parameters", () => { const method = getServiceMethodOfClient(sdkPackage); strictEqual(method.name, "myOp"); strictEqual(method.kind, "basic"); - strictEqual(method.crossLanguageDefintionId, "My.Service.myOp"); + strictEqual(method.crossLanguageDefinitionId, "My.Service.myOp"); strictEqual(method.parameters.length, 1); const methodParam = method.parameters[0]; @@ -102,7 +102,7 @@ describe("typespec-client-generator-core: parameters", () => { const method = getServiceMethodOfClient(sdkPackage); strictEqual(method.name, "pathInModel"); strictEqual(method.kind, "basic"); - strictEqual(method.crossLanguageDefintionId, "TestService.pathInModel"); + strictEqual(method.crossLanguageDefinitionId, "TestService.pathInModel"); strictEqual(method.parameters.length, 1); const pathMethod = method.parameters[0]; strictEqual(pathMethod.kind, "method"); @@ -139,7 +139,7 @@ describe("typespec-client-generator-core: parameters", () => { const method = getServiceMethodOfClient(sdkPackage); strictEqual(method.name, "myOp"); strictEqual(method.kind, "basic"); - strictEqual(method.crossLanguageDefintionId, "My.Service.myOp"); + strictEqual(method.crossLanguageDefinitionId, "My.Service.myOp"); strictEqual(method.parameters.length, 1); const methodParam = method.parameters[0]; diff --git a/packages/typespec-client-generator-core/test/public-utils.test.ts b/packages/typespec-client-generator-core/test/public-utils.test.ts index cc72670c8e..c2d94af7d1 100644 --- a/packages/typespec-client-generator-core/test/public-utils.test.ts +++ b/packages/typespec-client-generator-core/test/public-utils.test.ts @@ -1393,7 +1393,7 @@ describe("typespec-client-generator-core: public-utils", () => { strictEqual(unionEnum.kind, "enum"); strictEqual(unionEnum.name, "AStatus"); ok(unionEnum.isGeneratedName); - strictEqual(unionEnum.crossLanguageDefinitionId, "A.status.anonymous"); + strictEqual(unionEnum.crossLanguageDefinitionId, "TestService.A.status.anonymous"); strictEqual(models[0].kind, "model"); const statusProp = models[0].properties[0]; strictEqual(statusProp.kind, "property"); @@ -1465,7 +1465,7 @@ describe("typespec-client-generator-core: public-utils", () => { const unionEnum = test1.properties[0].type; strictEqual(unionEnum.name, "AChoiceStatus"); ok(unionEnum.isGeneratedName); - strictEqual(unionEnum.crossLanguageDefinitionId, "A.choice.status.anonymous"); + strictEqual(unionEnum.crossLanguageDefinitionId, "TestService.A.choice.status.anonymous"); }); }); @@ -1552,7 +1552,7 @@ describe("typespec-client-generator-core: public-utils", () => { strictEqual(unionEnum.name, "AStatus"); ok(unionEnum.isGeneratedName); // not a defined type in tsp, so no crossLanguageDefinitionId - strictEqual(unionEnum.crossLanguageDefinitionId, "A.status.anonymous"); + strictEqual(unionEnum.crossLanguageDefinitionId, "TestService.A.status.anonymous"); }); }); @@ -1611,7 +1611,7 @@ describe("typespec-client-generator-core: public-utils", () => { // not a defined type in tsp, so no crossLanguageDefinitionId strictEqual( unionEnum.crossLanguageDefinitionId, - "test.ResponseRepeatabilityResult.anonymous", + "MyService.test.ResponseRepeatabilityResult.anonymous", ); ok(unionEnum.isGeneratedName); }); @@ -1639,7 +1639,7 @@ describe("typespec-client-generator-core: public-utils", () => { // not a defined type in tsp, so no crossLanguageDefinitionId strictEqual( unionEnum.crossLanguageDefinitionId, - "test.RequestRepeatabilityResult.anonymous", + "MyService.test.RequestRepeatabilityResult.anonymous", ); ok(unionEnum.isGeneratedName); }); @@ -1673,7 +1673,7 @@ describe("typespec-client-generator-core: public-utils", () => { strictEqual(stringType.isGeneratedName, true); strictEqual( stringType.crossLanguageDefinitionId, - "test.RequestRepeatabilityResult.anonymous", + "MyService.test.RequestRepeatabilityResult.anonymous", ); }); diff --git a/packages/typespec-client-generator-core/test/public-utils/get-cross-language-definition-id.test.ts b/packages/typespec-client-generator-core/test/public-utils/get-cross-language-definition-id.test.ts new file mode 100644 index 0000000000..e1a1b59b9d --- /dev/null +++ b/packages/typespec-client-generator-core/test/public-utils/get-cross-language-definition-id.test.ts @@ -0,0 +1,109 @@ +import { AzureCoreTestLibrary } from "@azure-tools/typespec-azure-core/testing"; +import { strictEqual } from "assert"; +import { beforeEach, describe, it } from "vitest"; +import { createSdkTestRunner, SdkTestRunner } from "../test-host.js"; + +describe("typespec-client-generator-core: getCrossLanguageDefinitionId", () => { + let runner: SdkTestRunner; + + beforeEach(async () => { + runner = await createSdkTestRunner({ emitterName: "@azure-tools/typespec-python" }); + }); + + it("parameter's crossLanguageDefinitionId", async () => { + runner = await createSdkTestRunner({ + librariesToAdd: [AzureCoreTestLibrary], + autoUsings: ["Azure.Core", "Azure.Core.Traits"], + emitterName: "@azure-tools/typespec-java", + }); + await runner.compileWithBuiltInAzureCoreService(` + alias ServiceTraits = SupportsRepeatableRequests & + SupportsConditionalRequests & + SupportsClientRequestId; + + @route("service-status") + op getServiceStatus is RpcOperation< + {}, + { + statusString: string; + }, + ServiceTraits + >; + `); + + const sdkPackage = runner.context.sdkPackage; + strictEqual( + sdkPackage.clients[0].initialization.properties[1].crossLanguageDefinitionId, + "My.Service.getServiceStatus.apiVersion", + ); + const getServiceStatus = sdkPackage.clients[0].methods[0]; + strictEqual(getServiceStatus.kind, "basic"); + strictEqual( + getServiceStatus.parameters[0].crossLanguageDefinitionId, + "My.Service.getServiceStatus.clientRequestId", + ); + strictEqual( + getServiceStatus.parameters[1].crossLanguageDefinitionId, + "My.Service.getServiceStatus.accept", + ); + const operation = getServiceStatus.operation; + strictEqual( + operation.parameters[0].crossLanguageDefinitionId, + "My.Service.getServiceStatus.apiVersion", + ); + strictEqual( + operation.parameters[1].crossLanguageDefinitionId, + "My.Service.getServiceStatus.clientRequestId", + ); + strictEqual( + operation.parameters[2].crossLanguageDefinitionId, + "My.Service.getServiceStatus.accept", + ); + }); + + it("endpoint's crossLanguageDefinitionId", async () => { + runner = await createSdkTestRunner({ + librariesToAdd: [AzureCoreTestLibrary], + autoUsings: ["Azure.Core", "Azure.Core.Traits"], + emitterName: "@azure-tools/typespec-java", + }); + await runner.compile(` + @service({ + title: "Contoso Widget Manager", + }) + @server( + "{url}/widget", + "Contoso Widget APIs", + { + url: string, + } + ) + @versioned(Contoso.WidgetManager.Versions) + namespace Contoso.WidgetManager; + + enum Versions { + @useDependency(Azure.Core.Versions.v1_0_Preview_2) + "2022-08-30", + } + + op test(): void; + `); + + const sdkPackage = runner.context.sdkPackage; + const initialization = sdkPackage.clients[0].initialization; + const endpoint = initialization.properties[0]; + strictEqual(endpoint.crossLanguageDefinitionId, "Contoso.WidgetManager.endpoint"); + strictEqual(endpoint.type.kind, "union"); + strictEqual(endpoint.type.crossLanguageDefinitionId, "Contoso.WidgetManager.Endpoint"); + strictEqual(endpoint.type.variantTypes[0].kind, "endpoint"); + strictEqual( + endpoint.type.variantTypes[0].templateArguments[0].crossLanguageDefinitionId, + "Contoso.WidgetManager.url", + ); + strictEqual(endpoint.type.variantTypes[1].kind, "endpoint"); + strictEqual( + endpoint.type.variantTypes[1].templateArguments[0].crossLanguageDefinitionId, + "Contoso.WidgetManager.endpoint", + ); + }); +}); diff --git a/packages/typespec-client-generator-core/test/public-utils/get-http-operation-parameter.test.ts b/packages/typespec-client-generator-core/test/public-utils/get-http-operation-parameter.test.ts new file mode 100644 index 0000000000..4c1c7859f9 --- /dev/null +++ b/packages/typespec-client-generator-core/test/public-utils/get-http-operation-parameter.test.ts @@ -0,0 +1,511 @@ +import { ok, strictEqual } from "assert"; +import { beforeEach, describe, it } from "vitest"; +import { + SdkClientType, + SdkHttpOperation, + SdkMethodParameter, + SdkServiceMethod, +} from "../../src/interfaces.js"; +import { getHttpOperationParameter } from "../../src/public-utils.js"; +import { getServiceMethodOfClient } from "../packages/utils.js"; +import { createSdkTestRunner, SdkTestRunner } from "../test-host.js"; + +describe("typespec-client-generator-core: public-utils getHttpOperationParameter", () => { + let runner: SdkTestRunner; + + beforeEach(async () => { + runner = await createSdkTestRunner({ emitterName: "@azure-tools/typespec-python" }); + }); + + it("normal method case", async () => { + await runner.compileWithBuiltInService(` + op myOp(@header h: string, @query q: string, @path p: string, @body b: string): void; + `); + const sdkPackage = runner.context.sdkPackage; + const method = getServiceMethodOfClient(sdkPackage); + const parameters = method.parameters; + + for (const param of parameters) { + if (param.name === "h") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "header"); + strictEqual(httpParam.serializedName, "h"); + } else if (param.name === "q") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "query"); + strictEqual(httpParam.serializedName, "q"); + } else if (param.name === "p") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "path"); + strictEqual(httpParam.serializedName, "p"); + } else if (param.name === "b") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "body"); + strictEqual(httpParam.serializedName, "b"); + } else if (param.name === "contentType") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "header"); + strictEqual(httpParam.serializedName, "Content-Type"); + } + } + }); + + it("normal spread case", async () => { + await runner.compileWithBuiltInService(` + model Input { + key: string; + } + + op myOp(...Input): void; + `); + const sdkPackage = runner.context.sdkPackage; + const method = getServiceMethodOfClient(sdkPackage); + const parameters = method.parameters; + + strictEqual(parameters.length, 2); + + for (const param of parameters) { + if (param.name === "key") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "property"); + strictEqual(httpParam.name, "key"); + } else if (param.name === "contentType") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "header"); + strictEqual(httpParam.serializedName, "Content-Type"); + } + } + }); + + it("spread model with @body property", async () => { + await runner.compileWithBuiltInService(` + model Shelf { + name: string; + theme?: string; + } + model CreateShelfRequest { + @body + body: Shelf; + } + op createShelf(...CreateShelfRequest): Shelf; + `); + const method = getServiceMethodOfClient(runner.context.sdkPackage); + const parameters = method.parameters; + + strictEqual(parameters.length, 3); + + for (const param of parameters) { + if (param.name === "body") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "body"); + strictEqual(httpParam.serializedName, "body"); + } else if (param.name === "contentType") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "header"); + strictEqual(httpParam.serializedName, "Content-Type"); + } else if (param.name === "accept") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "header"); + strictEqual(httpParam.serializedName, "Accept"); + } + } + }); + + it("spread model with @bodyRoot property", async () => { + await runner.compileWithBuiltInService(` + model Shelf { + @query + name: string; + theme?: string; + } + model CreateShelfRequest { + @bodyRoot + body: Shelf; + } + op createShelf(...CreateShelfRequest): Shelf; + `); + const method = getServiceMethodOfClient(runner.context.sdkPackage); + const parameters = method.parameters; + + strictEqual(parameters.length, 3); + + for (const param of parameters) { + if (param.name === "body") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "body"); + strictEqual(httpParam.serializedName, "body"); + } else if (param.name === "contentType") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "header"); + strictEqual(httpParam.serializedName, "Content-Type"); + } else if (param.name === "accept") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "header"); + strictEqual(httpParam.serializedName, "Accept"); + } + } + strictEqual(parameters[0].type.kind, "model"); + for (const property of parameters[0].type.properties) { + if (property.name === "name") { + const httpParam = getHttpOperationParameter(method, property); + ok(httpParam); + strictEqual(httpParam.kind, "query"); + strictEqual(httpParam.serializedName, "name"); + } else if (property.name === "theme") { + const httpParam = getHttpOperationParameter(method, property); + ok(!httpParam); + } + } + }); + + it("implicit spread for body", async () => { + await runner.compileWithBuiltInService(` + op myOp(a: string, b: string): void; + `); + const method = getServiceMethodOfClient(runner.context.sdkPackage); + const parameters = method.parameters; + + strictEqual(parameters.length, 3); + + for (const param of parameters) { + if (param.name === "a") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "property"); + strictEqual(httpParam.name, "a"); + } else if (param.name === "b") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "property"); + strictEqual(httpParam.name, "b"); + } else if (param.name === "contentType") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "header"); + strictEqual(httpParam.serializedName, "Content-Type"); + } + } + }); + + it("implicit spread for header and body", async () => { + await runner.compileWithBuiltInService(` + op myOp(@header a: string, b: string): void; + `); + const method = getServiceMethodOfClient(runner.context.sdkPackage); + const parameters = method.parameters; + + strictEqual(parameters.length, 3); + + for (const param of parameters) { + if (param.name === "a") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "header"); + strictEqual(httpParam.serializedName, "a"); + } else if (param.name === "b") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "property"); + strictEqual(httpParam.name, "b"); + } else if (param.name === "contentType") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "header"); + strictEqual(httpParam.serializedName, "Content-Type"); + } + } + }); + + it("@bodyRoot case", async () => { + await runner.compileWithBuiltInService(` + model TestRequest { + @header + h: string; + @query + q: string; + prop1: string; + prop2: string; + } + op test(@bodyRoot request: TestRequest): void; + `); + const method = getServiceMethodOfClient(runner.context.sdkPackage); + const parameters = method.parameters; + + strictEqual(parameters.length, 2); + + for (const param of parameters) { + if (param.name === "request") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "body"); + strictEqual(httpParam.serializedName, "request"); + } else if (param.name === "contentType") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "header"); + strictEqual(httpParam.serializedName, "Content-Type"); + } + } + strictEqual(parameters[0].type.kind, "model"); + for (const property of parameters[0].type.properties) { + if (property.name === "h") { + const httpParam = getHttpOperationParameter(method, property); + ok(httpParam); + strictEqual(httpParam.kind, "header"); + strictEqual(httpParam.serializedName, "h"); + } else if (property.name === "q") { + const httpParam = getHttpOperationParameter(method, property); + ok(httpParam); + strictEqual(httpParam.kind, "query"); + strictEqual(httpParam.serializedName, "q"); + } else if (property.name === "prop1") { + const httpParam = getHttpOperationParameter(method, property); + ok(!httpParam); + } else if (property.name === "prop2") { + const httpParam = getHttpOperationParameter(method, property); + ok(!httpParam); + } + } + }); + + it("multipart case", async () => { + await runner.compileWithBuiltInService(` + @route("upload/{name}") + @post + op uploadFile( + @path name: string, + @header contentType: "multipart/form-data", + file_data: bytes, + + @visibility("read") readOnly: string, + + constant: "constant", + ): OkResponse; + `); + const method = getServiceMethodOfClient(runner.context.sdkPackage); + const parameters = method.parameters; + + strictEqual(parameters.length, 5); + + for (const param of parameters) { + if (param.name === "name") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "path"); + strictEqual(httpParam.serializedName, "name"); + } else if (param.name === "contentType") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "header"); + strictEqual(httpParam.serializedName, "content-type"); + } else if (param.name === "file_data") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "property"); + strictEqual(httpParam.name, "file_data"); + } else if (param.name === "readOnly") { + const httpParam = getHttpOperationParameter(method, param); + ok(!httpParam); + } else if (param.name === "constant") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "property"); + strictEqual(httpParam.name, "constant"); + } + } + }); + + it("template case", async () => { + await runner.compile(` + @service({ + title: "Pet Store Service", + }) + namespace PetStore; + using TypeSpec.Rest.Resource; + + @error + model PetStoreError { + code: int32; + message: string; + } + + @resource("pets") + model Pet { + @key("petId") + id: int32; + } + + @resource("checkups") + model Checkup { + @key("checkupId") + id: int32; + + vetName: string; + notes: string; + } + + interface PetCheckups + extends ExtensionResourceCreateOrUpdate, + ExtensionResourceList {} + `); + const sdkPackage = runner.context.sdkPackage; + const client = sdkPackage.clients[0].methods.find((x) => x.kind === "clientaccessor") + ?.response as SdkClientType; + const method = client.methods[0] as SdkServiceMethod; + const parameters = method.parameters; + + strictEqual(parameters.length, 5); + + for (const param of parameters) { + if (param.name === "petId") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "path"); + strictEqual(httpParam.serializedName, "petId"); + } else if (param.name === "checkupId") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "path"); + strictEqual(httpParam.serializedName, "checkupId"); + } else if (param.name === "resource") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "body"); + strictEqual(httpParam.serializedName, "resource"); + } else if (param.name === "contentType") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "header"); + strictEqual(httpParam.serializedName, "Content-Type"); + } else if (param.name === "accept") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "header"); + strictEqual(httpParam.serializedName, "Accept"); + } + } + }); + + it("api version parameter", async () => { + await runner.compileWithVersionedService(` + op test(@query apiVersion: string, @body body: string): void; + `); + const sdkPackage = runner.context.sdkPackage; + const client = sdkPackage.clients[0]; + const method = getServiceMethodOfClient(sdkPackage); + const httpParam = getHttpOperationParameter( + method, + client.initialization.properties[1] as SdkMethodParameter, + ); + ok(httpParam); + strictEqual(httpParam.kind, "query"); + strictEqual(httpParam.serializedName, "apiVersion"); + }); + + it("client parameter", async () => { + await runner.compileWithCustomization( + ` + @service + namespace MyService; + + op download(@path blob: string): void; + op upload(@path blobName: string): void; + `, + ` + namespace MyCustomizations; + + model MyClientInitialization { + @paramAlias("blob") + blobName: string; + } + + @@clientInitialization(MyService, MyCustomizations.MyClientInitialization); + `, + ); + const sdkPackage = runner.context.sdkPackage; + const client = sdkPackage.clients[0]; + let httpParam = getHttpOperationParameter( + client.methods[0] as SdkServiceMethod, + client.initialization.properties[0] as SdkMethodParameter, + ); + ok(httpParam); + strictEqual(httpParam.kind, "path"); + strictEqual(httpParam.serializedName, "blob"); + + httpParam = getHttpOperationParameter( + client.methods[1] as SdkServiceMethod, + client.initialization.properties[0] as SdkMethodParameter, + ); + ok(httpParam); + strictEqual(httpParam.kind, "path"); + strictEqual(httpParam.serializedName, "blobName"); + }); + + it("@override impact", async () => { + await runner.compileWithCustomization( + ` + @service + namespace MyService; + model Params { + foo: string; + bar: string; + } + + op func(...Params): void; + `, + ` + namespace MyCustomizations; + + op func(params: MyService.Params): void; + + @@override(MyService.func, MyCustomizations.func); + `, + ); + const sdkPackage = runner.context.sdkPackage; + const client = sdkPackage.clients[0]; + const method = client.methods[0] as SdkServiceMethod; + const parameters = method.parameters; + + strictEqual(parameters.length, 2); + + for (const param of parameters) { + if (param.name === "params") { + const httpParam = getHttpOperationParameter(method, param); + ok(!httpParam); + } else if (param.name === "contentType") { + const httpParam = getHttpOperationParameter(method, param); + ok(httpParam); + strictEqual(httpParam.kind, "header"); + strictEqual(httpParam.serializedName, "Content-Type"); + } + } + + strictEqual(parameters[0].type.kind, "model"); + for (const property of parameters[0].type.properties) { + if (property.name === "foo") { + const httpParam = getHttpOperationParameter(method, property); + ok(httpParam); + strictEqual(httpParam.kind, "property"); + strictEqual(httpParam.name, "foo"); + } else if (property.name === "bar") { + const httpParam = getHttpOperationParameter(method, property); + ok(httpParam); + strictEqual(httpParam.kind, "property"); + strictEqual(httpParam.name, "bar"); + } + } + }); +}); diff --git a/packages/typespec-client-generator-core/test/public-utils/is-paged-result-model.test.ts b/packages/typespec-client-generator-core/test/public-utils/is-paged-result-model.test.ts index 4563cb0505..1171c0eb82 100644 --- a/packages/typespec-client-generator-core/test/public-utils/is-paged-result-model.test.ts +++ b/packages/typespec-client-generator-core/test/public-utils/is-paged-result-model.test.ts @@ -4,7 +4,7 @@ import { beforeEach, describe, it } from "vitest"; import { isPagedResultModel } from "../../src/public-utils.js"; import { createSdkTestRunner, SdkTestRunner } from "../test-host.js"; -describe("typespec-client-generator-core: public-utils", () => { +describe("typespec-client-generator-core: public-utils isPagedResultModel", () => { let runner: SdkTestRunner; beforeEach(async () => { diff --git a/packages/typespec-client-generator-core/test/types/enum-types.test.ts b/packages/typespec-client-generator-core/test/types/enum-types.test.ts index ac887ce533..1b97f75908 100644 --- a/packages/typespec-client-generator-core/test/types/enum-types.test.ts +++ b/packages/typespec-client-generator-core/test/types/enum-types.test.ts @@ -559,11 +559,11 @@ describe("typespec-client-generator-core: enum types", () => { const modelType = getClientType(runner.context, Test) as SdkModelType; const enumType = modelType.properties[0].type as SdkEnumType; strictEqual(enumType.name, "TestColor"); - strictEqual(enumType.crossLanguageDefinitionId, "Test.color.anonymous"); + strictEqual(enumType.crossLanguageDefinitionId, "N.Test.color.anonymous"); strictEqual(enumType.isGeneratedName, true); strictEqual(enumType.isUnionAsEnum, true); // no cross language def id bc it's not a defined object in tsp - strictEqual(enumType.crossLanguageDefinitionId, "Test.color.anonymous"); + strictEqual(enumType.crossLanguageDefinitionId, "N.Test.color.anonymous"); const values = enumType.values; strictEqual(values[0].name, "left"); strictEqual(values[0].value, "left"); @@ -609,7 +609,7 @@ describe("typespec-client-generator-core: enum types", () => { const modelType = getClientType(runner.context, Test) as SdkModelType; const unionType = modelType.properties[0].type as SdkUnionType; strictEqual(unionType.name, "TestColor"); - strictEqual(unionType.crossLanguageDefinitionId, "Test.color.anonymous"); + strictEqual(unionType.crossLanguageDefinitionId, "N.Test.color.anonymous"); strictEqual(unionType.isGeneratedName, true); const variants = unionType.variantTypes; const lr = variants[0] as SdkEnumType; diff --git a/website/src/content/docs/docs/howtos/Generate client libraries/09versioning.mdx b/website/src/content/docs/docs/howtos/Generate client libraries/09versioning.mdx index 54d1b43f6c..7b8ed55345 100644 --- a/website/src/content/docs/docs/howtos/Generate client libraries/09versioning.mdx +++ b/website/src/content/docs/docs/howtos/Generate client libraries/09versioning.mdx @@ -473,3 +473,103 @@ ServiceClient client = new ServiceClient(endpoint, options); ``` + +## Overriding the Client Api Version Parameter + +By default, we find api version parameters in specs based off of names. There is special logic we do with api version parameters: + +1. These api version parameters get elevated up to the client level (if the service is versioned) +2. We auto-add api version information to next links when paging +3. We set the client default for these parameters to be the default api version for your service. + +There are cases where you have an api-versioning parameter without the explicit name `api-version`. In these cases, you can use the `@isApiVersion` decorator to override and explicitly say whether that parameter is an api version param or not. + + + +```typespec +import "@typespec/versioning"; +import "@typespec/http"; +import "@azure-tools/typespec-client-generator-core"; +using TypeSpec.Versioning; +using TypeSpec.Http; +using Azure.ClientGenerator.Core; +@versioned(My.Service.Versions) +@service +namespace My.Service; +enum Versions { + v2023_11_01: "2023-11-01", + v2024_04_01: "2024-04-01", +} +op get( + @isApiVersion + @header("x-ms-version") + version: string, +): void; +``` + +```python +from my.service import MyServiceClient + +client = MyServiceClient(endpoint=..., credential=...) +print(client.version) # == "2024-04-01", since that is the default + +client_with_specified_api_version = MyServiceClient(endpoint=..., credential=..., version="2023-11-01") +print(client.version) # == "2023-11-01", since we specified + +retval = client.get() # version is elevated onto the client +``` + +```csharp +//ServiceVersion enum +public enum ServiceVersion +{ + /// Service version "2023-11-01". + V2023_11_01 = 1, + /// Service version "2024-04-01". + v2024_04_01 = 2, +} + +Uri endpoint = new Uri(""); + +ServiceClient client = new ServiceClient(endpoint); +//client's version will be "2024-04-01" +ServiceClientOptions options = new ServiceClientOptions(ServiceVersion.V2023_11_01); +ServiceClient clientWithSpecifiedApiVersion = new ServiceClient(endpoint, options); +//client's version will be "2023-11-01" +Response response = client.get(); // version parameter is elevated onto the client +``` + +```typescript +// TODO +``` + +```java +// ServiceVersion enum +public enum ServiceServiceVersion implements ServiceVersion { + V2023_11_01("2023-11-01"); + V2024_04_01("2024-04-01"); + + public static ServiceServiceVersion getLatest() {} // V2024_04_01 +} + +// Client API +ServiceClientClient client = new ServiceClientClientBuilder() + // other configurations + .buildClient(); +// client's version will be 2024-04-01 + +ServiceClientClient clientWithSpecifiedApiVersion = new ServiceClientClientBuilder() + // other configurations + // override the api version, even if only one version is defined in the spec + .serviceVersion(ServiceServiceVersion.V2023_11_01) + .buildClient(); +// client's version will be 2023-11-01 + +client.get(); // version parameter is elevated onto the client +``` + +```go +// TODO +``` + + diff --git a/website/src/content/docs/docs/libraries/typespec-client-generator-core/reference/decorators.md b/website/src/content/docs/docs/libraries/typespec-client-generator-core/reference/decorators.md index b35a5cf617..196f10f15c 100644 --- a/website/src/content/docs/docs/libraries/typespec-client-generator-core/reference/decorators.md +++ b/website/src/content/docs/docs/libraries/typespec-client-generator-core/reference/decorators.md @@ -32,10 +32,10 @@ otherwise a warning will be added to diagnostics list. #### Parameters -| Name | Type | Description | -| ----- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| value | `EnumMember` | The access info you want to set for this model or operation. | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| value | `EnumMember` | The access info you want to set for this model or operation. | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | #### Examples @@ -162,10 +162,10 @@ The source type you want to apply the alternate type to. Only scalar types are s #### Parameters -| Name | Type | Description | -| --------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| alternate | `Scalar` | The alternate type you want applied to the target. Only scalar types are supported. | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| --------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| alternate | `Scalar` | The alternate type you want applied to the target. Only scalar types are supported. | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | #### Examples @@ -185,6 +185,39 @@ scalar storageDateTime extends utcDataTime; op test(@param @alternateType(string) date: utcDateTime): void; ``` +### `@apiVersion` {#@Azure.ClientGenerator.Core.apiVersion} + +Use to override default assumptions on whether a parameter is an api-version parameter or not. +By default, we do matches with the `api-version` or `apiversion` string in the parameter name. Since api versions are +a client parameter, we will also elevate this parameter up onto the client. + +```typespec +@Azure.ClientGenerator.Core.apiVersion(value?: valueof boolean, scope?: valueof string) +``` + +#### Target + +`ModelProperty` + +#### Parameters + +| Name | Type | Description | +| ----- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| value | `valueof boolean` | If true, we will treat this parameter as an api-version parameter. If false, we will not. Default is true. | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | + +#### Examples + +```typespec +namespace Contoso; + +op test( + @apiVersion + @header("x-ms-version") + version: string, +): void; +``` + ### `@client` {#@Azure.ClientGenerator.Core.client} Create a ClientGenerator.Core client out of a namespace or interface @@ -199,10 +232,10 @@ Create a ClientGenerator.Core client out of a namespace or interface #### Parameters -| Name | Type | Description | -| ----- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| value | `Model` | Optional configuration for the service. | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| value | `Model` | Optional configuration for the service. | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | #### Examples @@ -251,10 +284,10 @@ Client parameters you would like to add to the client. By default, we apply endp #### Parameters -| Name | Type | Description | -| ------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| options | `Model` | | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| options | `Model` | | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | #### Examples @@ -290,10 +323,10 @@ Changes the name of a method, parameter, property, or model generated in the cli #### Parameters -| Name | Type | Description | -| ------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| rename | `valueof string` | The rename you want applied to the object | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| rename | `valueof string` | The rename you want applied to the object | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | #### Examples @@ -325,10 +358,10 @@ By default, the client namespace for them will follow the TypeSpec namespace. #### Parameters -| Name | Type | Description | -| ------ | ---------------- | ------------------------------------------------------------------------------------------------------------- | -| rename | `valueof string` | The rename you want applied to the object | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters | +| Name | Type | Description | +| ------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| rename | `valueof string` | The rename you want applied to the object | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | #### Examples @@ -338,10 +371,10 @@ namespace Contoso; ``` ```typespec -@clientName("ContosoJava", "java") -@clientName("ContosoPython", "python") -@clientName("ContosoCSharp", "csharp") -@clientName("ContosoJavascript", "javascript") +@clientNamespace("ContosoJava", "java") +@clientNamespace("ContosoPython", "python") +@clientNamespace("ContosoCSharp", "csharp") +@clientNamespace("ContosoJavascript", "javascript") namespace Contoso; ``` @@ -359,10 +392,10 @@ Whether you want to generate an operation as a convenient operation. #### Parameters -| Name | Type | Description | -| ----- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| value | `valueof boolean` | Whether to generate the operation as convenience method or not. | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| value | `valueof boolean` | Whether to generate the operation as convenience method or not. | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | #### Examples @@ -389,9 +422,9 @@ Set whether a model property should be flattened or not. #### Parameters -| Name | Type | Description | -| ----- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | #### Examples @@ -417,9 +450,9 @@ Create a ClientGenerator.Core operation group out of a namespace or interface #### Parameters -| Name | Type | Description | -| ----- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | #### Examples @@ -443,10 +476,10 @@ Override the default client method generated by TCGC from your service definitio #### Parameters -| Name | Type | Description | -| -------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| override | `Operation` | : The override method definition that specifies the exact client method you want | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| -------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| override | `Operation` | : The override method definition that specifies the exact client method you want | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | #### Examples @@ -502,10 +535,10 @@ Alias the name of a client parameter to a different name. This permits you to ha #### Parameters -| Name | Type | Description | -| ---------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| paramAlias | `valueof string` | | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ---------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| paramAlias | `valueof string` | | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | #### Examples @@ -542,10 +575,10 @@ Whether you want to generate an operation as a protocol operation. #### Parameters -| Name | Type | Description | -| ----- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| value | `valueof boolean` | Whether to generate the operation as protocol or not. | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| value | `valueof boolean` | Whether to generate the operation as protocol or not. | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | #### Examples @@ -568,9 +601,9 @@ To define the client scope of an operation. #### Parameters -| Name | Type | Description | -| ----- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | #### Examples @@ -607,10 +640,10 @@ otherwise a warning will be added to diagnostics list. #### Parameters -| Name | Type | Description | -| ----- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| value | `EnumMember \| Union` | The usage info you want to set for this model. | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| value | `EnumMember \| Union` | The usage info you want to set for this model. | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | #### Examples @@ -678,9 +711,9 @@ Whether a model needs the custom JSON converter, this is only used for backward #### Parameters -| Name | Type | Description | -| ----- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | +| Name | Type | Description | +| ----- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
You can use "!" to specify negation such as "!(java, python)" or "!java, !python". | #### Examples diff --git a/website/src/content/docs/docs/libraries/typespec-client-generator-core/reference/index.mdx b/website/src/content/docs/docs/libraries/typespec-client-generator-core/reference/index.mdx index 513b74dca6..09a1f8c54a 100644 --- a/website/src/content/docs/docs/libraries/typespec-client-generator-core/reference/index.mdx +++ b/website/src/content/docs/docs/libraries/typespec-client-generator-core/reference/index.mdx @@ -42,6 +42,7 @@ npm install --save-peer @azure-tools/typespec-client-generator-core - [`@access`](./decorators.md#@Azure.ClientGenerator.Core.access) - [`@alternateType`](./decorators.md#@Azure.ClientGenerator.Core.alternateType) +- [`@apiVersion`](./decorators.md#@Azure.ClientGenerator.Core.apiVersion) - [`@client`](./decorators.md#@Azure.ClientGenerator.Core.client) - [`@clientInitialization`](./decorators.md#@Azure.ClientGenerator.Core.clientInitialization) - [`@clientName`](./decorators.md#@Azure.ClientGenerator.Core.clientName)