diff --git a/.chronus/changes/namespace_support-2024-10-1-18-10-34.md b/.chronus/changes/namespace_support-2024-10-1-18-10-34.md new file mode 100644 index 0000000000..871614537c --- /dev/null +++ b/.chronus/changes/namespace_support-2024-10-1-18-10-34.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +support client namespace \ No newline at end of file diff --git a/packages/typespec-client-generator-core/README.md b/packages/typespec-client-generator-core/README.md index d3d6ea98ea..b4a717b32e 100644 --- a/packages/typespec-client-generator-core/README.md +++ b/packages/typespec-client-generator-core/README.md @@ -16,6 +16,7 @@ npm install @azure-tools/typespec-client-generator-core - [`@client`](#@client) - [`@clientInitialization`](#@clientinitialization) - [`@clientName`](#@clientname) +- [`@clientNamespace`](#@clientnamespace) - [`@convenientAPI`](#@convenientapi) - [`@flattenProperty`](#@flattenproperty) - [`@operationGroup`](#@operationgroup) @@ -291,6 +292,41 @@ op nameInService: void; op nameInService: void; ``` +#### `@clientNamespace` + +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. + +```typespec +@Azure.ClientGenerator.Core.clientNamespace(rename: valueof string, scope?: valueof string) +``` + +##### Target + +`Namespace | Interface | Model | Enum | Union` + +##### 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 | + +##### Examples + +```typespec +@clientNamespace("ContosoClient") +namespace Contoso; +``` + +```typespec +@clientName("ContosoJava", "java") +@clientName("ContosoPython", "python") +@clientName("ContosoCSharp", "csharp") +@clientName("ContosoJavascript", "javascript") +namespace Contoso; +``` + #### `@convenientAPI` Whether you want to generate an operation as a convenient operation. 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 134ccd894b..7e3793d0e8 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 @@ -494,6 +494,33 @@ export type ParamAliasDecorator = ( scope?: string, ) => void; +/** + * 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 + * @example + * ```typespec + * @clientNamespace("ContosoClient") + * namespace Contoso; + * ``` + * @example + * ```typespec + * @clientName("ContosoJava", "java") + * @clientName("ContosoPython", "python") + * @clientName("ContosoCSharp", "csharp") + * @clientName("ContosoJavascript", "javascript") + * namespace Contoso; + * ``` + */ +export type ClientNamespaceDecorator = ( + context: DecoratorContext, + target: Namespace | Interface | Model | Enum | Union, + rename: string, + scope?: string, +) => void; + export type AzureClientGeneratorCoreDecorators = { clientName: ClientNameDecorator; convenientAPI: ConvenientAPIDecorator; @@ -507,4 +534,5 @@ export type AzureClientGeneratorCoreDecorators = { useSystemTextJsonConverter: UseSystemTextJsonConverterDecorator; clientInitialization: ClientInitializationDecorator; paramAlias: ParamAliasDecorator; + clientNamespace: ClientNamespaceDecorator; }; diff --git a/packages/typespec-client-generator-core/lib/decorators.tsp b/packages/typespec-client-generator-core/lib/decorators.tsp index 45435b5469..53670bd750 100644 --- a/packages/typespec-client-generator-core/lib/decorators.tsp +++ b/packages/typespec-client-generator-core/lib/decorators.tsp @@ -474,3 +474,30 @@ extern dec clientInitialization( * ``` */ extern dec paramAlias(original: ModelProperty, paramAlias: valueof string, scope?: valueof string); + +/** + * 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 + * + * @example + * ```typespec + * @clientNamespace("ContosoClient") + * namespace Contoso; + * ``` + * + * @example + * ```typespec + * @clientName("ContosoJava", "java") + * @clientName("ContosoPython", "python") + * @clientName("ContosoCSharp", "csharp") + * @clientName("ContosoJavascript", "javascript") + * namespace Contoso; + * ``` + */ +extern dec clientNamespace( + target: Namespace | Interface | Model | Enum | Union, + rename: valueof string, + scope?: valueof string +); diff --git a/packages/typespec-client-generator-core/package.json b/packages/typespec-client-generator-core/package.json index b475c054a7..690f2b1a79 100644 --- a/packages/typespec-client-generator-core/package.json +++ b/packages/typespec-client-generator-core/package.json @@ -23,7 +23,8 @@ "exports": { ".": { "types": "./dist/src/index.d.ts", - "default": "./dist/src/index.js" + "default": "./dist/src/index.js", + "typespec": "./lib/main.tsp" }, "./testing": { "types": "./dist/src/testing/index.d.ts", diff --git a/packages/typespec-client-generator-core/src/decorators.ts b/packages/typespec-client-generator-core/src/decorators.ts index 1052eaf23b..fbee98ce08 100644 --- a/packages/typespec-client-generator-core/src/decorators.ts +++ b/packages/typespec-client-generator-core/src/decorators.ts @@ -34,6 +34,7 @@ import { ClientDecorator, ClientInitializationDecorator, ClientNameDecorator, + ClientNamespaceDecorator, ConvenientAPIDecorator, FlattenPropertyDecorator, OperationGroupDecorator, @@ -61,6 +62,7 @@ import { import { AllScopes, clientNameKey, + clientNamespaceKey, getValidApiVersion, isAzureCoreTspModel, parseEmitterName, @@ -1041,3 +1043,49 @@ export const paramAliasDecorator: ParamAliasDecorator = ( export function getParamAlias(context: TCGCContext, original: ModelProperty): string | undefined { return getScopedDecoratorData(context, paramAliasKey, original); } + +export const $clientNamespace: ClientNamespaceDecorator = ( + context: DecoratorContext, + entity: Namespace | Interface | Model | Enum | Union, + value: string, + scope?: LanguageScopes, +) => { + if (value.trim() === "") { + reportDiagnostic(context.program, { + code: "empty-client-namespace", + format: {}, + target: entity, + }); + } + setScopedDecoratorData(context, $clientNamespace, clientNamespaceKey, entity, value, scope); +}; + +export function getClientNamespace( + context: TCGCContext, + entity: Namespace | Interface | Model | Enum | Union, +): string { + const override = getScopedDecoratorData(context, clientNamespaceKey, entity); + if (override) return override; + if (!entity.namespace) { + return ""; + } + if (entity.kind === "Namespace") { + return getNamespaceFullNameWithOverride(context, entity); + } + return getNamespaceFullNameWithOverride(context, entity.namespace); +} + +function getNamespaceFullNameWithOverride(context: TCGCContext, namespace: Namespace): string { + const segments = []; + let current: Namespace | undefined = namespace; + while (current && current.name !== "") { + const override = getScopedDecoratorData(context, clientNamespaceKey, current); + if (override) { + segments.unshift(override); + break; + } + segments.unshift(current.name); + current = current.namespace; + } + return segments.join("."); +} diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index 727de16746..a146aa7786 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -89,11 +89,15 @@ export interface SdkClientType __raw: SdkClient | SdkOperationGroup; kind: "client"; name: string; + clientNamespace: string; // fully qualified namespace doc?: string; summary?: string; initialization: SdkInitializationType; methods: SdkMethod[]; apiVersions: string[]; + /** + * @deprecated Use `clientNamespace` instead. + */ nameSpace: string; // fully qualified crossLanguageDefinitionId: string; parent?: SdkClientType; @@ -306,12 +310,14 @@ export interface SdkNullableType extends SdkTypeBase { type: SdkType; usage: UsageFlags; access: AccessFlags; + clientNamespace: string; // fully qualified namespace } export interface SdkEnumType extends SdkTypeBase { kind: "enum"; name: string; isGeneratedName: boolean; + clientNamespace: string; // fully qualified namespace valueType: SdkBuiltInType; values: SdkEnumValueType[]; isFixed: boolean; @@ -342,6 +348,7 @@ export interface SdkConstantType extends SdkTypeBase { export interface SdkUnionType extends SdkTypeBase { name: string; isGeneratedName: boolean; + clientNamespace: string; // fully qualified namespace kind: "union"; variantTypes: TValueType[]; crossLanguageDefinitionId: string; @@ -356,6 +363,7 @@ export interface SdkModelType extends SdkTypeBase { properties: SdkModelPropertyType[]; name: string; isGeneratedName: boolean; + clientNamespace: string; // fully qualified namespace access: AccessFlags; usage: UsageFlags; additionalProperties?: SdkType; @@ -690,6 +698,17 @@ export interface SdkPackage { enums: SdkEnumType[]; unions: (SdkUnionType | SdkNullableType)[]; crossLanguagePackageId: string; + namespaces: SdkNamespace[]; +} + +export interface SdkNamespace { + name: string; + fullName: string; + clients: SdkClientType[]; + models: SdkModelType[]; + enums: SdkEnumType[]; + unions: (SdkUnionType | SdkNullableType)[]; + namespaces: SdkNamespace[]; } export type SdkHttpPackage = SdkPackage; diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index d632dab0e3..e14ac4d92e 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -50,6 +50,7 @@ import { getClientTypeWithDiagnostics } from "./types.js"; export const AllScopes = Symbol.for("@azure-core/typespec-client-generator-core/all-scopes"); export const clientNameKey = createStateSymbol("clientName"); +export const clientNamespaceKey = createStateSymbol("clientNamespace"); /** * diff --git a/packages/typespec-client-generator-core/src/lib.ts b/packages/typespec-client-generator-core/src/lib.ts index 828dcfa04d..80c238c47d 100644 --- a/packages/typespec-client-generator-core/src/lib.ts +++ b/packages/typespec-client-generator-core/src/lib.ts @@ -216,6 +216,12 @@ export const $lib = createTypeSpecLibrary({ default: paramMessage`Decorator ${"decoratorName"} cannot be used twice on the same declaration with same scope.`, }, }, + "empty-client-namespace": { + severity: "warning", + messages: { + default: `Cannot pass an empty value to the @clientNamespace decorator`, + }, + }, }, }); diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index 47278bcc02..6a19169854 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -18,6 +18,7 @@ import { getAccess, getClientInitialization, getClientNameOverride, + getClientNamespace, getOverriddenClientMethod, listClients, listOperationGroups, @@ -42,6 +43,7 @@ import { SdkMethodResponse, SdkModelPropertyType, SdkModelType, + SdkNamespace, SdkNullableType, SdkOperationGroup, SdkPackage, @@ -114,11 +116,12 @@ function getSdkServiceOperation( function getSdkLroPagingServiceMethod( context: TCGCContext, operation: Operation, + client: SdkClientType, ): [SdkLroPagingServiceMethod, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); return diagnostics.wrap({ - ...diagnostics.pipe(getSdkLroServiceMethod(context, operation)), - ...diagnostics.pipe(getSdkPagingServiceMethod(context, operation)), + ...diagnostics.pipe(getSdkLroServiceMethod(context, operation, client)), + ...diagnostics.pipe(getSdkPagingServiceMethod(context, operation, client)), kind: "lropaging", }); } @@ -126,10 +129,13 @@ function getSdkLroPagingServiceMethod( context: TCGCContext, operation: Operation, + client: SdkClientType, ): [SdkPagingServiceMethod, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); const pagedMetadata = getPagedResult(context.program, operation)!; - const basic = diagnostics.pipe(getSdkBasicServiceMethod(context, operation)); + const basic = diagnostics.pipe( + getSdkBasicServiceMethod(context, operation, client), + ); if (pagedMetadata.itemsProperty) { basic.response.type = diagnostics.pipe( getClientTypeWithDiagnostics(context, pagedMetadata.itemsProperty.type), @@ -181,11 +187,12 @@ function getPathFromSegment(context: TCGCContext, type: Model, segments?: string function getSdkLroServiceMethod( context: TCGCContext, operation: Operation, + client: SdkClientType, ): [SdkLroServiceMethod, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); const metadata = getServiceMethodLroMetadata(context, operation)!; const basicServiceMethod = diagnostics.pipe( - getSdkBasicServiceMethod(context, operation), + getSdkBasicServiceMethod(context, operation, client), ); basicServiceMethod.response.type = metadata.finalResponse?.result; @@ -247,6 +254,7 @@ function getSdkMethodResponse( context: TCGCContext, operation: Operation, sdkOperation: SdkServiceOperation, + client: SdkClientType, ): SdkMethodResponse { const responses = sdkOperation.responses; // TODO: put head as bool here @@ -263,6 +271,7 @@ function getSdkMethodResponse( variantTypes: allResponseBodies, name: createGeneratedName(context, operation, "UnionResponse"), isGeneratedName: true, + clientNamespace: client.clientNamespace, crossLanguageDefinitionId: getCrossLanguageDefinitionId(context, operation), decorators: [], }; @@ -276,6 +285,7 @@ function getSdkMethodResponse( decorators: [], access: "public", usage: UsageFlags.Output, + clientNamespace: client.clientNamespace, }; } return { @@ -287,6 +297,7 @@ function getSdkMethodResponse( function getSdkBasicServiceMethod( context: TCGCContext, operation: Operation, + client: SdkClientType, ): [SdkServiceMethod, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); const methodParameters: SdkMethodParameter[] = []; @@ -345,7 +356,7 @@ function getSdkBasicServiceMethod addEncodeInfo(context, methodBodyParam.__raw!, methodBodyParam.type, defaultContentType), ); } - const response = getSdkMethodResponse(context, operation, serviceOperation); + const response = getSdkMethodResponse(context, operation, serviceOperation, client); const name = getLibraryName(context, operation); return diagnostics.wrap({ __raw: operation, @@ -378,17 +389,18 @@ function getSdkBasicServiceMethod function getSdkServiceMethod( context: TCGCContext, operation: Operation, + client: SdkClientType, ): [SdkServiceMethod, readonly Diagnostic[]] { const lro = getLroMetadata(context.program, operation); const paging = getPagedResult(context.program, operation); if (lro && paging) { - return getSdkLroPagingServiceMethod(context, operation); + return getSdkLroPagingServiceMethod(context, operation, client); } else if (paging) { - return getSdkPagingServiceMethod(context, operation); + return getSdkPagingServiceMethod(context, operation, client); } else if (lro) { - return getSdkLroServiceMethod(context, operation); + return getSdkLroServiceMethod(context, operation, client); } - return getSdkBasicServiceMethod(context, operation); + return getSdkBasicServiceMethod(context, operation, client); } function getClientDefaultApiVersion( @@ -436,6 +448,7 @@ function getSdkInitializationType( access, usage: UsageFlags.Input, crossLanguageDefinitionId: `${getNamespaceFullName(client.service.namespace!)}.${name}`, + clientNamespace: getClientNamespace(context, client.type), apiVersions: context.__tspTypeToApiVersions.get(client.type)!, decorators: [], }; @@ -487,7 +500,9 @@ function getSdkMethods( const diagnostics = createDiagnosticCollector(); const retval: SdkMethod[] = []; for (const operation of listOperationsInOperationGroup(context, client)) { - retval.push(diagnostics.pipe(getSdkServiceMethod(context, operation))); + retval.push( + diagnostics.pipe(getSdkServiceMethod(context, operation, sdkClientType)), + ); } for (const operationGroup of listOperationGroups(context, client)) { // We create a client accessor for each operation group @@ -632,6 +647,7 @@ function getSdkEndpointParameter; } else { @@ -675,6 +691,7 @@ function createSdkClientType( methods: [], apiVersions: context.__tspTypeToApiVersions.get(client.type)!, nameSpace: getClientNamespaceStringHelper(context, client.service)!, + clientNamespace: getClientNamespace(context, client.type), initialization: diagnostics.pipe(getSdkInitializationType(context, client)), decorators: diagnostics.pipe(getTypeDecorators(context, client.type)), parent, @@ -771,7 +788,7 @@ export function getSdkPackage( diagnostics.pipe(handleAllTypes(context)); const crossLanguagePackageId = diagnostics.pipe(getCrossLanguagePackageId(context)); const allReferencedTypes = getAllReferencedTypes(context); - return diagnostics.wrap({ + const sdkPackage: SdkPackage = { name: getClientNamespaceString(context)!, rootNamespace: getClientNamespaceString(context)!, clients: listClients(context).map((c) => diagnostics.pipe(createSdkClientType(context, c))), @@ -781,5 +798,62 @@ export function getSdkPackage( (x): x is SdkUnionType | SdkNullableType => x.kind === "union" || x.kind === "nullable", ), crossLanguagePackageId, - }); + namespaces: [], + }; + organizeNamespaces(sdkPackage); + return diagnostics.wrap(sdkPackage); +} + +function organizeNamespaces( + sdkPackage: SdkPackage, +) { + const clients = [...sdkPackage.clients]; + while (clients.length > 0) { + const client = clients.shift()!; + getSdkNamespace(sdkPackage, client.clientNamespace).clients.push(client); + client.methods + .filter((m) => m.kind === "clientaccessor") + .map((m) => m.response) + .map((c) => clients.push(c)); + } + for (const model of sdkPackage.models) { + getSdkNamespace(sdkPackage, model.clientNamespace).models.push(model); + } + for (const enumType of sdkPackage.enums) { + getSdkNamespace(sdkPackage, enumType.clientNamespace).enums.push(enumType); + } + for (const unionType of sdkPackage.unions) { + getSdkNamespace(sdkPackage, unionType.clientNamespace).unions.push(unionType); + } +} + +function getSdkNamespace( + sdkPackage: SdkPackage, + namespace: string, +) { + const segments = namespace.split("."); + let current: SdkPackage | SdkNamespace = sdkPackage; + let fullName = ""; + for (const segment of segments) { + fullName = fullName === "" ? segment : `${fullName}.${segment}`; + const ns: SdkNamespace | undefined = current.namespaces.find( + (ns) => ns.name === segment, + ); + if (ns === undefined) { + const newNs = { + name: segment, + fullName, + clients: [], + models: [], + enums: [], + unions: [], + namespaces: [], + }; + current.namespaces.push(newNs); + current = newNs; + } else { + current = ns; + } + } + return current; } diff --git a/packages/typespec-client-generator-core/src/tsp-index.ts b/packages/typespec-client-generator-core/src/tsp-index.ts index 97f59dc192..4a96dfae98 100644 --- a/packages/typespec-client-generator-core/src/tsp-index.ts +++ b/packages/typespec-client-generator-core/src/tsp-index.ts @@ -4,6 +4,7 @@ import { $client, $clientInitialization, $clientName, + $clientNamespace, $convenientAPI, $flattenProperty, $operationGroup, @@ -32,5 +33,6 @@ export const $decorators = { useSystemTextJsonConverter: $useSystemTextJsonConverter, clientInitialization: $clientInitialization, paramAlias: paramAliasDecorator, + clientNamespace: $clientNamespace, } as AzureClientGeneratorCoreDecorators, }; diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index 72a7796aae..fa022b2b43 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -43,6 +43,7 @@ import { } from "@typespec/http"; import { getAccessOverride, + getClientNamespace, getOverriddenClientMethod, getUsageOverride, listClients, @@ -528,6 +529,7 @@ export function getSdkUnionWithDiagnostics( ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, "union")), name: getLibraryName(context, type) || getGeneratedName(context, type, operation), isGeneratedName: !type.name, + clientNamespace: getClientNamespace(context, type), variantTypes: nonNullOptions.map((x) => diagnostics.pipe(getClientTypeWithDiagnostics(context, x, operation)), ), @@ -543,6 +545,7 @@ export function getSdkUnionWithDiagnostics( type: retval, access: "public", usage: UsageFlags.None, + clientNamespace: getClientNamespace(context, type), }; } @@ -723,6 +726,7 @@ export function getSdkModelWithDiagnostics( ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, "model")), name: name, isGeneratedName: !type.name, + clientNamespace: getClientNamespace(context, type), doc: getDoc(context.program, type), summary: getSummary(context.program, type), properties: [], @@ -852,6 +856,7 @@ function getSdkEnumWithDiagnostics( ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, "enum")), name: getLibraryName(context, type), isGeneratedName: false, + clientNamespace: getClientNamespace(context, type), doc: getDoc(context.program, type), summary: getSummary(context.program, type), valueType: diagnostics.pipe( @@ -919,6 +924,7 @@ export function getSdkUnionEnumWithDiagnostics( ...diagnostics.pipe(getSdkTypeBaseHelper(context, type.union, "enum")), name, isGeneratedName: !type.union.name, + clientNamespace: getClientNamespace(context, type.union), doc: getDoc(context.program, union), summary: getSummary(context.program, union), valueType: @@ -943,54 +949,6 @@ export function getSdkUnionEnumWithDiagnostics( return diagnostics.wrap(sdkType); } -function getKnownValuesEnum( - context: TCGCContext, - type: Scalar | ModelProperty, - operation?: Operation, -): [SdkEnumType | undefined, readonly Diagnostic[]] { - const diagnostics = createDiagnosticCollector(); - const knownValues = getKnownValues(context.program, type); - if (!knownValues) { - return diagnostics.wrap(undefined); - } - if (type.kind === "ModelProperty") { - const sdkType = diagnostics.pipe(getSdkEnumWithDiagnostics(context, knownValues, operation)); - return diagnostics.wrap(sdkType); - } else { - let sdkType = context.referencedTypeMap?.get(type) as SdkEnumType | undefined; - if (!sdkType) { - sdkType = { - ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, "enum")), - name: getLibraryName(context, type), - isGeneratedName: false, - doc: getDoc(context.program, type), - summary: getSummary(context.program, type), - valueType: diagnostics.pipe( - getSdkEnumValueType( - context, - [...knownValues.members.values()].map((v) => v.value), - ), - ), - values: [], - isFixed: false, - isFlags: false, - usage: UsageFlags.None, // We will add usage as we loop through the operations - access: "public", // Dummy value until we update models map - crossLanguageDefinitionId: getCrossLanguageDefinitionId(context, type, operation), - apiVersions: getAvailableApiVersions(context, type, type.namespace), - isUnionAsEnum: false, - }; - for (const member of knownValues.members.values()) { - sdkType.values.push( - diagnostics.pipe(getSdkEnumValueWithDiagnostics(context, sdkType, member)), - ); - } - } - updateReferencedTypeMap(context, type, sdkType); - return diagnostics.wrap(sdkType); - } -} - export function getClientTypeWithDiagnostics( context: TCGCContext, type: Type, @@ -1027,10 +985,6 @@ export function getClientTypeWithDiagnostics( retval = getSdkTypeForIntrinsic(context, type); break; case "Scalar": - retval = diagnostics.pipe(getKnownValuesEnum(context, type, operation)); - if (retval) { - break; - } retval = diagnostics.pipe(getSdkDateTimeOrDurationOrBuiltInType(context, type)); break; case "Enum": @@ -1040,11 +994,8 @@ export function getClientTypeWithDiagnostics( retval = diagnostics.pipe(getSdkUnionWithDiagnostics(context, type, operation)); break; case "ModelProperty": - const innerType = diagnostics.pipe( - getClientTypeWithDiagnostics(context, type.type, operation), - ); - diagnostics.pipe(addEncodeInfo(context, type, innerType)); - retval = diagnostics.pipe(getKnownValuesEnum(context, type, operation)) ?? innerType; + retval = diagnostics.pipe(getClientTypeWithDiagnostics(context, type.type, operation)); + diagnostics.pipe(addEncodeInfo(context, type, retval)); break; case "UnionVariant": const unionType = diagnostics.pipe( @@ -1131,6 +1082,7 @@ function getSdkCredentialType( variantTypes: credentialTypes, name: createGeneratedName(context, client.service, "CredentialUnion"), isGeneratedName: true, + clientNamespace: getClientNamespace(context, client.service), crossLanguageDefinitionId: getCrossLanguageDefinitionId(context, client.service), decorators: [], access: "public", diff --git a/packages/typespec-client-generator-core/test/decorators/client-namespace.test.ts b/packages/typespec-client-generator-core/test/decorators/client-namespace.test.ts new file mode 100644 index 0000000000..2a3ccf3038 --- /dev/null +++ b/packages/typespec-client-generator-core/test/decorators/client-namespace.test.ts @@ -0,0 +1,260 @@ +import { strictEqual } from "assert"; +import { beforeEach, describe, it } from "vitest"; +import { SdkClientType, SdkServiceOperation } from "../../src/interfaces.js"; +import { SdkTestRunner, createSdkTestRunner } from "../test-host.js"; + +describe("typespec-client-generator-core: @clientNamespace", () => { + let runner: SdkTestRunner; + + beforeEach(async () => { + runner = await createSdkTestRunner({ emitterName: "@azure-tools/typespec-python" }); + }); + + describe("normal namespace", () => { + it("namespace on model", async () => { + await runner.compileWithBuiltInService( + ` + model Test { + prop: string; + } + + op test(): Test; + `, + ); + strictEqual(runner.context.sdkPackage.models[0].clientNamespace, "TestService"); + }); + + it("namespace on enum", async () => { + await runner.compileWithBuiltInService( + ` + enum Test { + A + } + + op test(): Test; + `, + ); + strictEqual(runner.context.sdkPackage.enums[0].clientNamespace, "TestService"); + }); + + it("namespace on union", async () => { + await runner.compileWithBuiltInService( + ` + union Test { + string, int32 + } + + op test(param: Test): void; + `, + ); + strictEqual(runner.context.sdkPackage.unions[0].clientNamespace, "TestService"); + }); + + it("namespace on union as enum", async () => { + await runner.compileWithBuiltInService( + ` + union Test { + "A", "B" + } + + op test(param: Test): void; + `, + ); + strictEqual(runner.context.sdkPackage.enums[0].clientNamespace, "TestService"); + }); + + it("namespace on union with null", async () => { + await runner.compileWithBuiltInService( + ` + union Test { + string, null + } + + op test(param: Test): void; + `, + ); + strictEqual(runner.context.sdkPackage.unions[0].clientNamespace, "TestService"); + }); + + it("namespace on namespace", async () => { + await runner.compileWithBuiltInService( + ` + namespace Inner { + } + `, + ); + strictEqual( + ( + runner.context.sdkPackage.clients[0].methods[0] + .response as SdkClientType + ).clientNamespace, + "TestService.Inner", + ); + }); + + it("namespace on interface", async () => { + await runner.compileWithBuiltInService( + ` + interface Inner { + } + `, + ); + strictEqual( + ( + runner.context.sdkPackage.clients[0].methods[0] + .response as SdkClientType + ).clientNamespace, + "TestService", + ); + }); + }); + + describe("namespace override", () => { + it("namespace override on model", async () => { + await runner.compileWithBuiltInService( + ` + @clientNamespace("MyNamespace") + model Test { + prop: string; + } + + op test(): Test; + `, + ); + strictEqual(runner.context.sdkPackage.models[0].clientNamespace, "MyNamespace"); + }); + + it("namespace override on enum", async () => { + await runner.compileWithBuiltInService( + ` + @clientNamespace("MyNamespace") + enum Test { + A + } + + op test(): Test; + `, + ); + strictEqual(runner.context.sdkPackage.enums[0].clientNamespace, "MyNamespace"); + }); + + it("namespace override on union", async () => { + await runner.compileWithBuiltInService( + ` + @clientNamespace("MyNamespace") + union Test { + string, int32 + } + + op test(param: Test): void; + `, + ); + strictEqual(runner.context.sdkPackage.unions[0].clientNamespace, "MyNamespace"); + }); + + it("namespace override on union as enum", async () => { + await runner.compileWithBuiltInService( + ` + @clientNamespace("MyNamespace") + union Test { + "A", "B" + } + + op test(param: Test): void; + `, + ); + strictEqual(runner.context.sdkPackage.enums[0].clientNamespace, "MyNamespace"); + }); + + it("namespace override on union with null", async () => { + await runner.compileWithBuiltInService( + ` + @clientNamespace("MyNamespace") + union Test { + string, null + } + + op test(param: Test): void; + `, + ); + strictEqual(runner.context.sdkPackage.unions[0].clientNamespace, "MyNamespace"); + }); + + it("namespace override on namespace", async () => { + await runner.compileWithBuiltInService( + ` + namespace Inner { + } + + @@clientNamespace(Inner, "MyNamespace"); + `, + ); + strictEqual( + ( + runner.context.sdkPackage.clients[0].methods[0] + .response as SdkClientType + ).clientNamespace, + "MyNamespace", + ); + }); + + it("namespace override on interface", async () => { + await runner.compileWithBuiltInService( + ` + interface Inner { + } + + @@clientNamespace(Inner, "MyNamespace"); + `, + ); + strictEqual( + ( + runner.context.sdkPackage.clients[0].methods[0] + .response as SdkClientType + ).clientNamespace, + "MyNamespace", + ); + }); + + it("namespace override propagation", async () => { + await runner.compileWithBuiltInService( + ` + namespace Inner { + model Baz { + prop: string; + } + + namespace Test { + model Foo { + prop: string; + } + + op bar(@body body: Baz): Foo; + } + } + + @@clientNamespace(Inner, "MyNamespace"); + `, + ); + strictEqual(runner.context.sdkPackage.clients[0].clientNamespace, "TestService"); // root namespace + strictEqual( + ( + runner.context.sdkPackage.clients[0].methods[0] + .response as SdkClientType + ).clientNamespace, + "MyNamespace", + ); // Inner namespace with override + strictEqual( + ( + ( + runner.context.sdkPackage.clients[0].methods[0] + .response as SdkClientType + ).methods[0].response as SdkClientType + ).clientNamespace, + "MyNamespace.Test", + ); // Test namespace affected by Inner namespace override + strictEqual(runner.context.sdkPackage.models[0].clientNamespace, "MyNamespace"); + strictEqual(runner.context.sdkPackage.models[1].clientNamespace, "MyNamespace.Test"); + }); + }); +}); diff --git a/packages/typespec-client-generator-core/test/packages/namespaces.test.ts b/packages/typespec-client-generator-core/test/packages/namespaces.test.ts new file mode 100644 index 0000000000..4ce34082a6 --- /dev/null +++ b/packages/typespec-client-generator-core/test/packages/namespaces.test.ts @@ -0,0 +1,241 @@ +import { ok, strictEqual } from "assert"; +import { beforeEach, describe, it } from "vitest"; +import { SdkTestRunner, createSdkTestRunner } from "../test-host.js"; + +describe("typespec-client-generator-core: namespaces", () => { + let runner: SdkTestRunner; + + beforeEach(async () => { + runner = await createSdkTestRunner({ emitterName: "@azure-tools/typespec-python" }); + }); + + it("two sub-clients", async () => { + await runner.compile(` + @server("http://localhost:3000", "endpoint") + @service({}) + namespace Foo { + @route("/bar") + namespace Bar { + model BarResponse { + prop: string; + } + op get(): BarResponse; + } + + @route("/baz") + namespace Baz { + model BazResponse { + prop: string; + } + op get(): BazResponse; + } + } + `); + + const sdkPackage = runner.context.sdkPackage; + const fooNamespace = sdkPackage.namespaces.find((x) => x.name === "Foo"); + ok(fooNamespace); + strictEqual(fooNamespace.fullName, "Foo"); + strictEqual(fooNamespace.clients.length, 1); + strictEqual(fooNamespace.models.length, 0); + strictEqual(fooNamespace.enums.length, 0); + strictEqual(fooNamespace.unions.length, 0); + strictEqual(fooNamespace.namespaces.length, 2); + + const barNamespace = fooNamespace.namespaces.find((x) => x.name === "Bar"); + ok(barNamespace); + strictEqual(barNamespace.fullName, "Foo.Bar"); + strictEqual(barNamespace.clients.length, 1); + strictEqual(barNamespace.models.length, 1); + strictEqual(barNamespace.enums.length, 0); + strictEqual(barNamespace.unions.length, 0); + strictEqual(barNamespace.namespaces.length, 0); + + const bazNamespace = fooNamespace.namespaces.find((x) => x.name === "Baz"); + ok(bazNamespace); + strictEqual(bazNamespace.fullName, "Foo.Baz"); + strictEqual(bazNamespace.clients.length, 1); + strictEqual(bazNamespace.models.length, 1); + strictEqual(bazNamespace.enums.length, 0); + strictEqual(bazNamespace.unions.length, 0); + strictEqual(bazNamespace.namespaces.length, 0); + }); + + it("separate defined clients and operation groups", async () => { + await runner.compile(` + @server("http://localhost:3000", "endpoint") + @service({}) + namespace Service { + model BarResponse { + prop: string; + } + + @route("/bar") + op getBar(): BarResponse; + + model BazResponse { + prop: string; + } + + @route("/baz") + op getBaz(): BazResponse; + } + + @client({ + name: "FooClient", + service: Service, + }) + namespace Foo { + @operationGroup + namespace Bar { + op get is Service.getBar; + } + + @operationGroup + namespace Baz { + op get is Service.getBaz; + } + } + `); + + const sdkPackage = runner.context.sdkPackage; + + const serviceNamespace = sdkPackage.namespaces.find((x) => x.name === "Service"); + ok(serviceNamespace); + strictEqual(serviceNamespace.fullName, "Service"); + strictEqual(serviceNamespace.clients.length, 0); + strictEqual(serviceNamespace.models.length, 2); + strictEqual(serviceNamespace.enums.length, 0); + strictEqual(serviceNamespace.unions.length, 0); + + const fooNamespace = sdkPackage.namespaces.find((x) => x.name === "Foo"); + ok(fooNamespace); + strictEqual(fooNamespace.fullName, "Foo"); + strictEqual(fooNamespace.clients.length, 1); + strictEqual(fooNamespace.models.length, 0); + strictEqual(fooNamespace.enums.length, 0); + strictEqual(fooNamespace.unions.length, 0); + strictEqual(fooNamespace.namespaces.length, 2); + + const barNamespace = fooNamespace.namespaces.find((x) => x.name === "Bar"); + ok(barNamespace); + strictEqual(barNamespace.fullName, "Foo.Bar"); + strictEqual(barNamespace.clients.length, 1); + strictEqual(barNamespace.models.length, 0); + strictEqual(barNamespace.enums.length, 0); + strictEqual(barNamespace.unions.length, 0); + strictEqual(barNamespace.namespaces.length, 0); + + const bazNamespace = fooNamespace.namespaces.find((x) => x.name === "Baz"); + ok(bazNamespace); + strictEqual(bazNamespace.fullName, "Foo.Baz"); + strictEqual(bazNamespace.clients.length, 1); + strictEqual(bazNamespace.models.length, 0); + strictEqual(bazNamespace.enums.length, 0); + strictEqual(bazNamespace.unions.length, 0); + strictEqual(bazNamespace.namespaces.length, 0); + }); + + it("complicated namespaces", async () => { + await runner.compile(` + @service({}) + @route("/a") + namespace A { + interface AG { + } + namespace AA { + interface AAG { + } + namespace AAA{}; + namespace AAB{ + interface AABGroup1 { + } + interface AABGroup2 { + } + } + } + } + `); + + const sdkPackage = runner.context.sdkPackage; + const aNamespace = sdkPackage.namespaces.find((x) => x.name === "A"); + ok(aNamespace); + strictEqual(aNamespace.fullName, "A"); + strictEqual(aNamespace.clients.length, 2); // A and AG + strictEqual(aNamespace.models.length, 0); + strictEqual(aNamespace.enums.length, 0); + strictEqual(aNamespace.unions.length, 0); + strictEqual(aNamespace.namespaces.length, 1); // AA + + const aaNamespace = aNamespace.namespaces.find((x) => x.name === "AA"); + ok(aaNamespace); + strictEqual(aaNamespace.fullName, "A.AA"); + strictEqual(aaNamespace.clients.length, 2); // AA and AAG + strictEqual(aaNamespace.models.length, 0); + strictEqual(aaNamespace.enums.length, 0); + strictEqual(aaNamespace.unions.length, 0); + strictEqual(aaNamespace.namespaces.length, 2); // AAA and AAB + + const aaaNamespace = aaNamespace.namespaces.find((x) => x.name === "AAA"); + ok(aaaNamespace); + strictEqual(aaaNamespace.fullName, "A.AA.AAA"); + strictEqual(aaaNamespace.clients.length, 1); // AAA + strictEqual(aaaNamespace.models.length, 0); + strictEqual(aaaNamespace.enums.length, 0); + strictEqual(aaaNamespace.unions.length, 0); + strictEqual(aaaNamespace.namespaces.length, 0); + + const aabNamespace = aaNamespace.namespaces.find((x) => x.name === "AAB"); + ok(aabNamespace); + strictEqual(aabNamespace.fullName, "A.AA.AAB"); + strictEqual(aabNamespace.clients.length, 3); // AAB, AABGroup1 and AABGroup2 + strictEqual(aabNamespace.models.length, 0); + strictEqual(aabNamespace.enums.length, 0); + strictEqual(aabNamespace.unions.length, 0); + strictEqual(aabNamespace.namespaces.length, 0); + }); + + it("restructure client hierarchy with renaming of client name and client namespace name", async () => { + await runner.compileWithCustomization( + ` + @service({ + title: "Pet Store", + }) + namespace PetStore; + + @route("/feed") + op feed(): void; + + @route("/op2") + op pet(): void; + `, + ` + namespace PetStoreRenamed; // this namespace will be the namespace of the clients and operation groups defined in this customization file + + @client({ + name: "FoodClient", + service: PetStore + }) + interface Client1 { + feed is PetStore.feed + } + + @client({ + name: "PetActionClient", + service: PetStore + }) + @clientNamespace("PetStoreRenamed.SubNamespace") // use @clientNamespace to specify the namespace of the client + interface Client2 { + pet is PetStore.pet + } + `, + ); + const sdkPackage = runner.context.sdkPackage; + const foodClient = sdkPackage.clients.find((x) => x.name === "FoodClient"); + ok(foodClient); + strictEqual(foodClient.clientNamespace, "PetStoreRenamed"); + const petActionClient = sdkPackage.clients.find((x) => x.name === "PetActionClient"); + ok(petActionClient); + strictEqual(petActionClient.clientNamespace, "PetStoreRenamed.SubNamespace"); + }); +}); diff --git a/packages/typespec-client-generator-core/test/types/built-in-types.test.ts b/packages/typespec-client-generator-core/test/types/built-in-types.test.ts index 6019c9d4be..502a565028 100644 --- a/packages/typespec-client-generator-core/test/types/built-in-types.test.ts +++ b/packages/typespec-client-generator-core/test/types/built-in-types.test.ts @@ -1,5 +1,4 @@ import { AzureCoreTestLibrary } from "@azure-tools/typespec-azure-core/testing"; -import { expectDiagnostics } from "@typespec/compiler/testing"; import { ok, strictEqual } from "assert"; import { afterEach, beforeEach, describe, it } from "vitest"; import { SdkBuiltInType } from "../../src/interfaces.js"; @@ -326,50 +325,6 @@ describe("typespec-client-generator-core: built-in types", () => { strictEqual(type.baseType.baseType.baseType, undefined); }); - it("known values", async function () { - await runner.compileWithBuiltInService( - ` - enum TestEnum{ - one, - two, - three, - } - - #suppress "deprecated" "for testing" - @knownValues(TestEnum) - scalar testScalar extends string; - - model TestModel { - prop1: testScalar; - #suppress "deprecated" "for testing" - @knownValues(TestEnum) - prop2: string; - } - - op func( - @body body: TestModel - ): void; - `, - ); - expectDiagnostics(runner.context.diagnostics, []); - const m = runner.context.sdkPackage.models.find((x) => x.name === "TestModel"); - const e1 = runner.context.sdkPackage.enums.find((x) => x.name === "TestEnum"); - const e2 = runner.context.sdkPackage.enums.find((x) => x.name === "testScalar"); - ok(m && e1 && e2); - strictEqual(e1.kind, "enum"); - strictEqual(e1.isUnionAsEnum, false); - strictEqual(e1.valueType.kind, "string"); - strictEqual(e2.kind, "enum"); - strictEqual(e2.isUnionAsEnum, false); - strictEqual(e2.valueType.kind, "string"); - for (const property of m.properties) { - if (property.name === "prop1") { - strictEqual(property.type, e2); - } else if (property.name === "prop2") { - strictEqual(property.type, e1); - } - } - }); it("with doc", async () => { await runner.compileWithBuiltInService( ` diff --git a/website/src/content/docs/docs/howtos/Client Generation/02client.mdx b/website/src/content/docs/docs/howtos/Client Generation/02client.mdx index 3f7e930b71..01424d9822 100644 --- a/website/src/content/docs/docs/howtos/Client Generation/02client.mdx +++ b/website/src/content/docs/docs/howtos/Client Generation/02client.mdx @@ -14,6 +14,8 @@ By default, each namespace with `@service` decorator will be generated as a root Other nested namespacess and interfaces of each root client will be generated as operation groups with hierarchy. +The root client's namespace will follow the namespace decorated with `@service`. If an operation group comes from a nested namespace, it will follow that namespace. If it comes from a nested interface, it will follow the namespace to which the interface belongs. + Different language's code generator will have different way to organize clients and operation groups. Please refer the following examples. ### Single client @@ -24,7 +26,6 @@ Different language's code generator will have different way to organize clients ```typespec @service({ title: "Pet Store", - version: "v1", }) namespace PetStore; @@ -283,13 +284,16 @@ actionsClient.close(); ## Customizations Customization SHOULD always be done in a file called `client.tsp` along with the `main.tsp`. -If there is any customizations including `@client` and `@operationGroup`, client hierarchy will only be inferred from them. The logic defined in the default behaviors will not take affect anymore. + +You can use `@client` and `@operationGroup` to reconstruct the client hierarchy. However, if any customizations are made, the client hierarchy will only be inferred from those customizations. The logic defined in the default behaviors will no longer take effect. + +If any customizations are made, the client's namespace will follow the namespace decorated with `@client` or the namespace to which the interface decorated with `@client` belongs. The operation group's namespace follows the same logic for `@operationGroup`. You can use `@clientNamespace` to override it if needed. For this section, we will assume that you have service called `PetStore` in the namespace `PetStore`, defining the two operations `feed` and `pet`. -### Renaming the single client +### Renaming the client name -This can be achieved with the augment operator and the emitter package +This can be achieved with the augment decorator: `@clientName` from `typespec-client-generator-core`. @@ -348,35 +352,98 @@ client.pet(); +### Renaming the client namespace + +This can be achieved with the augment decorator: `@clientNamespace` from `typespec-client-generator-core`. + + + + +```typespec +import "./main.tsp"; +import "@azure-tools/typespec-client-generator-core"; + +using Azure.ClientGenerator.Core; + +@@clientNamespace(PetStore, "PetStoreRenamed"); +``` + + + + +```python +from pet_store_renamed import PetStoreClient + +client = PetStoreClient() +client.feed() +client.pet() +``` + + + + +```csharp +using PetStoreRenamed; + +PetStoreClient client = new PetStoreClient(); +client.Feed(); +client.Pet(); +``` + + + + +```typescript +import { PetStoreClient } from "@azure/package-name"; + +const client = new PetStoreClient(); +client.feed(); +client.pet(); +``` + + + + +```java +package com.petstorerenamed; + +PetStoreClient client = new PetStoreClientBuilder().buildClient(); +client.feed(); +client.pet(); +``` + + + + ### Splitting the operations into two clients -Two clients that separate the operations can be declared using the `@client` decorator from `typespec-client-generator-core`: +Two clients that separate the operations can be declared using the `@client` decorator from `typespec-client-generator-core`. ```typespec -import "./main.tsp" -import "@azure-tools/typespec-client-generator-core" +import "./main.tsp"; +import "@azure-tools/typespec-client-generator-core"; using Azure.ClientGenerator.Core; -namespace Customizations; # The actual name here doesn't matter and is here for organization purposes only +namespace PetStoreRenamed; // this namespace will be the namespace of the clients and operation groups defined in this customization file @client({ name: "FoodClient", - service: PetStoreNamespace + service: PetStore, }) interface Client1 { - feed is PetStoreNamespace.feed; + feed is PetStore.feed; } @client({ name: "PetActionClient", - service: PetStoreNamespace + service: PetStore, }) interface Client2 { - pet is PetStoreNamespace.pet; + pet is PetStore.pet; } ``` @@ -384,7 +451,7 @@ interface Client2 { ```python -from pet_store import FoodClient, PetActionClient +from pet_store_renamed import FoodClient, PetActionClient client1 = FoodClient() client2 = PetActionClient() @@ -397,7 +464,7 @@ client2.pet() ```csharp -using PetStore; +using PetStoreRenamed; PetActionClient petActionClient = new PetActionClient(); FoodClient foodClient = new FoodClient(); @@ -422,6 +489,8 @@ client2.feed(); ```java +package com.petstorerenamed; + FoodClient foodClient = new FoodClientBuilder().buildClient(); PetActionClient petActionClient = new PetActionClientBuilder().buildClient(); @@ -440,25 +509,25 @@ Two clients that separate the operations can be declared using the `@client` dec ```typespec -import "./main.tsp" -import "@azure-tools/typespec-client-generator-core" +import "./main.tsp"; +import "@azure-tools/typespec-client-generator-core"; using Azure.ClientGenerator.Core; @client({ name: "PetStoreClient", - service: PetStoreNamespace + service: PetStore, }) -namespace Customizations; # The actual name here doesn't matter and is here for organization purposes only +namespace PetStoreRenamed; // this namespace will be the namespace of the clients and operation groups defined in this customization file @operationGroup -interface OpGrp1{ - feed is PetStoreNamespace.feed +interface OpGrp1 { + feed is PetStore.feed; } @operationGroup interface OpGrp2 { - pet is PetStoreNamespace.pet + pet is PetStore.pet; } ``` @@ -466,7 +535,7 @@ interface OpGrp2 { ```python -from pet_store import PetStoreClient +from pet_store_renamed import PetStoreClient client = PetStoreClient() @@ -478,7 +547,7 @@ client.op_grp_2.pet() ```csharp -using PetStore; +using PetStoreRenamed; PetStoreClient client = new PetStoreClient(); @@ -501,6 +570,8 @@ client.opGrp2.pet(); ```java +package com.petstorerenamed; + PetStoreClientBuilder builder = new PetStoreClientBuilder(); OpGrp1Client opGrp1Client = builder.buildOpGrp1Client(); @@ -521,27 +592,28 @@ Two clients that separates the operations can be declared using the `client` dec ```typespec -import "./main.tsp" -import "@azure-tools/typespec-client-generator-core" +import "./main.tsp"; +import "@azure-tools/typespec-client-generator-core"; using Azure.ClientGenerator.Core; -namespace Customizations; # The actual name here doesn't matter and is here for organization purposes only +namespace PetStoreRenamed; // this namespace will be the namespace of the clients and operation groups defined in this customization file @client({ name: "FoodClient", - service: PetStoreNamespace + service: PetStore, }) interface Client1 { - feed is PetStoreNamespace.feed + feed is PetStore.feed; } @client({ - name: "SubNamespace.PetActionClient", - service: PetStoreNamespace + name: "PetActionClient", + service: PetStore, }) +@clientNamespace("PetStoreRenamed.SubNamespace") // use @clientNamespace to specify the namespace of the client interface Client2 { - pet is PetStoreNamespace.pet + pet is PetStore.pet; } ``` @@ -549,8 +621,8 @@ interface Client2 { ```python -from pet_store import FoodClient -from pet_store.sub_namespace import PetActionClient +from pet_store_renamed import FoodClient +from pet_store_renamed.sub_namespace import PetActionClient client1 = FoodClient() client2 = PetActionClient() @@ -563,8 +635,8 @@ client2.pet() ```csharp -using PetStore; -using PetStore.SubNamespace; +using PetStoreRenamed; +using PetStoreRenamed.SubNamespace; SubNamespacePetActionClient petActionClient = new SubNamespacePetActionClient(); FoodClient foodClient = new FoodClient(); @@ -584,10 +656,10 @@ NOT_SUPPORTED; ```java -import com.petstorenamespace.FoodClient; -import com.petstorenamespace.FoodClientBuilder; -import com.petstorenamespace.subnamespace.PetActionClient; -import com.petstorenamespace.subnamespace.PetActionClientBuilder; +import com.petstorerenamed.FoodClient; +import com.petstorerenamed.FoodClientBuilder; +import com.petstorerenamed.subnamespace.PetActionClient; +import com.petstorerenamed.subnamespace.PetActionClientBuilder; FoodClient foodClient = new FoodClientBuilder().buildClient(); PetActionClient petActionClient = new PetActionClientBuilder().buildClient(); diff --git a/website/src/content/docs/docs/howtos/Client Generation/06types.mdx b/website/src/content/docs/docs/howtos/Client Generation/06types.mdx index e72c52b1ba..32da8538ac 100644 --- a/website/src/content/docs/docs/howtos/Client Generation/06types.mdx +++ b/website/src/content/docs/docs/howtos/Client Generation/06types.mdx @@ -2379,3 +2379,59 @@ jsonWriter.writeStringField("prop", Objects.toString(this.value, null)); + +## Namespace + +The namespace for models, enums, and unions will follow the namespace they belong to. You can use `@clientNamespace` to override it if needed. + + + + +```tsp +namespace My.Service; + +model Foo { + prop: string; +} + +@clientNamespace("My.Service.Temp") +model Bar { + prop: string; +} +``` + + + + + +```python +from my.service import Foo +from my.service.temp import Bar + +foo = Foo(prop="hello") +bar = Bar(prop="world") +``` + + + + +```csharp +TODO +``` + + + + +```typescript +TODO; +``` + + + + +```java +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 62e5e53cac..ab0e451c5f 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 @@ -272,6 +272,41 @@ op nameInService: void; op nameInService: void; ``` +### `@clientNamespace` {#@Azure.ClientGenerator.Core.clientNamespace} + +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. + +```typespec +@Azure.ClientGenerator.Core.clientNamespace(rename: valueof string, scope?: valueof string) +``` + +#### Target + +`Namespace | Interface | Model | Enum | Union` + +#### 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 | + +#### Examples + +```typespec +@clientNamespace("ContosoClient") +namespace Contoso; +``` + +```typespec +@clientName("ContosoJava", "java") +@clientName("ContosoPython", "python") +@clientName("ContosoCSharp", "csharp") +@clientName("ContosoJavascript", "javascript") +namespace Contoso; +``` + ### `@convenientAPI` {#@Azure.ClientGenerator.Core.convenientAPI} Whether you want to generate an operation as a convenient operation. 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 d08f243442..196f5d1a42 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 @@ -40,6 +40,7 @@ npm install --save-peer @azure-tools/typespec-client-generator-core - [`@client`](./decorators.md#@Azure.ClientGenerator.Core.client) - [`@clientInitialization`](./decorators.md#@Azure.ClientGenerator.Core.clientInitialization) - [`@clientName`](./decorators.md#@Azure.ClientGenerator.Core.clientName) +- [`@clientNamespace`](./decorators.md#@Azure.ClientGenerator.Core.clientNamespace) - [`@convenientAPI`](./decorators.md#@Azure.ClientGenerator.Core.convenientAPI) - [`@flattenProperty`](./decorators.md#@Azure.ClientGenerator.Core.flattenProperty) - [`@operationGroup`](./decorators.md#@Azure.ClientGenerator.Core.operationGroup)