diff --git a/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts b/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts index f010c39312..fdc05bb349 100644 --- a/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts +++ b/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts @@ -60,7 +60,7 @@ export function createModel(sdkContext: SdkContext): CodeMode Enums: Array.from(sdkTypeMap.enums.values()), Models: Array.from(sdkTypeMap.models.values()), Clients: inputClients, - Auth: processServiceAuthentication(sdkPackage), + Auth: processServiceAuthentication(sdkContext, sdkPackage), }; return clientModel; diff --git a/packages/http-client-csharp/emitter/src/lib/lib.ts b/packages/http-client-csharp/emitter/src/lib/lib.ts index 4d5986f994..a4c8690cac 100644 --- a/packages/http-client-csharp/emitter/src/lib/lib.ts +++ b/packages/http-client-csharp/emitter/src/lib/lib.ts @@ -44,6 +44,12 @@ const $lib = createTypeSpecLibrary({ "Cannot generate CSharp SDK since no public root client is defined in typespec file.", }, }, + "unsupported-auth": { + severity: "warning", + messages: { + default: paramMessage`${"message"}`, + }, + }, }, emitter: { options: NetEmitterOptionsSchema, diff --git a/packages/http-client-csharp/emitter/src/lib/service-authentication.ts b/packages/http-client-csharp/emitter/src/lib/service-authentication.ts index 7150e804ba..1987d451d6 100644 --- a/packages/http-client-csharp/emitter/src/lib/service-authentication.ts +++ b/packages/http-client-csharp/emitter/src/lib/service-authentication.ts @@ -2,16 +2,20 @@ // Licensed under the MIT License. See License.txt in the project root for license information. import { + SdkContext, SdkCredentialParameter, SdkCredentialType, SdkHttpOperation, SdkPackage, } from "@azure-tools/typespec-client-generator-core"; +import { NoTarget } from "@typespec/compiler"; import { Oauth2Auth, OAuth2Flow } from "@typespec/http"; +import { NetEmitterOptions } from "../options.js"; import { InputAuth } from "../type/input-auth.js"; -import { Logger } from "./logger.js"; +import { reportDiagnostic } from "./lib.js"; export function processServiceAuthentication( + sdkContext: SdkContext, sdkPackage: SdkPackage, ): InputAuth | undefined { let authClientParameter: SdkCredentialParameter | undefined = undefined; @@ -28,11 +32,11 @@ export function processServiceAuthentication( return undefined; } if (authClientParameter.type.kind === "credential") { - return processAuthType(authClientParameter.type); + return processAuthType(sdkContext, authClientParameter.type); } const inputAuth: InputAuth = {}; for (const authType of authClientParameter.type.variantTypes) { - const auth = processAuthType(authType); + const auth = processAuthType(sdkContext, authType); if (auth?.ApiKey) { inputAuth.ApiKey = auth.ApiKey; } @@ -43,41 +47,61 @@ export function processServiceAuthentication( return inputAuth; } -function processAuthType(credentialType: SdkCredentialType): InputAuth | undefined { +function processAuthType( + sdkContext: SdkContext, + credentialType: SdkCredentialType, +): InputAuth | undefined { const scheme = credentialType.scheme; switch (scheme.type) { case "apiKey": - return { ApiKey: { Name: scheme.name } } as InputAuth; + if (scheme.in !== "header") { + reportDiagnostic(sdkContext.program, { + code: "unsupported-auth", + format: { + message: `Only header is supported for ApiKey authentication. ${scheme.in} is not supported.`, + }, + target: credentialType.__raw ?? NoTarget, + }); + return undefined; + } + return { ApiKey: { Name: scheme.name, In: scheme.in } } as InputAuth; case "oauth2": return processOAuth2(scheme); - case "http": - { - const schemeOrApiKeyPrefix = scheme.scheme; - switch (schemeOrApiKeyPrefix) { - case "basic": - Logger.getInstance().warn( - `${schemeOrApiKeyPrefix} auth method is currently not supported.`, - ); - return undefined; - case "bearer": - return { - ApiKey: { - Name: "Authorization", - Prefix: "Bearer", - }, - } as InputAuth; - default: - return { - ApiKey: { - Name: "Authorization", - Prefix: schemeOrApiKeyPrefix, - }, - } as InputAuth; - } + case "http": { + const schemeOrApiKeyPrefix = scheme.scheme; + switch (schemeOrApiKeyPrefix) { + case "basic": + reportDiagnostic(sdkContext.program, { + code: "unsupported-auth", + format: { message: `${schemeOrApiKeyPrefix} auth method is currently not supported.` }, + target: credentialType.__raw ?? NoTarget, + }); + return undefined; + case "bearer": + return { + ApiKey: { + Name: "Authorization", + In: "header", + Prefix: "Bearer", + }, + }; + default: + return { + ApiKey: { + Name: "Authorization", + In: "header", + Prefix: schemeOrApiKeyPrefix, + }, + }; } - break; + } default: - throw new Error(`un-supported authentication scheme ${scheme.type}`); + reportDiagnostic(sdkContext.program, { + code: "unsupported-auth", + format: { message: `un-supported authentication scheme ${scheme.type}` }, + target: credentialType.__raw ?? NoTarget, + }); + return undefined; } } diff --git a/packages/http-client-csharp/emitter/src/type/input-api-key-auth.ts b/packages/http-client-csharp/emitter/src/type/input-api-key-auth.ts index 54e87316f8..07c54ab26e 100644 --- a/packages/http-client-csharp/emitter/src/type/input-api-key-auth.ts +++ b/packages/http-client-csharp/emitter/src/type/input-api-key-auth.ts @@ -3,5 +3,8 @@ export interface InputApiKeyAuth { Name: string; + In: ApiKeyLocation; Prefix?: string; } + +export type ApiKeyLocation = "header"; // | "query" | "cookie"; // we do not support query or cookie yet diff --git a/packages/http-client-csharp/emitter/test/Unit/auth.test.ts b/packages/http-client-csharp/emitter/test/Unit/auth.test.ts new file mode 100644 index 0000000000..60108b5250 --- /dev/null +++ b/packages/http-client-csharp/emitter/test/Unit/auth.test.ts @@ -0,0 +1,70 @@ +import { TestHost } from "@typespec/compiler/testing"; +import { ok, strictEqual } from "assert"; +import { beforeEach, describe, it } from "vitest"; +import { createModel } from "../../src/lib/client-model-builder.js"; +import { + createEmitterContext, + createEmitterTestHost, + createNetSdkContext, + typeSpecCompile, +} from "./utils/test-util.js"; + +describe("Test auth", () => { + let runner: TestHost; + + beforeEach(async () => { + runner = await createEmitterTestHost(); + }); + + it("cookie header is not supported", async () => { + const program = await typeSpecCompile( + ` + op test(): NoContentResponse; + `, + runner, + { + AuthDecorator: `@useAuth(ApiKeyAuth)`, + }, + ); + const context = createEmitterContext(program); + const sdkContext = await createNetSdkContext(context); + const root = createModel(sdkContext); + const diagnostics = context.program.diagnostics; + + const noAuthDiagnostic = diagnostics.find( + (d) => d.code === "@typespec/http-client-csharp/unsupported-auth", + ); + ok(noAuthDiagnostic); + strictEqual( + noAuthDiagnostic.message, + "Only header is supported for ApiKey authentication. cookie is not supported.", + ); + strictEqual(root.Auth, undefined); // we do not support it therefore it falls back to undefined + }); + + it("query header is not supported", async () => { + const program = await typeSpecCompile( + ` + op test(): NoContentResponse; + `, + runner, + { + AuthDecorator: `@useAuth(ApiKeyAuth)`, + }, + ); + const context = createEmitterContext(program); + const sdkContext = await createNetSdkContext(context); + const root = createModel(sdkContext); + const diagnostics = context.program.diagnostics; + + const noAuthDiagnostic = diagnostics.find( + (d) => d.code === "@typespec/http-client-csharp/unsupported-auth", + ); + ok(noAuthDiagnostic); + strictEqual( + noAuthDiagnostic.message, + "Only header is supported for ApiKey authentication. query is not supported.", + ); + strictEqual(root.Auth, undefined); // we do not support it therefore it falls back to undefined + }); +}); diff --git a/packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts b/packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts index 1e3b657c49..5a6adb2958 100644 --- a/packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts +++ b/packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts @@ -43,6 +43,7 @@ export interface TypeSpecCompileOptions { IsAzureCoreNeeded?: boolean; IsTCGCNeeded?: boolean; IsXmlNeeded?: boolean; + AuthDecorator?: string; } export async function typeSpecCompile( @@ -54,9 +55,11 @@ export async function typeSpecCompile( const needAzureCore = options?.IsAzureCoreNeeded ?? false; const needTCGC = options?.IsTCGCNeeded ?? false; const needXml = options?.IsXmlNeeded ?? false; + const authDecorator = + options?.AuthDecorator ?? `@useAuth(ApiKeyAuth)`; const namespace = ` @versioned(Versions) - @useAuth(ApiKeyAuth) + ${authDecorator} @service({ title: "Azure Csharp emitter Testing", }) diff --git a/packages/http-client-csharp/generator/TestProjects/CadlRanch/http/authentication/api-key/tspCodeModel.json b/packages/http-client-csharp/generator/TestProjects/CadlRanch/http/authentication/api-key/tspCodeModel.json index ef0de4c6a0..31fab45bc2 100644 --- a/packages/http-client-csharp/generator/TestProjects/CadlRanch/http/authentication/api-key/tspCodeModel.json +++ b/packages/http-client-csharp/generator/TestProjects/CadlRanch/http/authentication/api-key/tspCodeModel.json @@ -168,7 +168,8 @@ "$id": "18", "ApiKey": { "$id": "19", - "Name": "x-ms-api-key" + "Name": "x-ms-api-key", + "In": "header" } } } diff --git a/packages/http-client-csharp/generator/TestProjects/CadlRanch/http/authentication/http/custom/tspCodeModel.json b/packages/http-client-csharp/generator/TestProjects/CadlRanch/http/authentication/http/custom/tspCodeModel.json index 66a0b73463..25a039a6a6 100644 --- a/packages/http-client-csharp/generator/TestProjects/CadlRanch/http/authentication/http/custom/tspCodeModel.json +++ b/packages/http-client-csharp/generator/TestProjects/CadlRanch/http/authentication/http/custom/tspCodeModel.json @@ -169,6 +169,7 @@ "ApiKey": { "$id": "19", "Name": "Authorization", + "In": "header", "Prefix": "SharedAccessKey" } } diff --git a/packages/http-client-csharp/generator/TestProjects/CadlRanch/http/authentication/union/tspCodeModel.json b/packages/http-client-csharp/generator/TestProjects/CadlRanch/http/authentication/union/tspCodeModel.json index a45094e11e..354f11d3bf 100644 --- a/packages/http-client-csharp/generator/TestProjects/CadlRanch/http/authentication/union/tspCodeModel.json +++ b/packages/http-client-csharp/generator/TestProjects/CadlRanch/http/authentication/union/tspCodeModel.json @@ -110,7 +110,8 @@ "$id": "12", "ApiKey": { "$id": "13", - "Name": "x-ms-api-key" + "Name": "x-ms-api-key", + "In": "header" }, "OAuth2": { "$id": "14", diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/tspCodeModel.json b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/tspCodeModel.json index 4633b09745..343ac81988 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/tspCodeModel.json +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/tspCodeModel.json @@ -3684,7 +3684,8 @@ "$id": "348", "ApiKey": { "$id": "349", - "Name": "my-api-key" + "Name": "my-api-key", + "In": "header" } } }