From f19d6d2aafd959cb145cc43d16180699fdc9431f Mon Sep 17 00:00:00 2001 From: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Date: Tue, 17 Mar 2026 14:18:48 -0400 Subject: [PATCH 01/11] feat(typescript): make readyState return ReadyState enum instead of number (#13639) Co-authored-by: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../src/GeneratedWebsocketSocketClassImpl.ts | 6 +++--- generators/typescript/sdk/versions.yml | 10 ++++++++++ .../core-utilities/src/core/websocket/ws.ts | 16 +++++++++++++--- .../src/api/resources/realtime/client/Socket.ts | 6 +++--- .../resources/realtimeNoAuth/client/Socket.ts | 6 +++--- .../websockets/src/core/websocket/ws.ts | 16 +++++++++++++--- .../src/api/resources/realtime/client/Socket.ts | 6 +++--- .../websockets/src/core/websocket/ws.ts | 16 +++++++++++++--- .../resources/emptyRealtime/client/Socket.ts | 4 ++-- .../src/api/resources/realtime/client/Socket.ts | 6 +++--- .../websocket/no-serde/src/core/websocket/ws.ts | 16 +++++++++++++--- .../resources/emptyRealtime/client/Socket.ts | 4 ++-- .../src/api/resources/realtime/client/Socket.ts | 6 +++--- .../websocket/serde/src/core/websocket/ws.ts | 16 +++++++++++++--- 14 files changed, 97 insertions(+), 37 deletions(-) diff --git a/generators/typescript/sdk/client-class-generator/src/GeneratedWebsocketSocketClassImpl.ts b/generators/typescript/sdk/client-class-generator/src/GeneratedWebsocketSocketClassImpl.ts index 6ab3088db98f..132fb84af6a1 100644 --- a/generators/typescript/sdk/client-class-generator/src/GeneratedWebsocketSocketClassImpl.ts +++ b/generators/typescript/sdk/client-class-generator/src/GeneratedWebsocketSocketClassImpl.ts @@ -120,7 +120,7 @@ export class GeneratedWebsocketSocketClassImpl implements GeneratedWebsocketSock description: "The current state of the connection; this is one of the readyState constants." } ], - returnType: "number", + returnType: `${getTextOfTsNode(context.coreUtilities.websocket.ReconnectingWebSocket._getReferenceToType())}.ReadyState`, statements: [`return this.${GeneratedWebsocketSocketClassImpl.SOCKET_PROPERTY_NAME}.readyState;`] } ], @@ -429,7 +429,7 @@ export class GeneratedWebsocketSocketClassImpl implements GeneratedWebsocketSock isAsync: true, returnType: `Promise<${getTextOfTsNode(context.coreUtilities.websocket.ReconnectingWebSocket._getReferenceToType())}>`, statements: [ - `if (this.${GeneratedWebsocketSocketClassImpl.SOCKET_PROPERTY_NAME}.readyState === ${getTextOfTsNode(context.coreUtilities.websocket.ReconnectingWebSocket._getReferenceToType())}.OPEN) {`, + `if (this.${GeneratedWebsocketSocketClassImpl.SOCKET_PROPERTY_NAME}.readyState === ${getTextOfTsNode(context.coreUtilities.websocket.ReconnectingWebSocket._getReferenceToType())}.ReadyState.OPEN) {`, ` return this.${GeneratedWebsocketSocketClassImpl.SOCKET_PROPERTY_NAME};`, "}", "return new Promise((resolve, reject) => {", @@ -461,7 +461,7 @@ export class GeneratedWebsocketSocketClassImpl implements GeneratedWebsocketSock ' throw new Error("Socket is not connected.");', "}", "", - `if (this.${GeneratedWebsocketSocketClassImpl.SOCKET_PROPERTY_NAME}.readyState !== ${getTextOfTsNode(context.coreUtilities.websocket.ReconnectingWebSocket._getReferenceToType())}.OPEN) {`, + `if (this.${GeneratedWebsocketSocketClassImpl.SOCKET_PROPERTY_NAME}.readyState !== ${getTextOfTsNode(context.coreUtilities.websocket.ReconnectingWebSocket._getReferenceToType())}.ReadyState.OPEN) {`, ' throw new Error("Socket is not open.");', "}" ], diff --git a/generators/typescript/sdk/versions.yml b/generators/typescript/sdk/versions.yml index cb4854438bf3..7198fb0a90cc 100644 --- a/generators/typescript/sdk/versions.yml +++ b/generators/typescript/sdk/versions.yml @@ -1,4 +1,14 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 3.59.0 + changelogEntry: + - summary: | + Make `readyState` on generated WebSocket socket classes return a `ReadyState` + enum (`CONNECTING`, `OPEN`, `CLOSING`, `CLOSED`) instead of a raw `number`, + improving developer experience with IDE autocompletion and self-documenting code. + type: feat + createdAt: "2026-03-17" + irVersion: 65 + - version: 3.58.1 changelogEntry: - summary: | diff --git a/generators/typescript/utils/core-utilities/src/core/websocket/ws.ts b/generators/typescript/utils/core-utilities/src/core/websocket/ws.ts index c5f96f8e5539..96c4c1080892 100644 --- a/generators/typescript/utils/core-utilities/src/core/websocket/ws.ts +++ b/generators/typescript/utils/core-utilities/src/core/websocket/ws.ts @@ -178,11 +178,11 @@ export class ReconnectingWebSocket { /** * The current state of the connection; this is one of the Ready state constants */ - get readyState(): number { + get readyState(): ReconnectingWebSocket.ReadyState { if (this._ws) { - return this._ws.readyState; + return this._ws.readyState as ReconnectingWebSocket.ReadyState; } - return this._options.startClosed ? ReconnectingWebSocket.CLOSED : ReconnectingWebSocket.CONNECTING; + return this._options.startClosed ? ReconnectingWebSocket.ReadyState.CLOSED : ReconnectingWebSocket.ReadyState.CONNECTING; } /** @@ -550,3 +550,13 @@ export class ReconnectingWebSocket { clearTimeout(this._uptimeTimeout); } } + +export namespace ReconnectingWebSocket { + export const ReadyState = { + CONNECTING: 0, + OPEN: 1, + CLOSING: 2, + CLOSED: 3, + } as const; + export type ReadyState = (typeof ReadyState)[keyof typeof ReadyState]; +} diff --git a/seed/ts-sdk/websocket-bearer-auth/websockets/src/api/resources/realtime/client/Socket.ts b/seed/ts-sdk/websocket-bearer-auth/websockets/src/api/resources/realtime/client/Socket.ts index 548a6750f892..70ed7ea401f7 100644 --- a/seed/ts-sdk/websocket-bearer-auth/websockets/src/api/resources/realtime/client/Socket.ts +++ b/seed/ts-sdk/websocket-bearer-auth/websockets/src/api/resources/realtime/client/Socket.ts @@ -50,7 +50,7 @@ export class RealtimeSocket { } /** The current state of the connection; this is one of the readyState constants. */ - get readyState(): number { + get readyState(): core.ReconnectingWebSocket.ReadyState { return this.socket.readyState; } @@ -109,7 +109,7 @@ export class RealtimeSocket { /** Returns a promise that resolves when the websocket is open. */ public async waitForOpen(): Promise { - if (this.socket.readyState === core.ReconnectingWebSocket.OPEN) { + if (this.socket.readyState === core.ReconnectingWebSocket.ReadyState.OPEN) { return this.socket; } @@ -130,7 +130,7 @@ export class RealtimeSocket { throw new Error("Socket is not connected."); } - if (this.socket.readyState !== core.ReconnectingWebSocket.OPEN) { + if (this.socket.readyState !== core.ReconnectingWebSocket.ReadyState.OPEN) { throw new Error("Socket is not open."); } } diff --git a/seed/ts-sdk/websocket-bearer-auth/websockets/src/api/resources/realtimeNoAuth/client/Socket.ts b/seed/ts-sdk/websocket-bearer-auth/websockets/src/api/resources/realtimeNoAuth/client/Socket.ts index c97f55d3cb04..564b826056d1 100644 --- a/seed/ts-sdk/websocket-bearer-auth/websockets/src/api/resources/realtimeNoAuth/client/Socket.ts +++ b/seed/ts-sdk/websocket-bearer-auth/websockets/src/api/resources/realtimeNoAuth/client/Socket.ts @@ -46,7 +46,7 @@ export class RealtimeNoAuthSocket { } /** The current state of the connection; this is one of the readyState constants. */ - get readyState(): number { + get readyState(): core.ReconnectingWebSocket.ReadyState { return this.socket.readyState; } @@ -98,7 +98,7 @@ export class RealtimeNoAuthSocket { /** Returns a promise that resolves when the websocket is open. */ public async waitForOpen(): Promise { - if (this.socket.readyState === core.ReconnectingWebSocket.OPEN) { + if (this.socket.readyState === core.ReconnectingWebSocket.ReadyState.OPEN) { return this.socket; } @@ -119,7 +119,7 @@ export class RealtimeNoAuthSocket { throw new Error("Socket is not connected."); } - if (this.socket.readyState !== core.ReconnectingWebSocket.OPEN) { + if (this.socket.readyState !== core.ReconnectingWebSocket.ReadyState.OPEN) { throw new Error("Socket is not open."); } } diff --git a/seed/ts-sdk/websocket-bearer-auth/websockets/src/core/websocket/ws.ts b/seed/ts-sdk/websocket-bearer-auth/websockets/src/core/websocket/ws.ts index 2a6f3bd5f4aa..44fea1cb4eca 100644 --- a/seed/ts-sdk/websocket-bearer-auth/websockets/src/core/websocket/ws.ts +++ b/seed/ts-sdk/websocket-bearer-auth/websockets/src/core/websocket/ws.ts @@ -178,11 +178,11 @@ export class ReconnectingWebSocket { /** * The current state of the connection; this is one of the Ready state constants */ - get readyState(): number { + get readyState(): ReconnectingWebSocket.ReadyState { if (this._ws) { - return this._ws.readyState; + return this._ws.readyState as ReconnectingWebSocket.ReadyState; } - return this._options.startClosed ? ReconnectingWebSocket.CLOSED : ReconnectingWebSocket.CONNECTING; + return this._options.startClosed ? ReconnectingWebSocket.ReadyState.CLOSED : ReconnectingWebSocket.ReadyState.CONNECTING; } /** @@ -550,3 +550,13 @@ export class ReconnectingWebSocket { clearTimeout(this._uptimeTimeout); } } + +export namespace ReconnectingWebSocket { + export const ReadyState = { + CONNECTING: 0, + OPEN: 1, + CLOSING: 2, + CLOSED: 3, + } as const; + export type ReadyState = (typeof ReadyState)[keyof typeof ReadyState]; +} diff --git a/seed/ts-sdk/websocket-inferred-auth/websockets/src/api/resources/realtime/client/Socket.ts b/seed/ts-sdk/websocket-inferred-auth/websockets/src/api/resources/realtime/client/Socket.ts index 57f28b6c3144..acd4d6bf6242 100644 --- a/seed/ts-sdk/websocket-inferred-auth/websockets/src/api/resources/realtime/client/Socket.ts +++ b/seed/ts-sdk/websocket-inferred-auth/websockets/src/api/resources/realtime/client/Socket.ts @@ -50,7 +50,7 @@ export class RealtimeSocket { } /** The current state of the connection; this is one of the readyState constants. */ - get readyState(): number { + get readyState(): core.ReconnectingWebSocket.ReadyState { return this.socket.readyState; } @@ -109,7 +109,7 @@ export class RealtimeSocket { /** Returns a promise that resolves when the websocket is open. */ public async waitForOpen(): Promise { - if (this.socket.readyState === core.ReconnectingWebSocket.OPEN) { + if (this.socket.readyState === core.ReconnectingWebSocket.ReadyState.OPEN) { return this.socket; } @@ -130,7 +130,7 @@ export class RealtimeSocket { throw new Error("Socket is not connected."); } - if (this.socket.readyState !== core.ReconnectingWebSocket.OPEN) { + if (this.socket.readyState !== core.ReconnectingWebSocket.ReadyState.OPEN) { throw new Error("Socket is not open."); } } diff --git a/seed/ts-sdk/websocket-inferred-auth/websockets/src/core/websocket/ws.ts b/seed/ts-sdk/websocket-inferred-auth/websockets/src/core/websocket/ws.ts index 2a6f3bd5f4aa..44fea1cb4eca 100644 --- a/seed/ts-sdk/websocket-inferred-auth/websockets/src/core/websocket/ws.ts +++ b/seed/ts-sdk/websocket-inferred-auth/websockets/src/core/websocket/ws.ts @@ -178,11 +178,11 @@ export class ReconnectingWebSocket { /** * The current state of the connection; this is one of the Ready state constants */ - get readyState(): number { + get readyState(): ReconnectingWebSocket.ReadyState { if (this._ws) { - return this._ws.readyState; + return this._ws.readyState as ReconnectingWebSocket.ReadyState; } - return this._options.startClosed ? ReconnectingWebSocket.CLOSED : ReconnectingWebSocket.CONNECTING; + return this._options.startClosed ? ReconnectingWebSocket.ReadyState.CLOSED : ReconnectingWebSocket.ReadyState.CONNECTING; } /** @@ -550,3 +550,13 @@ export class ReconnectingWebSocket { clearTimeout(this._uptimeTimeout); } } + +export namespace ReconnectingWebSocket { + export const ReadyState = { + CONNECTING: 0, + OPEN: 1, + CLOSING: 2, + CLOSED: 3, + } as const; + export type ReadyState = (typeof ReadyState)[keyof typeof ReadyState]; +} diff --git a/seed/ts-sdk/websocket/no-serde/src/api/resources/empty/resources/emptyRealtime/client/Socket.ts b/seed/ts-sdk/websocket/no-serde/src/api/resources/empty/resources/emptyRealtime/client/Socket.ts index af61ebb7186e..22c24bdb68e3 100644 --- a/seed/ts-sdk/websocket/no-serde/src/api/resources/empty/resources/emptyRealtime/client/Socket.ts +++ b/seed/ts-sdk/websocket/no-serde/src/api/resources/empty/resources/emptyRealtime/client/Socket.ts @@ -45,7 +45,7 @@ export class EmptyRealtimeSocket { } /** The current state of the connection; this is one of the readyState constants. */ - get readyState(): number { + get readyState(): core.ReconnectingWebSocket.ReadyState { return this.socket.readyState; } @@ -92,7 +92,7 @@ export class EmptyRealtimeSocket { /** Returns a promise that resolves when the websocket is open. */ public async waitForOpen(): Promise { - if (this.socket.readyState === core.ReconnectingWebSocket.OPEN) { + if (this.socket.readyState === core.ReconnectingWebSocket.ReadyState.OPEN) { return this.socket; } diff --git a/seed/ts-sdk/websocket/no-serde/src/api/resources/realtime/client/Socket.ts b/seed/ts-sdk/websocket/no-serde/src/api/resources/realtime/client/Socket.ts index 97f450640765..03baf66e5892 100644 --- a/seed/ts-sdk/websocket/no-serde/src/api/resources/realtime/client/Socket.ts +++ b/seed/ts-sdk/websocket/no-serde/src/api/resources/realtime/client/Socket.ts @@ -51,7 +51,7 @@ export class RealtimeSocket { } /** The current state of the connection; this is one of the readyState constants. */ - get readyState(): number { + get readyState(): core.ReconnectingWebSocket.ReadyState { return this.socket.readyState; } @@ -110,7 +110,7 @@ export class RealtimeSocket { /** Returns a promise that resolves when the websocket is open. */ public async waitForOpen(): Promise { - if (this.socket.readyState === core.ReconnectingWebSocket.OPEN) { + if (this.socket.readyState === core.ReconnectingWebSocket.ReadyState.OPEN) { return this.socket; } @@ -131,7 +131,7 @@ export class RealtimeSocket { throw new Error("Socket is not connected."); } - if (this.socket.readyState !== core.ReconnectingWebSocket.OPEN) { + if (this.socket.readyState !== core.ReconnectingWebSocket.ReadyState.OPEN) { throw new Error("Socket is not open."); } } diff --git a/seed/ts-sdk/websocket/no-serde/src/core/websocket/ws.ts b/seed/ts-sdk/websocket/no-serde/src/core/websocket/ws.ts index 2a6f3bd5f4aa..44fea1cb4eca 100644 --- a/seed/ts-sdk/websocket/no-serde/src/core/websocket/ws.ts +++ b/seed/ts-sdk/websocket/no-serde/src/core/websocket/ws.ts @@ -178,11 +178,11 @@ export class ReconnectingWebSocket { /** * The current state of the connection; this is one of the Ready state constants */ - get readyState(): number { + get readyState(): ReconnectingWebSocket.ReadyState { if (this._ws) { - return this._ws.readyState; + return this._ws.readyState as ReconnectingWebSocket.ReadyState; } - return this._options.startClosed ? ReconnectingWebSocket.CLOSED : ReconnectingWebSocket.CONNECTING; + return this._options.startClosed ? ReconnectingWebSocket.ReadyState.CLOSED : ReconnectingWebSocket.ReadyState.CONNECTING; } /** @@ -550,3 +550,13 @@ export class ReconnectingWebSocket { clearTimeout(this._uptimeTimeout); } } + +export namespace ReconnectingWebSocket { + export const ReadyState = { + CONNECTING: 0, + OPEN: 1, + CLOSING: 2, + CLOSED: 3, + } as const; + export type ReadyState = (typeof ReadyState)[keyof typeof ReadyState]; +} diff --git a/seed/ts-sdk/websocket/serde/src/api/resources/empty/resources/emptyRealtime/client/Socket.ts b/seed/ts-sdk/websocket/serde/src/api/resources/empty/resources/emptyRealtime/client/Socket.ts index 0da611aeafe6..fa17a0a1ab3a 100644 --- a/seed/ts-sdk/websocket/serde/src/api/resources/empty/resources/emptyRealtime/client/Socket.ts +++ b/seed/ts-sdk/websocket/serde/src/api/resources/empty/resources/emptyRealtime/client/Socket.ts @@ -57,7 +57,7 @@ export class EmptyRealtimeSocket { } /** The current state of the connection; this is one of the readyState constants. */ - get readyState(): number { + get readyState(): core.ReconnectingWebSocket.ReadyState { return this.socket.readyState; } @@ -104,7 +104,7 @@ export class EmptyRealtimeSocket { /** Returns a promise that resolves when the websocket is open. */ public async waitForOpen(): Promise { - if (this.socket.readyState === core.ReconnectingWebSocket.OPEN) { + if (this.socket.readyState === core.ReconnectingWebSocket.ReadyState.OPEN) { return this.socket; } diff --git a/seed/ts-sdk/websocket/serde/src/api/resources/realtime/client/Socket.ts b/seed/ts-sdk/websocket/serde/src/api/resources/realtime/client/Socket.ts index fee8d257bbdb..26dc462ed30e 100644 --- a/seed/ts-sdk/websocket/serde/src/api/resources/realtime/client/Socket.ts +++ b/seed/ts-sdk/websocket/serde/src/api/resources/realtime/client/Socket.ts @@ -66,7 +66,7 @@ export class RealtimeSocket { } /** The current state of the connection; this is one of the readyState constants. */ - get readyState(): number { + get readyState(): core.ReconnectingWebSocket.ReadyState { return this.socket.readyState; } @@ -146,7 +146,7 @@ export class RealtimeSocket { /** Returns a promise that resolves when the websocket is open. */ public async waitForOpen(): Promise { - if (this.socket.readyState === core.ReconnectingWebSocket.OPEN) { + if (this.socket.readyState === core.ReconnectingWebSocket.ReadyState.OPEN) { return this.socket; } @@ -167,7 +167,7 @@ export class RealtimeSocket { throw new Error("Socket is not connected."); } - if (this.socket.readyState !== core.ReconnectingWebSocket.OPEN) { + if (this.socket.readyState !== core.ReconnectingWebSocket.ReadyState.OPEN) { throw new Error("Socket is not open."); } } diff --git a/seed/ts-sdk/websocket/serde/src/core/websocket/ws.ts b/seed/ts-sdk/websocket/serde/src/core/websocket/ws.ts index 2a6f3bd5f4aa..44fea1cb4eca 100644 --- a/seed/ts-sdk/websocket/serde/src/core/websocket/ws.ts +++ b/seed/ts-sdk/websocket/serde/src/core/websocket/ws.ts @@ -178,11 +178,11 @@ export class ReconnectingWebSocket { /** * The current state of the connection; this is one of the Ready state constants */ - get readyState(): number { + get readyState(): ReconnectingWebSocket.ReadyState { if (this._ws) { - return this._ws.readyState; + return this._ws.readyState as ReconnectingWebSocket.ReadyState; } - return this._options.startClosed ? ReconnectingWebSocket.CLOSED : ReconnectingWebSocket.CONNECTING; + return this._options.startClosed ? ReconnectingWebSocket.ReadyState.CLOSED : ReconnectingWebSocket.ReadyState.CONNECTING; } /** @@ -550,3 +550,13 @@ export class ReconnectingWebSocket { clearTimeout(this._uptimeTimeout); } } + +export namespace ReconnectingWebSocket { + export const ReadyState = { + CONNECTING: 0, + OPEN: 1, + CLOSING: 2, + CLOSED: 3, + } as const; + export type ReadyState = (typeof ReadyState)[keyof typeof ReadyState]; +} From 5d8d7a41500d4a11a2a9bb0403ed3823e4afc541 Mon Sep 17 00:00:00 2001 From: Fern Support <126544928+fern-support@users.noreply.github.com> Date: Tue, 17 Mar 2026 14:32:25 -0400 Subject: [PATCH 02/11] chore(typescript): update ts-sdk seed (#13642) Co-authored-by: fern-support --- .../websocket-bearer-auth/websockets/src/core/websocket/ws.ts | 4 +++- .../websockets/src/core/websocket/ws.ts | 4 +++- seed/ts-sdk/websocket/no-serde/src/core/websocket/ws.ts | 4 +++- seed/ts-sdk/websocket/serde/src/core/websocket/ws.ts | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/seed/ts-sdk/websocket-bearer-auth/websockets/src/core/websocket/ws.ts b/seed/ts-sdk/websocket-bearer-auth/websockets/src/core/websocket/ws.ts index 44fea1cb4eca..95ad4991024d 100644 --- a/seed/ts-sdk/websocket-bearer-auth/websockets/src/core/websocket/ws.ts +++ b/seed/ts-sdk/websocket-bearer-auth/websockets/src/core/websocket/ws.ts @@ -182,7 +182,9 @@ export class ReconnectingWebSocket { if (this._ws) { return this._ws.readyState as ReconnectingWebSocket.ReadyState; } - return this._options.startClosed ? ReconnectingWebSocket.ReadyState.CLOSED : ReconnectingWebSocket.ReadyState.CONNECTING; + return this._options.startClosed + ? ReconnectingWebSocket.ReadyState.CLOSED + : ReconnectingWebSocket.ReadyState.CONNECTING; } /** diff --git a/seed/ts-sdk/websocket-inferred-auth/websockets/src/core/websocket/ws.ts b/seed/ts-sdk/websocket-inferred-auth/websockets/src/core/websocket/ws.ts index 44fea1cb4eca..95ad4991024d 100644 --- a/seed/ts-sdk/websocket-inferred-auth/websockets/src/core/websocket/ws.ts +++ b/seed/ts-sdk/websocket-inferred-auth/websockets/src/core/websocket/ws.ts @@ -182,7 +182,9 @@ export class ReconnectingWebSocket { if (this._ws) { return this._ws.readyState as ReconnectingWebSocket.ReadyState; } - return this._options.startClosed ? ReconnectingWebSocket.ReadyState.CLOSED : ReconnectingWebSocket.ReadyState.CONNECTING; + return this._options.startClosed + ? ReconnectingWebSocket.ReadyState.CLOSED + : ReconnectingWebSocket.ReadyState.CONNECTING; } /** diff --git a/seed/ts-sdk/websocket/no-serde/src/core/websocket/ws.ts b/seed/ts-sdk/websocket/no-serde/src/core/websocket/ws.ts index 44fea1cb4eca..95ad4991024d 100644 --- a/seed/ts-sdk/websocket/no-serde/src/core/websocket/ws.ts +++ b/seed/ts-sdk/websocket/no-serde/src/core/websocket/ws.ts @@ -182,7 +182,9 @@ export class ReconnectingWebSocket { if (this._ws) { return this._ws.readyState as ReconnectingWebSocket.ReadyState; } - return this._options.startClosed ? ReconnectingWebSocket.ReadyState.CLOSED : ReconnectingWebSocket.ReadyState.CONNECTING; + return this._options.startClosed + ? ReconnectingWebSocket.ReadyState.CLOSED + : ReconnectingWebSocket.ReadyState.CONNECTING; } /** diff --git a/seed/ts-sdk/websocket/serde/src/core/websocket/ws.ts b/seed/ts-sdk/websocket/serde/src/core/websocket/ws.ts index 44fea1cb4eca..95ad4991024d 100644 --- a/seed/ts-sdk/websocket/serde/src/core/websocket/ws.ts +++ b/seed/ts-sdk/websocket/serde/src/core/websocket/ws.ts @@ -182,7 +182,9 @@ export class ReconnectingWebSocket { if (this._ws) { return this._ws.readyState as ReconnectingWebSocket.ReadyState; } - return this._options.startClosed ? ReconnectingWebSocket.ReadyState.CLOSED : ReconnectingWebSocket.ReadyState.CONNECTING; + return this._options.startClosed + ? ReconnectingWebSocket.ReadyState.CLOSED + : ReconnectingWebSocket.ReadyState.CONNECTING; } /** From f42b9d4e6247617d0a5ac84ac5fb5842ad58cc39 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 14:33:00 -0400 Subject: [PATCH 03/11] fix(cli): enable WebSocket auth in AsyncAPI by populating IR auth schemes (#12676) * fix(cli): set auth on WebSocket channels based on AsyncAPI server security Co-Authored-By: will.kendall@buildwithfern.com * fix(cli): set auth on WebSocket channels based on auth overrides or security schemes The buildChannel.ts in openapi-ir-to-fern hardcoded auth: false for all WebSocket channels. This fix checks if auth is configured via generators.yml auth overrides or if the OpenAPI IR has security schemes detected from the AsyncAPI spec, and sets auth accordingly. This is the primary code path used by fern ir for non-OSS workspaces. Co-Authored-By: will.kendall@buildwithfern.com * fix(cli): use local variable to avoid biome non-null assertion lint error Co-Authored-By: will.kendall@buildwithfern.com * fix(cli): only set auth on WebSocket channels when authOverrides is configured Co-Authored-By: will.kendall@buildwithfern.com * fix(cli): pass authOverrides to AsyncAPIConverterContext for docs generation path Co-Authored-By: will.kendall@buildwithfern.com * add changes * feat(cli): implement convertAsyncApiSecuritySchemes and clean up debug logging - Implement convertAsyncApiSecuritySchemes() to convert native AsyncAPI security schemes (bearer, basic, apiKey, oauth2) from components.securitySchemes to IR AuthScheme objects - Add convertSecurityScheme() private method following the same pattern as the OpenAPI SecuritySchemeConverter - Remove debug logging from ChannelConverter3_0 and AsyncAPIConverter - This ensures auth schemes flow through to FDR registration so the docs explorer can render auth forms for WebSocket endpoints Co-Authored-By: will.kendall@buildwithfern.com * fix: update assembly snapshot for WebSocket auth changes Co-Authored-By: will.kendall@buildwithfern.com * fix(cli): use authOverrides only for channel auth in buildChannel.ts and regenerate exhaustive.json Co-Authored-By: will.kendall@buildwithfern.com * fix(cli): use securitySchemes instead of global security for channel auth detection Co-Authored-By: will.kendall@buildwithfern.com * fix(cli): use authOverrides only for channel auth in buildChannel.ts In namespaced workspaces, context.ir.securitySchemes and context.ir.security are polluted by other specs (e.g., trains OAuth2 bleeds into AsyncAPI channels). Only authOverrides reliably indicates per-channel auth intent. Revert assembly snapshot to match main (auth: false). Co-Authored-By: will.kendall@buildwithfern.com --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: will.kendall@buildwithfern.com Co-authored-by: willkendall01 Co-authored-by: William Kendall <43126467+willkendall01@users.noreply.github.com> --- .../src/2.x/channel/ChannelConverter2_X.ts | 26 +- .../asyncapi-to-ir/src/2.x/types.ts | 2 + .../src/3.0/channel/ChannelConverter3_0.ts | 27 +- .../asyncapi-to-ir/src/3.0/types.ts | 2 + .../asyncapi-to-ir/src/AsyncAPIConverter.ts | 115 +- .../openapi-ir-to-fern/src/buildChannel.ts | 4 +- packages/cli/cli/versions.yml | 13 +- .../__test__/test-definitions/exhaustive.json | 263 ++ .../__test__/test-definitions/exhaustive.json | 3719 ++++++++++++----- .../lazy-fern-workspace/src/OSSWorkspace.ts | 1 + 10 files changed, 3195 insertions(+), 977 deletions(-) diff --git a/packages/cli/api-importers/asyncapi-to-ir/src/2.x/channel/ChannelConverter2_X.ts b/packages/cli/api-importers/asyncapi-to-ir/src/2.x/channel/ChannelConverter2_X.ts index 3cb6ef47fec3..58da5389b484 100644 --- a/packages/cli/api-importers/asyncapi-to-ir/src/2.x/channel/ChannelConverter2_X.ts +++ b/packages/cli/api-importers/asyncapi-to-ir/src/2.x/channel/ChannelConverter2_X.ts @@ -104,6 +104,8 @@ export class ChannelConverter2_X extends AbstractChannelConverter 0) { + for (const serverName of this.channel.servers) { + const server = (servers as Record)[serverName]; + if (server?.security && server.security.length > 0) { + return true; + } + } + return false; + } + + for (const server of Object.values(servers)) { + const serverV2 = server as AsyncAPIV2.ServerV2; + if (serverV2.security && serverV2.security.length > 0) { + return true; + } + } + return false; + } + private convertMessage({ context, operation, diff --git a/packages/cli/api-importers/asyncapi-to-ir/src/2.x/types.ts b/packages/cli/api-importers/asyncapi-to-ir/src/2.x/types.ts index 56c06d9abc91..7423ebdb1b4a 100644 --- a/packages/cli/api-importers/asyncapi-to-ir/src/2.x/types.ts +++ b/packages/cli/api-importers/asyncapi-to-ir/src/2.x/types.ts @@ -9,6 +9,7 @@ export interface DocumentV2 { components?: { schemas?: Record; messages?: Record; + securitySchemes?: Record; }; tags?: Tag[]; } @@ -17,6 +18,7 @@ export interface ServerV2 { name: string; url: string; protocol: string; + security?: Array>; } export interface ChannelV2 { diff --git a/packages/cli/api-importers/asyncapi-to-ir/src/3.0/channel/ChannelConverter3_0.ts b/packages/cli/api-importers/asyncapi-to-ir/src/3.0/channel/ChannelConverter3_0.ts index ea879f6368b8..dc48e1a14b55 100644 --- a/packages/cli/api-importers/asyncapi-to-ir/src/3.0/channel/ChannelConverter3_0.ts +++ b/packages/cli/api-importers/asyncapi-to-ir/src/3.0/channel/ChannelConverter3_0.ts @@ -105,6 +105,8 @@ export class ChannelConverter3_0 extends AbstractChannelConverter 0) { + for (const serverRef of this.channel.servers) { + const serverName = serverRef.$ref.replace(SERVER_REFERENCE_PREFIX, ""); + const server = servers[serverName]; + if (server?.security && server.security.length > 0) { + return true; + } + } + return false; + } + + for (const server of Object.values(servers)) { + if (server.security && server.security.length > 0) { + return true; + } + } + return false; + } + private convertChannelParameters({ pathParameters, queryParameters, diff --git a/packages/cli/api-importers/asyncapi-to-ir/src/3.0/types.ts b/packages/cli/api-importers/asyncapi-to-ir/src/3.0/types.ts index e16483c508e9..b2a471fefc4f 100644 --- a/packages/cli/api-importers/asyncapi-to-ir/src/3.0/types.ts +++ b/packages/cli/api-importers/asyncapi-to-ir/src/3.0/types.ts @@ -11,6 +11,7 @@ export interface DocumentV3 { components?: { schemas?: Record; messages?: Record; + securitySchemes?: Record; }; } @@ -23,6 +24,7 @@ export interface ServerV3 { name: string; host: string; protocol: string; + security?: Array>; } export interface ChannelV3 { diff --git a/packages/cli/api-importers/asyncapi-to-ir/src/AsyncAPIConverter.ts b/packages/cli/api-importers/asyncapi-to-ir/src/AsyncAPIConverter.ts index fbfd47b7e44f..8f9cc60a431a 100644 --- a/packages/cli/api-importers/asyncapi-to-ir/src/AsyncAPIConverter.ts +++ b/packages/cli/api-importers/asyncapi-to-ir/src/AsyncAPIConverter.ts @@ -1,6 +1,7 @@ -import { FernIr, IntermediateRepresentation } from "@fern-api/ir-sdk"; +import { AuthScheme, FernIr, IntermediateRepresentation } from "@fern-api/ir-sdk"; +import { convertApiAuth } from "@fern-api/ir-utils"; import { AbstractConverter, AbstractSpecConverter, Converters, Extensions } from "@fern-api/v3-importer-commons"; -import { OpenAPIV3 } from "openapi-types"; +import { OpenAPIV3, OpenAPIV3_1 } from "openapi-types"; import { ChannelConverter2_X } from "./2.x/channel/ChannelConverter2_X.js"; import { AsyncAPIV2 } from "./2.x/index.js"; import { ServersConverter2_X } from "./2.x/servers/ServersConverter2_X.js"; @@ -42,6 +43,8 @@ export class AsyncAPIConverter extends AbstractSpecConverter 0) { + this.addAuthToIR({ + requirement: asyncApiSchemes.length === 1 ? "ALL" : "ANY", + schemes: asyncApiSchemes, + docs: undefined + }); + } + } + + private convertAsyncApiSecuritySchemes(): AuthScheme[] { + const securitySchemes: AuthScheme[] = []; + + for (const [id, securityScheme] of Object.entries(this.context.spec.components?.securitySchemes ?? {})) { + const resolvedSecurityScheme = this.context.resolveMaybeReference({ + schemaOrReference: securityScheme, + breadcrumbs: ["components", "securitySchemes", id] + }); + if (resolvedSecurityScheme == null) { + continue; + } + + const convertedScheme = this.convertSecurityScheme({ + securityScheme: resolvedSecurityScheme, + schemeId: id + }); + if (convertedScheme != null) { + securitySchemes.push(convertedScheme); + } + } + + return securitySchemes; + } + + private convertSecurityScheme({ + securityScheme, + schemeId + }: { + securityScheme: OpenAPIV3_1.SecuritySchemeObject; + schemeId: string; + }): AuthScheme | undefined { + switch (securityScheme.type) { + case "http": { + if (securityScheme.scheme?.toLowerCase() === "bearer") { + return AuthScheme.bearer({ + key: schemeId, + token: this.context.casingsGenerator.generateName("token"), + tokenEnvVar: undefined, + docs: securityScheme.description + }); + } + if (securityScheme.scheme?.toLowerCase() === "basic") { + return AuthScheme.basic({ + key: schemeId, + username: this.context.casingsGenerator.generateName("username"), + password: this.context.casingsGenerator.generateName("password"), + usernameEnvVar: undefined, + passwordEnvVar: undefined, + usernameOmit: false, + passwordOmit: false, + docs: securityScheme.description + }); + } + break; + } + case "apiKey": { + if (securityScheme.in === "header") { + return AuthScheme.header({ + key: schemeId, + name: { + name: this.context.casingsGenerator.generateName("apiKey"), + wireValue: securityScheme.name + }, + valueType: AbstractConverter.OPTIONAL_STRING, + prefix: undefined, + headerEnvVar: undefined, + docs: securityScheme.description + }); + } + break; + } + case "oauth2": { + return AuthScheme.bearer({ + key: schemeId, + token: this.context.casingsGenerator.generateName("token"), + tokenEnvVar: undefined, + docs: securityScheme.description + }); + } + } + return undefined; + } + private convertChannels(): void { for (const [channelPath, channel] of Object.entries(this.context.spec.channels ?? {})) { const groupNameExtension = new Extensions.SdkGroupNameExtension({ diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildChannel.ts b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildChannel.ts index abc347a2bdeb..5bda8afd2123 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildChannel.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildChannel.ts @@ -52,10 +52,12 @@ export function buildChannel({ } } + const hasAuth = context.authOverrides?.auth != null; + const convertedChannel: RawSchemas.WebSocketChannelSchema = { path: convertedPath, url: urlId, - auth: false + auth: hasAuth }; if (channel.audiences != null && channel.audiences.length > 0) { diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index bb35fe754bdb..df158ae0bee9 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,4 +1,15 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 4.31.3 + changelogEntry: + - summary: | + Enable WebSocket auth in AsyncAPI by populating IR auth schemes. The AsyncAPI + converter now converts security schemes from both generators.yml auth overrides + and native AsyncAPI spec securitySchemes into IR AuthScheme objects, so the docs + explorer correctly renders auth forms for WebSocket endpoints. + type: fix + createdAt: "2026-03-17" + irVersion: 65 + - version: 4.31.2 changelogEntry: - summary: | @@ -12,7 +23,6 @@ type: fix createdAt: "2026-03-16" irVersion: 65 - - version: 4.31.1 changelogEntry: - summary: | @@ -1492,6 +1502,7 @@ type: fix createdAt: "2026-02-24" irVersion: 65 + - version: 3.85.2 changelogEntry: - summary: | diff --git a/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/exhaustive.json b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/exhaustive.json index 70067745dd68..d1d567b4a1a4 100644 --- a/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/exhaustive.json +++ b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/exhaustive.json @@ -3526,6 +3526,123 @@ "type": "unknown" } }, + "type_types/object:MapOfDocumentedUnknownType": { + "type": "alias", + "declaration": { + "name": { + "originalName": "MapOfDocumentedUnknownType", + "camelCase": { + "unsafeName": "mapOfDocumentedUnknownType", + "safeName": "mapOfDocumentedUnknownType" + }, + "snakeCase": { + "unsafeName": "map_of_documented_unknown_type", + "safeName": "map_of_documented_unknown_type" + }, + "screamingSnakeCase": { + "unsafeName": "MAP_OF_DOCUMENTED_UNKNOWN_TYPE", + "safeName": "MAP_OF_DOCUMENTED_UNKNOWN_TYPE" + }, + "pascalCase": { + "unsafeName": "MapOfDocumentedUnknownType", + "safeName": "MapOfDocumentedUnknownType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + }, + { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + ], + "packagePath": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "file": { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + } + }, + "typeReference": { + "type": "map", + "key": { + "type": "primitive", + "value": "STRING" + }, + "value": { + "type": "named", + "value": "type_types/object:DocumentedUnknownType" + } + } + }, "type_types/union:Animal": { "type": "discriminatedUnion", "declaration": { @@ -7866,6 +7983,152 @@ }, "examples": null }, + "endpoint_endpoints/object.getAndReturnMapOfDocumentedUnknownType": { + "auth": { + "type": "bearer", + "token": { + "originalName": "token", + "camelCase": { + "unsafeName": "token", + "safeName": "token" + }, + "snakeCase": { + "unsafeName": "token", + "safeName": "token" + }, + "screamingSnakeCase": { + "unsafeName": "TOKEN", + "safeName": "TOKEN" + }, + "pascalCase": { + "unsafeName": "Token", + "safeName": "Token" + } + } + }, + "declaration": { + "name": { + "originalName": "getAndReturnMapOfDocumentedUnknownType", + "camelCase": { + "unsafeName": "getAndReturnMapOfDocumentedUnknownType", + "safeName": "getAndReturnMapOfDocumentedUnknownType" + }, + "snakeCase": { + "unsafeName": "get_and_return_map_of_documented_unknown_type", + "safeName": "get_and_return_map_of_documented_unknown_type" + }, + "screamingSnakeCase": { + "unsafeName": "GET_AND_RETURN_MAP_OF_DOCUMENTED_UNKNOWN_TYPE", + "safeName": "GET_AND_RETURN_MAP_OF_DOCUMENTED_UNKNOWN_TYPE" + }, + "pascalCase": { + "unsafeName": "GetAndReturnMapOfDocumentedUnknownType", + "safeName": "GetAndReturnMapOfDocumentedUnknownType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "endpoints", + "camelCase": { + "unsafeName": "endpoints", + "safeName": "endpoints" + }, + "snakeCase": { + "unsafeName": "endpoints", + "safeName": "endpoints" + }, + "screamingSnakeCase": { + "unsafeName": "ENDPOINTS", + "safeName": "ENDPOINTS" + }, + "pascalCase": { + "unsafeName": "Endpoints", + "safeName": "Endpoints" + } + }, + { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + ], + "packagePath": [ + { + "originalName": "endpoints", + "camelCase": { + "unsafeName": "endpoints", + "safeName": "endpoints" + }, + "snakeCase": { + "unsafeName": "endpoints", + "safeName": "endpoints" + }, + "screamingSnakeCase": { + "unsafeName": "ENDPOINTS", + "safeName": "ENDPOINTS" + }, + "pascalCase": { + "unsafeName": "Endpoints", + "safeName": "Endpoints" + } + } + ], + "file": { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + } + }, + "location": { + "method": "POST", + "path": "/object/get-and-return-map-of-documented-unknown-type" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": { + "type": "typeReference", + "value": { + "type": "named", + "value": "type_types/object:MapOfDocumentedUnknownType" + } + } + }, + "response": { + "type": "json" + }, + "examples": null + }, "endpoint_endpoints/object.getAndReturnWithDatetimeLikeString": { "auth": { "type": "bearer", diff --git a/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/exhaustive.json b/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/exhaustive.json index 5b6b2ce96a92..446d97908a6a 100644 --- a/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/exhaustive.json +++ b/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/exhaustive.json @@ -5270,6 +5270,275 @@ "availability": null, "docs": "Tests that unknown types are able to preserve their docstrings." }, + "type_types/object:MapOfDocumentedUnknownType": { + "inline": null, + "name": { + "name": { + "originalName": "MapOfDocumentedUnknownType", + "camelCase": { + "unsafeName": "mapOfDocumentedUnknownType", + "safeName": "mapOfDocumentedUnknownType" + }, + "snakeCase": { + "unsafeName": "map_of_documented_unknown_type", + "safeName": "map_of_documented_unknown_type" + }, + "screamingSnakeCase": { + "unsafeName": "MAP_OF_DOCUMENTED_UNKNOWN_TYPE", + "safeName": "MAP_OF_DOCUMENTED_UNKNOWN_TYPE" + }, + "pascalCase": { + "unsafeName": "MapOfDocumentedUnknownType", + "safeName": "MapOfDocumentedUnknownType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + }, + { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + ], + "packagePath": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "file": { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + }, + "displayName": null, + "typeId": "type_types/object:MapOfDocumentedUnknownType" + }, + "shape": { + "_type": "alias", + "aliasOf": { + "_type": "container", + "container": { + "_type": "map", + "keyType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "DocumentedUnknownType", + "camelCase": { + "unsafeName": "documentedUnknownType", + "safeName": "documentedUnknownType" + }, + "snakeCase": { + "unsafeName": "documented_unknown_type", + "safeName": "documented_unknown_type" + }, + "screamingSnakeCase": { + "unsafeName": "DOCUMENTED_UNKNOWN_TYPE", + "safeName": "DOCUMENTED_UNKNOWN_TYPE" + }, + "pascalCase": { + "unsafeName": "DocumentedUnknownType", + "safeName": "DocumentedUnknownType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + }, + { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + ], + "packagePath": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "file": { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + }, + "displayName": null, + "typeId": "type_types/object:DocumentedUnknownType", + "default": null, + "inline": null + } + } + }, + "resolvedType": { + "_type": "container", + "container": { + "_type": "map", + "keyType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "valueType": { + "_type": "unknown" + } + } + } + }, + "referencedTypes": [ + "type_types/object:DocumentedUnknownType" + ], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": "Tests that map value types with unknown types don't get spurious | undefined." + }, "type_types/union:Animal": { "inline": null, "name": { @@ -60897,24 +61166,24 @@ "docs": null }, { - "id": "endpoint_endpoints/object.getAndReturnWithDatetimeLikeString", + "id": "endpoint_endpoints/object.getAndReturnMapOfDocumentedUnknownType", "name": { - "originalName": "getAndReturnWithDatetimeLikeString", + "originalName": "getAndReturnMapOfDocumentedUnknownType", "camelCase": { - "unsafeName": "getAndReturnWithDatetimeLikeString", - "safeName": "getAndReturnWithDatetimeLikeString" + "unsafeName": "getAndReturnMapOfDocumentedUnknownType", + "safeName": "getAndReturnMapOfDocumentedUnknownType" }, "snakeCase": { - "unsafeName": "get_and_return_with_datetime_like_string", - "safeName": "get_and_return_with_datetime_like_string" + "unsafeName": "get_and_return_map_of_documented_unknown_type", + "safeName": "get_and_return_map_of_documented_unknown_type" }, "screamingSnakeCase": { - "unsafeName": "GET_AND_RETURN_WITH_DATETIME_LIKE_STRING", - "safeName": "GET_AND_RETURN_WITH_DATETIME_LIKE_STRING" + "unsafeName": "GET_AND_RETURN_MAP_OF_DOCUMENTED_UNKNOWN_TYPE", + "safeName": "GET_AND_RETURN_MAP_OF_DOCUMENTED_UNKNOWN_TYPE" }, "pascalCase": { - "unsafeName": "GetAndReturnWithDatetimeLikeString", - "safeName": "GetAndReturnWithDatetimeLikeString" + "unsafeName": "GetAndReturnMapOfDocumentedUnknownType", + "safeName": "GetAndReturnMapOfDocumentedUnknownType" } }, "displayName": null, @@ -60930,11 +61199,11 @@ "method": "POST", "basePath": null, "path": { - "head": "/get-and-return-with-datetime-like-string", + "head": "/get-and-return-map-of-documented-unknown-type", "parts": [] }, "fullPath": { - "head": "/object/get-and-return-with-datetime-like-string", + "head": "/object/get-and-return-map-of-documented-unknown-type", "parts": [] }, "pathParameters": [], @@ -60946,22 +61215,1265 @@ "requestBodyType": { "_type": "named", "name": { - "originalName": "ObjectWithDatetimeLikeString", + "originalName": "MapOfDocumentedUnknownType", "camelCase": { - "unsafeName": "objectWithDatetimeLikeString", - "safeName": "objectWithDatetimeLikeString" + "unsafeName": "mapOfDocumentedUnknownType", + "safeName": "mapOfDocumentedUnknownType" }, "snakeCase": { - "unsafeName": "object_with_datetime_like_string", - "safeName": "object_with_datetime_like_string" + "unsafeName": "map_of_documented_unknown_type", + "safeName": "map_of_documented_unknown_type" }, "screamingSnakeCase": { - "unsafeName": "OBJECT_WITH_DATETIME_LIKE_STRING", - "safeName": "OBJECT_WITH_DATETIME_LIKE_STRING" + "unsafeName": "MAP_OF_DOCUMENTED_UNKNOWN_TYPE", + "safeName": "MAP_OF_DOCUMENTED_UNKNOWN_TYPE" }, "pascalCase": { - "unsafeName": "ObjectWithDatetimeLikeString", - "safeName": "ObjectWithDatetimeLikeString" + "unsafeName": "MapOfDocumentedUnknownType", + "safeName": "MapOfDocumentedUnknownType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + }, + { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + ], + "packagePath": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "file": { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + }, + "displayName": null, + "typeId": "type_types/object:MapOfDocumentedUnknownType", + "default": null, + "inline": null + }, + "docs": null, + "contentType": null, + "v2Examples": null + }, + "v2RequestBodies": null, + "sdkRequest": { + "shape": { + "type": "justRequestBody", + "value": { + "type": "typeReference", + "requestBodyType": { + "_type": "named", + "name": { + "originalName": "MapOfDocumentedUnknownType", + "camelCase": { + "unsafeName": "mapOfDocumentedUnknownType", + "safeName": "mapOfDocumentedUnknownType" + }, + "snakeCase": { + "unsafeName": "map_of_documented_unknown_type", + "safeName": "map_of_documented_unknown_type" + }, + "screamingSnakeCase": { + "unsafeName": "MAP_OF_DOCUMENTED_UNKNOWN_TYPE", + "safeName": "MAP_OF_DOCUMENTED_UNKNOWN_TYPE" + }, + "pascalCase": { + "unsafeName": "MapOfDocumentedUnknownType", + "safeName": "MapOfDocumentedUnknownType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + }, + { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + ], + "packagePath": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "file": { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + }, + "displayName": null, + "typeId": "type_types/object:MapOfDocumentedUnknownType", + "default": null, + "inline": null + }, + "docs": null, + "contentType": null, + "v2Examples": null + } + }, + "requestParameterName": { + "originalName": "request", + "camelCase": { + "unsafeName": "request", + "safeName": "request" + }, + "snakeCase": { + "unsafeName": "request", + "safeName": "request" + }, + "screamingSnakeCase": { + "unsafeName": "REQUEST", + "safeName": "REQUEST" + }, + "pascalCase": { + "unsafeName": "Request", + "safeName": "Request" + } + }, + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": { + "originalName": "MapOfDocumentedUnknownType", + "camelCase": { + "unsafeName": "mapOfDocumentedUnknownType", + "safeName": "mapOfDocumentedUnknownType" + }, + "snakeCase": { + "unsafeName": "map_of_documented_unknown_type", + "safeName": "map_of_documented_unknown_type" + }, + "screamingSnakeCase": { + "unsafeName": "MAP_OF_DOCUMENTED_UNKNOWN_TYPE", + "safeName": "MAP_OF_DOCUMENTED_UNKNOWN_TYPE" + }, + "pascalCase": { + "unsafeName": "MapOfDocumentedUnknownType", + "safeName": "MapOfDocumentedUnknownType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + }, + { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + ], + "packagePath": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "file": { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + }, + "displayName": null, + "typeId": "type_types/object:MapOfDocumentedUnknownType", + "default": null, + "inline": null + }, + "docs": null, + "v2Examples": null + } + }, + "status-code": null, + "isWildcardStatusCode": null, + "docs": null + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [], + "autogeneratedExamples": [ + { + "example": { + "id": "9123ea79", + "url": "/object/get-and-return-map-of-documented-unknown-type", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": { + "type": "reference", + "shape": { + "type": "named", + "shape": { + "type": "alias", + "value": { + "shape": { + "type": "container", + "container": { + "type": "map", + "map": [ + { + "key": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + }, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "alias", + "value": { + "shape": { + "type": "unknown", + "unknown": { + "key": "value" + } + }, + "jsonExample": { + "key": "value" + } + } + }, + "typeName": { + "name": { + "originalName": "DocumentedUnknownType", + "camelCase": { + "unsafeName": "documentedUnknownType", + "safeName": "documentedUnknownType" + }, + "snakeCase": { + "unsafeName": "documented_unknown_type", + "safeName": "documented_unknown_type" + }, + "screamingSnakeCase": { + "unsafeName": "DOCUMENTED_UNKNOWN_TYPE", + "safeName": "DOCUMENTED_UNKNOWN_TYPE" + }, + "pascalCase": { + "unsafeName": "DocumentedUnknownType", + "safeName": "DocumentedUnknownType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + }, + { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + ], + "packagePath": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "file": { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + }, + "displayName": null, + "typeId": "type_types/object:DocumentedUnknownType" + } + }, + "jsonExample": { + "key": "value" + } + } + } + ], + "keyType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "DocumentedUnknownType", + "camelCase": { + "unsafeName": "documentedUnknownType", + "safeName": "documentedUnknownType" + }, + "snakeCase": { + "unsafeName": "documented_unknown_type", + "safeName": "documented_unknown_type" + }, + "screamingSnakeCase": { + "unsafeName": "DOCUMENTED_UNKNOWN_TYPE", + "safeName": "DOCUMENTED_UNKNOWN_TYPE" + }, + "pascalCase": { + "unsafeName": "DocumentedUnknownType", + "safeName": "DocumentedUnknownType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + }, + { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + ], + "packagePath": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "file": { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + }, + "displayName": null, + "typeId": "type_types/object:DocumentedUnknownType", + "default": null, + "inline": null + } + } + }, + "jsonExample": { + "string": { + "key": "value" + } + } + } + }, + "typeName": { + "name": { + "originalName": "MapOfDocumentedUnknownType", + "camelCase": { + "unsafeName": "mapOfDocumentedUnknownType", + "safeName": "mapOfDocumentedUnknownType" + }, + "snakeCase": { + "unsafeName": "map_of_documented_unknown_type", + "safeName": "map_of_documented_unknown_type" + }, + "screamingSnakeCase": { + "unsafeName": "MAP_OF_DOCUMENTED_UNKNOWN_TYPE", + "safeName": "MAP_OF_DOCUMENTED_UNKNOWN_TYPE" + }, + "pascalCase": { + "unsafeName": "MapOfDocumentedUnknownType", + "safeName": "MapOfDocumentedUnknownType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + }, + { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + ], + "packagePath": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "file": { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + }, + "displayName": null, + "typeId": "type_types/object:MapOfDocumentedUnknownType" + } + }, + "jsonExample": { + "string": { + "key": "value" + } + } + }, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "alias", + "value": { + "shape": { + "type": "container", + "container": { + "type": "map", + "map": [ + { + "key": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + }, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "alias", + "value": { + "shape": { + "type": "unknown", + "unknown": { + "key": "value" + } + }, + "jsonExample": { + "key": "value" + } + } + }, + "typeName": { + "name": { + "originalName": "DocumentedUnknownType", + "camelCase": { + "unsafeName": "documentedUnknownType", + "safeName": "documentedUnknownType" + }, + "snakeCase": { + "unsafeName": "documented_unknown_type", + "safeName": "documented_unknown_type" + }, + "screamingSnakeCase": { + "unsafeName": "DOCUMENTED_UNKNOWN_TYPE", + "safeName": "DOCUMENTED_UNKNOWN_TYPE" + }, + "pascalCase": { + "unsafeName": "DocumentedUnknownType", + "safeName": "DocumentedUnknownType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + }, + { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + ], + "packagePath": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "file": { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + }, + "displayName": null, + "typeId": "type_types/object:DocumentedUnknownType" + } + }, + "jsonExample": { + "key": "value" + } + } + } + ], + "keyType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "DocumentedUnknownType", + "camelCase": { + "unsafeName": "documentedUnknownType", + "safeName": "documentedUnknownType" + }, + "snakeCase": { + "unsafeName": "documented_unknown_type", + "safeName": "documented_unknown_type" + }, + "screamingSnakeCase": { + "unsafeName": "DOCUMENTED_UNKNOWN_TYPE", + "safeName": "DOCUMENTED_UNKNOWN_TYPE" + }, + "pascalCase": { + "unsafeName": "DocumentedUnknownType", + "safeName": "DocumentedUnknownType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + }, + { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + ], + "packagePath": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "file": { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + }, + "displayName": null, + "typeId": "type_types/object:DocumentedUnknownType", + "default": null, + "inline": null + } + } + }, + "jsonExample": { + "string": { + "key": "value" + } + } + } + }, + "typeName": { + "name": { + "originalName": "MapOfDocumentedUnknownType", + "camelCase": { + "unsafeName": "mapOfDocumentedUnknownType", + "safeName": "mapOfDocumentedUnknownType" + }, + "snakeCase": { + "unsafeName": "map_of_documented_unknown_type", + "safeName": "map_of_documented_unknown_type" + }, + "screamingSnakeCase": { + "unsafeName": "MAP_OF_DOCUMENTED_UNKNOWN_TYPE", + "safeName": "MAP_OF_DOCUMENTED_UNKNOWN_TYPE" + }, + "pascalCase": { + "unsafeName": "MapOfDocumentedUnknownType", + "safeName": "MapOfDocumentedUnknownType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + }, + { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + ], + "packagePath": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "file": { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + }, + "displayName": null, + "typeId": "type_types/object:MapOfDocumentedUnknownType" + } + }, + "jsonExample": { + "string": { + "key": "value" + } + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "retries": null, + "apiPlayground": null, + "responseHeaders": [], + "availability": null, + "docs": null + }, + { + "id": "endpoint_endpoints/object.getAndReturnWithDatetimeLikeString", + "name": { + "originalName": "getAndReturnWithDatetimeLikeString", + "camelCase": { + "unsafeName": "getAndReturnWithDatetimeLikeString", + "safeName": "getAndReturnWithDatetimeLikeString" + }, + "snakeCase": { + "unsafeName": "get_and_return_with_datetime_like_string", + "safeName": "get_and_return_with_datetime_like_string" + }, + "screamingSnakeCase": { + "unsafeName": "GET_AND_RETURN_WITH_DATETIME_LIKE_STRING", + "safeName": "GET_AND_RETURN_WITH_DATETIME_LIKE_STRING" + }, + "pascalCase": { + "unsafeName": "GetAndReturnWithDatetimeLikeString", + "safeName": "GetAndReturnWithDatetimeLikeString" + } + }, + "displayName": null, + "auth": true, + "security": [ + { + "bearer": [] + } + ], + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "POST", + "basePath": null, + "path": { + "head": "/get-and-return-with-datetime-like-string", + "parts": [] + }, + "fullPath": { + "head": "/object/get-and-return-with-datetime-like-string", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": { + "type": "reference", + "requestBodyType": { + "_type": "named", + "name": { + "originalName": "ObjectWithDatetimeLikeString", + "camelCase": { + "unsafeName": "objectWithDatetimeLikeString", + "safeName": "objectWithDatetimeLikeString" + }, + "snakeCase": { + "unsafeName": "object_with_datetime_like_string", + "safeName": "object_with_datetime_like_string" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT_WITH_DATETIME_LIKE_STRING", + "safeName": "OBJECT_WITH_DATETIME_LIKE_STRING" + }, + "pascalCase": { + "unsafeName": "ObjectWithDatetimeLikeString", + "safeName": "ObjectWithDatetimeLikeString" } }, "fernFilepath": { @@ -86330,7 +87842,8 @@ "type_types/object:ObjectWithDatetimeLikeString", "type_types/object:ObjectWithUnknownField", "type_types/object:ObjectWithDocumentedUnknownType", - "type_types/object:DocumentedUnknownType" + "type_types/object:DocumentedUnknownType", + "type_types/object:MapOfDocumentedUnknownType" ], "service_endpoints/union": [ "type_types/union:Animal", @@ -89178,32 +90691,315 @@ { "name": { "name": { - "originalName": "optionalAlias", + "originalName": "optionalAlias", + "camelCase": { + "unsafeName": "optionalAlias", + "safeName": "optionalAlias" + }, + "snakeCase": { + "unsafeName": "optional_alias", + "safeName": "optional_alias" + }, + "screamingSnakeCase": { + "unsafeName": "OPTIONAL_ALIAS", + "safeName": "OPTIONAL_ALIAS" + }, + "pascalCase": { + "unsafeName": "OptionalAlias", + "safeName": "OptionalAlias" + } + }, + "wireValue": "optionalAlias" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "named", + "value": "type_types/object:OptionalAlias" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_types/object:OptionalAlias": { + "type": "alias", + "declaration": { + "name": { + "originalName": "OptionalAlias", + "camelCase": { + "unsafeName": "optionalAlias", + "safeName": "optionalAlias" + }, + "snakeCase": { + "unsafeName": "optional_alias", + "safeName": "optional_alias" + }, + "screamingSnakeCase": { + "unsafeName": "OPTIONAL_ALIAS", + "safeName": "OPTIONAL_ALIAS" + }, + "pascalCase": { + "unsafeName": "OptionalAlias", + "safeName": "OptionalAlias" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + }, + { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + ], + "packagePath": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "file": { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + } + }, + "type_types/object:ObjectWithDatetimeLikeString": { + "type": "object", + "declaration": { + "name": { + "originalName": "ObjectWithDatetimeLikeString", + "camelCase": { + "unsafeName": "objectWithDatetimeLikeString", + "safeName": "objectWithDatetimeLikeString" + }, + "snakeCase": { + "unsafeName": "object_with_datetime_like_string", + "safeName": "object_with_datetime_like_string" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT_WITH_DATETIME_LIKE_STRING", + "safeName": "OBJECT_WITH_DATETIME_LIKE_STRING" + }, + "pascalCase": { + "unsafeName": "ObjectWithDatetimeLikeString", + "safeName": "ObjectWithDatetimeLikeString" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + }, + { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + ], + "packagePath": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "file": { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + } + } + }, + "properties": [ + { + "name": { + "name": { + "originalName": "datetimeLikeString", "camelCase": { - "unsafeName": "optionalAlias", - "safeName": "optionalAlias" + "unsafeName": "datetimeLikeString", + "safeName": "datetimeLikeString" }, "snakeCase": { - "unsafeName": "optional_alias", - "safeName": "optional_alias" + "unsafeName": "datetime_like_string", + "safeName": "datetime_like_string" }, "screamingSnakeCase": { - "unsafeName": "OPTIONAL_ALIAS", - "safeName": "OPTIONAL_ALIAS" + "unsafeName": "DATETIME_LIKE_STRING", + "safeName": "DATETIME_LIKE_STRING" }, "pascalCase": { - "unsafeName": "OptionalAlias", - "safeName": "OptionalAlias" + "unsafeName": "DatetimeLikeString", + "safeName": "DatetimeLikeString" } }, - "wireValue": "optionalAlias" + "wireValue": "datetimeLikeString" }, "typeReference": { - "type": "optional", - "value": { - "type": "named", - "value": "type_types/object:OptionalAlias" - } + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "name": { + "originalName": "actualDatetime", + "camelCase": { + "unsafeName": "actualDatetime", + "safeName": "actualDatetime" + }, + "snakeCase": { + "unsafeName": "actual_datetime", + "safeName": "actual_datetime" + }, + "screamingSnakeCase": { + "unsafeName": "ACTUAL_DATETIME", + "safeName": "ACTUAL_DATETIME" + }, + "pascalCase": { + "unsafeName": "ActualDatetime", + "safeName": "ActualDatetime" + } + }, + "wireValue": "actualDatetime" + }, + "typeReference": { + "type": "primitive", + "value": "DATE_TIME" }, "propertyAccess": null, "variable": null @@ -89212,26 +91008,26 @@ "extends": null, "additionalProperties": false }, - "type_types/object:OptionalAlias": { - "type": "alias", + "type_types/object:ObjectWithUnknownField": { + "type": "object", "declaration": { "name": { - "originalName": "OptionalAlias", + "originalName": "ObjectWithUnknownField", "camelCase": { - "unsafeName": "optionalAlias", - "safeName": "optionalAlias" + "unsafeName": "objectWithUnknownField", + "safeName": "objectWithUnknownField" }, "snakeCase": { - "unsafeName": "optional_alias", - "safeName": "optional_alias" + "unsafeName": "object_with_unknown_field", + "safeName": "object_with_unknown_field" }, "screamingSnakeCase": { - "unsafeName": "OPTIONAL_ALIAS", - "safeName": "OPTIONAL_ALIAS" + "unsafeName": "OBJECT_WITH_UNKNOWN_FIELD", + "safeName": "OBJECT_WITH_UNKNOWN_FIELD" }, "pascalCase": { - "unsafeName": "OptionalAlias", - "safeName": "OptionalAlias" + "unsafeName": "ObjectWithUnknownField", + "safeName": "ObjectWithUnknownField" } }, "fernFilepath": { @@ -89317,34 +91113,60 @@ } } }, - "typeReference": { - "type": "optional", - "value": { - "type": "primitive", - "value": "STRING" + "properties": [ + { + "name": { + "name": { + "originalName": "unknown", + "camelCase": { + "unsafeName": "unknown", + "safeName": "unknown" + }, + "snakeCase": { + "unsafeName": "unknown", + "safeName": "unknown" + }, + "screamingSnakeCase": { + "unsafeName": "UNKNOWN", + "safeName": "UNKNOWN" + }, + "pascalCase": { + "unsafeName": "Unknown", + "safeName": "Unknown" + } + }, + "wireValue": "unknown" + }, + "typeReference": { + "type": "unknown" + }, + "propertyAccess": null, + "variable": null } - } + ], + "extends": null, + "additionalProperties": false }, - "type_types/object:ObjectWithDatetimeLikeString": { + "type_types/object:ObjectWithDocumentedUnknownType": { "type": "object", "declaration": { "name": { - "originalName": "ObjectWithDatetimeLikeString", + "originalName": "ObjectWithDocumentedUnknownType", "camelCase": { - "unsafeName": "objectWithDatetimeLikeString", - "safeName": "objectWithDatetimeLikeString" + "unsafeName": "objectWithDocumentedUnknownType", + "safeName": "objectWithDocumentedUnknownType" }, "snakeCase": { - "unsafeName": "object_with_datetime_like_string", - "safeName": "object_with_datetime_like_string" + "unsafeName": "object_with_documented_unknown_type", + "safeName": "object_with_documented_unknown_type" }, "screamingSnakeCase": { - "unsafeName": "OBJECT_WITH_DATETIME_LIKE_STRING", - "safeName": "OBJECT_WITH_DATETIME_LIKE_STRING" + "unsafeName": "OBJECT_WITH_DOCUMENTED_UNKNOWN_TYPE", + "safeName": "OBJECT_WITH_DOCUMENTED_UNKNOWN_TYPE" }, "pascalCase": { - "unsafeName": "ObjectWithDatetimeLikeString", - "safeName": "ObjectWithDatetimeLikeString" + "unsafeName": "ObjectWithDocumentedUnknownType", + "safeName": "ObjectWithDocumentedUnknownType" } }, "fernFilepath": { @@ -89434,59 +91256,29 @@ { "name": { "name": { - "originalName": "datetimeLikeString", - "camelCase": { - "unsafeName": "datetimeLikeString", - "safeName": "datetimeLikeString" - }, - "snakeCase": { - "unsafeName": "datetime_like_string", - "safeName": "datetime_like_string" - }, - "screamingSnakeCase": { - "unsafeName": "DATETIME_LIKE_STRING", - "safeName": "DATETIME_LIKE_STRING" - }, - "pascalCase": { - "unsafeName": "DatetimeLikeString", - "safeName": "DatetimeLikeString" - } - }, - "wireValue": "datetimeLikeString" - }, - "typeReference": { - "type": "primitive", - "value": "STRING" - }, - "propertyAccess": null, - "variable": null - }, - { - "name": { - "name": { - "originalName": "actualDatetime", + "originalName": "documentedUnknownType", "camelCase": { - "unsafeName": "actualDatetime", - "safeName": "actualDatetime" + "unsafeName": "documentedUnknownType", + "safeName": "documentedUnknownType" }, "snakeCase": { - "unsafeName": "actual_datetime", - "safeName": "actual_datetime" + "unsafeName": "documented_unknown_type", + "safeName": "documented_unknown_type" }, "screamingSnakeCase": { - "unsafeName": "ACTUAL_DATETIME", - "safeName": "ACTUAL_DATETIME" + "unsafeName": "DOCUMENTED_UNKNOWN_TYPE", + "safeName": "DOCUMENTED_UNKNOWN_TYPE" }, "pascalCase": { - "unsafeName": "ActualDatetime", - "safeName": "ActualDatetime" + "unsafeName": "DocumentedUnknownType", + "safeName": "DocumentedUnknownType" } }, - "wireValue": "actualDatetime" + "wireValue": "documentedUnknownType" }, "typeReference": { - "type": "primitive", - "value": "DATE_TIME" + "type": "named", + "value": "type_types/object:DocumentedUnknownType" }, "propertyAccess": null, "variable": null @@ -89495,26 +91287,26 @@ "extends": null, "additionalProperties": false }, - "type_types/object:ObjectWithUnknownField": { - "type": "object", + "type_types/object:DocumentedUnknownType": { + "type": "alias", "declaration": { "name": { - "originalName": "ObjectWithUnknownField", + "originalName": "DocumentedUnknownType", "camelCase": { - "unsafeName": "objectWithUnknownField", - "safeName": "objectWithUnknownField" + "unsafeName": "documentedUnknownType", + "safeName": "documentedUnknownType" }, "snakeCase": { - "unsafeName": "object_with_unknown_field", - "safeName": "object_with_unknown_field" + "unsafeName": "documented_unknown_type", + "safeName": "documented_unknown_type" }, "screamingSnakeCase": { - "unsafeName": "OBJECT_WITH_UNKNOWN_FIELD", - "safeName": "OBJECT_WITH_UNKNOWN_FIELD" + "unsafeName": "DOCUMENTED_UNKNOWN_TYPE", + "safeName": "DOCUMENTED_UNKNOWN_TYPE" }, "pascalCase": { - "unsafeName": "ObjectWithUnknownField", - "safeName": "ObjectWithUnknownField" + "unsafeName": "DocumentedUnknownType", + "safeName": "DocumentedUnknownType" } }, "fernFilepath": { @@ -89600,60 +91392,30 @@ } } }, - "properties": [ - { - "name": { - "name": { - "originalName": "unknown", - "camelCase": { - "unsafeName": "unknown", - "safeName": "unknown" - }, - "snakeCase": { - "unsafeName": "unknown", - "safeName": "unknown" - }, - "screamingSnakeCase": { - "unsafeName": "UNKNOWN", - "safeName": "UNKNOWN" - }, - "pascalCase": { - "unsafeName": "Unknown", - "safeName": "Unknown" - } - }, - "wireValue": "unknown" - }, - "typeReference": { - "type": "unknown" - }, - "propertyAccess": null, - "variable": null - } - ], - "extends": null, - "additionalProperties": false + "typeReference": { + "type": "unknown" + } }, - "type_types/object:ObjectWithDocumentedUnknownType": { - "type": "object", + "type_types/object:MapOfDocumentedUnknownType": { + "type": "alias", "declaration": { "name": { - "originalName": "ObjectWithDocumentedUnknownType", + "originalName": "MapOfDocumentedUnknownType", "camelCase": { - "unsafeName": "objectWithDocumentedUnknownType", - "safeName": "objectWithDocumentedUnknownType" + "unsafeName": "mapOfDocumentedUnknownType", + "safeName": "mapOfDocumentedUnknownType" }, "snakeCase": { - "unsafeName": "object_with_documented_unknown_type", - "safeName": "object_with_documented_unknown_type" + "unsafeName": "map_of_documented_unknown_type", + "safeName": "map_of_documented_unknown_type" }, "screamingSnakeCase": { - "unsafeName": "OBJECT_WITH_DOCUMENTED_UNKNOWN_TYPE", - "safeName": "OBJECT_WITH_DOCUMENTED_UNKNOWN_TYPE" + "unsafeName": "MAP_OF_DOCUMENTED_UNKNOWN_TYPE", + "safeName": "MAP_OF_DOCUMENTED_UNKNOWN_TYPE" }, "pascalCase": { - "unsafeName": "ObjectWithDocumentedUnknownType", - "safeName": "ObjectWithDocumentedUnknownType" + "unsafeName": "MapOfDocumentedUnknownType", + "safeName": "MapOfDocumentedUnknownType" } }, "fernFilepath": { @@ -89739,148 +91501,16 @@ } } }, - "properties": [ - { - "name": { - "name": { - "originalName": "documentedUnknownType", - "camelCase": { - "unsafeName": "documentedUnknownType", - "safeName": "documentedUnknownType" - }, - "snakeCase": { - "unsafeName": "documented_unknown_type", - "safeName": "documented_unknown_type" - }, - "screamingSnakeCase": { - "unsafeName": "DOCUMENTED_UNKNOWN_TYPE", - "safeName": "DOCUMENTED_UNKNOWN_TYPE" - }, - "pascalCase": { - "unsafeName": "DocumentedUnknownType", - "safeName": "DocumentedUnknownType" - } - }, - "wireValue": "documentedUnknownType" - }, - "typeReference": { - "type": "named", - "value": "type_types/object:DocumentedUnknownType" - }, - "propertyAccess": null, - "variable": null - } - ], - "extends": null, - "additionalProperties": false - }, - "type_types/object:DocumentedUnknownType": { - "type": "alias", - "declaration": { - "name": { - "originalName": "DocumentedUnknownType", - "camelCase": { - "unsafeName": "documentedUnknownType", - "safeName": "documentedUnknownType" - }, - "snakeCase": { - "unsafeName": "documented_unknown_type", - "safeName": "documented_unknown_type" - }, - "screamingSnakeCase": { - "unsafeName": "DOCUMENTED_UNKNOWN_TYPE", - "safeName": "DOCUMENTED_UNKNOWN_TYPE" - }, - "pascalCase": { - "unsafeName": "DocumentedUnknownType", - "safeName": "DocumentedUnknownType" - } + "typeReference": { + "type": "map", + "key": { + "type": "primitive", + "value": "STRING" }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - }, - { - "originalName": "object", - "camelCase": { - "unsafeName": "object", - "safeName": "object" - }, - "snakeCase": { - "unsafeName": "object", - "safeName": "object" - }, - "screamingSnakeCase": { - "unsafeName": "OBJECT", - "safeName": "OBJECT" - }, - "pascalCase": { - "unsafeName": "Object", - "safeName": "Object" - } - } - ], - "packagePath": [ - { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - ], - "file": { - "originalName": "object", - "camelCase": { - "unsafeName": "object", - "safeName": "object" - }, - "snakeCase": { - "unsafeName": "object", - "safeName": "object" - }, - "screamingSnakeCase": { - "unsafeName": "OBJECT", - "safeName": "OBJECT" - }, - "pascalCase": { - "unsafeName": "Object", - "safeName": "Object" - } - } + "value": { + "type": "named", + "value": "type_types/object:DocumentedUnknownType" } - }, - "typeReference": { - "type": "unknown" } }, "type_types/union:Animal": { @@ -90665,7 +92295,156 @@ }, "location": { "method": "POST", - "path": "/container/list-of-primitives" + "path": "/container/list-of-primitives" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": { + "type": "typeReference", + "value": { + "type": "list", + "value": { + "type": "primitive", + "value": "STRING" + } + } + } + }, + "response": { + "type": "json" + }, + "examples": null + }, + "endpoint_endpoints/container.getAndReturnListOfObjects": { + "auth": { + "type": "bearer", + "token": { + "originalName": "token", + "camelCase": { + "unsafeName": "token", + "safeName": "token" + }, + "snakeCase": { + "unsafeName": "token", + "safeName": "token" + }, + "screamingSnakeCase": { + "unsafeName": "TOKEN", + "safeName": "TOKEN" + }, + "pascalCase": { + "unsafeName": "Token", + "safeName": "Token" + } + } + }, + "declaration": { + "name": { + "originalName": "getAndReturnListOfObjects", + "camelCase": { + "unsafeName": "getAndReturnListOfObjects", + "safeName": "getAndReturnListOfObjects" + }, + "snakeCase": { + "unsafeName": "get_and_return_list_of_objects", + "safeName": "get_and_return_list_of_objects" + }, + "screamingSnakeCase": { + "unsafeName": "GET_AND_RETURN_LIST_OF_OBJECTS", + "safeName": "GET_AND_RETURN_LIST_OF_OBJECTS" + }, + "pascalCase": { + "unsafeName": "GetAndReturnListOfObjects", + "safeName": "GetAndReturnListOfObjects" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "endpoints", + "camelCase": { + "unsafeName": "endpoints", + "safeName": "endpoints" + }, + "snakeCase": { + "unsafeName": "endpoints", + "safeName": "endpoints" + }, + "screamingSnakeCase": { + "unsafeName": "ENDPOINTS", + "safeName": "ENDPOINTS" + }, + "pascalCase": { + "unsafeName": "Endpoints", + "safeName": "Endpoints" + } + }, + { + "originalName": "container", + "camelCase": { + "unsafeName": "container", + "safeName": "container" + }, + "snakeCase": { + "unsafeName": "container", + "safeName": "container" + }, + "screamingSnakeCase": { + "unsafeName": "CONTAINER", + "safeName": "CONTAINER" + }, + "pascalCase": { + "unsafeName": "Container", + "safeName": "Container" + } + } + ], + "packagePath": [ + { + "originalName": "endpoints", + "camelCase": { + "unsafeName": "endpoints", + "safeName": "endpoints" + }, + "snakeCase": { + "unsafeName": "endpoints", + "safeName": "endpoints" + }, + "screamingSnakeCase": { + "unsafeName": "ENDPOINTS", + "safeName": "ENDPOINTS" + }, + "pascalCase": { + "unsafeName": "Endpoints", + "safeName": "Endpoints" + } + } + ], + "file": { + "originalName": "container", + "camelCase": { + "unsafeName": "container", + "safeName": "container" + }, + "snakeCase": { + "unsafeName": "container", + "safeName": "container" + }, + "screamingSnakeCase": { + "unsafeName": "CONTAINER", + "safeName": "CONTAINER" + }, + "pascalCase": { + "unsafeName": "Container", + "safeName": "Container" + } + } + } + }, + "location": { + "method": "POST", + "path": "/container/list-of-objects" }, "request": { "type": "body", @@ -90674,6 +92453,155 @@ "type": "typeReference", "value": { "type": "list", + "value": { + "type": "named", + "value": "type_types/object:ObjectWithRequiredField" + } + } + } + }, + "response": { + "type": "json" + }, + "examples": null + }, + "endpoint_endpoints/container.getAndReturnSetOfPrimitives": { + "auth": { + "type": "bearer", + "token": { + "originalName": "token", + "camelCase": { + "unsafeName": "token", + "safeName": "token" + }, + "snakeCase": { + "unsafeName": "token", + "safeName": "token" + }, + "screamingSnakeCase": { + "unsafeName": "TOKEN", + "safeName": "TOKEN" + }, + "pascalCase": { + "unsafeName": "Token", + "safeName": "Token" + } + } + }, + "declaration": { + "name": { + "originalName": "getAndReturnSetOfPrimitives", + "camelCase": { + "unsafeName": "getAndReturnSetOfPrimitives", + "safeName": "getAndReturnSetOfPrimitives" + }, + "snakeCase": { + "unsafeName": "get_and_return_set_of_primitives", + "safeName": "get_and_return_set_of_primitives" + }, + "screamingSnakeCase": { + "unsafeName": "GET_AND_RETURN_SET_OF_PRIMITIVES", + "safeName": "GET_AND_RETURN_SET_OF_PRIMITIVES" + }, + "pascalCase": { + "unsafeName": "GetAndReturnSetOfPrimitives", + "safeName": "GetAndReturnSetOfPrimitives" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "endpoints", + "camelCase": { + "unsafeName": "endpoints", + "safeName": "endpoints" + }, + "snakeCase": { + "unsafeName": "endpoints", + "safeName": "endpoints" + }, + "screamingSnakeCase": { + "unsafeName": "ENDPOINTS", + "safeName": "ENDPOINTS" + }, + "pascalCase": { + "unsafeName": "Endpoints", + "safeName": "Endpoints" + } + }, + { + "originalName": "container", + "camelCase": { + "unsafeName": "container", + "safeName": "container" + }, + "snakeCase": { + "unsafeName": "container", + "safeName": "container" + }, + "screamingSnakeCase": { + "unsafeName": "CONTAINER", + "safeName": "CONTAINER" + }, + "pascalCase": { + "unsafeName": "Container", + "safeName": "Container" + } + } + ], + "packagePath": [ + { + "originalName": "endpoints", + "camelCase": { + "unsafeName": "endpoints", + "safeName": "endpoints" + }, + "snakeCase": { + "unsafeName": "endpoints", + "safeName": "endpoints" + }, + "screamingSnakeCase": { + "unsafeName": "ENDPOINTS", + "safeName": "ENDPOINTS" + }, + "pascalCase": { + "unsafeName": "Endpoints", + "safeName": "Endpoints" + } + } + ], + "file": { + "originalName": "container", + "camelCase": { + "unsafeName": "container", + "safeName": "container" + }, + "snakeCase": { + "unsafeName": "container", + "safeName": "container" + }, + "screamingSnakeCase": { + "unsafeName": "CONTAINER", + "safeName": "CONTAINER" + }, + "pascalCase": { + "unsafeName": "Container", + "safeName": "Container" + } + } + } + }, + "location": { + "method": "POST", + "path": "/container/set-of-primitives" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": { + "type": "typeReference", + "value": { + "type": "set", "value": { "type": "primitive", "value": "STRING" @@ -90686,7 +92614,7 @@ }, "examples": null }, - "endpoint_endpoints/container.getAndReturnListOfObjects": { + "endpoint_endpoints/container.getAndReturnSetOfObjects": { "auth": { "type": "bearer", "token": { @@ -90711,22 +92639,22 @@ }, "declaration": { "name": { - "originalName": "getAndReturnListOfObjects", + "originalName": "getAndReturnSetOfObjects", "camelCase": { - "unsafeName": "getAndReturnListOfObjects", - "safeName": "getAndReturnListOfObjects" + "unsafeName": "getAndReturnSetOfObjects", + "safeName": "getAndReturnSetOfObjects" }, "snakeCase": { - "unsafeName": "get_and_return_list_of_objects", - "safeName": "get_and_return_list_of_objects" + "unsafeName": "get_and_return_set_of_objects", + "safeName": "get_and_return_set_of_objects" }, "screamingSnakeCase": { - "unsafeName": "GET_AND_RETURN_LIST_OF_OBJECTS", - "safeName": "GET_AND_RETURN_LIST_OF_OBJECTS" + "unsafeName": "GET_AND_RETURN_SET_OF_OBJECTS", + "safeName": "GET_AND_RETURN_SET_OF_OBJECTS" }, "pascalCase": { - "unsafeName": "GetAndReturnListOfObjects", - "safeName": "GetAndReturnListOfObjects" + "unsafeName": "GetAndReturnSetOfObjects", + "safeName": "GetAndReturnSetOfObjects" } }, "fernFilepath": { @@ -90814,7 +92742,7 @@ }, "location": { "method": "POST", - "path": "/container/list-of-objects" + "path": "/container/set-of-objects" }, "request": { "type": "body", @@ -90822,7 +92750,7 @@ "body": { "type": "typeReference", "value": { - "type": "list", + "type": "set", "value": { "type": "named", "value": "type_types/object:ObjectWithRequiredField" @@ -90835,7 +92763,7 @@ }, "examples": null }, - "endpoint_endpoints/container.getAndReturnSetOfPrimitives": { + "endpoint_endpoints/container.getAndReturnMapPrimToPrim": { "auth": { "type": "bearer", "token": { @@ -90860,22 +92788,22 @@ }, "declaration": { "name": { - "originalName": "getAndReturnSetOfPrimitives", + "originalName": "getAndReturnMapPrimToPrim", "camelCase": { - "unsafeName": "getAndReturnSetOfPrimitives", - "safeName": "getAndReturnSetOfPrimitives" + "unsafeName": "getAndReturnMapPrimToPrim", + "safeName": "getAndReturnMapPrimToPrim" }, "snakeCase": { - "unsafeName": "get_and_return_set_of_primitives", - "safeName": "get_and_return_set_of_primitives" + "unsafeName": "get_and_return_map_prim_to_prim", + "safeName": "get_and_return_map_prim_to_prim" }, "screamingSnakeCase": { - "unsafeName": "GET_AND_RETURN_SET_OF_PRIMITIVES", - "safeName": "GET_AND_RETURN_SET_OF_PRIMITIVES" + "unsafeName": "GET_AND_RETURN_MAP_PRIM_TO_PRIM", + "safeName": "GET_AND_RETURN_MAP_PRIM_TO_PRIM" }, "pascalCase": { - "unsafeName": "GetAndReturnSetOfPrimitives", - "safeName": "GetAndReturnSetOfPrimitives" + "unsafeName": "GetAndReturnMapPrimToPrim", + "safeName": "GetAndReturnMapPrimToPrim" } }, "fernFilepath": { @@ -90963,7 +92891,7 @@ }, "location": { "method": "POST", - "path": "/container/set-of-primitives" + "path": "/container/map-prim-to-prim" }, "request": { "type": "body", @@ -90971,7 +92899,11 @@ "body": { "type": "typeReference", "value": { - "type": "set", + "type": "map", + "key": { + "type": "primitive", + "value": "STRING" + }, "value": { "type": "primitive", "value": "STRING" @@ -90984,7 +92916,7 @@ }, "examples": null }, - "endpoint_endpoints/container.getAndReturnSetOfObjects": { + "endpoint_endpoints/container.getAndReturnMapOfPrimToObject": { "auth": { "type": "bearer", "token": { @@ -91009,22 +92941,22 @@ }, "declaration": { "name": { - "originalName": "getAndReturnSetOfObjects", + "originalName": "getAndReturnMapOfPrimToObject", "camelCase": { - "unsafeName": "getAndReturnSetOfObjects", - "safeName": "getAndReturnSetOfObjects" + "unsafeName": "getAndReturnMapOfPrimToObject", + "safeName": "getAndReturnMapOfPrimToObject" }, "snakeCase": { - "unsafeName": "get_and_return_set_of_objects", - "safeName": "get_and_return_set_of_objects" + "unsafeName": "get_and_return_map_of_prim_to_object", + "safeName": "get_and_return_map_of_prim_to_object" }, "screamingSnakeCase": { - "unsafeName": "GET_AND_RETURN_SET_OF_OBJECTS", - "safeName": "GET_AND_RETURN_SET_OF_OBJECTS" + "unsafeName": "GET_AND_RETURN_MAP_OF_PRIM_TO_OBJECT", + "safeName": "GET_AND_RETURN_MAP_OF_PRIM_TO_OBJECT" }, "pascalCase": { - "unsafeName": "GetAndReturnSetOfObjects", - "safeName": "GetAndReturnSetOfObjects" + "unsafeName": "GetAndReturnMapOfPrimToObject", + "safeName": "GetAndReturnMapOfPrimToObject" } }, "fernFilepath": { @@ -91112,7 +93044,7 @@ }, "location": { "method": "POST", - "path": "/container/set-of-objects" + "path": "/container/map-prim-to-object" }, "request": { "type": "body", @@ -91120,7 +93052,11 @@ "body": { "type": "typeReference", "value": { - "type": "set", + "type": "map", + "key": { + "type": "primitive", + "value": "STRING" + }, "value": { "type": "named", "value": "type_types/object:ObjectWithRequiredField" @@ -91133,7 +93069,7 @@ }, "examples": null }, - "endpoint_endpoints/container.getAndReturnMapPrimToPrim": { + "endpoint_endpoints/container.getAndReturnMapOfPrimToUndiscriminatedUnion": { "auth": { "type": "bearer", "token": { @@ -91158,22 +93094,22 @@ }, "declaration": { "name": { - "originalName": "getAndReturnMapPrimToPrim", + "originalName": "getAndReturnMapOfPrimToUndiscriminatedUnion", "camelCase": { - "unsafeName": "getAndReturnMapPrimToPrim", - "safeName": "getAndReturnMapPrimToPrim" + "unsafeName": "getAndReturnMapOfPrimToUndiscriminatedUnion", + "safeName": "getAndReturnMapOfPrimToUndiscriminatedUnion" }, "snakeCase": { - "unsafeName": "get_and_return_map_prim_to_prim", - "safeName": "get_and_return_map_prim_to_prim" + "unsafeName": "get_and_return_map_of_prim_to_undiscriminated_union", + "safeName": "get_and_return_map_of_prim_to_undiscriminated_union" }, "screamingSnakeCase": { - "unsafeName": "GET_AND_RETURN_MAP_PRIM_TO_PRIM", - "safeName": "GET_AND_RETURN_MAP_PRIM_TO_PRIM" + "unsafeName": "GET_AND_RETURN_MAP_OF_PRIM_TO_UNDISCRIMINATED_UNION", + "safeName": "GET_AND_RETURN_MAP_OF_PRIM_TO_UNDISCRIMINATED_UNION" }, "pascalCase": { - "unsafeName": "GetAndReturnMapPrimToPrim", - "safeName": "GetAndReturnMapPrimToPrim" + "unsafeName": "GetAndReturnMapOfPrimToUndiscriminatedUnion", + "safeName": "GetAndReturnMapOfPrimToUndiscriminatedUnion" } }, "fernFilepath": { @@ -91261,7 +93197,7 @@ }, "location": { "method": "POST", - "path": "/container/map-prim-to-prim" + "path": "/container/map-prim-to-union" }, "request": { "type": "body", @@ -91275,8 +93211,8 @@ "value": "STRING" }, "value": { - "type": "primitive", - "value": "STRING" + "type": "named", + "value": "type_types/union:MixedType" } } } @@ -91286,7 +93222,7 @@ }, "examples": null }, - "endpoint_endpoints/container.getAndReturnMapOfPrimToObject": { + "endpoint_endpoints/container.getAndReturnOptional": { "auth": { "type": "bearer", "token": { @@ -91311,22 +93247,22 @@ }, "declaration": { "name": { - "originalName": "getAndReturnMapOfPrimToObject", + "originalName": "getAndReturnOptional", "camelCase": { - "unsafeName": "getAndReturnMapOfPrimToObject", - "safeName": "getAndReturnMapOfPrimToObject" + "unsafeName": "getAndReturnOptional", + "safeName": "getAndReturnOptional" }, "snakeCase": { - "unsafeName": "get_and_return_map_of_prim_to_object", - "safeName": "get_and_return_map_of_prim_to_object" + "unsafeName": "get_and_return_optional", + "safeName": "get_and_return_optional" }, "screamingSnakeCase": { - "unsafeName": "GET_AND_RETURN_MAP_OF_PRIM_TO_OBJECT", - "safeName": "GET_AND_RETURN_MAP_OF_PRIM_TO_OBJECT" + "unsafeName": "GET_AND_RETURN_OPTIONAL", + "safeName": "GET_AND_RETURN_OPTIONAL" }, "pascalCase": { - "unsafeName": "GetAndReturnMapOfPrimToObject", - "safeName": "GetAndReturnMapOfPrimToObject" + "unsafeName": "GetAndReturnOptional", + "safeName": "GetAndReturnOptional" } }, "fernFilepath": { @@ -91414,7 +93350,7 @@ }, "location": { "method": "POST", - "path": "/container/map-prim-to-object" + "path": "/container/opt-objects" }, "request": { "type": "body", @@ -91422,11 +93358,7 @@ "body": { "type": "typeReference", "value": { - "type": "map", - "key": { - "type": "primitive", - "value": "STRING" - }, + "type": "optional", "value": { "type": "named", "value": "type_types/object:ObjectWithRequiredField" @@ -91439,7 +93371,7 @@ }, "examples": null }, - "endpoint_endpoints/container.getAndReturnMapOfPrimToUndiscriminatedUnion": { + "endpoint_endpoints/content-type.postJsonPatchContentType": { "auth": { "type": "bearer", "token": { @@ -91464,22 +93396,22 @@ }, "declaration": { "name": { - "originalName": "getAndReturnMapOfPrimToUndiscriminatedUnion", + "originalName": "postJsonPatchContentType", "camelCase": { - "unsafeName": "getAndReturnMapOfPrimToUndiscriminatedUnion", - "safeName": "getAndReturnMapOfPrimToUndiscriminatedUnion" + "unsafeName": "postJSONPatchContentType", + "safeName": "postJSONPatchContentType" }, "snakeCase": { - "unsafeName": "get_and_return_map_of_prim_to_undiscriminated_union", - "safeName": "get_and_return_map_of_prim_to_undiscriminated_union" + "unsafeName": "post_json_patch_content_type", + "safeName": "post_json_patch_content_type" }, "screamingSnakeCase": { - "unsafeName": "GET_AND_RETURN_MAP_OF_PRIM_TO_UNDISCRIMINATED_UNION", - "safeName": "GET_AND_RETURN_MAP_OF_PRIM_TO_UNDISCRIMINATED_UNION" + "unsafeName": "POST_JSON_PATCH_CONTENT_TYPE", + "safeName": "POST_JSON_PATCH_CONTENT_TYPE" }, "pascalCase": { - "unsafeName": "GetAndReturnMapOfPrimToUndiscriminatedUnion", - "safeName": "GetAndReturnMapOfPrimToUndiscriminatedUnion" + "unsafeName": "PostJSONPatchContentType", + "safeName": "PostJSONPatchContentType" } }, "fernFilepath": { @@ -91504,22 +93436,22 @@ } }, { - "originalName": "container", + "originalName": "content-type", "camelCase": { - "unsafeName": "container", - "safeName": "container" + "unsafeName": "contentType", + "safeName": "contentType" }, "snakeCase": { - "unsafeName": "container", - "safeName": "container" + "unsafeName": "content_type", + "safeName": "content_type" }, "screamingSnakeCase": { - "unsafeName": "CONTAINER", - "safeName": "CONTAINER" + "unsafeName": "CONTENT_TYPE", + "safeName": "CONTENT_TYPE" }, "pascalCase": { - "unsafeName": "Container", - "safeName": "Container" + "unsafeName": "ContentType", + "safeName": "ContentType" } } ], @@ -91545,29 +93477,29 @@ } ], "file": { - "originalName": "container", + "originalName": "content-type", "camelCase": { - "unsafeName": "container", - "safeName": "container" + "unsafeName": "contentType", + "safeName": "contentType" }, "snakeCase": { - "unsafeName": "container", - "safeName": "container" + "unsafeName": "content_type", + "safeName": "content_type" }, "screamingSnakeCase": { - "unsafeName": "CONTAINER", - "safeName": "CONTAINER" + "unsafeName": "CONTENT_TYPE", + "safeName": "CONTENT_TYPE" }, "pascalCase": { - "unsafeName": "Container", - "safeName": "Container" + "unsafeName": "ContentType", + "safeName": "ContentType" } } } }, "location": { "method": "POST", - "path": "/container/map-prim-to-union" + "path": "/foo/bar" }, "request": { "type": "body", @@ -91575,15 +93507,8 @@ "body": { "type": "typeReference", "value": { - "type": "map", - "key": { - "type": "primitive", - "value": "STRING" - }, - "value": { - "type": "named", - "value": "type_types/union:MixedType" - } + "type": "named", + "value": "type_types/object:ObjectWithOptionalField" } } }, @@ -91592,7 +93517,7 @@ }, "examples": null }, - "endpoint_endpoints/container.getAndReturnOptional": { + "endpoint_endpoints/content-type.postJsonPatchContentWithCharsetType": { "auth": { "type": "bearer", "token": { @@ -91617,22 +93542,22 @@ }, "declaration": { "name": { - "originalName": "getAndReturnOptional", + "originalName": "postJsonPatchContentWithCharsetType", "camelCase": { - "unsafeName": "getAndReturnOptional", - "safeName": "getAndReturnOptional" + "unsafeName": "postJSONPatchContentWithCharsetType", + "safeName": "postJSONPatchContentWithCharsetType" }, "snakeCase": { - "unsafeName": "get_and_return_optional", - "safeName": "get_and_return_optional" + "unsafeName": "post_json_patch_content_with_charset_type", + "safeName": "post_json_patch_content_with_charset_type" }, "screamingSnakeCase": { - "unsafeName": "GET_AND_RETURN_OPTIONAL", - "safeName": "GET_AND_RETURN_OPTIONAL" + "unsafeName": "POST_JSON_PATCH_CONTENT_WITH_CHARSET_TYPE", + "safeName": "POST_JSON_PATCH_CONTENT_WITH_CHARSET_TYPE" }, "pascalCase": { - "unsafeName": "GetAndReturnOptional", - "safeName": "GetAndReturnOptional" + "unsafeName": "PostJSONPatchContentWithCharsetType", + "safeName": "PostJSONPatchContentWithCharsetType" } }, "fernFilepath": { @@ -91657,22 +93582,22 @@ } }, { - "originalName": "container", + "originalName": "content-type", "camelCase": { - "unsafeName": "container", - "safeName": "container" + "unsafeName": "contentType", + "safeName": "contentType" }, "snakeCase": { - "unsafeName": "container", - "safeName": "container" + "unsafeName": "content_type", + "safeName": "content_type" }, "screamingSnakeCase": { - "unsafeName": "CONTAINER", - "safeName": "CONTAINER" + "unsafeName": "CONTENT_TYPE", + "safeName": "CONTENT_TYPE" }, "pascalCase": { - "unsafeName": "Container", - "safeName": "Container" + "unsafeName": "ContentType", + "safeName": "ContentType" } } ], @@ -91698,29 +93623,29 @@ } ], "file": { - "originalName": "container", + "originalName": "content-type", "camelCase": { - "unsafeName": "container", - "safeName": "container" + "unsafeName": "contentType", + "safeName": "contentType" }, "snakeCase": { - "unsafeName": "container", - "safeName": "container" + "unsafeName": "content_type", + "safeName": "content_type" }, "screamingSnakeCase": { - "unsafeName": "CONTAINER", - "safeName": "CONTAINER" + "unsafeName": "CONTENT_TYPE", + "safeName": "CONTENT_TYPE" }, "pascalCase": { - "unsafeName": "Container", - "safeName": "Container" + "unsafeName": "ContentType", + "safeName": "ContentType" } } } }, "location": { "method": "POST", - "path": "/container/opt-objects" + "path": "/foo/baz" }, "request": { "type": "body", @@ -91728,11 +93653,8 @@ "body": { "type": "typeReference", "value": { - "type": "optional", - "value": { - "type": "named", - "value": "type_types/object:ObjectWithRequiredField" - } + "type": "named", + "value": "type_types/object:ObjectWithOptionalField" } } }, @@ -91741,7 +93663,7 @@ }, "examples": null }, - "endpoint_endpoints/content-type.postJsonPatchContentType": { + "endpoint_endpoints/enum.getAndReturnEnum": { "auth": { "type": "bearer", "token": { @@ -91766,22 +93688,22 @@ }, "declaration": { "name": { - "originalName": "postJsonPatchContentType", + "originalName": "getAndReturnEnum", "camelCase": { - "unsafeName": "postJSONPatchContentType", - "safeName": "postJSONPatchContentType" + "unsafeName": "getAndReturnEnum", + "safeName": "getAndReturnEnum" }, "snakeCase": { - "unsafeName": "post_json_patch_content_type", - "safeName": "post_json_patch_content_type" + "unsafeName": "get_and_return_enum", + "safeName": "get_and_return_enum" }, "screamingSnakeCase": { - "unsafeName": "POST_JSON_PATCH_CONTENT_TYPE", - "safeName": "POST_JSON_PATCH_CONTENT_TYPE" + "unsafeName": "GET_AND_RETURN_ENUM", + "safeName": "GET_AND_RETURN_ENUM" }, "pascalCase": { - "unsafeName": "PostJSONPatchContentType", - "safeName": "PostJSONPatchContentType" + "unsafeName": "GetAndReturnEnum", + "safeName": "GetAndReturnEnum" } }, "fernFilepath": { @@ -91806,22 +93728,22 @@ } }, { - "originalName": "content-type", + "originalName": "enum", "camelCase": { - "unsafeName": "contentType", - "safeName": "contentType" + "unsafeName": "enum", + "safeName": "enum" }, "snakeCase": { - "unsafeName": "content_type", - "safeName": "content_type" + "unsafeName": "enum", + "safeName": "enum" }, "screamingSnakeCase": { - "unsafeName": "CONTENT_TYPE", - "safeName": "CONTENT_TYPE" + "unsafeName": "ENUM", + "safeName": "ENUM" }, "pascalCase": { - "unsafeName": "ContentType", - "safeName": "ContentType" + "unsafeName": "Enum", + "safeName": "Enum" } } ], @@ -91847,29 +93769,29 @@ } ], "file": { - "originalName": "content-type", + "originalName": "enum", "camelCase": { - "unsafeName": "contentType", - "safeName": "contentType" + "unsafeName": "enum", + "safeName": "enum" }, "snakeCase": { - "unsafeName": "content_type", - "safeName": "content_type" + "unsafeName": "enum", + "safeName": "enum" }, "screamingSnakeCase": { - "unsafeName": "CONTENT_TYPE", - "safeName": "CONTENT_TYPE" + "unsafeName": "ENUM", + "safeName": "ENUM" }, "pascalCase": { - "unsafeName": "ContentType", - "safeName": "ContentType" + "unsafeName": "Enum", + "safeName": "Enum" } } } }, "location": { "method": "POST", - "path": "/foo/bar" + "path": "/enum" }, "request": { "type": "body", @@ -91878,7 +93800,7 @@ "type": "typeReference", "value": { "type": "named", - "value": "type_types/object:ObjectWithOptionalField" + "value": "type_types/enum:WeatherReport" } } }, @@ -91887,7 +93809,7 @@ }, "examples": null }, - "endpoint_endpoints/content-type.postJsonPatchContentWithCharsetType": { + "endpoint_endpoints/http-methods.testGet": { "auth": { "type": "bearer", "token": { @@ -91912,22 +93834,22 @@ }, "declaration": { "name": { - "originalName": "postJsonPatchContentWithCharsetType", + "originalName": "testGet", "camelCase": { - "unsafeName": "postJSONPatchContentWithCharsetType", - "safeName": "postJSONPatchContentWithCharsetType" + "unsafeName": "testGet", + "safeName": "testGet" }, "snakeCase": { - "unsafeName": "post_json_patch_content_with_charset_type", - "safeName": "post_json_patch_content_with_charset_type" + "unsafeName": "test_get", + "safeName": "test_get" }, "screamingSnakeCase": { - "unsafeName": "POST_JSON_PATCH_CONTENT_WITH_CHARSET_TYPE", - "safeName": "POST_JSON_PATCH_CONTENT_WITH_CHARSET_TYPE" + "unsafeName": "TEST_GET", + "safeName": "TEST_GET" }, "pascalCase": { - "unsafeName": "PostJSONPatchContentWithCharsetType", - "safeName": "PostJSONPatchContentWithCharsetType" + "unsafeName": "TestGet", + "safeName": "TestGet" } }, "fernFilepath": { @@ -91952,22 +93874,22 @@ } }, { - "originalName": "content-type", + "originalName": "http-methods", "camelCase": { - "unsafeName": "contentType", - "safeName": "contentType" + "unsafeName": "httpMethods", + "safeName": "httpMethods" }, "snakeCase": { - "unsafeName": "content_type", - "safeName": "content_type" + "unsafeName": "http_methods", + "safeName": "http_methods" }, "screamingSnakeCase": { - "unsafeName": "CONTENT_TYPE", - "safeName": "CONTENT_TYPE" + "unsafeName": "HTTP_METHODS", + "safeName": "HTTP_METHODS" }, "pascalCase": { - "unsafeName": "ContentType", - "safeName": "ContentType" + "unsafeName": "HTTPMethods", + "safeName": "HTTPMethods" } } ], @@ -91993,47 +93915,72 @@ } ], "file": { - "originalName": "content-type", + "originalName": "http-methods", "camelCase": { - "unsafeName": "contentType", - "safeName": "contentType" + "unsafeName": "httpMethods", + "safeName": "httpMethods" }, "snakeCase": { - "unsafeName": "content_type", - "safeName": "content_type" + "unsafeName": "http_methods", + "safeName": "http_methods" }, "screamingSnakeCase": { - "unsafeName": "CONTENT_TYPE", - "safeName": "CONTENT_TYPE" + "unsafeName": "HTTP_METHODS", + "safeName": "HTTP_METHODS" }, "pascalCase": { - "unsafeName": "ContentType", - "safeName": "ContentType" + "unsafeName": "HTTPMethods", + "safeName": "HTTPMethods" } } } }, "location": { - "method": "POST", - "path": "/foo/baz" + "method": "GET", + "path": "/http-methods/{id}" }, "request": { "type": "body", - "pathParameters": [], - "body": { - "type": "typeReference", - "value": { - "type": "named", - "value": "type_types/object:ObjectWithOptionalField" + "pathParameters": [ + { + "name": { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "wireValue": "id" + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null } - } + ], + "body": null }, "response": { "type": "json" }, "examples": null }, - "endpoint_endpoints/enum.getAndReturnEnum": { + "endpoint_endpoints/http-methods.testPost": { "auth": { "type": "bearer", "token": { @@ -92058,22 +94005,22 @@ }, "declaration": { "name": { - "originalName": "getAndReturnEnum", + "originalName": "testPost", "camelCase": { - "unsafeName": "getAndReturnEnum", - "safeName": "getAndReturnEnum" + "unsafeName": "testPost", + "safeName": "testPost" }, "snakeCase": { - "unsafeName": "get_and_return_enum", - "safeName": "get_and_return_enum" + "unsafeName": "test_post", + "safeName": "test_post" }, "screamingSnakeCase": { - "unsafeName": "GET_AND_RETURN_ENUM", - "safeName": "GET_AND_RETURN_ENUM" + "unsafeName": "TEST_POST", + "safeName": "TEST_POST" }, "pascalCase": { - "unsafeName": "GetAndReturnEnum", - "safeName": "GetAndReturnEnum" + "unsafeName": "TestPost", + "safeName": "TestPost" } }, "fernFilepath": { @@ -92098,22 +94045,22 @@ } }, { - "originalName": "enum", + "originalName": "http-methods", "camelCase": { - "unsafeName": "enum", - "safeName": "enum" + "unsafeName": "httpMethods", + "safeName": "httpMethods" }, "snakeCase": { - "unsafeName": "enum", - "safeName": "enum" + "unsafeName": "http_methods", + "safeName": "http_methods" }, "screamingSnakeCase": { - "unsafeName": "ENUM", - "safeName": "ENUM" + "unsafeName": "HTTP_METHODS", + "safeName": "HTTP_METHODS" }, "pascalCase": { - "unsafeName": "Enum", - "safeName": "Enum" + "unsafeName": "HTTPMethods", + "safeName": "HTTPMethods" } } ], @@ -92139,29 +94086,29 @@ } ], "file": { - "originalName": "enum", + "originalName": "http-methods", "camelCase": { - "unsafeName": "enum", - "safeName": "enum" + "unsafeName": "httpMethods", + "safeName": "httpMethods" }, "snakeCase": { - "unsafeName": "enum", - "safeName": "enum" + "unsafeName": "http_methods", + "safeName": "http_methods" }, "screamingSnakeCase": { - "unsafeName": "ENUM", - "safeName": "ENUM" + "unsafeName": "HTTP_METHODS", + "safeName": "HTTP_METHODS" }, "pascalCase": { - "unsafeName": "Enum", - "safeName": "Enum" + "unsafeName": "HTTPMethods", + "safeName": "HTTPMethods" } } } }, "location": { "method": "POST", - "path": "/enum" + "path": "/http-methods" }, "request": { "type": "body", @@ -92170,7 +94117,7 @@ "type": "typeReference", "value": { "type": "named", - "value": "type_types/enum:WeatherReport" + "value": "type_types/object:ObjectWithRequiredField" } } }, @@ -92179,7 +94126,7 @@ }, "examples": null }, - "endpoint_endpoints/http-methods.testGet": { + "endpoint_endpoints/http-methods.testPut": { "auth": { "type": "bearer", "token": { @@ -92204,22 +94151,22 @@ }, "declaration": { "name": { - "originalName": "testGet", + "originalName": "testPut", "camelCase": { - "unsafeName": "testGet", - "safeName": "testGet" + "unsafeName": "testPut", + "safeName": "testPut" }, "snakeCase": { - "unsafeName": "test_get", - "safeName": "test_get" + "unsafeName": "test_put", + "safeName": "test_put" }, "screamingSnakeCase": { - "unsafeName": "TEST_GET", - "safeName": "TEST_GET" + "unsafeName": "TEST_PUT", + "safeName": "TEST_PUT" }, "pascalCase": { - "unsafeName": "TestGet", - "safeName": "TestGet" + "unsafeName": "TestPut", + "safeName": "TestPut" } }, "fernFilepath": { @@ -92306,7 +94253,7 @@ } }, "location": { - "method": "GET", + "method": "PUT", "path": "/http-methods/{id}" }, "request": { @@ -92343,14 +94290,20 @@ "variable": null } ], - "body": null + "body": { + "type": "typeReference", + "value": { + "type": "named", + "value": "type_types/object:ObjectWithRequiredField" + } + } }, "response": { "type": "json" }, "examples": null }, - "endpoint_endpoints/http-methods.testPost": { + "endpoint_endpoints/http-methods.testPatch": { "auth": { "type": "bearer", "token": { @@ -92375,22 +94328,22 @@ }, "declaration": { "name": { - "originalName": "testPost", + "originalName": "testPatch", "camelCase": { - "unsafeName": "testPost", - "safeName": "testPost" + "unsafeName": "testPatch", + "safeName": "testPatch" }, "snakeCase": { - "unsafeName": "test_post", - "safeName": "test_post" + "unsafeName": "test_patch", + "safeName": "test_patch" }, "screamingSnakeCase": { - "unsafeName": "TEST_POST", - "safeName": "TEST_POST" + "unsafeName": "TEST_PATCH", + "safeName": "TEST_PATCH" }, "pascalCase": { - "unsafeName": "TestPost", - "safeName": "TestPost" + "unsafeName": "TestPatch", + "safeName": "TestPatch" } }, "fernFilepath": { @@ -92477,17 +94430,48 @@ } }, "location": { - "method": "POST", - "path": "/http-methods" + "method": "PATCH", + "path": "/http-methods/{id}" }, "request": { "type": "body", - "pathParameters": [], + "pathParameters": [ + { + "name": { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "wireValue": "id" + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + } + ], "body": { "type": "typeReference", "value": { "type": "named", - "value": "type_types/object:ObjectWithRequiredField" + "value": "type_types/object:ObjectWithOptionalField" } } }, @@ -92496,7 +94480,7 @@ }, "examples": null }, - "endpoint_endpoints/http-methods.testPut": { + "endpoint_endpoints/http-methods.testDelete": { "auth": { "type": "bearer", "token": { @@ -92521,22 +94505,22 @@ }, "declaration": { "name": { - "originalName": "testPut", + "originalName": "testDelete", "camelCase": { - "unsafeName": "testPut", - "safeName": "testPut" + "unsafeName": "testDelete", + "safeName": "testDelete" }, "snakeCase": { - "unsafeName": "test_put", - "safeName": "test_put" + "unsafeName": "test_delete", + "safeName": "test_delete" }, "screamingSnakeCase": { - "unsafeName": "TEST_PUT", - "safeName": "TEST_PUT" + "unsafeName": "TEST_DELETE", + "safeName": "TEST_DELETE" }, "pascalCase": { - "unsafeName": "TestPut", - "safeName": "TestPut" + "unsafeName": "TestDelete", + "safeName": "TestDelete" } }, "fernFilepath": { @@ -92623,7 +94607,7 @@ } }, "location": { - "method": "PUT", + "method": "DELETE", "path": "/http-methods/{id}" }, "request": { @@ -92660,20 +94644,14 @@ "variable": null } ], - "body": { - "type": "typeReference", - "value": { - "type": "named", - "value": "type_types/object:ObjectWithRequiredField" - } - } + "body": null }, "response": { "type": "json" }, "examples": null }, - "endpoint_endpoints/http-methods.testPatch": { + "endpoint_endpoints/object.getAndReturnWithOptionalField": { "auth": { "type": "bearer", "token": { @@ -92698,22 +94676,22 @@ }, "declaration": { "name": { - "originalName": "testPatch", + "originalName": "getAndReturnWithOptionalField", "camelCase": { - "unsafeName": "testPatch", - "safeName": "testPatch" + "unsafeName": "getAndReturnWithOptionalField", + "safeName": "getAndReturnWithOptionalField" }, "snakeCase": { - "unsafeName": "test_patch", - "safeName": "test_patch" + "unsafeName": "get_and_return_with_optional_field", + "safeName": "get_and_return_with_optional_field" }, "screamingSnakeCase": { - "unsafeName": "TEST_PATCH", - "safeName": "TEST_PATCH" + "unsafeName": "GET_AND_RETURN_WITH_OPTIONAL_FIELD", + "safeName": "GET_AND_RETURN_WITH_OPTIONAL_FIELD" }, "pascalCase": { - "unsafeName": "TestPatch", - "safeName": "TestPatch" + "unsafeName": "GetAndReturnWithOptionalField", + "safeName": "GetAndReturnWithOptionalField" } }, "fernFilepath": { @@ -92738,22 +94716,22 @@ } }, { - "originalName": "http-methods", + "originalName": "object", "camelCase": { - "unsafeName": "httpMethods", - "safeName": "httpMethods" + "unsafeName": "object", + "safeName": "object" }, "snakeCase": { - "unsafeName": "http_methods", - "safeName": "http_methods" + "unsafeName": "object", + "safeName": "object" }, "screamingSnakeCase": { - "unsafeName": "HTTP_METHODS", - "safeName": "HTTP_METHODS" + "unsafeName": "OBJECT", + "safeName": "OBJECT" }, "pascalCase": { - "unsafeName": "HTTPMethods", - "safeName": "HTTPMethods" + "unsafeName": "Object", + "safeName": "Object" } } ], @@ -92779,64 +94757,33 @@ } ], "file": { - "originalName": "http-methods", + "originalName": "object", "camelCase": { - "unsafeName": "httpMethods", - "safeName": "httpMethods" + "unsafeName": "object", + "safeName": "object" }, "snakeCase": { - "unsafeName": "http_methods", - "safeName": "http_methods" + "unsafeName": "object", + "safeName": "object" }, "screamingSnakeCase": { - "unsafeName": "HTTP_METHODS", - "safeName": "HTTP_METHODS" + "unsafeName": "OBJECT", + "safeName": "OBJECT" }, "pascalCase": { - "unsafeName": "HTTPMethods", - "safeName": "HTTPMethods" + "unsafeName": "Object", + "safeName": "Object" } } } }, "location": { - "method": "PATCH", - "path": "/http-methods/{id}" + "method": "POST", + "path": "/object/get-and-return-with-optional-field" }, "request": { "type": "body", - "pathParameters": [ - { - "name": { - "name": { - "originalName": "id", - "camelCase": { - "unsafeName": "id", - "safeName": "id" - }, - "snakeCase": { - "unsafeName": "id", - "safeName": "id" - }, - "screamingSnakeCase": { - "unsafeName": "ID", - "safeName": "ID" - }, - "pascalCase": { - "unsafeName": "ID", - "safeName": "ID" - } - }, - "wireValue": "id" - }, - "typeReference": { - "type": "primitive", - "value": "STRING" - }, - "propertyAccess": null, - "variable": null - } - ], + "pathParameters": [], "body": { "type": "typeReference", "value": { @@ -92850,7 +94797,7 @@ }, "examples": null }, - "endpoint_endpoints/http-methods.testDelete": { + "endpoint_endpoints/object.getAndReturnWithRequiredField": { "auth": { "type": "bearer", "token": { @@ -92875,22 +94822,22 @@ }, "declaration": { "name": { - "originalName": "testDelete", + "originalName": "getAndReturnWithRequiredField", "camelCase": { - "unsafeName": "testDelete", - "safeName": "testDelete" + "unsafeName": "getAndReturnWithRequiredField", + "safeName": "getAndReturnWithRequiredField" }, "snakeCase": { - "unsafeName": "test_delete", - "safeName": "test_delete" + "unsafeName": "get_and_return_with_required_field", + "safeName": "get_and_return_with_required_field" }, "screamingSnakeCase": { - "unsafeName": "TEST_DELETE", - "safeName": "TEST_DELETE" + "unsafeName": "GET_AND_RETURN_WITH_REQUIRED_FIELD", + "safeName": "GET_AND_RETURN_WITH_REQUIRED_FIELD" }, "pascalCase": { - "unsafeName": "TestDelete", - "safeName": "TestDelete" + "unsafeName": "GetAndReturnWithRequiredField", + "safeName": "GetAndReturnWithRequiredField" } }, "fernFilepath": { @@ -92915,22 +94862,22 @@ } }, { - "originalName": "http-methods", + "originalName": "object", "camelCase": { - "unsafeName": "httpMethods", - "safeName": "httpMethods" + "unsafeName": "object", + "safeName": "object" }, "snakeCase": { - "unsafeName": "http_methods", - "safeName": "http_methods" + "unsafeName": "object", + "safeName": "object" }, "screamingSnakeCase": { - "unsafeName": "HTTP_METHODS", - "safeName": "HTTP_METHODS" + "unsafeName": "OBJECT", + "safeName": "OBJECT" }, "pascalCase": { - "unsafeName": "HTTPMethods", - "safeName": "HTTPMethods" + "unsafeName": "Object", + "safeName": "Object" } } ], @@ -92956,72 +94903,47 @@ } ], "file": { - "originalName": "http-methods", + "originalName": "object", "camelCase": { - "unsafeName": "httpMethods", - "safeName": "httpMethods" + "unsafeName": "object", + "safeName": "object" }, "snakeCase": { - "unsafeName": "http_methods", - "safeName": "http_methods" + "unsafeName": "object", + "safeName": "object" }, "screamingSnakeCase": { - "unsafeName": "HTTP_METHODS", - "safeName": "HTTP_METHODS" + "unsafeName": "OBJECT", + "safeName": "OBJECT" }, "pascalCase": { - "unsafeName": "HTTPMethods", - "safeName": "HTTPMethods" + "unsafeName": "Object", + "safeName": "Object" } } } }, "location": { - "method": "DELETE", - "path": "/http-methods/{id}" + "method": "POST", + "path": "/object/get-and-return-with-required-field" }, "request": { "type": "body", - "pathParameters": [ - { - "name": { - "name": { - "originalName": "id", - "camelCase": { - "unsafeName": "id", - "safeName": "id" - }, - "snakeCase": { - "unsafeName": "id", - "safeName": "id" - }, - "screamingSnakeCase": { - "unsafeName": "ID", - "safeName": "ID" - }, - "pascalCase": { - "unsafeName": "ID", - "safeName": "ID" - } - }, - "wireValue": "id" - }, - "typeReference": { - "type": "primitive", - "value": "STRING" - }, - "propertyAccess": null, - "variable": null + "pathParameters": [], + "body": { + "type": "typeReference", + "value": { + "type": "named", + "value": "type_types/object:ObjectWithRequiredField" } - ], - "body": null + } }, "response": { "type": "json" }, "examples": null }, - "endpoint_endpoints/object.getAndReturnWithOptionalField": { + "endpoint_endpoints/object.getAndReturnWithMapOfMap": { "auth": { "type": "bearer", "token": { @@ -93046,22 +94968,22 @@ }, "declaration": { "name": { - "originalName": "getAndReturnWithOptionalField", + "originalName": "getAndReturnWithMapOfMap", "camelCase": { - "unsafeName": "getAndReturnWithOptionalField", - "safeName": "getAndReturnWithOptionalField" + "unsafeName": "getAndReturnWithMapOfMap", + "safeName": "getAndReturnWithMapOfMap" }, "snakeCase": { - "unsafeName": "get_and_return_with_optional_field", - "safeName": "get_and_return_with_optional_field" + "unsafeName": "get_and_return_with_map_of_map", + "safeName": "get_and_return_with_map_of_map" }, "screamingSnakeCase": { - "unsafeName": "GET_AND_RETURN_WITH_OPTIONAL_FIELD", - "safeName": "GET_AND_RETURN_WITH_OPTIONAL_FIELD" + "unsafeName": "GET_AND_RETURN_WITH_MAP_OF_MAP", + "safeName": "GET_AND_RETURN_WITH_MAP_OF_MAP" }, "pascalCase": { - "unsafeName": "GetAndReturnWithOptionalField", - "safeName": "GetAndReturnWithOptionalField" + "unsafeName": "GetAndReturnWithMapOfMap", + "safeName": "GetAndReturnWithMapOfMap" } }, "fernFilepath": { @@ -93149,7 +95071,7 @@ }, "location": { "method": "POST", - "path": "/object/get-and-return-with-optional-field" + "path": "/object/get-and-return-with-map-of-map" }, "request": { "type": "body", @@ -93158,7 +95080,7 @@ "type": "typeReference", "value": { "type": "named", - "value": "type_types/object:ObjectWithOptionalField" + "value": "type_types/object:ObjectWithMapOfMap" } } }, @@ -93167,7 +95089,7 @@ }, "examples": null }, - "endpoint_endpoints/object.getAndReturnWithRequiredField": { + "endpoint_endpoints/object.getAndReturnNestedWithOptionalField": { "auth": { "type": "bearer", "token": { @@ -93192,22 +95114,22 @@ }, "declaration": { "name": { - "originalName": "getAndReturnWithRequiredField", + "originalName": "getAndReturnNestedWithOptionalField", "camelCase": { - "unsafeName": "getAndReturnWithRequiredField", - "safeName": "getAndReturnWithRequiredField" + "unsafeName": "getAndReturnNestedWithOptionalField", + "safeName": "getAndReturnNestedWithOptionalField" }, "snakeCase": { - "unsafeName": "get_and_return_with_required_field", - "safeName": "get_and_return_with_required_field" + "unsafeName": "get_and_return_nested_with_optional_field", + "safeName": "get_and_return_nested_with_optional_field" }, "screamingSnakeCase": { - "unsafeName": "GET_AND_RETURN_WITH_REQUIRED_FIELD", - "safeName": "GET_AND_RETURN_WITH_REQUIRED_FIELD" + "unsafeName": "GET_AND_RETURN_NESTED_WITH_OPTIONAL_FIELD", + "safeName": "GET_AND_RETURN_NESTED_WITH_OPTIONAL_FIELD" }, "pascalCase": { - "unsafeName": "GetAndReturnWithRequiredField", - "safeName": "GetAndReturnWithRequiredField" + "unsafeName": "GetAndReturnNestedWithOptionalField", + "safeName": "GetAndReturnNestedWithOptionalField" } }, "fernFilepath": { @@ -93295,7 +95217,7 @@ }, "location": { "method": "POST", - "path": "/object/get-and-return-with-required-field" + "path": "/object/get-and-return-nested-with-optional-field" }, "request": { "type": "body", @@ -93304,7 +95226,7 @@ "type": "typeReference", "value": { "type": "named", - "value": "type_types/object:ObjectWithRequiredField" + "value": "type_types/object:NestedObjectWithOptionalField" } } }, @@ -93313,7 +95235,7 @@ }, "examples": null }, - "endpoint_endpoints/object.getAndReturnWithMapOfMap": { + "endpoint_endpoints/object.getAndReturnNestedWithRequiredField": { "auth": { "type": "bearer", "token": { @@ -93338,22 +95260,22 @@ }, "declaration": { "name": { - "originalName": "getAndReturnWithMapOfMap", + "originalName": "getAndReturnNestedWithRequiredField", "camelCase": { - "unsafeName": "getAndReturnWithMapOfMap", - "safeName": "getAndReturnWithMapOfMap" + "unsafeName": "getAndReturnNestedWithRequiredField", + "safeName": "getAndReturnNestedWithRequiredField" }, "snakeCase": { - "unsafeName": "get_and_return_with_map_of_map", - "safeName": "get_and_return_with_map_of_map" + "unsafeName": "get_and_return_nested_with_required_field", + "safeName": "get_and_return_nested_with_required_field" }, "screamingSnakeCase": { - "unsafeName": "GET_AND_RETURN_WITH_MAP_OF_MAP", - "safeName": "GET_AND_RETURN_WITH_MAP_OF_MAP" + "unsafeName": "GET_AND_RETURN_NESTED_WITH_REQUIRED_FIELD", + "safeName": "GET_AND_RETURN_NESTED_WITH_REQUIRED_FIELD" }, "pascalCase": { - "unsafeName": "GetAndReturnWithMapOfMap", - "safeName": "GetAndReturnWithMapOfMap" + "unsafeName": "GetAndReturnNestedWithRequiredField", + "safeName": "GetAndReturnNestedWithRequiredField" } }, "fernFilepath": { @@ -93441,16 +95363,47 @@ }, "location": { "method": "POST", - "path": "/object/get-and-return-with-map-of-map" + "path": "/object/get-and-return-nested-with-required-field/{string}" }, "request": { "type": "body", - "pathParameters": [], + "pathParameters": [ + { + "name": { + "name": { + "originalName": "string", + "camelCase": { + "unsafeName": "string", + "safeName": "string" + }, + "snakeCase": { + "unsafeName": "string", + "safeName": "string" + }, + "screamingSnakeCase": { + "unsafeName": "STRING", + "safeName": "STRING" + }, + "pascalCase": { + "unsafeName": "String", + "safeName": "String" + } + }, + "wireValue": "string" + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + } + ], "body": { "type": "typeReference", "value": { "type": "named", - "value": "type_types/object:ObjectWithMapOfMap" + "value": "type_types/object:NestedObjectWithRequiredField" } } }, @@ -93459,7 +95412,7 @@ }, "examples": null }, - "endpoint_endpoints/object.getAndReturnNestedWithOptionalField": { + "endpoint_endpoints/object.getAndReturnNestedWithRequiredFieldAsList": { "auth": { "type": "bearer", "token": { @@ -93484,22 +95437,22 @@ }, "declaration": { "name": { - "originalName": "getAndReturnNestedWithOptionalField", + "originalName": "getAndReturnNestedWithRequiredFieldAsList", "camelCase": { - "unsafeName": "getAndReturnNestedWithOptionalField", - "safeName": "getAndReturnNestedWithOptionalField" + "unsafeName": "getAndReturnNestedWithRequiredFieldAsList", + "safeName": "getAndReturnNestedWithRequiredFieldAsList" }, "snakeCase": { - "unsafeName": "get_and_return_nested_with_optional_field", - "safeName": "get_and_return_nested_with_optional_field" + "unsafeName": "get_and_return_nested_with_required_field_as_list", + "safeName": "get_and_return_nested_with_required_field_as_list" }, "screamingSnakeCase": { - "unsafeName": "GET_AND_RETURN_NESTED_WITH_OPTIONAL_FIELD", - "safeName": "GET_AND_RETURN_NESTED_WITH_OPTIONAL_FIELD" + "unsafeName": "GET_AND_RETURN_NESTED_WITH_REQUIRED_FIELD_AS_LIST", + "safeName": "GET_AND_RETURN_NESTED_WITH_REQUIRED_FIELD_AS_LIST" }, "pascalCase": { - "unsafeName": "GetAndReturnNestedWithOptionalField", - "safeName": "GetAndReturnNestedWithOptionalField" + "unsafeName": "GetAndReturnNestedWithRequiredFieldAsList", + "safeName": "GetAndReturnNestedWithRequiredFieldAsList" } }, "fernFilepath": { @@ -93587,7 +95540,7 @@ }, "location": { "method": "POST", - "path": "/object/get-and-return-nested-with-optional-field" + "path": "/object/get-and-return-nested-with-required-field-list" }, "request": { "type": "body", @@ -93595,194 +95548,20 @@ "body": { "type": "typeReference", "value": { - "type": "named", - "value": "type_types/object:NestedObjectWithOptionalField" - } - } - }, - "response": { - "type": "json" - }, - "examples": null - }, - "endpoint_endpoints/object.getAndReturnNestedWithRequiredField": { - "auth": { - "type": "bearer", - "token": { - "originalName": "token", - "camelCase": { - "unsafeName": "token", - "safeName": "token" - }, - "snakeCase": { - "unsafeName": "token", - "safeName": "token" - }, - "screamingSnakeCase": { - "unsafeName": "TOKEN", - "safeName": "TOKEN" - }, - "pascalCase": { - "unsafeName": "Token", - "safeName": "Token" - } - } - }, - "declaration": { - "name": { - "originalName": "getAndReturnNestedWithRequiredField", - "camelCase": { - "unsafeName": "getAndReturnNestedWithRequiredField", - "safeName": "getAndReturnNestedWithRequiredField" - }, - "snakeCase": { - "unsafeName": "get_and_return_nested_with_required_field", - "safeName": "get_and_return_nested_with_required_field" - }, - "screamingSnakeCase": { - "unsafeName": "GET_AND_RETURN_NESTED_WITH_REQUIRED_FIELD", - "safeName": "GET_AND_RETURN_NESTED_WITH_REQUIRED_FIELD" - }, - "pascalCase": { - "unsafeName": "GetAndReturnNestedWithRequiredField", - "safeName": "GetAndReturnNestedWithRequiredField" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "endpoints", - "camelCase": { - "unsafeName": "endpoints", - "safeName": "endpoints" - }, - "snakeCase": { - "unsafeName": "endpoints", - "safeName": "endpoints" - }, - "screamingSnakeCase": { - "unsafeName": "ENDPOINTS", - "safeName": "ENDPOINTS" - }, - "pascalCase": { - "unsafeName": "Endpoints", - "safeName": "Endpoints" - } - }, - { - "originalName": "object", - "camelCase": { - "unsafeName": "object", - "safeName": "object" - }, - "snakeCase": { - "unsafeName": "object", - "safeName": "object" - }, - "screamingSnakeCase": { - "unsafeName": "OBJECT", - "safeName": "OBJECT" - }, - "pascalCase": { - "unsafeName": "Object", - "safeName": "Object" - } - } - ], - "packagePath": [ - { - "originalName": "endpoints", - "camelCase": { - "unsafeName": "endpoints", - "safeName": "endpoints" - }, - "snakeCase": { - "unsafeName": "endpoints", - "safeName": "endpoints" - }, - "screamingSnakeCase": { - "unsafeName": "ENDPOINTS", - "safeName": "ENDPOINTS" - }, - "pascalCase": { - "unsafeName": "Endpoints", - "safeName": "Endpoints" - } - } - ], - "file": { - "originalName": "object", - "camelCase": { - "unsafeName": "object", - "safeName": "object" - }, - "snakeCase": { - "unsafeName": "object", - "safeName": "object" - }, - "screamingSnakeCase": { - "unsafeName": "OBJECT", - "safeName": "OBJECT" - }, - "pascalCase": { - "unsafeName": "Object", - "safeName": "Object" + "type": "list", + "value": { + "type": "named", + "value": "type_types/object:NestedObjectWithRequiredField" } } } }, - "location": { - "method": "POST", - "path": "/object/get-and-return-nested-with-required-field/{string}" - }, - "request": { - "type": "body", - "pathParameters": [ - { - "name": { - "name": { - "originalName": "string", - "camelCase": { - "unsafeName": "string", - "safeName": "string" - }, - "snakeCase": { - "unsafeName": "string", - "safeName": "string" - }, - "screamingSnakeCase": { - "unsafeName": "STRING", - "safeName": "STRING" - }, - "pascalCase": { - "unsafeName": "String", - "safeName": "String" - } - }, - "wireValue": "string" - }, - "typeReference": { - "type": "primitive", - "value": "STRING" - }, - "propertyAccess": null, - "variable": null - } - ], - "body": { - "type": "typeReference", - "value": { - "type": "named", - "value": "type_types/object:NestedObjectWithRequiredField" - } - } - }, "response": { "type": "json" }, "examples": null }, - "endpoint_endpoints/object.getAndReturnNestedWithRequiredFieldAsList": { + "endpoint_endpoints/object.getAndReturnWithUnknownField": { "auth": { "type": "bearer", "token": { @@ -93807,22 +95586,22 @@ }, "declaration": { "name": { - "originalName": "getAndReturnNestedWithRequiredFieldAsList", + "originalName": "getAndReturnWithUnknownField", "camelCase": { - "unsafeName": "getAndReturnNestedWithRequiredFieldAsList", - "safeName": "getAndReturnNestedWithRequiredFieldAsList" + "unsafeName": "getAndReturnWithUnknownField", + "safeName": "getAndReturnWithUnknownField" }, "snakeCase": { - "unsafeName": "get_and_return_nested_with_required_field_as_list", - "safeName": "get_and_return_nested_with_required_field_as_list" + "unsafeName": "get_and_return_with_unknown_field", + "safeName": "get_and_return_with_unknown_field" }, "screamingSnakeCase": { - "unsafeName": "GET_AND_RETURN_NESTED_WITH_REQUIRED_FIELD_AS_LIST", - "safeName": "GET_AND_RETURN_NESTED_WITH_REQUIRED_FIELD_AS_LIST" + "unsafeName": "GET_AND_RETURN_WITH_UNKNOWN_FIELD", + "safeName": "GET_AND_RETURN_WITH_UNKNOWN_FIELD" }, "pascalCase": { - "unsafeName": "GetAndReturnNestedWithRequiredFieldAsList", - "safeName": "GetAndReturnNestedWithRequiredFieldAsList" + "unsafeName": "GetAndReturnWithUnknownField", + "safeName": "GetAndReturnWithUnknownField" } }, "fernFilepath": { @@ -93910,7 +95689,7 @@ }, "location": { "method": "POST", - "path": "/object/get-and-return-nested-with-required-field-list" + "path": "/object/get-and-return-with-unknown-field" }, "request": { "type": "body", @@ -93918,11 +95697,8 @@ "body": { "type": "typeReference", "value": { - "type": "list", - "value": { - "type": "named", - "value": "type_types/object:NestedObjectWithRequiredField" - } + "type": "named", + "value": "type_types/object:ObjectWithUnknownField" } } }, @@ -93931,7 +95707,7 @@ }, "examples": null }, - "endpoint_endpoints/object.getAndReturnWithUnknownField": { + "endpoint_endpoints/object.getAndReturnWithDocumentedUnknownType": { "auth": { "type": "bearer", "token": { @@ -93956,22 +95732,22 @@ }, "declaration": { "name": { - "originalName": "getAndReturnWithUnknownField", + "originalName": "getAndReturnWithDocumentedUnknownType", "camelCase": { - "unsafeName": "getAndReturnWithUnknownField", - "safeName": "getAndReturnWithUnknownField" + "unsafeName": "getAndReturnWithDocumentedUnknownType", + "safeName": "getAndReturnWithDocumentedUnknownType" }, "snakeCase": { - "unsafeName": "get_and_return_with_unknown_field", - "safeName": "get_and_return_with_unknown_field" + "unsafeName": "get_and_return_with_documented_unknown_type", + "safeName": "get_and_return_with_documented_unknown_type" }, "screamingSnakeCase": { - "unsafeName": "GET_AND_RETURN_WITH_UNKNOWN_FIELD", - "safeName": "GET_AND_RETURN_WITH_UNKNOWN_FIELD" + "unsafeName": "GET_AND_RETURN_WITH_DOCUMENTED_UNKNOWN_TYPE", + "safeName": "GET_AND_RETURN_WITH_DOCUMENTED_UNKNOWN_TYPE" }, "pascalCase": { - "unsafeName": "GetAndReturnWithUnknownField", - "safeName": "GetAndReturnWithUnknownField" + "unsafeName": "GetAndReturnWithDocumentedUnknownType", + "safeName": "GetAndReturnWithDocumentedUnknownType" } }, "fernFilepath": { @@ -94059,7 +95835,7 @@ }, "location": { "method": "POST", - "path": "/object/get-and-return-with-unknown-field" + "path": "/object/get-and-return-with-documented-unknown-type" }, "request": { "type": "body", @@ -94068,7 +95844,7 @@ "type": "typeReference", "value": { "type": "named", - "value": "type_types/object:ObjectWithUnknownField" + "value": "type_types/object:ObjectWithDocumentedUnknownType" } } }, @@ -94077,7 +95853,7 @@ }, "examples": null }, - "endpoint_endpoints/object.getAndReturnWithDocumentedUnknownType": { + "endpoint_endpoints/object.getAndReturnMapOfDocumentedUnknownType": { "auth": { "type": "bearer", "token": { @@ -94102,22 +95878,22 @@ }, "declaration": { "name": { - "originalName": "getAndReturnWithDocumentedUnknownType", + "originalName": "getAndReturnMapOfDocumentedUnknownType", "camelCase": { - "unsafeName": "getAndReturnWithDocumentedUnknownType", - "safeName": "getAndReturnWithDocumentedUnknownType" + "unsafeName": "getAndReturnMapOfDocumentedUnknownType", + "safeName": "getAndReturnMapOfDocumentedUnknownType" }, "snakeCase": { - "unsafeName": "get_and_return_with_documented_unknown_type", - "safeName": "get_and_return_with_documented_unknown_type" + "unsafeName": "get_and_return_map_of_documented_unknown_type", + "safeName": "get_and_return_map_of_documented_unknown_type" }, "screamingSnakeCase": { - "unsafeName": "GET_AND_RETURN_WITH_DOCUMENTED_UNKNOWN_TYPE", - "safeName": "GET_AND_RETURN_WITH_DOCUMENTED_UNKNOWN_TYPE" + "unsafeName": "GET_AND_RETURN_MAP_OF_DOCUMENTED_UNKNOWN_TYPE", + "safeName": "GET_AND_RETURN_MAP_OF_DOCUMENTED_UNKNOWN_TYPE" }, "pascalCase": { - "unsafeName": "GetAndReturnWithDocumentedUnknownType", - "safeName": "GetAndReturnWithDocumentedUnknownType" + "unsafeName": "GetAndReturnMapOfDocumentedUnknownType", + "safeName": "GetAndReturnMapOfDocumentedUnknownType" } }, "fernFilepath": { @@ -94205,7 +95981,7 @@ }, "location": { "method": "POST", - "path": "/object/get-and-return-with-documented-unknown-type" + "path": "/object/get-and-return-map-of-documented-unknown-type" }, "request": { "type": "body", @@ -94214,7 +95990,7 @@ "type": "typeReference", "value": { "type": "named", - "value": "type_types/object:ObjectWithDocumentedUnknownType" + "value": "type_types/object:MapOfDocumentedUnknownType" } } }, @@ -102325,7 +104101,8 @@ "type_types/object:ObjectWithDatetimeLikeString", "type_types/object:ObjectWithUnknownField", "type_types/object:ObjectWithDocumentedUnknownType", - "type_types/object:DocumentedUnknownType" + "type_types/object:DocumentedUnknownType", + "type_types/object:MapOfDocumentedUnknownType" ], "errors": [ "error_types/object:ObjectWithOptionalFieldError", diff --git a/packages/cli/workspace/lazy-fern-workspace/src/OSSWorkspace.ts b/packages/cli/workspace/lazy-fern-workspace/src/OSSWorkspace.ts index 4c60da035399..47de6d605295 100644 --- a/packages/cli/workspace/lazy-fern-workspace/src/OSSWorkspace.ts +++ b/packages/cli/workspace/lazy-fern-workspace/src/OSSWorkspace.ts @@ -337,6 +337,7 @@ export class OSSWorkspace extends BaseOpenAPIWorkspace { spec: document.value as any, exampleGenerationArgs: { disabled: false }, errorCollector, + authOverrides, enableUniqueErrorsPerEndpoint, settings: getOpenAPISettings({ options: document.settings }), generateV1Examples From d0c9d4823de0ed25e9d5eb9a6259f84facb55690 Mon Sep 17 00:00:00 2001 From: Naman Anand Date: Wed, 18 Mar 2026 00:04:21 +0530 Subject: [PATCH 04/11] fix(csharp): add global:: prefix for namespace/type name collisions (CS0426) (#13571) * fix: add global:: prefix for C# namespace/type name collisions (CS0426) When a client class name matches a namespace root segment (e.g., class 'Candid' in namespace 'Candid.Net'), the C# compiler resolves 'Candid.Net' as looking for a 'Net' member on the 'Candid' type instead of the 'Candid.Net' namespace. This fix: - Adds hasTypeNamespaceConflict() to NameRegistry to detect when a type name shadows a namespace root segment - Updates ClassReference.writeInternal to use global:: prefix when the first namespace segment has a type/namespace conflict - Updates Writer.stringifyImports to add global:: to using directives when the namespace root is shadowed by a type - Registers NUnit and OneOf as known built-in identifiers to prevent false positives on framework namespaces * fix(csharp): add global:: prefix for namespace/type name collisions (CS0426) * Qualify C# namespaces to avoid conflicts Add a qualifyNamespace helper to Generation (and expose via WithGeneration) so codegen can prefix namespaces with `global::` when a root segment conflicts with a type, avoiding CS0426. Pass qualifyNamespace into template contexts in CsharpProject and update templates/generators to use it (tests, JsonAssert, Http/Request/WebSocket generators and request wrappers) so emitted C# references are qualified when necessary. * fix(csharp): qualify remaining template namespaces and add regression seed fixture - Apply qualifyNamespace() to OptionalComparer.Template.cs and AdditionalPropertiesComparer.Template.cs - Add csharp-namespace-conflict seed fixture with client-class-name matching namespace root (namespace: Seed.CsharpNamespaceConflict, client-class-name: Seed) to prevent regressions * refactor(csharp): use pre-qualified namespace properties and fix CS0118 in DynamicSnippets - Add qualifiedRoot/qualifiedCore to namespaces object in generation-info.ts - Update all template files to use namespaces.qualifiedRoot/qualifiedCore - Update endpoint generators to use this.namespaces.qualifiedCore - Remove qualifyNamespace lambda from CsharpProject.ts template contexts - Remove unused qualifyNamespace method from WithGeneration base class - Fix CS0118 in ClassReference.ts when class name matches namespace root - Merge PR #13597 seed test and remove namespace-client-collision from allowedFailures - Regenerate seed output for namespace-client-collision and namespace-conflict --------- Co-authored-by: Naman Anand --- .../MultipartFormTests.Template.cs | 4 +- .../RawClientTests/RetriesTests.Template.cs | 18 +- .../AdditionalPropertiesComparer.Template.cs | 4 +- .../asIs/test/Utils/JsonAssert.Template.cs | 2 +- .../test/Utils/OptionalComparer.Template.cs | 2 +- .../csharp/codegen/src/ast/core/Writer.ts | 15 +- .../codegen/src/ast/types/ClassReference.ts | 11 +- .../codegen/src/context/generation-info.ts | 17 + .../codegen/src/context/name-registry.ts | 32 + .../endpoint/http/HttpEndpointGenerator.ts | 2 +- .../request/BytesOnlyEndpointRequest.ts | 2 +- .../request/ReferencedEndpointRequest.ts | 2 +- .../request/WrappedEndpointRequest.ts | 4 +- .../src/websocket/WebsocketClientGenerator.ts | 2 +- generators/csharp/sdk/versions.yml | 12 + .../namespace-client-collision/README.md | 4 +- .../namespace-client-collision/snippet.json | 10 +- .../Core/RawClientTests/MultipartFormTests.cs | 4 +- .../Core/RawClientTests/RetriesTests.cs | 6 +- .../Unit/MockServer/BaseMockServerTest.cs | 6 +- .../Unit/MockServer/CreateTaskTest.cs | 2 +- .../Unit/MockServer/CreateUserTest.cs | 2 +- .../Unit/MockServer/System/CreateTaskTest.cs | 4 +- .../Unit/MockServer/System/CreateUserTest.cs | 4 +- .../Unit/MockServer/System/GetUserTest.cs | 4 +- .../Utils/AdditionalPropertiesComparer.cs | 4 +- .../src/Contoso.Net.Test/Utils/JsonAssert.cs | 2 +- .../Utils/OptionalComparer.cs | 2 +- .../src/Contoso.Net/Contoso.cs | 8 +- .../Contoso.Net/Core/Public/ClientOptions.cs | 2 +- .../Contoso.Net/Core/Public/RequestOptions.cs | 2 +- .../src/Contoso.Net/IContoso.cs | 2 +- .../src/Contoso.Net/System/ISystemClient.cs | 2 +- .../src/Contoso.Net/System/SystemClient.cs | 10 +- .../src/Contoso.Net/System/Types/Task.cs | 4 +- .../src/Contoso.Net/System/Types/User.cs | 4 +- .../src/Contoso.Net/Types/Task.cs | 2 +- .../src/Contoso.Net/Types/User.cs | 2 +- .../src/SeedApi.DynamicSnippets/Example0.cs | 4 +- .../src/SeedApi.DynamicSnippets/Example1.cs | 4 +- .../src/SeedApi.DynamicSnippets/Example2.cs | 4 +- .../src/SeedApi.DynamicSnippets/Example3.cs | 4 +- .../src/SeedApi.DynamicSnippets/Example4.cs | 4 +- .../.editorconfig | 35 + .../.fern/metadata.json | 12 + .../.github/workflows/ci.yml | 52 + .../.gitignore | 484 +++++++ .../README.md | 170 +++ .../Seed.CsharpNamespaceConflict.slnx | 4 + .../reference.md | 27 + .../snippet.json | 17 + .../Core/HeadersBuilderTests.cs | 326 +++++ .../Core/Json/AdditionalPropertiesTests.cs | 365 ++++++ .../Core/Json/DateOnlyJsonTests.cs | 76 ++ .../Core/Json/DateTimeJsonTests.cs | 110 ++ .../Core/Json/JsonAccessAttributeTests.cs | 160 +++ .../Core/QueryStringBuilderTests.cs | 560 ++++++++ .../Core/QueryStringConverterTests.cs | 158 +++ .../Core/RawClientTests/MultipartFormTests.cs | 1121 +++++++++++++++++ .../RawClientTests/QueryParameterTests.cs | 108 ++ .../Core/RawClientTests/RetriesTests.cs | 406 ++++++ .../Core/WithRawResponseTests.cs | 269 ++++ ....CsharpNamespaceConflict.Test.Custom.props | 6 + .../Seed.CsharpNamespaceConflict.Test.csproj | 39 + .../TestClient.cs | 6 + .../Unit/MockServer/BaseMockServerTest.cs | 37 + .../Unit/MockServer/Tasktest/HelloTest.cs | 19 + .../Utils/AdditionalPropertiesComparer.cs | 126 ++ .../Utils/JsonAssert.cs | 19 + .../Utils/JsonElementComparer.cs | 236 ++++ .../Utils/NUnitExtensions.cs | 30 + .../Utils/OneOfComparer.cs | 86 ++ .../Utils/OptionalComparer.cs | 104 ++ .../Utils/ReadOnlyMemoryComparer.cs | 87 ++ .../A/Aa/Types/A.cs | 26 + .../A/Aa/Types/B.cs | 26 + .../A/Aa/Types/SubTestType.cs | 32 + .../B/Types/TestType.cs | 32 + .../Core/ApiResponse.cs | 13 + .../Core/BaseRequest.cs | 67 + .../Core/CollectionItemSerializer.cs | 89 ++ .../Core/Constants.cs | 7 + .../Core/DateOnlyConverter.cs | 747 +++++++++++ .../Core/DateTimeSerializer.cs | 22 + .../Core/EmptyRequest.cs | 11 + .../Core/EncodingCache.cs | 11 + .../Core/Extensions.cs | 55 + .../Core/FormUrlEncoder.cs | 33 + .../Core/HeaderValue.cs | 52 + .../Core/Headers.cs | 28 + .../Core/HeadersBuilder.cs | 197 +++ .../Core/HttpContentExtensions.cs | 20 + .../Core/HttpMethodExtensions.cs | 8 + .../Core/IIsRetryableContent.cs | 6 + .../Core/IRequestOptions.cs | 83 ++ .../Core/JsonAccessAttribute.cs | 15 + .../Core/JsonConfiguration.cs | 275 ++++ .../Core/JsonRequest.cs | 36 + .../Core/MultipartFormRequest.cs | 294 +++++ .../Core/NullableAttribute.cs | 18 + .../Core/OneOfSerializer.cs | 145 +++ .../Core/Optional.cs | 474 +++++++ .../Core/OptionalAttribute.cs | 17 + .../Core/Public/AdditionalProperties.cs | 353 ++++++ .../Core/Public/ClientOptions.cs | 84 ++ .../Core/Public/FileParameter.cs | 63 + .../Core/Public/RawResponse.cs | 24 + .../Core/Public/RequestOptions.cs | 86 ++ .../Core/Public/SeedApiException.cs | 22 + .../Core/Public/SeedException.cs | 7 + .../Core/Public/Version.cs | 7 + .../Core/Public/WithRawResponse.cs | 18 + .../Core/Public/WithRawResponseTask.cs | 144 +++ .../Core/QueryStringBuilder.cs | 484 +++++++ .../Core/QueryStringConverter.cs | 259 ++++ .../Core/RawClient.cs | 344 +++++ .../Core/RawResponse.cs | 24 + .../Core/ResponseHeaders.cs | 108 ++ .../Core/StreamRequest.cs | 29 + .../Core/StringEnum.cs | 6 + .../Core/StringEnumExtensions.cs | 6 + .../Core/ValueConvert.cs | 114 ++ .../src/Seed.CsharpNamespaceConflict/ISeed.cs | 6 + .../Seed.CsharpNamespaceConflict.Custom.props | 20 + .../Seed.CsharpNamespaceConflict.csproj | 61 + .../src/Seed.CsharpNamespaceConflict/Seed.cs | 33 + .../Tasktest/ITasktestClient.cs | 9 + .../Tasktest/TasktestClient.cs | 55 + .../Tasktest/Types/Task.cs | 28 + .../src/SeedApi.DynamicSnippets/Example0.cs | 17 + .../SeedApi.DynamicSnippets.csproj | 13 + seed/csharp-sdk/seed.yml | 7 +- 132 files changed, 10683 insertions(+), 83 deletions(-) create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/.editorconfig create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/.fern/metadata.json create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/.github/workflows/ci.yml create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/.gitignore create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/README.md create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/Seed.CsharpNamespaceConflict.slnx create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/reference.md create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/snippet.json create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/HeadersBuilderTests.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/Json/AdditionalPropertiesTests.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/Json/DateOnlyJsonTests.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/Json/DateTimeJsonTests.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/Json/JsonAccessAttributeTests.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/QueryStringBuilderTests.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/QueryStringConverterTests.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/RawClientTests/MultipartFormTests.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/RawClientTests/QueryParameterTests.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/RawClientTests/RetriesTests.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/WithRawResponseTests.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Seed.CsharpNamespaceConflict.Test.Custom.props create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Seed.CsharpNamespaceConflict.Test.csproj create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/TestClient.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Unit/MockServer/BaseMockServerTest.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Unit/MockServer/Tasktest/HelloTest.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/AdditionalPropertiesComparer.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/JsonAssert.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/JsonElementComparer.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/NUnitExtensions.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/OneOfComparer.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/OptionalComparer.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/ReadOnlyMemoryComparer.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/A/Aa/Types/A.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/A/Aa/Types/B.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/A/Aa/Types/SubTestType.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/B/Types/TestType.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/ApiResponse.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/BaseRequest.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/CollectionItemSerializer.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Constants.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/DateOnlyConverter.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/DateTimeSerializer.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/EmptyRequest.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/EncodingCache.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Extensions.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/FormUrlEncoder.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/HeaderValue.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Headers.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/HeadersBuilder.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/HttpContentExtensions.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/HttpMethodExtensions.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/IIsRetryableContent.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/IRequestOptions.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/JsonAccessAttribute.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/JsonConfiguration.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/JsonRequest.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/MultipartFormRequest.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/NullableAttribute.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/OneOfSerializer.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Optional.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/OptionalAttribute.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/AdditionalProperties.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/ClientOptions.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/FileParameter.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/RawResponse.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/RequestOptions.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/SeedApiException.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/SeedException.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/Version.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/WithRawResponse.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/WithRawResponseTask.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/QueryStringBuilder.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/QueryStringConverter.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/RawClient.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/RawResponse.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/ResponseHeaders.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/StreamRequest.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/StringEnum.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/StringEnumExtensions.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/ValueConvert.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/ISeed.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Seed.CsharpNamespaceConflict.Custom.props create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Seed.CsharpNamespaceConflict.csproj create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Seed.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Tasktest/ITasktestClient.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Tasktest/TasktestClient.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Tasktest/Types/Task.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/SeedApi.DynamicSnippets/Example0.cs create mode 100644 seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj diff --git a/generators/csharp/base/src/asIs/test/RawClientTests/MultipartFormTests.Template.cs b/generators/csharp/base/src/asIs/test/RawClientTests/MultipartFormTests.Template.cs index e75302c9641d..6c37ac4babf6 100644 --- a/generators/csharp/base/src/asIs/test/RawClientTests/MultipartFormTests.Template.cs +++ b/generators/csharp/base/src/asIs/test/RawClientTests/MultipartFormTests.Template.cs @@ -1055,9 +1055,9 @@ private static string GetBoundary(MultipartFormDataContent content) .Value?.Trim('"') ?? throw new global::System.Exception("Boundary not found"); } - private static <%= namespaces.core %>.MultipartFormRequest CreateMultipartFormRequest() + private static <%= namespaces.qualifiedCore %>.MultipartFormRequest CreateMultipartFormRequest() { - return new <%= namespaces.core %>.MultipartFormRequest + return new <%= namespaces.qualifiedCore %>.MultipartFormRequest { BaseUrl = "https://localhost", Method = HttpMethod.Post, diff --git a/generators/csharp/base/src/asIs/test/RawClientTests/RetriesTests.Template.cs b/generators/csharp/base/src/asIs/test/RawClientTests/RetriesTests.Template.cs index cd5eb0289889..c5ff126b47ad 100644 --- a/generators/csharp/base/src/asIs/test/RawClientTests/RetriesTests.Template.cs +++ b/generators/csharp/base/src/asIs/test/RawClientTests/RetriesTests.Template.cs @@ -145,7 +145,7 @@ public async SystemTask SendRequestAsync_ShouldNotRetry_WithMultiPartFormRequest .WillSetStateTo("Server Error") .RespondWith(WireMockResponse.Create().WithStatusCode(429).WithBody("Failure")); - var request = new <%= namespaces.core %>.MultipartFormRequest{ + var request = new <%= namespaces.qualifiedCore %>.MultipartFormRequest{ BaseUrl = _baseUrl, Method = HttpMethod.Post, Path = "/test", @@ -185,12 +185,12 @@ public async SystemTask SendRequestAsync_ShouldRetry_WithMultiPartFormRequest_Wi .WhenStateIs("Success") .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - var request = new <%= context.namespaces.core %>.MultipartFormRequest{ - BaseUrl = _baseUrl, - Method = HttpMethod.Post, - Path = "/test", - }; - request.AddJsonPart("object", new {}); + var request = new <%= context.namespaces.qualifiedCore %>.MultipartFormRequest{ + BaseUrl = _baseUrl, + Method = HttpMethod.Post, + Path = "/test", + }; + request.AddJsonPart("object", new {}); var response = await _rawClient.SendRequestAsync(request); Assert.That(response.StatusCode, Is.EqualTo(200)); @@ -373,8 +373,8 @@ public async SystemTask SendRequestAsync_ShouldPreserveMultipartBody_OnRetry() .WhenStateIs("Success") .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - var request = new <%= context.namespaces.core %>.MultipartFormRequest - { + var request = new <%= context.namespaces.qualifiedCore %>.MultipartFormRequest + { BaseUrl = _baseUrl, Method = HttpMethod.Post, Path = "/test", diff --git a/generators/csharp/base/src/asIs/test/Utils/AdditionalPropertiesComparer.Template.cs b/generators/csharp/base/src/asIs/test/Utils/AdditionalPropertiesComparer.Template.cs index 07179d968c3b..190f5eb085cd 100644 --- a/generators/csharp/base/src/asIs/test/Utils/AdditionalPropertiesComparer.Template.cs +++ b/generators/csharp/base/src/asIs/test/Utils/AdditionalPropertiesComparer.Template.cs @@ -1,7 +1,7 @@ using System.Text.Json; using NUnit.Framework.Constraints; -using <%= namespaces.root %>; -using <%= namespaces.root %>.Core; +using <%= namespaces.qualifiedRoot %>; +using <%= namespaces.qualifiedRoot %>.Core; namespace NUnit.Framework; diff --git a/generators/csharp/base/src/asIs/test/Utils/JsonAssert.Template.cs b/generators/csharp/base/src/asIs/test/Utils/JsonAssert.Template.cs index 5874af4c5e2a..b38905514b21 100644 --- a/generators/csharp/base/src/asIs/test/Utils/JsonAssert.Template.cs +++ b/generators/csharp/base/src/asIs/test/Utils/JsonAssert.Template.cs @@ -1,6 +1,6 @@ using global::System.Text.Json; using NUnit.Framework; -using <%= namespaces.core%>; +using <%= namespaces.qualifiedCore%>; namespace <%= namespace%>; diff --git a/generators/csharp/base/src/asIs/test/Utils/OptionalComparer.Template.cs b/generators/csharp/base/src/asIs/test/Utils/OptionalComparer.Template.cs index 01e8347b6608..5cc26a39e527 100644 --- a/generators/csharp/base/src/asIs/test/Utils/OptionalComparer.Template.cs +++ b/generators/csharp/base/src/asIs/test/Utils/OptionalComparer.Template.cs @@ -1,5 +1,5 @@ using NUnit.Framework.Constraints; -using <%= namespaces.root %>.Core;<% if (!context.generation.settings.shouldGenerateUndiscriminatedUnions) { %> +using <%= namespaces.qualifiedRoot %>.Core;<% if (!context.generation.settings.shouldGenerateUndiscriminatedUnions) { %> using OneOf;<% } %> namespace NUnit.Framework; diff --git a/generators/csharp/codegen/src/ast/core/Writer.ts b/generators/csharp/codegen/src/ast/core/Writer.ts index c560581164f3..fb166ac1a4e8 100644 --- a/generators/csharp/codegen/src/ast/core/Writer.ts +++ b/generators/csharp/codegen/src/ast/core/Writer.ts @@ -130,10 +130,17 @@ ${this.buffer}`; !this.isCurrentNamespace(ns) && // filter out the current namespace. !this.generation.registry.isNamespaceImplicit(ns) // filter out implicitly imported namespaces. ) - .map( - ([ns, refs]) => - `using ${refs.some((ref) => ref?.global) ? "global::" : ""}${refs.length > 0 ? (refs[0] as ClassReference).resolveNamespace() : ns};` - ) + .map(([ns, refs]) => { + const resolvedNs = refs.length > 0 ? (refs[0] as ClassReference).resolveNamespace() : ns; + const firstSegment = resolvedNs.split(".")[0]; + // Add global:: prefix when: + // 1. Any ref explicitly requires global qualification, OR + // 2. The first segment of the namespace is both a type name and a namespace root, + // which would cause CS0426 in C# (e.g., class "Candid" shadowing namespace "Candid.Net") + const needsGlobal = + refs.some((ref) => ref?.global) || this.generation.registry.hasTypeNamespaceConflict(firstSegment); + return `using ${needsGlobal ? "global::" : ""}${resolvedNs};`; + }) .join("\n"); if (result.length > 0) { diff --git a/generators/csharp/codegen/src/ast/types/ClassReference.ts b/generators/csharp/codegen/src/ast/types/ClassReference.ts index 7786642fffac..b44739d0257e 100644 --- a/generators/csharp/codegen/src/ast/types/ClassReference.ts +++ b/generators/csharp/codegen/src/ast/types/ClassReference.ts @@ -167,6 +167,9 @@ export class ClassReference extends Node implements Type { // if the first segment in a FQN is ambiguous, then we need to globally qualify the type if it gets expanded this.registry.isAmbiguousTypeName(this.namespaceSegments[0]) || this.registry.isAmbiguousNamespaceName(this.namespaceSegments[0]) || + // if the first namespace segment is both a type name and a namespace root, + // the C# compiler will resolve it to the type instead of the namespace (CS0426) + this.registry.hasTypeNamespaceConflict(this.namespaceSegments[0]) || // or we always are going to be using fully qualified namespaces writer.generation.settings.useFullyQualifiedNamespaces; @@ -214,7 +217,8 @@ export class ClassReference extends Node implements Type { const segments = typeQualification.split("."); if ( this.registry.isAmbiguousTypeName(segments[0]) || - this.registry.isAmbiguousNamespaceName(segments[0]) + this.registry.isAmbiguousNamespaceName(segments[0]) || + this.registry.hasTypeNamespaceConflict(segments[0]) ) { writer.write(fqName); } else { @@ -225,6 +229,11 @@ export class ClassReference extends Node implements Type { // we must to fully qualify the type // writer.addReference(this); writer.write(fqName); + } else if (this.registry.hasTypeNamespaceConflict(this.name)) { + // If the class name itself matches a root namespace segment, + // the C# compiler resolves it as the namespace instead of the type (CS0118). + // Use the fully qualified name to disambiguate. + writer.write(fqName); } else { // If the class is not ambiguous and is in this specific namespace, // we can use the short name diff --git a/generators/csharp/codegen/src/context/generation-info.ts b/generators/csharp/codegen/src/context/generation-info.ts index 68f7c367a2e1..bbb96bd88f0b 100644 --- a/generators/csharp/codegen/src/context/generation-info.ts +++ b/generators/csharp/codegen/src/context/generation-info.ts @@ -328,6 +328,10 @@ export class Generation { root: (): string => this.settings.namespace, /** Internal Core namespace for SDK implementation details and utilities ({root}.Core). */ core: (): string => `${this.namespaces.root}.Core`, + /** Pre-qualified root namespace with global:: prefix when the root segment has a type-namespace conflict. */ + qualifiedRoot: (): string => this.qualifyNamespace(this.namespaces.root), + /** Pre-qualified Core namespace with global:: prefix when the root segment has a type-namespace conflict. */ + qualifiedCore: (): string => this.qualifyNamespace(this.namespaces.core), /** Test namespace for all test-related code, canonicalized to avoid conflicts ({root}.Test). */ test: (): string => this.registry.canonicalizeNamespace(`${this.namespaces.root}.Test`), /** Test utilities namespace for helper methods and fixtures ({root}.Test.Utils). */ @@ -1201,4 +1205,17 @@ export class Generation { public get WireMock() { return this.extern.WireMock; } + + /** + * Returns a namespace string with a `global::` prefix if the first segment + * has a type-namespace conflict (e.g., class "Candid" shadowing namespace "Candid.Net"). + * Use this when writing raw namespace strings in string interpolations to avoid CS0426. + */ + public qualifyNamespace(ns: string): string { + const firstSegment = ns.split(".")[0]; + if (firstSegment && this.registry.hasTypeNamespaceConflict(firstSegment)) { + return `global::${ns}`; + } + return ns; + } } diff --git a/generators/csharp/codegen/src/context/name-registry.ts b/generators/csharp/codegen/src/context/name-registry.ts index 8082ecec13d7..6ab1793601b1 100644 --- a/generators/csharp/codegen/src/context/name-registry.ts +++ b/generators/csharp/codegen/src/context/name-registry.ts @@ -735,6 +735,10 @@ export class NameRegistry { this.typeNames.set("System", new Set(["System"])); this.typeNames.set("NUnit", new Set(["NUnit"])); this.typeNames.set("OneOf", new Set(["OneOf"])); + // Also track NUnit and OneOf as known built-in identifiers so they + // are excluded from type-namespace conflict detection (just like System) + this.knownBuiltInIdentifiers.add("NUnit"); + this.knownBuiltInIdentifiers.add("OneOf"); } /** @@ -855,6 +859,34 @@ export class NameRegistry { return name ? (this.namespaceNames.get(name)?.size ?? 0) > 1 : false; } + /** + * Checks if a name is both a registered type name and a root-level namespace segment. + * This detects cases where a class name shadows a namespace root, causing CS0426 errors. + * + * For example, if there's a class `Candid` in namespace `Candid.Net`, then any reference + * to `Candid.Net.Something` from within the `Candid.Net` namespace tree will fail because + * the C# compiler resolves `Candid` to the class instead of the namespace. + * + * @param name - The name to check (optional) + * @returns `true` if the name is both a type name and a root namespace segment, `false` otherwise + */ + public hasTypeNamespaceConflict(name?: string): boolean { + if (!name) { + return false; + } + // Exclude known built-in identifiers (System, NUnit, OneOf, etc.) since these + // are framework names that don't create shadowing conflicts in user code. + // The conflict we're detecting is when a USER-DEFINED type name (like a client + // class "Candid") matches a root namespace segment (like "Candid" in "Candid.Net"). + if (this.knownBuiltInIdentifiers.has(name)) { + return false; + } + // Check if this name is a tracked type name AND a root-level namespace segment + // (i.e., it appears as the first segment of some namespace, indicated by having + // an empty string "" as a parent in the namespaceNames registry) + return this.typeNames.has(name) && (this.namespaceNames.get(name)?.has("") ?? false); + } + /** * Generates a fully qualified name string from a class reference identity. * For nested types, includes the enclosing type in the qualified name. diff --git a/generators/csharp/sdk/src/endpoint/http/HttpEndpointGenerator.ts b/generators/csharp/sdk/src/endpoint/http/HttpEndpointGenerator.ts index 4b3277efb0b9..f8c6e14fbfea 100644 --- a/generators/csharp/sdk/src/endpoint/http/HttpEndpointGenerator.ts +++ b/generators/csharp/sdk/src/endpoint/http/HttpEndpointGenerator.ts @@ -1830,7 +1830,7 @@ export class HttpEndpointGenerator extends AbstractEndpointGenerator { return { code: this.csharp.codeblock((writer) => { writer.write( - `var ${this.names.variables.headers} = await new ${this.namespaces.core}.HeadersBuilder.Builder()` + `var ${this.names.variables.headers} = await new ${this.namespaces.qualifiedCore}.HeadersBuilder.Builder()` ); writer.indent(); writer.writeLine(".Add(_client.Options.Headers)"); diff --git a/generators/csharp/sdk/src/endpoint/request/BytesOnlyEndpointRequest.ts b/generators/csharp/sdk/src/endpoint/request/BytesOnlyEndpointRequest.ts index 79c9092f957e..270e0dddb5be 100644 --- a/generators/csharp/sdk/src/endpoint/request/BytesOnlyEndpointRequest.ts +++ b/generators/csharp/sdk/src/endpoint/request/BytesOnlyEndpointRequest.ts @@ -36,7 +36,7 @@ export class BytesOnlyEndpointRequest extends EndpointRequest { code: this.csharp.codeblock((writer) => { // Start with HeadersBuilder.Builder instance writer.write( - `var ${this.names.variables.headers} = await new ${this.namespaces.core}.HeadersBuilder.Builder()` + `var ${this.names.variables.headers} = await new ${this.namespaces.qualifiedCore}.HeadersBuilder.Builder()` ); writer.indent(); diff --git a/generators/csharp/sdk/src/endpoint/request/ReferencedEndpointRequest.ts b/generators/csharp/sdk/src/endpoint/request/ReferencedEndpointRequest.ts index 6ceda565e5a4..505de43eea49 100644 --- a/generators/csharp/sdk/src/endpoint/request/ReferencedEndpointRequest.ts +++ b/generators/csharp/sdk/src/endpoint/request/ReferencedEndpointRequest.ts @@ -46,7 +46,7 @@ export class ReferencedEndpointRequest extends EndpointRequest { code: this.csharp.codeblock((writer) => { // Start with HeadersBuilder.Builder instance writer.write( - `var ${this.names.variables.headers} = await new ${this.namespaces.core}.HeadersBuilder.Builder()` + `var ${this.names.variables.headers} = await new ${this.namespaces.qualifiedCore}.HeadersBuilder.Builder()` ); writer.indent(); diff --git a/generators/csharp/sdk/src/endpoint/request/WrappedEndpointRequest.ts b/generators/csharp/sdk/src/endpoint/request/WrappedEndpointRequest.ts index c65efb924e86..82b3923ae6fb 100644 --- a/generators/csharp/sdk/src/endpoint/request/WrappedEndpointRequest.ts +++ b/generators/csharp/sdk/src/endpoint/request/WrappedEndpointRequest.ts @@ -59,7 +59,7 @@ export class WrappedEndpointRequest extends EndpointRequest { return { code: this.csharp.codeblock((writer) => { writer.write( - `var ${queryStringVar} = new ${this.namespaces.core}.QueryStringBuilder.Builder(capacity: ${this.endpoint.queryParameters.length})` + `var ${queryStringVar} = new ${this.namespaces.qualifiedCore}.QueryStringBuilder.Builder(capacity: ${this.endpoint.queryParameters.length})` ); writer.indent(); for (const query of this.endpoint.queryParameters) { @@ -185,7 +185,7 @@ export class WrappedEndpointRequest extends EndpointRequest { code: this.csharp.codeblock((writer) => { // Start with HeadersBuilder.Builder instance writer.write( - `var ${this.names.variables.headers} = await new ${this.namespaces.core}.HeadersBuilder.Builder()` + `var ${this.names.variables.headers} = await new ${this.namespaces.qualifiedCore}.HeadersBuilder.Builder()` ); writer.indent(); diff --git a/generators/csharp/sdk/src/websocket/WebsocketClientGenerator.ts b/generators/csharp/sdk/src/websocket/WebsocketClientGenerator.ts index 52662ec98c15..14a9d3ba024d 100644 --- a/generators/csharp/sdk/src/websocket/WebsocketClientGenerator.ts +++ b/generators/csharp/sdk/src/websocket/WebsocketClientGenerator.ts @@ -486,7 +486,7 @@ export class WebSocketClientGenerator extends WithGeneration { if (hasQueryParameters) { writer.write( - `\n{\n Query = new ${this.namespaces.core}.QueryStringBuilder.Builder(capacity: ${this.websocketChannel.queryParameters.length})` + `\n{\n Query = new ${this.namespaces.qualifiedCore}.QueryStringBuilder.Builder(capacity: ${this.websocketChannel.queryParameters.length})` ); for (const queryParameter of this.websocketChannel.queryParameters) { const isComplexType = this.isComplexType(queryParameter.valueType); diff --git a/generators/csharp/sdk/versions.yml b/generators/csharp/sdk/versions.yml index 3f3d82fb1822..4a9a060b3a9e 100644 --- a/generators/csharp/sdk/versions.yml +++ b/generators/csharp/sdk/versions.yml @@ -1,4 +1,16 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 2.31.1 + changelogEntry: + - summary: | + Fix CS0426 compilation error when the client class name matches a namespace + root segment (e.g., class `Candid` in namespace `Candid.Net`). The C# compiler + previously resolved `Candid.Net` as looking for a `Net` member on the `Candid` + type instead of the `Candid.Net` namespace. The generator now uses `global::` + prefixes in both inline references and `using` directives to disambiguate. + type: fix + createdAt: "2026-03-16" + irVersion: 65 + - version: 2.31.0 changelogEntry: - summary: | diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/README.md b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/README.md index 98ab45a85b57..2197185a22b8 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/README.md +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/README.md @@ -39,9 +39,9 @@ A full reference for this library is available [here](./reference.md). Instantiate and use the client with the following: ```csharp -using Contoso.Net; -var client = new Contoso(); + +var client = new global::Contoso.Net.Contoso(); await client.CreateUserAsync( new global::Contoso.Net.User { diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/snippet.json b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/snippet.json index 31cd30b1d86e..9e5da2308cf4 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/snippet.json +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/snippet.json @@ -10,7 +10,7 @@ }, "snippet": { "type": "csharp", - "client": "using Contoso.Net;\n\nvar client = new Contoso();\nawait client.CreateUserAsync(\n new global::Contoso.Net.User\n {\n Id = \"id\",\n Name = \"name\",\n Email = \"email\",\n Password = \"password\",\n }\n);\n" + "client": "\n\nvar client = new global::Contoso.Net.Contoso();\nawait client.CreateUserAsync(\n new global::Contoso.Net.User\n {\n Id = \"id\",\n Name = \"name\",\n Email = \"email\",\n Password = \"password\",\n }\n);\n" } }, { @@ -22,7 +22,7 @@ }, "snippet": { "type": "csharp", - "client": "using Contoso.Net;\n\nvar client = new Contoso();\nawait client.CreateTaskAsync(\n new global::Contoso.Net.Task\n {\n Id = \"id\",\n Name = \"name\",\n Email = \"email\",\n Password = \"password\",\n }\n);\n" + "client": "\n\nvar client = new global::Contoso.Net.Contoso();\nawait client.CreateTaskAsync(\n new global::Contoso.Net.Task\n {\n Id = \"id\",\n Name = \"name\",\n Email = \"email\",\n Password = \"password\",\n }\n);\n" } }, { @@ -34,7 +34,7 @@ }, "snippet": { "type": "csharp", - "client": "using Contoso.Net;\n\nvar client = new Contoso();\nawait client.System.CreateUserAsync(\n new global::Contoso.Net.System.User\n {\n Line1 = \"line1\",\n Line2 = \"line2\",\n City = \"city\",\n State = \"state\",\n Zip = \"zip\",\n Country = \"USA\",\n }\n);\n" + "client": "\n\nvar client = new global::Contoso.Net.Contoso();\nawait client.System.CreateUserAsync(\n new global::Contoso.Net.System.User\n {\n Line1 = \"line1\",\n Line2 = \"line2\",\n City = \"city\",\n State = \"state\",\n Zip = \"zip\",\n Country = \"USA\",\n }\n);\n" } }, { @@ -46,7 +46,7 @@ }, "snippet": { "type": "csharp", - "client": "using Contoso.Net;\n\nvar client = new Contoso();\nawait client.System.CreateTaskAsync(\n new global::Contoso.Net.System.Task\n {\n Name = \"name\",\n User = new global::Contoso.Net.System.User\n {\n Line1 = \"line1\",\n Line2 = \"line2\",\n City = \"city\",\n State = \"state\",\n Zip = \"zip\",\n Country = \"USA\",\n },\n Owner = new global::Contoso.Net.System.User\n {\n Line1 = \"line1\",\n Line2 = \"line2\",\n City = \"city\",\n State = \"state\",\n Zip = \"zip\",\n Country = \"USA\",\n },\n }\n);\n" + "client": "\n\nvar client = new global::Contoso.Net.Contoso();\nawait client.System.CreateTaskAsync(\n new global::Contoso.Net.System.Task\n {\n Name = \"name\",\n User = new global::Contoso.Net.System.User\n {\n Line1 = \"line1\",\n Line2 = \"line2\",\n City = \"city\",\n State = \"state\",\n Zip = \"zip\",\n Country = \"USA\",\n },\n Owner = new global::Contoso.Net.System.User\n {\n Line1 = \"line1\",\n Line2 = \"line2\",\n City = \"city\",\n State = \"state\",\n Zip = \"zip\",\n Country = \"USA\",\n },\n }\n);\n" } }, { @@ -58,7 +58,7 @@ }, "snippet": { "type": "csharp", - "client": "using Contoso.Net;\n\nvar client = new Contoso();\nawait client.System.GetUserAsync(\"userId\");\n" + "client": "\n\nvar client = new global::Contoso.Net.Contoso();\nawait client.System.GetUserAsync(\"userId\");\n" } } ] diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Core/RawClientTests/MultipartFormTests.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Core/RawClientTests/MultipartFormTests.cs index 48e5014db254..f3ebdf75b665 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Core/RawClientTests/MultipartFormTests.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Core/RawClientTests/MultipartFormTests.cs @@ -1056,9 +1056,9 @@ private static string GetBoundary(MultipartFormDataContent content) ?? throw new global::System.Exception("Boundary not found"); } - private static Contoso.Net.Core.MultipartFormRequest CreateMultipartFormRequest() + private static global::Contoso.Net.Core.MultipartFormRequest CreateMultipartFormRequest() { - return new Contoso.Net.Core.MultipartFormRequest + return new global::Contoso.Net.Core.MultipartFormRequest { BaseUrl = "https://localhost", Method = HttpMethod.Post, diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Core/RawClientTests/RetriesTests.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Core/RawClientTests/RetriesTests.cs index 208ecdfee11b..bfe196bdfb6a 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Core/RawClientTests/RetriesTests.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Core/RawClientTests/RetriesTests.cs @@ -146,7 +146,7 @@ public async SystemTask SendRequestAsync_ShouldNotRetry_WithMultiPartFormRequest .WillSetStateTo("Server Error") .RespondWith(WireMockResponse.Create().WithStatusCode(429).WithBody("Failure")); - var request = new Contoso.Net.Core.MultipartFormRequest + var request = new global::Contoso.Net.Core.MultipartFormRequest { BaseUrl = _baseUrl, Method = HttpMethod.Post, @@ -187,7 +187,7 @@ public async SystemTask SendRequestAsync_ShouldRetry_WithMultiPartFormRequest_Wi .WhenStateIs("Success") .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - var request = new Contoso.Net.Core.MultipartFormRequest + var request = new global::Contoso.Net.Core.MultipartFormRequest { BaseUrl = _baseUrl, Method = HttpMethod.Post, @@ -373,7 +373,7 @@ public async SystemTask SendRequestAsync_ShouldPreserveMultipartBody_OnRetry() .WhenStateIs("Success") .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - var request = new Contoso.Net.Core.MultipartFormRequest + var request = new global::Contoso.Net.Core.MultipartFormRequest { BaseUrl = _baseUrl, Method = HttpMethod.Post, diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/BaseMockServerTest.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/BaseMockServerTest.cs index ce4dc98b39fb..ce47f4439150 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/BaseMockServerTest.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/BaseMockServerTest.cs @@ -1,4 +1,4 @@ -using Contoso.Net; +using global::Contoso.Net; using NUnit.Framework; using WireMock.Logging; using WireMock.Server; @@ -10,7 +10,7 @@ public class BaseMockServerTest { protected WireMockServer Server { get; set; } = null!; - protected Contoso.Net.Contoso Client { get; set; } = null!; + protected global::Contoso.Net.Contoso Client { get; set; } = null!; protected RequestOptions RequestOptions { get; set; } = new(); @@ -23,7 +23,7 @@ public void GlobalSetup() ); // Initialize the Client - Client = new Contoso.Net.Contoso( + Client = new global::Contoso.Net.Contoso( clientOptions: new ClientOptions { BaseUrl = Server.Urls[0], MaxRetries = 0 } ); } diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/CreateTaskTest.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/CreateTaskTest.cs index a5277c200db0..3f049bc5b617 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/CreateTaskTest.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/CreateTaskTest.cs @@ -1,4 +1,4 @@ -using Contoso.Net.Test.Utils; +using global::Contoso.Net.Test.Utils; using NUnit.Framework; namespace Contoso.Net.Test.Unit.MockServer; diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/CreateUserTest.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/CreateUserTest.cs index 29e7b043aa89..201b37ffe35d 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/CreateUserTest.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/CreateUserTest.cs @@ -1,4 +1,4 @@ -using Contoso.Net.Test.Utils; +using global::Contoso.Net.Test.Utils; using NUnit.Framework; namespace Contoso.Net.Test.Unit.MockServer; diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/System/CreateTaskTest.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/System/CreateTaskTest.cs index 24b6d6aa9dac..7c97d6cf14da 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/System/CreateTaskTest.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/System/CreateTaskTest.cs @@ -1,5 +1,5 @@ -using Contoso.Net.Test.Unit.MockServer; -using Contoso.Net.Test.Utils; +using global::Contoso.Net.Test.Unit.MockServer; +using global::Contoso.Net.Test.Utils; using NUnit.Framework; namespace Contoso.Net.Test.Unit.MockServer.System; diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/System/CreateUserTest.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/System/CreateUserTest.cs index 2f50e8c98fbf..cf4c2d9b90f7 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/System/CreateUserTest.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/System/CreateUserTest.cs @@ -1,5 +1,5 @@ -using Contoso.Net.Test.Unit.MockServer; -using Contoso.Net.Test.Utils; +using global::Contoso.Net.Test.Unit.MockServer; +using global::Contoso.Net.Test.Utils; using NUnit.Framework; namespace Contoso.Net.Test.Unit.MockServer.System; diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/System/GetUserTest.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/System/GetUserTest.cs index 7f04355acd72..05a5712708a5 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/System/GetUserTest.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Unit/MockServer/System/GetUserTest.cs @@ -1,5 +1,5 @@ -using Contoso.Net.Test.Unit.MockServer; -using Contoso.Net.Test.Utils; +using global::Contoso.Net.Test.Unit.MockServer; +using global::Contoso.Net.Test.Utils; using NUnit.Framework; namespace Contoso.Net.Test.Unit.MockServer.System; diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Utils/AdditionalPropertiesComparer.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Utils/AdditionalPropertiesComparer.cs index 1827aca41e05..a0e690b7d471 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Utils/AdditionalPropertiesComparer.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Utils/AdditionalPropertiesComparer.cs @@ -1,6 +1,6 @@ using System.Text.Json; -using Contoso.Net; -using Contoso.Net.Core; +using global::Contoso.Net; +using global::Contoso.Net.Core; using NUnit.Framework.Constraints; namespace NUnit.Framework; diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Utils/JsonAssert.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Utils/JsonAssert.cs index f546f6476527..b45f2f74706c 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Utils/JsonAssert.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Utils/JsonAssert.cs @@ -1,4 +1,4 @@ -using Contoso.Net.Core; +using global::Contoso.Net.Core; using global::System.Text.Json; using NUnit.Framework; diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Utils/OptionalComparer.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Utils/OptionalComparer.cs index 6a24fc4368c1..9953ff9378d3 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Utils/OptionalComparer.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net.Test/Utils/OptionalComparer.cs @@ -1,4 +1,4 @@ -using Contoso.Net.Core; +using global::Contoso.Net.Core; using NUnit.Framework.Constraints; using OneOf; diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Contoso.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Contoso.cs index 55f315c25b1c..a9bb325cb4f5 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Contoso.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Contoso.cs @@ -1,6 +1,6 @@ using System.Text.Json; -using Contoso.Net.Core; -using Contoso.Net.System; +using global::Contoso.Net.Core; +using global::Contoso.Net.System; namespace Contoso.Net; @@ -39,7 +39,7 @@ public Contoso(ClientOptions? clientOptions = null) CancellationToken cancellationToken = default ) { - var _headers = await new Contoso.Net.Core.HeadersBuilder.Builder() + var _headers = await new global::Contoso.Net.Core.HeadersBuilder.Builder() .Add(_client.Options.Headers) .Add(_client.Options.AdditionalHeaders) .Add(options?.AdditionalHeaders) @@ -105,7 +105,7 @@ public Contoso(ClientOptions? clientOptions = null) CancellationToken cancellationToken = default ) { - var _headers = await new Contoso.Net.Core.HeadersBuilder.Builder() + var _headers = await new global::Contoso.Net.Core.HeadersBuilder.Builder() .Add(_client.Options.Headers) .Add(_client.Options.AdditionalHeaders) .Add(options?.AdditionalHeaders) diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Core/Public/ClientOptions.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Core/Public/ClientOptions.cs index 82c9a31b40b4..cc3155399c0b 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Core/Public/ClientOptions.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Core/Public/ClientOptions.cs @@ -1,4 +1,4 @@ -using Contoso.Net.Core; +using global::Contoso.Net.Core; namespace Contoso.Net; diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Core/Public/RequestOptions.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Core/Public/RequestOptions.cs index 7a8196d8636c..d039abaf055f 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Core/Public/RequestOptions.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Core/Public/RequestOptions.cs @@ -1,4 +1,4 @@ -using Contoso.Net.Core; +using global::Contoso.Net.Core; namespace Contoso.Net; diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/IContoso.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/IContoso.cs index 8c707ead7fc9..887c73e9943d 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/IContoso.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/IContoso.cs @@ -1,4 +1,4 @@ -using Contoso.Net.System; +using global::Contoso.Net.System; namespace Contoso.Net; diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/System/ISystemClient.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/System/ISystemClient.cs index 23cebcd42efb..10e45356bc00 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/System/ISystemClient.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/System/ISystemClient.cs @@ -1,4 +1,4 @@ -using Contoso.Net; +using global::Contoso.Net; namespace Contoso.Net.System; diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/System/SystemClient.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/System/SystemClient.cs index 5b256467c872..c3e664931d42 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/System/SystemClient.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/System/SystemClient.cs @@ -1,6 +1,6 @@ using System.Text.Json; -using Contoso.Net; -using Contoso.Net.Core; +using global::Contoso.Net; +using global::Contoso.Net.Core; namespace Contoso.Net.System; @@ -19,7 +19,7 @@ internal SystemClient(RawClient client) CancellationToken cancellationToken = default ) { - var _headers = await new Contoso.Net.Core.HeadersBuilder.Builder() + var _headers = await new global::Contoso.Net.Core.HeadersBuilder.Builder() .Add(_client.Options.Headers) .Add(_client.Options.AdditionalHeaders) .Add(options?.AdditionalHeaders) @@ -85,7 +85,7 @@ internal SystemClient(RawClient client) CancellationToken cancellationToken = default ) { - var _headers = await new Contoso.Net.Core.HeadersBuilder.Builder() + var _headers = await new global::Contoso.Net.Core.HeadersBuilder.Builder() .Add(_client.Options.Headers) .Add(_client.Options.AdditionalHeaders) .Add(options?.AdditionalHeaders) @@ -151,7 +151,7 @@ internal SystemClient(RawClient client) CancellationToken cancellationToken = default ) { - var _headers = await new Contoso.Net.Core.HeadersBuilder.Builder() + var _headers = await new global::Contoso.Net.Core.HeadersBuilder.Builder() .Add(_client.Options.Headers) .Add(_client.Options.AdditionalHeaders) .Add(options?.AdditionalHeaders) diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/System/Types/Task.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/System/Types/Task.cs index ccf4d22b9be4..fbf6f396b037 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/System/Types/Task.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/System/Types/Task.cs @@ -1,7 +1,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -using Contoso.Net; -using Contoso.Net.Core; +using global::Contoso.Net; +using global::Contoso.Net.Core; namespace Contoso.Net.System; diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/System/Types/User.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/System/Types/User.cs index 3cebe688feb2..4fd55a675a38 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/System/Types/User.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/System/Types/User.cs @@ -1,7 +1,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -using Contoso.Net; -using Contoso.Net.Core; +using global::Contoso.Net; +using global::Contoso.Net.Core; namespace Contoso.Net.System; diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Types/Task.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Types/Task.cs index 675fc6a90d2c..eee1687ab5ec 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Types/Task.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Types/Task.cs @@ -1,6 +1,6 @@ using System.Text.Json; using System.Text.Json.Serialization; -using Contoso.Net.Core; +using global::Contoso.Net.Core; namespace Contoso.Net; diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Types/User.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Types/User.cs index 56b4d5424e38..35f285119ef3 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Types/User.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/Contoso.Net/Types/User.cs @@ -1,6 +1,6 @@ using System.Text.Json; using System.Text.Json.Serialization; -using Contoso.Net.Core; +using global::Contoso.Net.Core; namespace Contoso.Net; diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example0.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example0.cs index 68218991e4ae..2ebf76048eb8 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example0.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example0.cs @@ -1,11 +1,11 @@ -using Contoso.Net; +using global::Contoso.Net; namespace Usage; public class Example0 { public async global::System.Threading.Tasks.Task Do() { - var client = new Contoso( + var client = new global::Contoso.Net.Contoso( clientOptions: new ClientOptions { BaseUrl = "https://api.fern.com" } diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example1.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example1.cs index 09bf7b2ba63a..cf98cfbf39b4 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example1.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example1.cs @@ -1,11 +1,11 @@ -using Contoso.Net; +using global::Contoso.Net; namespace Usage; public class Example1 { public async global::System.Threading.Tasks.Task Do() { - var client = new Contoso( + var client = new global::Contoso.Net.Contoso( clientOptions: new ClientOptions { BaseUrl = "https://api.fern.com" } diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example2.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example2.cs index 80c99083821f..49faf8437d9c 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example2.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example2.cs @@ -1,11 +1,11 @@ -using Contoso.Net; +using global::Contoso.Net; namespace Usage; public class Example2 { public async global::System.Threading.Tasks.Task Do() { - var client = new Contoso( + var client = new global::Contoso.Net.Contoso( clientOptions: new ClientOptions { BaseUrl = "https://api.fern.com" } diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example3.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example3.cs index 59842b69acd1..82fa69cd894a 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example3.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example3.cs @@ -1,11 +1,11 @@ -using Contoso.Net; +using global::Contoso.Net; namespace Usage; public class Example3 { public async global::System.Threading.Tasks.Task Do() { - var client = new Contoso( + var client = new global::Contoso.Net.Contoso( clientOptions: new ClientOptions { BaseUrl = "https://api.fern.com" } diff --git a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example4.cs b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example4.cs index 1117f921c8b2..c5d1d3b7bc0e 100644 --- a/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example4.cs +++ b/seed/csharp-sdk/csharp-namespace-collision/namespace-client-collision/src/SeedApi.DynamicSnippets/Example4.cs @@ -1,11 +1,11 @@ -using Contoso.Net; +using global::Contoso.Net; namespace Usage; public class Example4 { public async global::System.Threading.Tasks.Task Do() { - var client = new Contoso( + var client = new global::Contoso.Net.Contoso( clientOptions: new ClientOptions { BaseUrl = "https://api.fern.com" } diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/.editorconfig b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/.editorconfig new file mode 100644 index 000000000000..1e7a0adbac80 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/.editorconfig @@ -0,0 +1,35 @@ +root = true + +[*.cs] +resharper_arrange_object_creation_when_type_evident_highlighting = hint +resharper_auto_property_can_be_made_get_only_global_highlighting = hint +resharper_check_namespace_highlighting = hint +resharper_class_never_instantiated_global_highlighting = hint +resharper_class_never_instantiated_local_highlighting = hint +resharper_collection_never_updated_global_highlighting = hint +resharper_convert_type_check_pattern_to_null_check_highlighting = hint +resharper_inconsistent_naming_highlighting = hint +resharper_member_can_be_private_global_highlighting = hint +resharper_member_hides_static_from_outer_class_highlighting = hint +resharper_not_accessed_field_local_highlighting = hint +resharper_nullable_warning_suppression_is_used_highlighting = suggestion +resharper_partial_type_with_single_part_highlighting = hint +resharper_prefer_concrete_value_over_default_highlighting = none +resharper_private_field_can_be_converted_to_local_variable_highlighting = hint +resharper_property_can_be_made_init_only_global_highlighting = hint +resharper_property_can_be_made_init_only_local_highlighting = hint +resharper_redundant_name_qualifier_highlighting = none +resharper_redundant_using_directive_highlighting = hint +resharper_replace_slice_with_range_indexer_highlighting = none +resharper_unused_auto_property_accessor_global_highlighting = hint +resharper_unused_auto_property_accessor_local_highlighting = hint +resharper_unused_member_global_highlighting = hint +resharper_unused_type_global_highlighting = hint +resharper_use_string_interpolation_highlighting = hint +dotnet_diagnostic.CS1591.severity = suggestion + +[src/**/Types/*.cs] +resharper_check_namespace_highlighting = none + +[src/**/Core/Public/*.cs] +resharper_check_namespace_highlighting = none \ No newline at end of file diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/.fern/metadata.json b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/.fern/metadata.json new file mode 100644 index 000000000000..8529dc2dc850 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/.fern/metadata.json @@ -0,0 +1,12 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-csharp-sdk", + "generatorVersion": "latest", + "generatorConfig": { + "namespace": "Seed.CsharpNamespaceConflict", + "client-class-name": "Seed", + "experimental-fully-qualified-namespaces": true + }, + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/.github/workflows/ci.yml b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/.github/workflows/ci.yml new file mode 100644 index 000000000000..6d7c4a5cd406 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +env: + DOTNET_NOLOGO: true + +jobs: + ci: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.x + + - name: Install tools + run: dotnet tool restore + + - name: Restore dependencies + run: dotnet restore src/Seed.CsharpNamespaceConflict/Seed.CsharpNamespaceConflict.csproj + + - name: Build + run: dotnet build src/Seed.CsharpNamespaceConflict/Seed.CsharpNamespaceConflict.csproj --no-restore -c Release + + - name: Restore test dependencies + run: dotnet restore src/Seed.CsharpNamespaceConflict.Test/Seed.CsharpNamespaceConflict.Test.csproj + + - name: Build tests + run: dotnet build src/Seed.CsharpNamespaceConflict.Test/Seed.CsharpNamespaceConflict.Test.csproj --no-restore -c Release + + - name: Test + run: dotnet test src/Seed.CsharpNamespaceConflict.Test/Seed.CsharpNamespaceConflict.Test.csproj --no-restore --no-build -c Release + + - name: Pack + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + run: dotnet pack src/Seed.CsharpNamespaceConflict/Seed.CsharpNamespaceConflict.csproj --no-build --no-restore -c Release + + - name: Publish to NuGet.org + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_TOKEN }} + run: dotnet nuget push src/Seed.CsharpNamespaceConflict/bin/Release/*.nupkg --api-key $NUGET_API_KEY --source "nuget.org" + diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/.gitignore b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/.gitignore new file mode 100644 index 000000000000..11014f2b33d7 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/.gitignore @@ -0,0 +1,484 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +## This is based on `dotnet new gitignore` and customized by Fern + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +# [Rr]elease/ (Ignored by Fern) +# [Rr]eleases/ (Ignored by Fern) +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +# [Ll]og/ (Ignored by Fern) +# [Ll]ogs/ (Ignored by Fern) + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/README.md b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/README.md new file mode 100644 index 000000000000..316563cf0d24 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/README.md @@ -0,0 +1,170 @@ +# Seed C# Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FC%23) +[![nuget shield](https://img.shields.io/nuget/v/Seed.CsharpNamespaceConflict)](https://nuget.org/packages/Seed.CsharpNamespaceConflict) + +The Seed C# library provides convenient access to the Seed APIs from C#. + +## Table of Contents + +- [Requirements](#requirements) +- [Installation](#installation) +- [Reference](#reference) +- [Usage](#usage) +- [Exception Handling](#exception-handling) +- [Advanced](#advanced) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Raw Response](#raw-response) + - [Additional Headers](#additional-headers) + - [Additional Query Parameters](#additional-query-parameters) +- [Contributing](#contributing) + +## Requirements + +This SDK requires: + +## Installation + +```sh +dotnet add package Seed.CsharpNamespaceConflict +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```csharp + + +var client = new global::Seed.CsharpNamespaceConflict.Seed(); +await client.Tasktest.HelloAsync(); +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```csharp +using Seed.CsharpNamespaceConflict; + +try { + var response = await client.Tasktest.HelloAsync(...); +} catch (SeedApiException e) { + System.Console.WriteLine(e.Body); + System.Console.WriteLine(e.StatusCode); +} +``` + +## Advanced + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `MaxRetries` request option to configure this behavior. + +```csharp +var response = await client.Tasktest.HelloAsync( + ..., + new RequestOptions { + MaxRetries: 0 // Override MaxRetries at the request level + } +); +``` + +### Timeouts + +The SDK defaults to a 30 second timeout. Use the `Timeout` option to configure this behavior. + +```csharp +var response = await client.Tasktest.HelloAsync( + ..., + new RequestOptions { + Timeout: TimeSpan.FromSeconds(3) // Override timeout to 3s + } +); +``` + +### Raw Response + +Access raw HTTP response data (status code, headers, URL) alongside parsed response data using the `.WithRawResponse()` method. + +```csharp +using Seed.CsharpNamespaceConflict; + +// Access raw response data (status code, headers, etc.) alongside the parsed response +var result = await client.Tasktest.HelloAsync(...).WithRawResponse(); + +// Access the parsed data +var data = result.Data; + +// Access raw response metadata +var statusCode = result.RawResponse.StatusCode; +var headers = result.RawResponse.Headers; +var url = result.RawResponse.Url; + +// Access specific headers (case-insensitive) +if (headers.TryGetValue("X-Request-Id", out var requestId)) +{ + System.Console.WriteLine($"Request ID: {requestId}"); +} + +// For the default behavior, simply await without .WithRawResponse() +var data = await client.Tasktest.HelloAsync(...); +``` + +### Additional Headers + +If you would like to send additional headers as part of the request, use the `AdditionalHeaders` request option. + +```csharp +var response = await client.Tasktest.HelloAsync( + ..., + new RequestOptions { + AdditionalHeaders = new Dictionary + { + { "X-Custom-Header", "custom-value" } + } + } +); +``` + +### Additional Query Parameters + +If you would like to send additional query parameters as part of the request, use the `AdditionalQueryParameters` request option. + +```csharp +var response = await client.Tasktest.HelloAsync( + ..., + new RequestOptions { + AdditionalQueryParameters = new Dictionary + { + { "custom_param", "custom-value" } + } + } +); +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/Seed.CsharpNamespaceConflict.slnx b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/Seed.CsharpNamespaceConflict.slnx new file mode 100644 index 000000000000..de80588d1a98 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/Seed.CsharpNamespaceConflict.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/reference.md b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/reference.md new file mode 100644 index 000000000000..4c6c8826f8bf --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/reference.md @@ -0,0 +1,27 @@ +# Reference +## Tasktest +
client.Tasktest.HelloAsync() +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Tasktest.HelloAsync(); +``` +
+
+
+
+ + +
+
+
+ diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/snippet.json b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/snippet.json new file mode 100644 index 000000000000..c6b924ec68d1 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/snippet.json @@ -0,0 +1,17 @@ +{ + "types": {}, + "endpoints": [ + { + "example_identifier": null, + "id": { + "path": "/hello", + "method": "GET", + "identifier_override": "endpoint_tasktest.hello" + }, + "snippet": { + "type": "csharp", + "client": "\n\nvar client = new global::Seed.CsharpNamespaceConflict.Seed();\nawait client.Tasktest.HelloAsync();\n" + } + } + ] +} \ No newline at end of file diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/HeadersBuilderTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/HeadersBuilderTests.cs new file mode 100644 index 000000000000..2df31eb34336 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/HeadersBuilderTests.cs @@ -0,0 +1,326 @@ +using NUnit.Framework; +using Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict.Test.Core; + +[TestFixture] +public class HeadersBuilderTests +{ + [Test] + public async global::System.Threading.Tasks.Task Add_SimpleHeaders() + { + var headers = await new HeadersBuilder.Builder() + .Add("Content-Type", "application/json") + .Add("Authorization", "Bearer token123") + .Add("X-API-Key", "key456") + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(3)); + Assert.That(headers["Content-Type"], Is.EqualTo("application/json")); + Assert.That(headers["Authorization"], Is.EqualTo("Bearer token123")); + Assert.That(headers["X-API-Key"], Is.EqualTo("key456")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_NullValuesIgnored() + { + var headers = await new HeadersBuilder.Builder() + .Add("Header1", "value1") + .Add("Header2", null) + .Add("Header3", "value3") + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(2)); + Assert.That(headers.ContainsKey("Header1"), Is.True); + Assert.That(headers.ContainsKey("Header2"), Is.False); + Assert.That(headers.ContainsKey("Header3"), Is.True); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_OverwritesExistingHeader() + { + var headers = await new HeadersBuilder.Builder() + .Add("Content-Type", "application/json") + .Add("Content-Type", "application/xml") + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(1)); + Assert.That(headers["Content-Type"], Is.EqualTo("application/xml")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_HeadersOverload_MergesExistingHeaders() + { + var existingHeaders = new Headers( + new Dictionary { { "Header1", "value1" }, { "Header2", "value2" } } + ); + + var result = await new HeadersBuilder.Builder() + .Add("Header3", "value3") + .Add(existingHeaders) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result.Count, Is.EqualTo(3)); + Assert.That(result["Header1"], Is.EqualTo("value1")); + Assert.That(result["Header2"], Is.EqualTo("value2")); + Assert.That(result["Header3"], Is.EqualTo("value3")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_HeadersOverload_OverwritesExistingHeaders() + { + var existingHeaders = new Headers( + new Dictionary { { "Header1", "override" } } + ); + + var result = await new HeadersBuilder.Builder() + .Add("Header1", "original") + .Add("Header2", "keep") + .Add(existingHeaders) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result.Count, Is.EqualTo(2)); + Assert.That(result["Header1"], Is.EqualTo("override")); + Assert.That(result["Header2"], Is.EqualTo("keep")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_HeadersOverload_NullHeadersIgnored() + { + var result = await new HeadersBuilder.Builder() + .Add("Header1", "value1") + .Add((Headers?)null) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result.Count, Is.EqualTo(1)); + Assert.That(result["Header1"], Is.EqualTo("value1")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_KeyValuePairOverload_AddsHeaders() + { + var additionalHeaders = new List> + { + new("Header1", "value1"), + new("Header2", "value2"), + }; + + var headers = await new HeadersBuilder.Builder() + .Add("Header3", "value3") + .Add(additionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(3)); + Assert.That(headers["Header1"], Is.EqualTo("value1")); + Assert.That(headers["Header2"], Is.EqualTo("value2")); + Assert.That(headers["Header3"], Is.EqualTo("value3")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_KeyValuePairOverload_IgnoresNullValues() + { + var additionalHeaders = new List> + { + new("Header1", "value1"), + new("Header2", null), // Should be ignored + }; + + var headers = await new HeadersBuilder.Builder() + .Add(additionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(1)); + Assert.That(headers.ContainsKey("Header2"), Is.False); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_DictionaryOverload_AddsHeaders() + { + var dict = new Dictionary + { + { "Header1", "value1" }, + { "Header2", "value2" }, + }; + + var headers = await new HeadersBuilder.Builder() + .Add("Header3", "value3") + .Add(dict) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(3)); + Assert.That(headers["Header1"], Is.EqualTo("value1")); + Assert.That(headers["Header2"], Is.EqualTo("value2")); + Assert.That(headers["Header3"], Is.EqualTo("value3")); + } + + [Test] + public async global::System.Threading.Tasks.Task EmptyBuilder_ReturnsEmptyHeaders() + { + var headers = await new HeadersBuilder.Builder().BuildAsync().ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(0)); + } + + [Test] + public async global::System.Threading.Tasks.Task OnlyNullValues_ReturnsEmptyHeaders() + { + var headers = await new HeadersBuilder.Builder() + .Add("Header1", null) + .Add("Header2", null) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(0)); + } + + [Test] + public async global::System.Threading.Tasks.Task ComplexMergingScenario() + { + // Simulates real SDK usage: endpoint headers + client headers + request options + var clientHeaders = new Headers( + new Dictionary + { + { "X-Client-Version", "1.0.0" }, + { "User-Agent", "MyClient/1.0" }, + } + ); + + var clientAdditionalHeaders = new List> + { + new("X-Custom-Header", "custom-value"), + }; + + var requestOptionsHeaders = new Headers( + new Dictionary + { + { "Authorization", "Bearer user-token" }, + { "User-Agent", "MyClient/2.0" }, // Override + } + ); + + var requestAdditionalHeaders = new List> + { + new("X-Request-ID", "req-123"), + new("X-Custom-Header", "overridden-value"), // Override + }; + + var headers = await new HeadersBuilder.Builder() + .Add("Content-Type", "application/json") // Endpoint header + .Add("X-Endpoint-ID", "endpoint-1") + .Add(clientHeaders) + .Add(clientAdditionalHeaders) + .Add(requestOptionsHeaders) + .Add(requestAdditionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + + // Verify precedence + Assert.That(headers["Content-Type"], Is.EqualTo("application/json")); + Assert.That(headers["X-Endpoint-ID"], Is.EqualTo("endpoint-1")); + Assert.That(headers["X-Client-Version"], Is.EqualTo("1.0.0")); + Assert.That(headers["User-Agent"], Is.EqualTo("MyClient/2.0")); // Overridden + Assert.That(headers["Authorization"], Is.EqualTo("Bearer user-token")); + Assert.That(headers["X-Request-ID"], Is.EqualTo("req-123")); + Assert.That(headers["X-Custom-Header"], Is.EqualTo("overridden-value")); // Overridden + } + + [Test] + public async global::System.Threading.Tasks.Task Builder_WithCapacity() + { + // Test that capacity constructor works without errors + var headers = await new HeadersBuilder.Builder(capacity: 10) + .Add("Header1", "value1") + .Add("Header2", "value2") + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(2)); + Assert.That(headers["Header1"], Is.EqualTo("value1")); + Assert.That(headers["Header2"], Is.EqualTo("value2")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_HeadersOverload_ResolvesDynamicHeaderValues() + { + // Test that BuildAsync properly resolves HeaderValue instances + var existingHeaders = new Headers(); + existingHeaders["DynamicHeader"] = + (Func>)( + () => global::System.Threading.Tasks.Task.FromResult("dynamic-value") + ); + + var result = await new HeadersBuilder.Builder() + .Add("StaticHeader", "static-value") + .Add(existingHeaders) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result.Count, Is.EqualTo(2)); + Assert.That(result["StaticHeader"], Is.EqualTo("static-value")); + Assert.That(result["DynamicHeader"], Is.EqualTo("dynamic-value")); + } + + [Test] + public async global::System.Threading.Tasks.Task MultipleSyncAdds() + { + var headers1 = new Headers(new Dictionary { { "H1", "v1" } }); + var headers2 = new Headers(new Dictionary { { "H2", "v2" } }); + var headers3 = new Headers(new Dictionary { { "H3", "v3" } }); + + var result = await new HeadersBuilder.Builder() + .Add(headers1) + .Add(headers2) + .Add(headers3) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result.Count, Is.EqualTo(3)); + Assert.That(result["H1"], Is.EqualTo("v1")); + Assert.That(result["H2"], Is.EqualTo("v2")); + Assert.That(result["H3"], Is.EqualTo("v3")); + } + + [Test] + public async global::System.Threading.Tasks.Task PrecedenceOrder_LatestWins() + { + // Test that later operations override earlier ones + var headers1 = new Headers(new Dictionary { { "Key", "value1" } }); + var headers2 = new Headers(new Dictionary { { "Key", "value2" } }); + var additional = new List> { new("Key", "value3") }; + + var result = await new HeadersBuilder.Builder() + .Add("Key", "value0") + .Add(headers1) + .Add(headers2) + .Add(additional) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result["Key"], Is.EqualTo("value3")); + } + + [Test] + public async global::System.Threading.Tasks.Task CaseInsensitiveKeys() + { + // Test that header keys are case-insensitive + var headers = await new HeadersBuilder.Builder() + .Add("content-type", "application/json") + .Add("Content-Type", "application/xml") // Should overwrite + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(1)); + Assert.That(headers["content-type"], Is.EqualTo("application/xml")); + Assert.That(headers["Content-Type"], Is.EqualTo("application/xml")); + Assert.That(headers["CONTENT-TYPE"], Is.EqualTo("application/xml")); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/Json/AdditionalPropertiesTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/Json/AdditionalPropertiesTests.cs new file mode 100644 index 000000000000..c272fa735495 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/Json/AdditionalPropertiesTests.cs @@ -0,0 +1,365 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using NUnit.Framework; +using Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict.Test.Core.Json; + +[TestFixture] +public class AdditionalPropertiesTests +{ + [Test] + public void Record_OnDeserialized_ShouldPopulateAdditionalProperties() + { + // Arrange + const string json = """ + { + "id": "1", + "category": "fiction", + "title": "The Hobbit" + } + """; + + // Act + var record = JsonUtils.Deserialize(json); + + // Assert + Assert.That(record, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(record.Id, Is.EqualTo("1")); + Assert.That(record.AdditionalProperties["category"].GetString(), Is.EqualTo("fiction")); + Assert.That(record.AdditionalProperties["title"].GetString(), Is.EqualTo("The Hobbit")); + }); + } + + [Test] + public void RecordWithWriteableAdditionalProperties_OnSerialization_ShouldIncludeAdditionalProperties() + { + // Arrange + var record = new WriteableRecord + { + Id = "1", + AdditionalProperties = { ["category"] = "fiction", ["title"] = "The Hobbit" }, + }; + + // Act + var json = JsonUtils.Serialize(record); + var deserializedRecord = JsonUtils.Deserialize(json); + + // Assert + Assert.That(deserializedRecord, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(deserializedRecord.Id, Is.EqualTo("1")); + Assert.That( + deserializedRecord.AdditionalProperties["category"], + Is.InstanceOf() + ); + Assert.That( + ((JsonElement)deserializedRecord.AdditionalProperties["category"]!).GetString(), + Is.EqualTo("fiction") + ); + Assert.That( + deserializedRecord.AdditionalProperties["title"], + Is.InstanceOf() + ); + Assert.That( + ((JsonElement)deserializedRecord.AdditionalProperties["title"]!).GetString(), + Is.EqualTo("The Hobbit") + ); + }); + } + + [Test] + public void ReadOnlyAdditionalProperties_ShouldRetrieveValuesCorrectly() + { + // Arrange + var extensionData = new Dictionary + { + ["key1"] = JsonUtils.SerializeToElement("value1"), + ["key2"] = JsonUtils.SerializeToElement(123), + }; + var readOnlyProps = new ReadOnlyAdditionalProperties(); + readOnlyProps.CopyFromExtensionData(extensionData); + + // Act & Assert + Assert.That(readOnlyProps["key1"].GetString(), Is.EqualTo("value1")); + Assert.That(readOnlyProps["key2"].GetInt32(), Is.EqualTo(123)); + } + + [Test] + public void AdditionalProperties_ShouldBehaveAsDictionary() + { + // Arrange + var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; + + // Act + additionalProps["key3"] = true; + + // Assert + Assert.Multiple(() => + { + Assert.That(additionalProps["key1"], Is.EqualTo("value1")); + Assert.That(additionalProps["key2"], Is.EqualTo(123)); + Assert.That((bool)additionalProps["key3"]!, Is.True); + Assert.That(additionalProps.Count, Is.EqualTo(3)); + }); + } + + [Test] + public void AdditionalProperties_ToJsonObject_ShouldSerializeCorrectly() + { + // Arrange + var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; + + // Act + var jsonObject = additionalProps.ToJsonObject(); + + Assert.Multiple(() => + { + // Assert + Assert.That(jsonObject["key1"]!.GetValue(), Is.EqualTo("value1")); + Assert.That(jsonObject["key2"]!.GetValue(), Is.EqualTo(123)); + }); + } + + [Test] + public void AdditionalProperties_MixReadAndWrite_ShouldOverwriteDeserializedProperty() + { + // Arrange + const string json = """ + { + "id": "1", + "category": "fiction", + "title": "The Hobbit" + } + """; + var record = JsonUtils.Deserialize(json); + + // Act + record.AdditionalProperties["category"] = "non-fiction"; + + // Assert + Assert.Multiple(() => + { + Assert.That(record, Is.Not.Null); + Assert.That(record.Id, Is.EqualTo("1")); + Assert.That(record.AdditionalProperties["category"], Is.EqualTo("non-fiction")); + Assert.That(record.AdditionalProperties["title"], Is.InstanceOf()); + Assert.That( + ((JsonElement)record.AdditionalProperties["title"]!).GetString(), + Is.EqualTo("The Hobbit") + ); + }); + } + + [Test] + public void RecordWithReadonlyAdditionalPropertiesInts_OnDeserialized_ShouldPopulateAdditionalProperties() + { + // Arrange + const string json = """ + { + "extra1": 42, + "extra2": 99 + } + """; + + // Act + var record = JsonUtils.Deserialize(json); + + // Assert + Assert.That(record, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(record.AdditionalProperties["extra1"], Is.EqualTo(42)); + Assert.That(record.AdditionalProperties["extra2"], Is.EqualTo(99)); + }); + } + + [Test] + public void RecordWithAdditionalPropertiesInts_OnSerialization_ShouldIncludeAdditionalProperties() + { + // Arrange + var record = new WriteableRecordWithInts + { + AdditionalProperties = { ["extra1"] = 42, ["extra2"] = 99 }, + }; + + // Act + var json = JsonUtils.Serialize(record); + var deserializedRecord = JsonUtils.Deserialize(json); + + // Assert + Assert.That(deserializedRecord, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(deserializedRecord.AdditionalProperties["extra1"], Is.EqualTo(42)); + Assert.That(deserializedRecord.AdditionalProperties["extra2"], Is.EqualTo(99)); + }); + } + + [Test] + public void RecordWithReadonlyAdditionalPropertiesDictionaries_OnDeserialized_ShouldPopulateAdditionalProperties() + { + // Arrange + const string json = """ + { + "extra1": { "key1": true, "key2": false }, + "extra2": { "key3": true } + } + """; + + // Act + var record = JsonUtils.Deserialize(json); + + // Assert + Assert.That(record, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(record.AdditionalProperties["extra1"]["key1"], Is.True); + Assert.That(record.AdditionalProperties["extra1"]["key2"], Is.False); + Assert.That(record.AdditionalProperties["extra2"]["key3"], Is.True); + }); + } + + [Test] + public void RecordWithAdditionalPropertiesDictionaries_OnSerialization_ShouldIncludeAdditionalProperties() + { + // Arrange + var record = new WriteableRecordWithDictionaries + { + AdditionalProperties = + { + ["extra1"] = new Dictionary { { "key1", true }, { "key2", false } }, + ["extra2"] = new Dictionary { { "key3", true } }, + }, + }; + + // Act + var json = JsonUtils.Serialize(record); + var deserializedRecord = JsonUtils.Deserialize(json); + + // Assert + Assert.That(deserializedRecord, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key1"], Is.True); + Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key2"], Is.False); + Assert.That(deserializedRecord.AdditionalProperties["extra2"]["key3"], Is.True); + }); + } + + private record Record : IJsonOnDeserialized + { + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + } + + private record WriteableRecord : IJsonOnDeserialized, IJsonOnSerializing + { + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public AdditionalProperties AdditionalProperties { get; set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + + void IJsonOnSerializing.OnSerializing() + { + AdditionalProperties.CopyToExtensionData(_extensionData); + } + } + + private record RecordWithInts : IJsonOnDeserialized + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + } + + private record WriteableRecordWithInts : IJsonOnDeserialized, IJsonOnSerializing + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public AdditionalProperties AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + + void IJsonOnSerializing.OnSerializing() + { + AdditionalProperties.CopyToExtensionData(_extensionData); + } + } + + private record RecordWithDictionaries : IJsonOnDeserialized + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties< + Dictionary + > AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + } + + private record WriteableRecordWithDictionaries : IJsonOnDeserialized, IJsonOnSerializing + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public AdditionalProperties> AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + + void IJsonOnSerializing.OnSerializing() + { + AdditionalProperties.CopyToExtensionData(_extensionData); + } + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/Json/DateOnlyJsonTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/Json/DateOnlyJsonTests.cs new file mode 100644 index 000000000000..1d69a7c24e73 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/Json/DateOnlyJsonTests.cs @@ -0,0 +1,76 @@ +using NUnit.Framework; +using Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict.Test.Core.Json; + +[TestFixture] +public class DateOnlyJsonTests +{ + [Test] + public void SerializeDateOnly_ShouldMatchExpectedFormat() + { + (DateOnly dateOnly, string expected)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (new DateOnly(2023, 1, 1), "\"2023-01-01\""), + (new DateOnly(2023, 12, 31), "\"2023-12-31\""), + (new DateOnly(2023, 6, 15), "\"2023-06-15\""), + (new DateOnly(2023, 3, 10), "\"2023-03-10\""), + ]; + foreach (var (dateOnly, expected) in testCases) + { + var json = JsonUtils.Serialize(dateOnly); + Assert.That(json, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeDateOnly_ShouldMatchExpectedDateOnly() + { + (DateOnly expected, string json)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (new DateOnly(2023, 1, 1), "\"2023-01-01\""), + (new DateOnly(2023, 12, 31), "\"2023-12-31\""), + (new DateOnly(2023, 6, 15), "\"2023-06-15\""), + (new DateOnly(2023, 3, 10), "\"2023-03-10\""), + ]; + + foreach (var (expected, json) in testCases) + { + var dateOnly = JsonUtils.Deserialize(json); + Assert.That(dateOnly, Is.EqualTo(expected)); + } + } + + [Test] + public void SerializeNullableDateOnly_ShouldMatchExpectedFormat() + { + (DateOnly? dateOnly, string expected)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (null, "null"), + ]; + foreach (var (dateOnly, expected) in testCases) + { + var json = JsonUtils.Serialize(dateOnly); + Assert.That(json, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeNullableDateOnly_ShouldMatchExpectedDateOnly() + { + (DateOnly? expected, string json)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (null, "null"), + ]; + + foreach (var (expected, json) in testCases) + { + var dateOnly = JsonUtils.Deserialize(json); + Assert.That(dateOnly, Is.EqualTo(expected)); + } + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/Json/DateTimeJsonTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/Json/DateTimeJsonTests.cs new file mode 100644 index 000000000000..f135c544d70c --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/Json/DateTimeJsonTests.cs @@ -0,0 +1,110 @@ +using NUnit.Framework; +using Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict.Test.Core.Json; + +[TestFixture] +public class DateTimeJsonTests +{ + [Test] + public void SerializeDateTime_ShouldMatchExpectedFormat() + { + (DateTime dateTime, string expected)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), + ( + new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), + "\"2023-12-31T23:59:59.000Z\"" + ), + (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), + ( + new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.000Z\"" + ), + ( + new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.123Z\"" + ), + ]; + foreach (var (dateTime, expected) in testCases) + { + var json = JsonUtils.Serialize(dateTime); + Assert.That(json, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeDateTime_ShouldMatchExpectedDateTime() + { + (DateTime expected, string json)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), + ( + new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), + "\"2023-12-31T23:59:59.000Z\"" + ), + (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), + ( + new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.000Z\"" + ), + (new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), "\"2023-03-10T08:45:30Z\""), + ( + new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.123Z\"" + ), + ]; + + foreach (var (expected, json) in testCases) + { + var dateTime = JsonUtils.Deserialize(json); + Assert.That(dateTime, Is.EqualTo(expected)); + } + } + + [Test] + public void SerializeNullableDateTime_ShouldMatchExpectedFormat() + { + (DateTime? expected, string json)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (null, "null"), + ]; + + foreach (var (expected, json) in testCases) + { + var dateTime = JsonUtils.Deserialize(json); + Assert.That(dateTime, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeNullableDateTime_ShouldMatchExpectedDateTime() + { + (DateTime? expected, string json)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (null, "null"), + ]; + + foreach (var (expected, json) in testCases) + { + var dateTime = JsonUtils.Deserialize(json); + Assert.That(dateTime, Is.EqualTo(expected)); + } + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/Json/JsonAccessAttributeTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/Json/JsonAccessAttributeTests.cs new file mode 100644 index 000000000000..fbd137606761 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/Json/JsonAccessAttributeTests.cs @@ -0,0 +1,160 @@ +using global::System.Text.Json.Serialization; +using NUnit.Framework; +using Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict.Test.Core.Json; + +[TestFixture] +public class JsonAccessAttributeTests +{ + private class MyClass + { + [JsonPropertyName("read_only_prop")] + [JsonAccess(JsonAccessType.ReadOnly)] + public string? ReadOnlyProp { get; set; } + + [JsonPropertyName("write_only_prop")] + [JsonAccess(JsonAccessType.WriteOnly)] + public string? WriteOnlyProp { get; set; } + + [JsonPropertyName("normal_prop")] + public string? NormalProp { get; set; } + + [JsonPropertyName("read_only_nullable_list")] + [JsonAccess(JsonAccessType.ReadOnly)] + public IEnumerable? ReadOnlyNullableList { get; set; } + + [JsonPropertyName("read_only_list")] + [JsonAccess(JsonAccessType.ReadOnly)] + public IEnumerable ReadOnlyList { get; set; } = []; + + [JsonPropertyName("write_only_nullable_list")] + [JsonAccess(JsonAccessType.WriteOnly)] + public IEnumerable? WriteOnlyNullableList { get; set; } + + [JsonPropertyName("write_only_list")] + [JsonAccess(JsonAccessType.WriteOnly)] + public IEnumerable WriteOnlyList { get; set; } = []; + + [JsonPropertyName("normal_list")] + public IEnumerable NormalList { get; set; } = []; + + [JsonPropertyName("normal_nullable_list")] + public IEnumerable? NullableNormalList { get; set; } + } + + [Test] + public void JsonAccessAttribute_ShouldWorkAsExpected() + { + const string json = """ + { + "read_only_prop": "read", + "write_only_prop": "write", + "normal_prop": "normal_prop", + "read_only_nullable_list": ["item1", "item2"], + "read_only_list": ["item3", "item4"], + "write_only_nullable_list": ["item5", "item6"], + "write_only_list": ["item7", "item8"], + "normal_list": ["normal1", "normal2"], + "normal_nullable_list": ["normal1", "normal2"] + } + """; + var obj = JsonUtils.Deserialize(json); + + Assert.Multiple(() => + { + // String properties + Assert.That(obj.ReadOnlyProp, Is.EqualTo("read")); + Assert.That(obj.WriteOnlyProp, Is.Null); + Assert.That(obj.NormalProp, Is.EqualTo("normal_prop")); + + // List properties - read only + var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); + Assert.That(nullableReadOnlyList, Is.Not.Null); + Assert.That(nullableReadOnlyList, Has.Length.EqualTo(2)); + Assert.That(nullableReadOnlyList![0], Is.EqualTo("item1")); + Assert.That(nullableReadOnlyList![1], Is.EqualTo("item2")); + + var readOnlyList = obj.ReadOnlyList.ToArray(); + Assert.That(readOnlyList, Is.Not.Null); + Assert.That(readOnlyList, Has.Length.EqualTo(2)); + Assert.That(readOnlyList[0], Is.EqualTo("item3")); + Assert.That(readOnlyList[1], Is.EqualTo("item4")); + + // List properties - write only + Assert.That(obj.WriteOnlyNullableList, Is.Null); + Assert.That(obj.WriteOnlyList, Is.Not.Null); + Assert.That(obj.WriteOnlyList, Is.Empty); + + // Normal list property + var normalList = obj.NormalList.ToArray(); + Assert.That(normalList, Is.Not.Null); + Assert.That(normalList, Has.Length.EqualTo(2)); + Assert.That(normalList[0], Is.EqualTo("normal1")); + Assert.That(normalList[1], Is.EqualTo("normal2")); + }); + + // Set up values for serialization + obj.WriteOnlyProp = "write"; + obj.NormalProp = "new_value"; + obj.WriteOnlyNullableList = new List { "write1", "write2" }; + obj.WriteOnlyList = new List { "write3", "write4" }; + obj.NormalList = new List { "new_normal" }; + obj.NullableNormalList = new List { "new_normal" }; + + var serializedJson = JsonUtils.Serialize(obj); + const string expectedJson = """ + { + "write_only_prop": "write", + "normal_prop": "new_value", + "write_only_nullable_list": [ + "write1", + "write2" + ], + "write_only_list": [ + "write3", + "write4" + ], + "normal_list": [ + "new_normal" + ], + "normal_nullable_list": [ + "new_normal" + ] + } + """; + Assert.That(serializedJson, Is.EqualTo(expectedJson).IgnoreWhiteSpace); + } + + [Test] + public void JsonAccessAttribute_WithNullListsInJson_ShouldWorkAsExpected() + { + const string json = """ + { + "read_only_prop": "read", + "normal_prop": "normal_prop", + "read_only_nullable_list": null, + "read_only_list": [] + } + """; + var obj = JsonUtils.Deserialize(json); + + Assert.Multiple(() => + { + // Read-only nullable list should be null when JSON contains null + var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); + Assert.That(nullableReadOnlyList, Is.Null); + + // Read-only non-nullable list should never be null, but empty when JSON contains null + var readOnlyList = obj.ReadOnlyList.ToArray(); // This should be initialized to an empty list by default + Assert.That(readOnlyList, Is.Not.Null); + Assert.That(readOnlyList, Is.Empty); + }); + + // Serialize and verify read-only lists are not included + var serializedJson = JsonUtils.Serialize(obj); + Assert.That(serializedJson, Does.Not.Contain("read_only_prop")); + Assert.That(serializedJson, Does.Not.Contain("read_only_nullable_list")); + Assert.That(serializedJson, Does.Not.Contain("read_only_list")); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/QueryStringBuilderTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/QueryStringBuilderTests.cs new file mode 100644 index 000000000000..5f0afea70898 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/QueryStringBuilderTests.cs @@ -0,0 +1,560 @@ +using NUnit.Framework; +using Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict.Test.Core; + +[TestFixture] +public class QueryStringBuilderTests +{ + [Test] + public void Build_SimpleParameters() + { + var parameters = new List> + { + new("name", "John Doe"), + new("age", "30"), + new("city", "New York"), + }; + + var result = QueryStringBuilder.Build(parameters); + + Assert.That(result, Is.EqualTo("?name=John%20Doe&age=30&city=New%20York")); + } + + [Test] + public void Build_EmptyList_ReturnsEmptyString() + { + var parameters = new List>(); + + var result = QueryStringBuilder.Build(parameters); + + Assert.That(result, Is.EqualTo(string.Empty)); + } + + [Test] + public void Build_SpecialCharacters() + { + var parameters = new List> + { + new("email", "test@example.com"), + new("url", "https://example.com/path?query=value"), + new("special", "a+b=c&d"), + }; + + var result = QueryStringBuilder.Build(parameters); + + Assert.That( + result, + Is.EqualTo( + "?email=test%40example.com&url=https%3A%2F%2Fexample.com%2Fpath%3Fquery%3Dvalue&special=a%2Bb%3Dc%26d" + ) + ); + } + + [Test] + public void Build_UnicodeCharacters() + { + var parameters = new List> { new("greeting", "Hello 世界") }; + + var result = QueryStringBuilder.Build(parameters); + + // Verify the Chinese characters are properly UTF-8 encoded + Assert.That(result, Does.StartWith("?greeting=Hello%20")); + Assert.That(result, Does.Contain("%E4%B8%96%E7%95%8C")); // 世界 + } + + [Test] + public void Build_SessionSettings_DeepObject() + { + // Simulate session settings with nested properties + var sessionSettings = new + { + custom_session_id = "my-custom-session-id", + system_prompt = "You are a helpful assistant", + variables = new Dictionary + { + { "userName", "John" }, + { "userAge", 30 }, + { "isPremium", true }, + }, + }; + + // Build query parameters list + var queryParams = new List> { new("api_key", "test_key_123") }; + + // Add session_settings with prefix using the new overload + queryParams.AddRange( + QueryStringConverter.ToDeepObject("session_settings", sessionSettings) + ); + + var result = QueryStringBuilder.Build(queryParams); + + // Verify the result contains properly formatted deep object notation + // Note: Square brackets are URL-encoded as %5B and %5D + Assert.That(result, Does.StartWith("?api_key=test_key_123")); + Assert.That( + result, + Does.Contain("session_settings%5Bcustom_session_id%5D=my-custom-session-id") + ); + Assert.That( + result, + Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20a%20helpful%20assistant") + ); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserName%5D=John")); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserAge%5D=30")); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BisPremium%5D=true")); + + // Verify it's NOT JSON encoded (no braces or quotes in the original format) + Assert.That(result, Does.Not.Contain("%7B%22")); // Not {" sequence + } + + [Test] + public void Build_ChatApiLikeParameters() + { + // Simulate what ChatApi constructor does + var sessionSettings = new + { + system_prompt = "You are helpful", + variables = new Dictionary { { "name", "Alice" } }, + }; + + var queryParams = new List>(); + + // Simple parameters + var simpleParams = new Dictionary + { + { "access_token", "token123" }, + { "config_id", "config456" }, + { "api_key", "key789" }, + }; + queryParams.AddRange(QueryStringConverter.ToExplodedForm(simpleParams)); + + // Session settings as deep object with prefix + queryParams.AddRange( + QueryStringConverter.ToDeepObject("session_settings", sessionSettings) + ); + + var result = QueryStringBuilder.Build(queryParams); + + // Verify structure (square brackets are URL-encoded) + Assert.That(result, Does.StartWith("?")); + Assert.That(result, Does.Contain("access_token=token123")); + Assert.That(result, Does.Contain("config_id=config456")); + Assert.That(result, Does.Contain("api_key=key789")); + Assert.That( + result, + Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20helpful") + ); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bname%5D=Alice")); + } + + [Test] + public void Build_ReservedCharacters_NotEncoded() + { + var parameters = new List> + { + new("path", "some-path"), + new("id", "123-456_789.test~value"), + }; + + var result = QueryStringBuilder.Build(parameters); + + // Unreserved characters: A-Z a-z 0-9 - _ . ~ + Assert.That(result, Is.EqualTo("?path=some-path&id=123-456_789.test~value")); + } + + [Test] + public void Builder_Add_SimpleParameters() + { + var result = new QueryStringBuilder.Builder() + .Add("name", "John Doe") + .Add("age", 30) + .Add("active", true) + .Build(); + + Assert.That(result, Does.Contain("name=John%20Doe")); + Assert.That(result, Does.Contain("age=30")); + Assert.That(result, Does.Contain("active=true")); + } + + [Test] + public void Builder_Add_NullValuesIgnored() + { + var result = new QueryStringBuilder.Builder() + .Add("name", "John") + .Add("middle", null) + .Add("age", 30) + .Build(); + + Assert.That(result, Does.Contain("name=John")); + Assert.That(result, Does.Contain("age=30")); + Assert.That(result, Does.Not.Contain("middle")); + } + + [Test] + public void Builder_AddDeepObject_WithPrefix() + { + var settings = new + { + custom_session_id = "id-123", + system_prompt = "You are helpful", + variables = new { name = "Alice", age = 25 }, + }; + + var result = new QueryStringBuilder.Builder() + .Add("api_key", "key123") + .AddDeepObject("session_settings", settings) + .Build(); + + Assert.That(result, Does.Contain("api_key=key123")); + Assert.That(result, Does.Contain("session_settings%5Bcustom_session_id%5D=id-123")); + Assert.That( + result, + Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20helpful") + ); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bname%5D=Alice")); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bage%5D=25")); + } + + [Test] + public void Builder_AddDeepObject_NullIgnored() + { + var result = new QueryStringBuilder.Builder() + .Add("api_key", "key123") + .AddDeepObject("settings", null) + .Build(); + + Assert.That(result, Is.EqualTo("?api_key=key123")); + Assert.That(result, Does.Not.Contain("settings")); + } + + [Test] + public void Builder_AddExploded_WithPrefix() + { + var filter = new { status = "active", type = "user" }; + + var result = new QueryStringBuilder.Builder() + .Add("api_key", "key123") + .AddExploded("filter", filter) + .Build(); + + Assert.That(result, Does.Contain("api_key=key123")); + Assert.That(result, Does.Contain("filter%5Bstatus%5D=active")); + Assert.That(result, Does.Contain("filter%5Btype%5D=user")); + } + + [Test] + public void Builder_AddExploded_NullIgnored() + { + var result = new QueryStringBuilder.Builder() + .Add("api_key", "key123") + .AddExploded("filter", null) + .Build(); + + Assert.That(result, Is.EqualTo("?api_key=key123")); + Assert.That(result, Does.Not.Contain("filter")); + } + + [Test] + public void Builder_WithCapacity() + { + // Test that capacity constructor works without errors + var result = new QueryStringBuilder.Builder(capacity: 10) + .Add("param1", "value1") + .Add("param2", "value2") + .Build(); + + Assert.That(result, Does.Contain("param1=value1")); + Assert.That(result, Does.Contain("param2=value2")); + } + + [Test] + public void Builder_ChatApiLikeUsage() + { + // Simulate real usage from ChatApi + var sessionSettings = new + { + custom_session_id = "session-123", + variables = new Dictionary + { + { "userName", "John" }, + { "userAge", 30 }, + }, + }; + + var result = new QueryStringBuilder.Builder(capacity: 16) + .Add("access_token", "token123") + .Add("allow_connection", true) + .Add("config_id", "config456") + .Add("api_key", "key789") + .AddDeepObject("session_settings", sessionSettings) + .Build(); + + Assert.That(result, Does.StartWith("?")); + Assert.That(result, Does.Contain("access_token=token123")); + Assert.That(result, Does.Contain("allow_connection=true")); + Assert.That(result, Does.Contain("config_id=config456")); + Assert.That(result, Does.Contain("api_key=key789")); + Assert.That(result, Does.Contain("session_settings%5Bcustom_session_id%5D=session-123")); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserName%5D=John")); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserAge%5D=30")); + } + + [Test] + public void Builder_EmptyBuilder_ReturnsEmptyString() + { + var result = new QueryStringBuilder.Builder().Build(); + + Assert.That(result, Is.EqualTo(string.Empty)); + } + + [Test] + public void Builder_OnlyNullValues_ReturnsEmptyString() + { + var result = new QueryStringBuilder.Builder() + .Add("param1", null) + .Add("param2", null) + .AddDeepObject("settings", null) + .Build(); + + Assert.That(result, Is.EqualTo(string.Empty)); + } + + [Test] + public void Builder_Set_OverridesSingleValue() + { + var result = new QueryStringBuilder.Builder() + .Add("foo", "original") + .Set("foo", "override") + .Build(); + + Assert.That(result, Is.EqualTo("?foo=override")); + } + + [Test] + public void Builder_Set_OverridesMultipleValues() + { + var result = new QueryStringBuilder.Builder() + .Add("foo", "value1") + .Add("foo", "value2") + .Set("foo", "override") + .Build(); + + Assert.That(result, Is.EqualTo("?foo=override")); + } + + [Test] + public void Builder_Set_WithArray_CreatesMultipleParameters() + { + var result = new QueryStringBuilder.Builder() + .Add("foo", "original") + .Set("foo", new[] { "value1", "value2" }) + .Build(); + + Assert.That(result, Is.EqualTo("?foo=value1&foo=value2")); + } + + [Test] + public void Builder_Set_WithNull_RemovesParameter() + { + var result = new QueryStringBuilder.Builder() + .Add("foo", "original") + .Add("bar", "keep") + .Set("foo", null) + .Build(); + + Assert.That(result, Is.EqualTo("?bar=keep")); + } + + [Test] + public void Builder_MergeAdditional_WithSingleValues() + { + var additional = new List> + { + new("foo", "bar"), + new("baz", "qux"), + }; + + var result = new QueryStringBuilder.Builder() + .Add("existing", "value") + .MergeAdditional(additional) + .Build(); + + Assert.That(result, Does.Contain("existing=value")); + Assert.That(result, Does.Contain("foo=bar")); + Assert.That(result, Does.Contain("baz=qux")); + } + + [Test] + public void Builder_MergeAdditional_WithDuplicateKeys_CreatesList() + { + var additional = new List> + { + new("foo", "bar1"), + new("foo", "bar2"), + new("baz", "qux"), + }; + + var result = new QueryStringBuilder.Builder() + .Add("existing", "value") + .MergeAdditional(additional) + .Build(); + + Assert.That(result, Does.Contain("existing=value")); + Assert.That(result, Does.Contain("foo=bar1")); + Assert.That(result, Does.Contain("foo=bar2")); + Assert.That(result, Does.Contain("baz=qux")); + } + + [Test] + public void Builder_MergeAdditional_OverridesExistingParameters() + { + var additional = new List> { new("foo", "override") }; + + var result = new QueryStringBuilder.Builder() + .Add("foo", "original1") + .Add("foo", "original2") + .Add("bar", "keep") + .MergeAdditional(additional) + .Build(); + + Assert.That(result, Does.Contain("bar=keep")); + Assert.That(result, Does.Contain("foo=override")); + Assert.That(result, Does.Not.Contain("original1")); + Assert.That(result, Does.Not.Contain("original2")); + } + + [Test] + public void Builder_MergeAdditional_WithDuplicates_OverridesExisting() + { + var additional = new List> + { + new("foo", "new1"), + new("foo", "new2"), + new("foo", "new3"), + }; + + var result = new QueryStringBuilder.Builder() + .Add("foo", "original1") + .Add("foo", "original2") + .Add("bar", "keep") + .MergeAdditional(additional) + .Build(); + + Assert.That(result, Does.Contain("bar=keep")); + Assert.That(result, Does.Contain("foo=new1")); + Assert.That(result, Does.Contain("foo=new2")); + Assert.That(result, Does.Contain("foo=new3")); + Assert.That(result, Does.Not.Contain("original1")); + Assert.That(result, Does.Not.Contain("original2")); + } + + [Test] + public void Builder_MergeAdditional_WithNull_NoOp() + { + var result = new QueryStringBuilder.Builder() + .Add("foo", "value") + .MergeAdditional(null) + .Build(); + + Assert.That(result, Is.EqualTo("?foo=value")); + } + + [Test] + public void Builder_MergeAdditional_WithEmptyList_NoOp() + { + var additional = new List>(); + + var result = new QueryStringBuilder.Builder() + .Add("foo", "value") + .MergeAdditional(additional) + .Build(); + + Assert.That(result, Is.EqualTo("?foo=value")); + } + + [Test] + public void Builder_MergeAdditional_RealWorldScenario() + { + // SDK generates foo=foo1&foo=foo2 + var builder = new QueryStringBuilder.Builder() + .Add("foo", "foo1") + .Add("foo", "foo2") + .Add("bar", "baz"); + + // User provides foo=override in AdditionalQueryParameters + var additional = new List> { new("foo", "override") }; + + var result = builder.MergeAdditional(additional).Build(); + + // Result should be foo=override&bar=baz (user overrides SDK) + Assert.That(result, Does.Contain("bar=baz")); + Assert.That(result, Does.Contain("foo=override")); + Assert.That(result, Does.Not.Contain("foo1")); + Assert.That(result, Does.Not.Contain("foo2")); + } + + [Test] + public void Builder_MergeAdditional_UserProvidesMultipleValues() + { + // SDK generates no foo parameter + var builder = new QueryStringBuilder.Builder().Add("bar", "baz"); + + // User provides foo=bar1&foo=bar2 in AdditionalQueryParameters + var additional = new List> + { + new("foo", "bar1"), + new("foo", "bar2"), + }; + + var result = builder.MergeAdditional(additional).Build(); + + // Result should be bar=baz&foo=bar1&foo=bar2 + Assert.That(result, Does.Contain("bar=baz")); + Assert.That(result, Does.Contain("foo=bar1")); + Assert.That(result, Does.Contain("foo=bar2")); + } + + [Test] + public void Builder_Add_WithCollection_CreatesMultipleParameters() + { + var tags = new[] { "tag1", "tag2", "tag3" }; + var result = new QueryStringBuilder.Builder().Add("tag", tags).Build(); + + Assert.That(result, Does.Contain("tag=tag1")); + Assert.That(result, Does.Contain("tag=tag2")); + Assert.That(result, Does.Contain("tag=tag3")); + } + + [Test] + public void Builder_Add_WithList_CreatesMultipleParameters() + { + var ids = new List { 1, 2, 3 }; + var result = new QueryStringBuilder.Builder().Add("id", ids).Build(); + + Assert.That(result, Does.Contain("id=1")); + Assert.That(result, Does.Contain("id=2")); + Assert.That(result, Does.Contain("id=3")); + } + + [Test] + public void Builder_Set_WithCollection_ReplacesAllPreviousValues() + { + var result = new QueryStringBuilder.Builder() + .Add("id", 1) + .Add("id", 2) + .Set("id", new[] { 10, 20, 30 }) + .Build(); + + Assert.That(result, Does.Contain("id=10")); + Assert.That(result, Does.Contain("id=20")); + Assert.That(result, Does.Contain("id=30")); + // Check that old values are not present (use word boundaries to avoid false positives with id=10) + Assert.That(result, Does.Not.Contain("id=1&")); + Assert.That(result, Does.Not.Contain("id=2&")); + Assert.That(result, Does.Not.Contain("id=1?")); + Assert.That(result, Does.Not.Contain("id=2?")); + Assert.That(result, Does.Not.EndWith("id=1")); + Assert.That(result, Does.Not.EndWith("id=2")); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/QueryStringConverterTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/QueryStringConverterTests.cs new file mode 100644 index 000000000000..51fc2785d936 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/QueryStringConverterTests.cs @@ -0,0 +1,158 @@ +using NUnit.Framework; +using Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict.Test.Core; + +[TestFixture] +public class QueryStringConverterTests +{ + [Test] + public void ToQueryStringCollection_Form() + { + var obj = new + { + Name = "John", + Age = 30, + Address = new + { + Street = "123 Main St", + City = "Anytown", + Coordinates = new[] { 39.781721f, -89.650148f }, + }, + Tags = new[] { "Developer", "Blogger" }, + }; + var result = QueryStringConverter.ToForm(obj); + var expected = new List> + { + new("Name", "John"), + new("Age", "30"), + new("Address[Street]", "123 Main St"), + new("Address[City]", "Anytown"), + new("Address[Coordinates]", "39.78172,-89.65015"), + new("Tags", "Developer,Blogger"), + }; + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void ToQueryStringCollection_ExplodedForm() + { + var obj = new + { + Name = "John", + Age = 30, + Address = new + { + Street = "123 Main St", + City = "Anytown", + Coordinates = new[] { 39.781721f, -89.650148f }, + }, + Tags = new[] { "Developer", "Blogger" }, + }; + var result = QueryStringConverter.ToExplodedForm(obj); + var expected = new List> + { + new("Name", "John"), + new("Age", "30"), + new("Address[Street]", "123 Main St"), + new("Address[City]", "Anytown"), + new("Address[Coordinates]", "39.78172"), + new("Address[Coordinates]", "-89.65015"), + new("Tags", "Developer"), + new("Tags", "Blogger"), + }; + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void ToQueryStringCollection_DeepObject() + { + var obj = new + { + Name = "John", + Age = 30, + Address = new + { + Street = "123 Main St", + City = "Anytown", + Coordinates = new[] { 39.781721f, -89.650148f }, + }, + Tags = new[] { "Developer", "Blogger" }, + }; + var result = QueryStringConverter.ToDeepObject(obj); + var expected = new List> + { + new("Name", "John"), + new("Age", "30"), + new("Address[Street]", "123 Main St"), + new("Address[City]", "Anytown"), + new("Address[Coordinates][0]", "39.78172"), + new("Address[Coordinates][1]", "-89.65015"), + new("Tags[0]", "Developer"), + new("Tags[1]", "Blogger"), + }; + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void ToQueryStringCollection_OnString_ThrowsException() + { + var exception = Assert.Throws(() => + QueryStringConverter.ToForm("invalid") + ); + Assert.That( + exception.Message, + Is.EqualTo( + "Only objects can be converted to query string collections. Given type is String." + ) + ); + } + + [Test] + public void ToQueryStringCollection_OnArray_ThrowsException() + { + var exception = Assert.Throws(() => + QueryStringConverter.ToForm(Array.Empty()) + ); + Assert.That( + exception.Message, + Is.EqualTo( + "Only objects can be converted to query string collections. Given type is Array." + ) + ); + } + + [Test] + public void ToQueryStringCollection_DeepObject_WithPrefix() + { + var obj = new + { + custom_session_id = "my-id", + system_prompt = "You are helpful", + variables = new { name = "Alice", age = 25 }, + }; + var result = QueryStringConverter.ToDeepObject("session_settings", obj); + var expected = new List> + { + new("session_settings[custom_session_id]", "my-id"), + new("session_settings[system_prompt]", "You are helpful"), + new("session_settings[variables][name]", "Alice"), + new("session_settings[variables][age]", "25"), + }; + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void ToQueryStringCollection_ExplodedForm_WithPrefix() + { + var obj = new { Name = "John", Tags = new[] { "Developer", "Blogger" } }; + var result = QueryStringConverter.ToExplodedForm("user", obj); + var expected = new List> + { + new("user[Name]", "John"), + new("user[Tags]", "Developer"), + new("user[Tags]", "Blogger"), + }; + Assert.That(result, Is.EqualTo(expected)); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/RawClientTests/MultipartFormTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/RawClientTests/MultipartFormTests.cs new file mode 100644 index 000000000000..fc4dafa1a281 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/RawClientTests/MultipartFormTests.cs @@ -0,0 +1,1121 @@ +using global::System.Net.Http; +using global::System.Text; +using global::System.Text.Json.Serialization; +using NUnit.Framework; +using Seed.CsharpNamespaceConflict.Core; +using SystemTask = global::System.Threading.Tasks.Task; + +namespace Seed.CsharpNamespaceConflict.Test.Core.RawClientTests; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class MultipartFormTests +{ + private static SimpleObject _simpleObject = new(); + + private static string _simpleFormEncoded = + "meta=data&Date=2023-10-01&Time=12:00:00&Duration=01:00:00&Id=1a1bb98f-47c6-407b-9481-78476affe52a&IsActive=true&Count=42&Initial=A&Values=data,2023-10-01,12:00:00,01:00:00,1a1bb98f-47c6-407b-9481-78476affe52a,true,42,A"; + + private static string _simpleExplodedFormEncoded = + "meta=data&Date=2023-10-01&Time=12:00:00&Duration=01:00:00&Id=1a1bb98f-47c6-407b-9481-78476affe52a&IsActive=true&Count=42&Initial=A&Values=data&Values=2023-10-01&Values=12:00:00&Values=01:00:00&Values=1a1bb98f-47c6-407b-9481-78476affe52a&Values=true&Values=42&Values=A"; + + private static ComplexObject _complexObject = new(); + + private static string _complexJson = """ + { + "meta": "data", + "Nested": { + "foo": "value" + }, + "NestedDictionary": { + "key": { + "foo": "value" + } + }, + "ListOfObjects": [ + { + "foo": "value" + }, + { + "foo": "value2" + } + ], + "Date": "2023-10-01", + "Time": "12:00:00", + "Duration": "01:00:00", + "Id": "1a1bb98f-47c6-407b-9481-78476affe52a", + "IsActive": true, + "Count": 42, + "Initial": "A" + } + """; + + [Test] + public async SystemTask ShouldAddStringPart() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringPart("string", partInput); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=string + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringParts() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringParts("strings", [partInput, partInput]); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask GivenNull_ShouldNotAddStringPart() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringPart("string", null); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringParts_WithNullsInList() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringParts("strings", [partInput, null, partInput]); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringPart_WithContentType() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringPart("string", partInput, "text/xml"); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/xml + Content-Disposition: form-data; name=string + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringPart_WithContentTypeAndCharset() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringPart("string", partInput, "text/xml; charset=utf-8"); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/xml; charset=utf-8 + Content-Disposition: form-data; name=string + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringParts_WithContentType() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringParts("strings", [partInput, partInput], "text/xml"); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/xml + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary} + Content-Type: text/xml + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringParts_WithContentTypeAndCharset() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringParts( + "strings", + [partInput, partInput], + "text/xml; charset=utf-8" + ); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/xml; charset=utf-8 + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary} + Content-Type: text/xml; charset=utf-8 + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithFileName() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", file); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithoutFileName() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", partInput); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithContentType() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var file = new FileParameter + { + Stream = partInput, + FileName = "test.txt", + ContentType = "text/plain", + }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", file, "ignored-fallback-content-type"); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithContentTypeAndCharset() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var file = new FileParameter + { + Stream = partInput, + FileName = "test.txt", + ContentType = "text/plain; charset=utf-8", + }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", file, "ignored-fallback-content-type"); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain; charset=utf-8 + Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithFallbackContentType() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", file, "text/plain"); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithFallbackContentTypeAndCharset() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", file, "text/plain; charset=utf-8"); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain; charset=utf-8 + Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameters() + { + var (partInput1, partExpectedString1) = GetFileParameterTestData(); + var (partInput2, partExpectedString2) = GetFileParameterTestData(); + var file1 = new FileParameter { Stream = partInput1, FileName = "test1.txt" }; + var file2 = new FileParameter { Stream = partInput2, FileName = "test2.txt" }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterParts("file", [file1, file2]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file; filename=test1.txt; filename*=utf-8''test1.txt + + {partExpectedString1} + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file; filename=test2.txt; filename*=utf-8''test2.txt + + {partExpectedString2} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameters_WithNullsInList() + { + var (partInput1, partExpectedString1) = GetFileParameterTestData(); + var (partInput2, partExpectedString2) = GetFileParameterTestData(); + var file1 = new FileParameter { Stream = partInput1, FileName = "test1.txt" }; + var file2 = new FileParameter { Stream = partInput2, FileName = "test2.txt" }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterParts("file", [file1, null, file2]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file; filename=test1.txt; filename*=utf-8''test1.txt + + {partExpectedString1} + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file; filename=test2.txt; filename*=utf-8''test2.txt + + {partExpectedString2} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask GivenNull_ShouldNotAddFileParameter() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", null); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddJsonPart_WithComplexObject() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddJsonPart("object", _complexObject); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/json + Content-Disposition: form-data; name=object + + {_complexJson} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddJsonPart_WithComplexObjectList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddJsonParts("objects", [_complexObject, _complexObject]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/json + Content-Disposition: form-data; name=objects + + {_complexJson} + --{boundary} + Content-Type: application/json + Content-Disposition: form-data; name=objects + + {_complexJson} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask GivenNull_ShouldNotAddJsonPart() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddJsonPart("object", null); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddJsonParts_WithNullsInList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddJsonParts("objects", [_complexObject, null]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/json + Content-Disposition: form-data; name=objects + + {_complexJson} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddJsonParts_WithContentType() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddJsonParts("objects", [new { }], "application/json-patch+json"); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $$""" + --{{boundary}} + Content-Type: application/json-patch+json + Content-Disposition: form-data; name=objects + + {} + --{{boundary}}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedParts_WithSimpleObject() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedPart("object", _simpleObject); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=object + + {EscapeFormEncodedString(_simpleFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedParts_WithSimpleObjectList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedParts("objects", [_simpleObject, _simpleObject]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleFormEncoded)} + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldNotAddFormEncodedParts_WithNull() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedParts("object", null); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldNotAddFormEncodedParts_WithNullsInList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedParts("objects", [_simpleObject, null]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedPart_WithContentType() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedPart( + "objects", + new { foo = "bar" }, + "application/x-www-form-urlencoded" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedPart_WithContentTypeAndCharset() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedPart( + "objects", + new { foo = "bar" }, + "application/x-www-form-urlencoded; charset=utf-8" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded; charset=utf-8 + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedParts_WithContentType() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedParts( + "objects", + [new { foo = "bar" }], + "application/x-www-form-urlencoded" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedParts_WithContentTypeAndCharset() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedParts( + "objects", + [new { foo = "bar" }], + "application/x-www-form-urlencoded; charset=utf-8" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded; charset=utf-8 + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedParts_WithSimpleObject() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedPart("object", _simpleObject); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=object + + {EscapeFormEncodedString(_simpleExplodedFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedParts_WithSimpleObjectList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedParts("objects", [_simpleObject, _simpleObject]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleExplodedFormEncoded)} + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleExplodedFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldNotAddExplodedFormEncodedParts_WithNull() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedPart("object", null); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldNotAddExplodedFormEncodedParts_WithNullsInList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedParts("objects", [_simpleObject, null]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleExplodedFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedPart_WithContentType() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedPart( + "objects", + new { foo = "bar" }, + "application/x-www-form-urlencoded" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedPart_WithContentTypeAndCharset() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedPart( + "objects", + new { foo = "bar" }, + "application/x-www-form-urlencoded; charset=utf-8" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded; charset=utf-8 + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedParts_WithContentType() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedParts( + "objects", + [new { foo = "bar" }], + "application/x-www-form-urlencoded" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedParts_WithContentTypeAndCharset() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedParts( + "objects", + [new { foo = "bar" }], + "application/x-www-form-urlencoded; charset=utf-8" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded; charset=utf-8 + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + private static string EscapeFormEncodedString(string input) + { + return string.Join( + "&", + input + .Split('&') + .Select(x => x.Split('=')) + .Select(x => $"{Uri.EscapeDataString(x[0])}={Uri.EscapeDataString(x[1])}") + ); + } + + private static string GetBoundary(MultipartFormDataContent content) + { + return content + .Headers.ContentType?.Parameters.Single(p => + p.Name.Equals("boundary", StringComparison.OrdinalIgnoreCase) + ) + .Value?.Trim('"') + ?? throw new global::System.Exception("Boundary not found"); + } + + private static global::Seed.CsharpNamespaceConflict.Core.MultipartFormRequest CreateMultipartFormRequest() + { + return new global::Seed.CsharpNamespaceConflict.Core.MultipartFormRequest + { + BaseUrl = "https://localhost", + Method = HttpMethod.Post, + Path = "", + }; + } + + private static (Stream partInput, string partExpectedString) GetFileParameterTestData() + { + const string partExpectedString = "file content"; + var partInput = new MemoryStream(Encoding.Default.GetBytes(partExpectedString)); + return (partInput, partExpectedString); + } + + private class SimpleObject + { + [JsonPropertyName("meta")] + public string Meta { get; set; } = "data"; + public DateOnly Date { get; set; } = DateOnly.Parse("2023-10-01"); + public TimeOnly Time { get; set; } = TimeOnly.Parse("12:00:00"); + public TimeSpan Duration { get; set; } = TimeSpan.FromHours(1); + public Guid Id { get; set; } = Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"); + public bool IsActive { get; set; } = true; + public int Count { get; set; } = 42; + public char Initial { get; set; } = 'A'; + public IEnumerable Values { get; set; } = + [ + "data", + DateOnly.Parse("2023-10-01"), + TimeOnly.Parse("12:00:00"), + TimeSpan.FromHours(1), + Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"), + true, + 42, + 'A', + ]; + } + + private class ComplexObject + { + [JsonPropertyName("meta")] + public string Meta { get; set; } = "data"; + + public object Nested { get; set; } = new { foo = "value" }; + + public Dictionary NestedDictionary { get; set; } = + new() { { "key", new { foo = "value" } } }; + + public IEnumerable ListOfObjects { get; set; } = + new List { new { foo = "value" }, new { foo = "value2" } }; + + public DateOnly Date { get; set; } = DateOnly.Parse("2023-10-01"); + public TimeOnly Time { get; set; } = TimeOnly.Parse("12:00:00"); + public TimeSpan Duration { get; set; } = TimeSpan.FromHours(1); + public Guid Id { get; set; } = Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"); + public bool IsActive { get; set; } = true; + public int Count { get; set; } = 42; + public char Initial { get; set; } = 'A'; + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/RawClientTests/QueryParameterTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/RawClientTests/QueryParameterTests.cs new file mode 100644 index 000000000000..6af524fe234d --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/RawClientTests/QueryParameterTests.cs @@ -0,0 +1,108 @@ +using NUnit.Framework; +using Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict.Test.Core.RawClientTests; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class QueryParameterTests +{ + [Test] + public void QueryParameters_BasicParameters() + { + var queryString = new QueryStringBuilder.Builder() + .Add("foo", "bar") + .Add("baz", "qux") + .Build(); + + Assert.That(queryString, Is.EqualTo("?foo=bar&baz=qux")); + } + + [Test] + public void QueryParameters_SpecialCharacterEscaping() + { + var queryString = new QueryStringBuilder.Builder() + .Add("email", "bob+test@example.com") + .Add("%Complete", "100") + .Add("space test", "hello world") + .Build(); + + Assert.That(queryString, Does.Contain("email=bob%2Btest%40example.com")); + Assert.That(queryString, Does.Contain("%25Complete=100")); + Assert.That(queryString, Does.Contain("space%20test=hello%20world")); + } + + [Test] + public void QueryParameters_MergeAdditionalParameters() + { + var queryString = new QueryStringBuilder.Builder() + .Add("sdk", "param") + .MergeAdditional(new List> { new("user", "value") }) + .Build(); + + Assert.That(queryString, Does.Contain("sdk=param")); + Assert.That(queryString, Does.Contain("user=value")); + } + + [Test] + public void QueryParameters_AdditionalOverridesSdk() + { + var queryString = new QueryStringBuilder.Builder() + .Add("foo", "sdk_value") + .MergeAdditional(new List> { new("foo", "user_override") }) + .Build(); + + Assert.That(queryString, Does.Contain("foo=user_override")); + Assert.That(queryString, Does.Not.Contain("sdk_value")); + } + + [Test] + public void QueryParameters_AdditionalMultipleValues() + { + var queryString = new QueryStringBuilder.Builder() + .Add("foo", "sdk_value") + .MergeAdditional( + new List> { new("foo", "user1"), new("foo", "user2") } + ) + .Build(); + + Assert.That(queryString, Does.Contain("foo=user1")); + Assert.That(queryString, Does.Contain("foo=user2")); + Assert.That(queryString, Does.Not.Contain("sdk_value")); + } + + [Test] + public void QueryParameters_OnlyAdditionalParameters() + { + var queryString = new QueryStringBuilder.Builder() + .MergeAdditional( + new List> { new("foo", "bar"), new("baz", "qux") } + ) + .Build(); + + Assert.That(queryString, Does.Contain("foo=bar")); + Assert.That(queryString, Does.Contain("baz=qux")); + } + + [Test] + public void QueryParameters_EmptyAdditionalParameters() + { + var queryString = new QueryStringBuilder.Builder() + .Add("foo", "bar") + .MergeAdditional(new List>()) + .Build(); + + Assert.That(queryString, Is.EqualTo("?foo=bar")); + } + + [Test] + public void QueryParameters_NullAdditionalParameters() + { + var queryString = new QueryStringBuilder.Builder() + .Add("foo", "bar") + .MergeAdditional(null) + .Build(); + + Assert.That(queryString, Is.EqualTo("?foo=bar")); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/RawClientTests/RetriesTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/RawClientTests/RetriesTests.cs new file mode 100644 index 000000000000..2a53aaff5f39 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/RawClientTests/RetriesTests.cs @@ -0,0 +1,406 @@ +using global::System.Net.Http; +using global::System.Text.Json; +using NUnit.Framework; +using Seed.CsharpNamespaceConflict.Core; +using WireMock.Server; +using SystemTask = global::System.Threading.Tasks.Task; +using WireMockRequest = WireMock.RequestBuilders.Request; +using WireMockResponse = WireMock.ResponseBuilders.Response; + +namespace Seed.CsharpNamespaceConflict.Test.Core.RawClientTests; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class RetriesTests +{ + private const int MaxRetries = 3; + private WireMockServer _server; + private HttpClient _httpClient; + private RawClient _rawClient; + private string _baseUrl; + + [SetUp] + public void SetUp() + { + _server = WireMockServer.Start(); + _baseUrl = _server.Url ?? ""; + _httpClient = new HttpClient { BaseAddress = new Uri(_baseUrl) }; + _rawClient = new RawClient( + new ClientOptions { HttpClient = _httpClient, MaxRetries = MaxRetries } + ) + { + BaseRetryDelay = 0, + }; + } + + [Test] + [TestCase(408)] + [TestCase(429)] + [TestCase(500)] + [TestCase(504)] + public async SystemTask SendRequestAsync_ShouldRetry_OnRetryableStatusCodes(int statusCode) + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("Retry") + .WillSetStateTo("Server Error") + .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("Retry") + .WhenStateIs("Server Error") + .WillSetStateTo("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("Retry") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new EmptyRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Get, + Path = "/test", + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + using (Assert.EnterMultipleScope()) + { + Assert.That(content, Is.EqualTo("Success")); + + Assert.That(_server.LogEntries, Has.Count.EqualTo(MaxRetries)); + } + } + + [Test] + [TestCase(400)] + [TestCase(409)] + public async SystemTask SendRequestAsync_ShouldRetry_OnNonRetryableStatusCodes(int statusCode) + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("Retry") + .WillSetStateTo("Server Error") + .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode).WithBody("Failure")); + + var request = new JsonRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Get, + Path = "/test", + Body = new { }, + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(statusCode)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Failure")); + + Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldNotRetry_WithStreamRequest() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("Retry") + .WillSetStateTo("Server Error") + .RespondWith(WireMockResponse.Create().WithStatusCode(429).WithBody("Failure")); + + var request = new StreamRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Post, + Path = "/test", + Body = new MemoryStream(), + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(429)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Failure")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldNotRetry_WithMultiPartFormRequest_WithStream() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("Retry") + .WillSetStateTo("Server Error") + .RespondWith(WireMockResponse.Create().WithStatusCode(429).WithBody("Failure")); + + var request = new global::Seed.CsharpNamespaceConflict.Core.MultipartFormRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Post, + Path = "/test", + }; + request.AddFileParameterPart("file", new MemoryStream()); + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(429)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Failure")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldRetry_WithMultiPartFormRequest_WithoutStream() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("Retry") + .WillSetStateTo("Server Error") + .RespondWith(WireMockResponse.Create().WithStatusCode(429)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("Retry") + .WhenStateIs("Server Error") + .WillSetStateTo("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(429)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("Retry") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new global::Seed.CsharpNamespaceConflict.Core.MultipartFormRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Post, + Path = "/test", + }; + request.AddJsonPart("object", new { }); + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(MaxRetries)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldRespectRetryAfterHeader_WithSecondsValue() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RetryAfter") + .WillSetStateTo("Success") + .RespondWith( + WireMockResponse.Create().WithStatusCode(429).WithHeader("Retry-After", "1") + ); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RetryAfter") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new EmptyRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Get, + Path = "/test", + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldRespectRetryAfterHeader_WithHttpDateValue() + { + var retryAfterDate = DateTimeOffset.UtcNow.AddSeconds(1).ToString("R"); + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RetryAfterDate") + .WillSetStateTo("Success") + .RespondWith( + WireMockResponse + .Create() + .WithStatusCode(429) + .WithHeader("Retry-After", retryAfterDate) + ); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RetryAfterDate") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new EmptyRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Get, + Path = "/test", + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldRespectXRateLimitResetHeader() + { + var resetTime = DateTimeOffset.UtcNow.AddSeconds(1).ToUnixTimeSeconds().ToString(); + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RateLimitReset") + .WillSetStateTo("Success") + .RespondWith( + WireMockResponse + .Create() + .WithStatusCode(429) + .WithHeader("X-RateLimit-Reset", resetTime) + ); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RateLimitReset") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new EmptyRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Get, + Path = "/test", + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldPreserveJsonBody_OnRetry() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("RetryWithBody") + .WillSetStateTo("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(500)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("RetryWithBody") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new JsonRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Post, + Path = "/test", + Body = new { key = "value" }, + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + using (Assert.EnterMultipleScope()) + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); + + // Verify the retried request preserved the JSON body (compare parsed to ignore formatting differences) + var retriedEntry = _server.LogEntries.ElementAt(1); + using var actualJson = JsonDocument.Parse(retriedEntry.RequestMessage.Body!); + Assert.That(actualJson.RootElement.GetProperty("key").GetString(), Is.EqualTo("value")); + } + } + + [Test] + public async SystemTask SendRequestAsync_ShouldPreserveMultipartBody_OnRetry() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("RetryMultipart") + .WillSetStateTo("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(500)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("RetryMultipart") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new global::Seed.CsharpNamespaceConflict.Core.MultipartFormRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Post, + Path = "/test", + }; + request.AddJsonPart("object", new { key = "value" }); + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + using (Assert.EnterMultipleScope()) + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); + + // Verify the retried request preserved the multipart body (check key/value presence to ignore formatting differences) + var retriedEntry = _server.LogEntries.ElementAt(1); + Assert.That(retriedEntry.RequestMessage.Body, Does.Contain("\"key\"")); + Assert.That(retriedEntry.RequestMessage.Body, Does.Contain("\"value\"")); + } + } + + [TearDown] + public void TearDown() + { + _server.Dispose(); + _httpClient.Dispose(); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/WithRawResponseTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/WithRawResponseTests.cs new file mode 100644 index 000000000000..151dcad198e1 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Core/WithRawResponseTests.cs @@ -0,0 +1,269 @@ +using global::System.Net; +using global::System.Net.Http.Headers; +using NUnit.Framework; +using Seed.CsharpNamespaceConflict; +using Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict.Test.Core; + +[TestFixture] +public class WithRawResponseTests +{ + [Test] + public async global::System.Threading.Tasks.Task WithRawResponseTask_DirectAwait_ReturnsData() + { + // Arrange + var expectedData = "test-data"; + var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); + + // Act + var result = await task; + + // Assert + Assert.That(result, Is.EqualTo(expectedData)); + } + + [Test] + public async global::System.Threading.Tasks.Task WithRawResponseTask_WithRawResponse_ReturnsDataAndMetadata() + { + // Arrange + var expectedData = "test-data"; + var expectedStatusCode = HttpStatusCode.Created; + var task = CreateWithRawResponseTask(expectedData, expectedStatusCode); + + // Act + var result = await task.WithRawResponse(); + + // Assert + Assert.That(result.Data, Is.EqualTo(expectedData)); + Assert.That(result.RawResponse.StatusCode, Is.EqualTo(expectedStatusCode)); + Assert.That(result.RawResponse.Url, Is.Not.Null); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValue_CaseInsensitive() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Headers.Add("X-Request-Id", "12345"); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act & Assert + Assert.That(headers.TryGetValue("X-Request-Id", out var value), Is.True); + Assert.That(value, Is.EqualTo("12345")); + + Assert.That(headers.TryGetValue("x-request-id", out value), Is.True); + Assert.That(value, Is.EqualTo("12345")); + + Assert.That(headers.TryGetValue("X-REQUEST-ID", out value), Is.True); + Assert.That(value, Is.EqualTo("12345")); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValues_ReturnsMultipleValues() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Headers.Add("Set-Cookie", new[] { "cookie1=value1", "cookie2=value2" }); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var success = headers.TryGetValues("Set-Cookie", out var values); + + // Assert + Assert.That(success, Is.True); + Assert.That(values, Is.Not.Null); + Assert.That(values!.Count(), Is.EqualTo(2)); + Assert.That(values, Does.Contain("cookie1=value1")); + Assert.That(values, Does.Contain("cookie2=value2")); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_ContentType_ReturnsValue() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Content = new StringContent( + "{}", + global::System.Text.Encoding.UTF8, + "application/json" + ); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var contentType = headers.ContentType; + + // Assert + Assert.That(contentType, Is.Not.Null); + Assert.That(contentType, Does.Contain("application/json")); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_ContentLength_ReturnsValue() + { + // Arrange + var content = "test content"; + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Content = new StringContent(content); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var contentLength = headers.ContentLength; + + // Assert + Assert.That(contentLength, Is.Not.Null); + Assert.That(contentLength, Is.GreaterThan(0)); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_Contains_ReturnsTrueForExistingHeader() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Headers.Add("X-Custom-Header", "value"); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act & Assert + Assert.That(headers.Contains("X-Custom-Header"), Is.True); + Assert.That(headers.Contains("x-custom-header"), Is.True); + Assert.That(headers.Contains("NonExistent"), Is.False); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_Enumeration_IncludesAllHeaders() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Headers.Add("X-Header-1", "value1"); + response.Headers.Add("X-Header-2", "value2"); + response.Content = new StringContent("test"); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var allHeaders = headers.ToList(); + + // Assert + Assert.That(allHeaders.Count, Is.GreaterThan(0)); + Assert.That(allHeaders.Any(h => h.Name == "X-Header-1"), Is.True); + Assert.That(allHeaders.Any(h => h.Name == "X-Header-2"), Is.True); + } + + [Test] + public async global::System.Threading.Tasks.Task WithRawResponseTask_ErrorStatusCode_StillReturnsMetadata() + { + // Arrange + var expectedData = "error-data"; + var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.BadRequest); + + // Act + var result = await task.WithRawResponse(); + + // Assert + Assert.That(result.Data, Is.EqualTo(expectedData)); + Assert.That(result.RawResponse.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async global::System.Threading.Tasks.Task WithRawResponseTask_Url_IsPreserved() + { + // Arrange + var expectedUrl = new Uri("https://api.example.com/users/123"); + var task = CreateWithRawResponseTask("data", HttpStatusCode.OK, expectedUrl); + + // Act + var result = await task.WithRawResponse(); + + // Assert + Assert.That(result.RawResponse.Url, Is.EqualTo(expectedUrl)); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValue_NonExistentHeader_ReturnsFalse() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var success = headers.TryGetValue("X-NonExistent", out var value); + + // Assert + Assert.That(success, Is.False); + Assert.That(value, Is.Null); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValues_NonExistentHeader_ReturnsFalse() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var success = headers.TryGetValues("X-NonExistent", out var values); + + // Assert + Assert.That(success, Is.False); + Assert.That(values, Is.Null); + } + + [Test] + public async global::System.Threading.Tasks.Task WithRawResponseTask_ImplicitConversion_ToTask() + { + // Arrange + var expectedData = "test-data"; + var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); + + // Act - implicitly convert to Task + global::System.Threading.Tasks.Task regularTask = task; + var result = await regularTask; + + // Assert + Assert.That(result, Is.EqualTo(expectedData)); + } + + [Test] + public void WithRawResponseTask_ImplicitConversion_AssignToTaskVariable() + { + // Arrange + var expectedData = "test-data"; + var wrappedTask = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); + + // Act - assign to Task variable + global::System.Threading.Tasks.Task regularTask = wrappedTask; + + // Assert + Assert.That(regularTask, Is.Not.Null); + Assert.That(regularTask, Is.InstanceOf>()); + } + + // Helper methods + + private static WithRawResponseTask CreateWithRawResponseTask( + T data, + HttpStatusCode statusCode, + Uri? url = null + ) + { + url ??= new Uri("https://api.example.com/test"); + using var httpResponse = CreateHttpResponse(statusCode); + httpResponse.RequestMessage = new HttpRequestMessage(HttpMethod.Get, url); + + var rawResponse = new RawResponse + { + StatusCode = statusCode, + Url = url, + Headers = ResponseHeaders.FromHttpResponseMessage(httpResponse), + }; + + var withRawResponse = new WithRawResponse { Data = data, RawResponse = rawResponse }; + + var task = global::System.Threading.Tasks.Task.FromResult(withRawResponse); + return new WithRawResponseTask(task); + } + + private static HttpResponseMessage CreateHttpResponse(HttpStatusCode statusCode) + { + return new HttpResponseMessage(statusCode) { Content = new StringContent("") }; + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Seed.CsharpNamespaceConflict.Test.Custom.props b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Seed.CsharpNamespaceConflict.Test.Custom.props new file mode 100644 index 000000000000..aac9b5020d80 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Seed.CsharpNamespaceConflict.Test.Custom.props @@ -0,0 +1,6 @@ + + diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Seed.CsharpNamespaceConflict.Test.csproj b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Seed.CsharpNamespaceConflict.Test.csproj new file mode 100644 index 000000000000..b80010b5fdbb --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Seed.CsharpNamespaceConflict.Test.csproj @@ -0,0 +1,39 @@ + + + net8.0 + 12 + enable + enable + false + true + true + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/TestClient.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/TestClient.cs new file mode 100644 index 000000000000..6be692d893c8 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/TestClient.cs @@ -0,0 +1,6 @@ +using NUnit.Framework; + +namespace Seed.CsharpNamespaceConflict.Test; + +[TestFixture] +public class TestClient; diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Unit/MockServer/BaseMockServerTest.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Unit/MockServer/BaseMockServerTest.cs new file mode 100644 index 000000000000..64a5917f2737 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Unit/MockServer/BaseMockServerTest.cs @@ -0,0 +1,37 @@ +using global::Seed.CsharpNamespaceConflict; +using NUnit.Framework; +using WireMock.Logging; +using WireMock.Server; +using WireMock.Settings; + +namespace Seed.CsharpNamespaceConflict.Test.Unit.MockServer; + +public class BaseMockServerTest +{ + protected WireMockServer Server { get; set; } = null!; + + protected global::Seed.CsharpNamespaceConflict.Seed Client { get; set; } = null!; + + protected RequestOptions RequestOptions { get; set; } = new(); + + [OneTimeSetUp] + public void GlobalSetup() + { + // Start the WireMock server + Server = WireMockServer.Start( + new WireMockServerSettings { Logger = new WireMockConsoleLogger() } + ); + + // Initialize the Client + Client = new global::Seed.CsharpNamespaceConflict.Seed( + clientOptions: new ClientOptions { BaseUrl = Server.Urls[0], MaxRetries = 0 } + ); + } + + [OneTimeTearDown] + public void GlobalTeardown() + { + Server.Stop(); + Server.Dispose(); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Unit/MockServer/Tasktest/HelloTest.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Unit/MockServer/Tasktest/HelloTest.cs new file mode 100644 index 000000000000..a173e19f4c8f --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Unit/MockServer/Tasktest/HelloTest.cs @@ -0,0 +1,19 @@ +using global::Seed.CsharpNamespaceConflict.Test.Unit.MockServer; +using NUnit.Framework; + +namespace Seed.CsharpNamespaceConflict.Test.Unit.MockServer.Tasktest; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class HelloTest : BaseMockServerTest +{ + [global::NUnit.Framework.Test] + public void MockServerTest() + { + Server + .Given(WireMock.RequestBuilders.Request.Create().WithPath("/hello").UsingGet()) + .RespondWith(WireMock.ResponseBuilders.Response.Create().WithStatusCode(200)); + + Assert.DoesNotThrowAsync(async () => await Client.Tasktest.HelloAsync()); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/AdditionalPropertiesComparer.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/AdditionalPropertiesComparer.cs new file mode 100644 index 000000000000..c0e15d7e4adf --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/AdditionalPropertiesComparer.cs @@ -0,0 +1,126 @@ +using System.Text.Json; +using global::Seed.CsharpNamespaceConflict; +using global::Seed.CsharpNamespaceConflict.Core; +using NUnit.Framework.Constraints; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle AdditionalProperties values. +/// +public static class AdditionalPropertiesComparerExtensions +{ + /// + /// Modifies the EqualConstraint to handle AdditionalProperties instances by comparing their + /// serialized JSON representations. This handles the type mismatch between native C# types + /// and JsonElement values that occur when comparing manually constructed objects with + /// deserialized objects. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingAdditionalPropertiesComparer(this EqualConstraint constraint) + { + constraint.Using( + (x, y) => + { + if (x.Count != y.Count) + { + return false; + } + + foreach (var key in x.Keys) + { + if (!y.ContainsKey(key)) + { + return false; + } + + var xElement = JsonUtils.SerializeToElement(x[key]); + var yElement = JsonUtils.SerializeToElement(y[key]); + + if (!JsonElementsAreEqual(xElement, yElement)) + { + return false; + } + } + + return true; + } + ); + + return constraint; + } + + private static bool JsonElementsAreEqual(JsonElement x, JsonElement y) + { + if (x.ValueKind != y.ValueKind) + { + return false; + } + + return x.ValueKind switch + { + JsonValueKind.Object => CompareJsonObjects(x, y), + JsonValueKind.Array => CompareJsonArrays(x, y), + JsonValueKind.String => x.GetString() == y.GetString(), + JsonValueKind.Number => x.GetDecimal() == y.GetDecimal(), + JsonValueKind.True => true, + JsonValueKind.False => true, + JsonValueKind.Null => true, + _ => false, + }; + } + + private static bool CompareJsonObjects(JsonElement x, JsonElement y) + { + var xProps = new Dictionary(); + var yProps = new Dictionary(); + + foreach (var prop in x.EnumerateObject()) + xProps[prop.Name] = prop.Value; + + foreach (var prop in y.EnumerateObject()) + yProps[prop.Name] = prop.Value; + + if (xProps.Count != yProps.Count) + { + return false; + } + + foreach (var key in xProps.Keys) + { + if (!yProps.ContainsKey(key)) + { + return false; + } + + if (!JsonElementsAreEqual(xProps[key], yProps[key])) + { + return false; + } + } + + return true; + } + + private static bool CompareJsonArrays(JsonElement x, JsonElement y) + { + var xArray = x.EnumerateArray().ToList(); + var yArray = y.EnumerateArray().ToList(); + + if (xArray.Count != yArray.Count) + { + return false; + } + + for (var i = 0; i < xArray.Count; i++) + { + if (!JsonElementsAreEqual(xArray[i], yArray[i])) + { + return false; + } + } + + return true; + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/JsonAssert.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/JsonAssert.cs new file mode 100644 index 000000000000..b1b89a52955e --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/JsonAssert.cs @@ -0,0 +1,19 @@ +using global::Seed.CsharpNamespaceConflict.Core; +using global::System.Text.Json; +using NUnit.Framework; + +namespace Seed.CsharpNamespaceConflict.Test.Utils; + +internal static class JsonAssert +{ + /// + /// Asserts that the serialized JSON of an object equals the expected JSON string. + /// Uses JsonElement comparison for reliable deep equality of collections and union types. + /// + internal static void AreEqual(object actual, string expectedJson) + { + var actualElement = JsonUtils.SerializeToElement(actual); + var expectedElement = JsonUtils.Deserialize(expectedJson); + Assert.That(actualElement, Is.EqualTo(expectedElement).UsingJsonElementComparer()); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/JsonElementComparer.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/JsonElementComparer.cs new file mode 100644 index 000000000000..a37ef402c1ac --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/JsonElementComparer.cs @@ -0,0 +1,236 @@ +using global::System.Text.Json; +using NUnit.Framework.Constraints; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle JsonElement objects. +/// +public static class JsonElementComparerExtensions +{ + /// + /// Extension method for comparing JsonElement objects in NUnit tests. + /// Property order doesn't matter, but array order does matter. + /// Includes special handling for DateTime string formats. + /// + /// The Is.EqualTo() constraint instance. + /// A constraint that can compare JsonElements with detailed diffs. + public static EqualConstraint UsingJsonElementComparer(this EqualConstraint constraint) + { + return constraint.Using(new JsonElementComparer()); + } +} + +/// +/// Equality comparer for JsonElement with detailed reporting. +/// Property order doesn't matter, but array order does matter. +/// Now includes special handling for DateTime string formats with improved null handling. +/// +public class JsonElementComparer : IEqualityComparer +{ + private string _failurePath = string.Empty; + + /// + public bool Equals(JsonElement x, JsonElement y) + { + _failurePath = string.Empty; + return CompareJsonElements(x, y, string.Empty); + } + + /// + public int GetHashCode(JsonElement obj) + { + return JsonSerializer.Serialize(obj).GetHashCode(); + } + + private bool CompareJsonElements(JsonElement x, JsonElement y, string path) + { + // If value kinds don't match, they're not equivalent + if (x.ValueKind != y.ValueKind) + { + _failurePath = $"{path}: Expected {x.ValueKind} but got {y.ValueKind}"; + return false; + } + + switch (x.ValueKind) + { + case JsonValueKind.Object: + return CompareJsonObjects(x, y, path); + + case JsonValueKind.Array: + return CompareJsonArraysInOrder(x, y, path); + + case JsonValueKind.String: + string? xStr = x.GetString(); + string? yStr = y.GetString(); + + // Handle null strings + if (xStr is null && yStr is null) + return true; + + if (xStr is null || yStr is null) + { + _failurePath = + $"{path}: Expected {(xStr is null ? "null" : $"\"{xStr}\"")} but got {(yStr is null ? "null" : $"\"{yStr}\"")}"; + return false; + } + + // Check if they are identical strings + if (xStr == yStr) + return true; + + // Try to handle DateTime strings + if (IsLikelyDateTimeString(xStr) && IsLikelyDateTimeString(yStr)) + { + if (AreEquivalentDateTimeStrings(xStr, yStr)) + return true; + } + + _failurePath = $"{path}: Expected \"{xStr}\" but got \"{yStr}\""; + return false; + + case JsonValueKind.Number: + if (x.GetDecimal() != y.GetDecimal()) + { + _failurePath = $"{path}: Expected {x.GetDecimal()} but got {y.GetDecimal()}"; + return false; + } + + return true; + + case JsonValueKind.True: + case JsonValueKind.False: + if (x.GetBoolean() != y.GetBoolean()) + { + _failurePath = $"{path}: Expected {x.GetBoolean()} but got {y.GetBoolean()}"; + return false; + } + + return true; + + case JsonValueKind.Null: + return true; + + default: + _failurePath = $"{path}: Unsupported JsonValueKind {x.ValueKind}"; + return false; + } + } + + private bool IsLikelyDateTimeString(string? str) + { + // Simple heuristic to identify likely ISO date time strings + return str is not null + && (str.Contains("T") && (str.EndsWith("Z") || str.Contains("+") || str.Contains("-"))); + } + + private bool AreEquivalentDateTimeStrings(string str1, string str2) + { + // Try to parse both as DateTime + if (DateTime.TryParse(str1, out DateTime dt1) && DateTime.TryParse(str2, out DateTime dt2)) + { + return dt1 == dt2; + } + + return false; + } + + private bool CompareJsonObjects(JsonElement x, JsonElement y, string path) + { + // Create dictionaries for both JSON objects + var xProps = new Dictionary(); + var yProps = new Dictionary(); + + foreach (var prop in x.EnumerateObject()) + xProps[prop.Name] = prop.Value; + + foreach (var prop in y.EnumerateObject()) + yProps[prop.Name] = prop.Value; + + // Check if all properties in x exist in y + foreach (var key in xProps.Keys) + { + if (!yProps.ContainsKey(key)) + { + _failurePath = $"{path}: Missing property '{key}'"; + return false; + } + } + + // Check if y has extra properties + foreach (var key in yProps.Keys) + { + if (!xProps.ContainsKey(key)) + { + _failurePath = $"{path}: Unexpected property '{key}'"; + return false; + } + } + + // Compare each property value + foreach (var key in xProps.Keys) + { + var propPath = string.IsNullOrEmpty(path) ? key : $"{path}.{key}"; + if (!CompareJsonElements(xProps[key], yProps[key], propPath)) + { + return false; + } + } + + return true; + } + + private bool CompareJsonArraysInOrder(JsonElement x, JsonElement y, string path) + { + var xArray = x.EnumerateArray(); + var yArray = y.EnumerateArray(); + + // Count x elements + var xCount = 0; + var xElements = new List(); + foreach (var item in xArray) + { + xElements.Add(item); + xCount++; + } + + // Count y elements + var yCount = 0; + var yElements = new List(); + foreach (var item in yArray) + { + yElements.Add(item); + yCount++; + } + + // Check if counts match + if (xCount != yCount) + { + _failurePath = $"{path}: Expected {xCount} items but found {yCount}"; + return false; + } + + // Compare elements in order + for (var i = 0; i < xCount; i++) + { + var itemPath = $"{path}[{i}]"; + if (!CompareJsonElements(xElements[i], yElements[i], itemPath)) + { + return false; + } + } + + return true; + } + + /// + public override string ToString() + { + if (!string.IsNullOrEmpty(_failurePath)) + { + return $"JSON comparison failed at {_failurePath}"; + } + + return "JsonElementEqualityComparer"; + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/NUnitExtensions.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/NUnitExtensions.cs new file mode 100644 index 000000000000..170795cb2c3e --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/NUnitExtensions.cs @@ -0,0 +1,30 @@ +using NUnit.Framework.Constraints; + +namespace NUnit.Framework; + +/// +/// Extensions for NUnit constraints. +/// +public static class NUnitExtensions +{ + /// + /// Modifies the EqualConstraint to use our own set of default comparers. + /// + /// + /// + public static EqualConstraint UsingDefaults(this EqualConstraint constraint) => + constraint + .UsingPropertiesComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingOneOfComparer() + .UsingJsonElementComparer() + .UsingOptionalComparer() + .UsingAdditionalPropertiesComparer(); +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/OneOfComparer.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/OneOfComparer.cs new file mode 100644 index 000000000000..767439174363 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/OneOfComparer.cs @@ -0,0 +1,86 @@ +using NUnit.Framework.Constraints; +using OneOf; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle OneOf values. +/// +public static class EqualConstraintExtensions +{ + /// + /// Modifies the EqualConstraint to handle OneOf instances by comparing their inner values. + /// This works alongside other comparison modifiers like UsingPropertiesComparer. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingOneOfComparer(this EqualConstraint constraint) + { + // Register a comparer factory for IOneOf types + constraint.Using( + (x, y) => + { + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (x.Value is null && y.Value is null) + { + return true; + } + + if (x.Value is null) + { + return false; + } + + var propertiesComparer = new NUnitEqualityComparer(); + var tolerance = Tolerance.Default; + propertiesComparer.CompareProperties = true; + // Add OneOf comparer to handle nested OneOf values (e.g., in Lists) + propertiesComparer.ExternalComparers.Add( + new OneOfEqualityAdapter(propertiesComparer) + ); + return propertiesComparer.AreEqual(x.Value, y.Value, ref tolerance); + } + ); + + return constraint; + } + + /// + /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. + /// This enables recursive comparison of nested OneOf values. + /// + private class OneOfEqualityAdapter : EqualityAdapter + { + private readonly NUnitEqualityComparer _comparer; + + public OneOfEqualityAdapter(NUnitEqualityComparer comparer) + { + _comparer = comparer; + } + + public override bool CanCompare(object? x, object? y) + { + return x is IOneOf && y is IOneOf; + } + + public override bool AreEqual(object? x, object? y) + { + var oneOfX = (IOneOf?)x; + var oneOfY = (IOneOf?)y; + + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (oneOfX?.Value is null && oneOfY?.Value is null) + { + return true; + } + + if (oneOfX?.Value is null || oneOfY?.Value is null) + { + return false; + } + + var tolerance = Tolerance.Default; + return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); + } + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/OptionalComparer.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/OptionalComparer.cs new file mode 100644 index 000000000000..6c78567c64ac --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/OptionalComparer.cs @@ -0,0 +1,104 @@ +using global::Seed.CsharpNamespaceConflict.Core; +using NUnit.Framework.Constraints; +using OneOf; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle Optional values. +/// +public static class OptionalComparerExtensions +{ + /// + /// Modifies the EqualConstraint to handle Optional instances by comparing their IsDefined state and inner values. + /// This works alongside other comparison modifiers like UsingPropertiesComparer. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingOptionalComparer(this EqualConstraint constraint) + { + // Register a comparer factory for IOptional types + constraint.Using( + (x, y) => + { + // Both must have the same IsDefined state + if (x.IsDefined != y.IsDefined) + { + return false; + } + + // If both are undefined, they're equal + if (!x.IsDefined) + { + return true; + } + + // Both are defined, compare their boxed values + var xValue = x.GetBoxedValue(); + var yValue = y.GetBoxedValue(); + + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (xValue is null && yValue is null) + { + return true; + } + + if (xValue is null || yValue is null) + { + return false; + } + + // Use NUnit's property comparer for the inner values + var propertiesComparer = new NUnitEqualityComparer(); + var tolerance = Tolerance.Default; + propertiesComparer.CompareProperties = true; + // Add OneOf comparer to handle nested OneOf values (e.g., in Lists within Optional) + propertiesComparer.ExternalComparers.Add( + new OneOfEqualityAdapter(propertiesComparer) + ); + return propertiesComparer.AreEqual(xValue, yValue, ref tolerance); + } + ); + + return constraint; + } + + /// + /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. + /// This enables recursive comparison of nested OneOf values within Optional types. + /// + private class OneOfEqualityAdapter : EqualityAdapter + { + private readonly NUnitEqualityComparer _comparer; + + public OneOfEqualityAdapter(NUnitEqualityComparer comparer) + { + _comparer = comparer; + } + + public override bool CanCompare(object? x, object? y) + { + return x is IOneOf && y is IOneOf; + } + + public override bool AreEqual(object? x, object? y) + { + var oneOfX = (IOneOf?)x; + var oneOfY = (IOneOf?)y; + + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (oneOfX?.Value is null && oneOfY?.Value is null) + { + return true; + } + + if (oneOfX?.Value is null || oneOfY?.Value is null) + { + return false; + } + + var tolerance = Tolerance.Default; + return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); + } + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/ReadOnlyMemoryComparer.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/ReadOnlyMemoryComparer.cs new file mode 100644 index 000000000000..fc0b595a5e54 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict.Test/Utils/ReadOnlyMemoryComparer.cs @@ -0,0 +1,87 @@ +using NUnit.Framework.Constraints; + +namespace NUnit.Framework; + +/// +/// Extensions for NUnit constraints. +/// +public static class ReadOnlyMemoryComparerExtensions +{ + /// + /// Extension method for comparing ReadOnlyMemory<T> in NUnit tests. + /// + /// The type of elements in the ReadOnlyMemory. + /// The Is.EqualTo() constraint instance. + /// A constraint that can compare ReadOnlyMemory<T>. + public static EqualConstraint UsingReadOnlyMemoryComparer(this EqualConstraint constraint) + where T : IComparable + { + return constraint.Using(new ReadOnlyMemoryComparer()); + } +} + +/// +/// Comparer for ReadOnlyMemory<T>. Compares sequences by value. +/// +/// +/// The type of elements in the ReadOnlyMemory. +/// +public class ReadOnlyMemoryComparer : IComparer> + where T : IComparable +{ + /// + public int Compare(ReadOnlyMemory x, ReadOnlyMemory y) + { + // Check if sequences are equal + var xSpan = x.Span; + var ySpan = y.Span; + + // Optimized case for IEquatable implementations + if (typeof(IEquatable).IsAssignableFrom(typeof(T))) + { + var areEqual = xSpan.SequenceEqual(ySpan); + if (areEqual) + { + return 0; // Sequences are equal + } + } + else + { + // Manual equality check for non-IEquatable types + if (xSpan.Length == ySpan.Length) + { + var areEqual = true; + for (var i = 0; i < xSpan.Length; i++) + { + if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) + { + areEqual = false; + break; + } + } + + if (areEqual) + { + return 0; // Sequences are equal + } + } + } + + // For non-equal sequences, we need to return a consistent ordering + // First compare lengths + if (x.Length != y.Length) + return x.Length.CompareTo(y.Length); + + // Same length but different content - compare first differing element + for (var i = 0; i < x.Length; i++) + { + if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) + { + return xSpan[i].CompareTo(ySpan[i]); + } + } + + // Should never reach here if not equal + return 0; + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/A/Aa/Types/A.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/A/Aa/Types/A.cs new file mode 100644 index 000000000000..cf86afa14492 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/A/Aa/Types/A.cs @@ -0,0 +1,26 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using global::Seed.CsharpNamespaceConflict; +using global::Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict.A.Aa; + +[Serializable] +public record A : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/A/Aa/Types/B.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/A/Aa/Types/B.cs new file mode 100644 index 000000000000..e85354849ca1 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/A/Aa/Types/B.cs @@ -0,0 +1,26 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using global::Seed.CsharpNamespaceConflict; +using global::Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict.A.Aa; + +[Serializable] +public record B : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/A/Aa/Types/SubTestType.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/A/Aa/Types/SubTestType.cs new file mode 100644 index 000000000000..fdb6cfc0d9af --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/A/Aa/Types/SubTestType.cs @@ -0,0 +1,32 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using global::Seed.CsharpNamespaceConflict; +using global::Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict.A.Aa; + +[Serializable] +public record SubTestType : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("a")] + public required A A { get; set; } + + [JsonPropertyName("b")] + public required B B { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/B/Types/TestType.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/B/Types/TestType.cs new file mode 100644 index 000000000000..398392e8be9a --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/B/Types/TestType.cs @@ -0,0 +1,32 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using global::Seed.CsharpNamespaceConflict; +using global::Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict.B; + +[Serializable] +public record TestType : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("a")] + public required global::Seed.CsharpNamespaceConflict.A.Aa.A A { get; set; } + + [JsonPropertyName("b")] + public required global::Seed.CsharpNamespaceConflict.A.Aa.B B { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/ApiResponse.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/ApiResponse.cs new file mode 100644 index 000000000000..3cfc5000bb7b --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/ApiResponse.cs @@ -0,0 +1,13 @@ +using global::System.Net.Http; + +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// The response object returned from the API. +/// +internal record ApiResponse +{ + internal required int StatusCode { get; init; } + + internal required HttpResponseMessage Raw { get; init; } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/BaseRequest.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/BaseRequest.cs new file mode 100644 index 000000000000..0e9303357a56 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/BaseRequest.cs @@ -0,0 +1,67 @@ +using global::System.Net.Http; +using global::System.Net.Http.Headers; +using global::System.Text; + +namespace Seed.CsharpNamespaceConflict.Core; + +internal abstract record BaseRequest +{ + internal string? BaseUrl { get; init; } + + internal required HttpMethod Method { get; init; } + + internal required string Path { get; init; } + + internal string? ContentType { get; init; } + + /// + /// The query string for this request (including the leading '?' if non-empty). + /// + internal string? QueryString { get; init; } + + internal Dictionary Headers { get; init; } = + new(StringComparer.OrdinalIgnoreCase); + + internal IRequestOptions? Options { get; init; } + + internal abstract HttpContent? CreateContent(); + + protected static ( + Encoding encoding, + string? charset, + string mediaType + ) ParseContentTypeOrDefault( + string? contentType, + Encoding encodingFallback, + string mediaTypeFallback + ) + { + var encoding = encodingFallback; + var mediaType = mediaTypeFallback; + string? charset = null; + if (string.IsNullOrEmpty(contentType)) + { + return (encoding, charset, mediaType); + } + + if (!MediaTypeHeaderValue.TryParse(contentType, out var mediaTypeHeaderValue)) + { + return (encoding, charset, mediaType); + } + + if (!string.IsNullOrEmpty(mediaTypeHeaderValue.CharSet)) + { + charset = mediaTypeHeaderValue.CharSet; + encoding = Encoding.GetEncoding(mediaTypeHeaderValue.CharSet); + } + + if (!string.IsNullOrEmpty(mediaTypeHeaderValue.MediaType)) + { + mediaType = mediaTypeHeaderValue.MediaType; + } + + return (encoding, charset, mediaType); + } + + protected static Encoding Utf8NoBom => EncodingCache.Utf8NoBom; +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/CollectionItemSerializer.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/CollectionItemSerializer.cs new file mode 100644 index 000000000000..1416636686c1 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/CollectionItemSerializer.cs @@ -0,0 +1,89 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// Json collection converter. +/// +/// Type of item to convert. +/// Converter to use for individual items. +internal class CollectionItemSerializer + : JsonConverter> + where TConverterType : JsonConverter +{ + /// + /// Reads a json string and deserializes it into an object. + /// + /// Json reader. + /// Type to convert. + /// Serializer options. + /// Created object. + public override IEnumerable? Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType == JsonTokenType.Null) + { + return default; + } + + var jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(Activator.CreateInstance()); + + var returnValue = new List(); + + while (reader.TokenType != JsonTokenType.EndArray) + { + if (reader.TokenType != JsonTokenType.StartArray) + { + var item = (TDatatype)( + JsonSerializer.Deserialize(ref reader, typeof(TDatatype), jsonSerializerOptions) + ?? throw new global::System.Exception( + $"Failed to deserialize collection item of type {typeof(TDatatype)}" + ) + ); + returnValue.Add(item); + } + + reader.Read(); + } + + return returnValue; + } + + /// + /// Writes a json string. + /// + /// Json writer. + /// Value to write. + /// Serializer options. + public override void Write( + Utf8JsonWriter writer, + IEnumerable? value, + JsonSerializerOptions options + ) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + var jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(Activator.CreateInstance()); + + writer.WriteStartArray(); + + foreach (var data in value) + { + JsonSerializer.Serialize(writer, data, jsonSerializerOptions); + } + + writer.WriteEndArray(); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Constants.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Constants.cs new file mode 100644 index 000000000000..f6cf562c23f3 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Constants.cs @@ -0,0 +1,7 @@ +namespace Seed.CsharpNamespaceConflict.Core; + +internal static class Constants +{ + public const string DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffK"; + public const string DateFormat = "yyyy-MM-dd"; +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/DateOnlyConverter.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/DateOnlyConverter.cs new file mode 100644 index 000000000000..86e637e465bf --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/DateOnlyConverter.cs @@ -0,0 +1,747 @@ +// ReSharper disable All +#pragma warning disable + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using global::System.Diagnostics; +using global::System.Diagnostics.CodeAnalysis; +using global::System.Globalization; +using global::System.Runtime.CompilerServices; +using global::System.Runtime.InteropServices; +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +// ReSharper disable SuggestVarOrType_SimpleTypes +// ReSharper disable SuggestVarOrType_BuiltInTypes + +namespace Seed.CsharpNamespaceConflict.Core +{ + /// + /// Custom converter for handling the data type with the System.Text.Json library. + /// + /// + /// This class backported from: + /// + /// System.Text.Json.Serialization.Converters.DateOnlyConverter + /// + public sealed class DateOnlyConverter : JsonConverter + { + private const int FormatLength = 10; // YYYY-MM-DD + + private const int MaxEscapedFormatLength = + FormatLength * JsonConstants.MaxExpansionFactorWhileEscaping; + + /// + public override DateOnly Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType != JsonTokenType.String) + { + ThrowHelper.ThrowInvalidOperationException_ExpectedString(reader.TokenType); + } + + return ReadCore(ref reader); + } + + /// + public override DateOnly ReadAsPropertyName( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + return ReadCore(ref reader); + } + + private static DateOnly ReadCore(ref Utf8JsonReader reader) + { + if ( + !JsonHelpers.IsInRangeInclusive( + reader.ValueLength(), + FormatLength, + MaxEscapedFormatLength + ) + ) + { + ThrowHelper.ThrowFormatException(DataType.DateOnly); + } + + scoped ReadOnlySpan source; + if (!reader.HasValueSequence && !reader.ValueIsEscaped) + { + source = reader.ValueSpan; + } + else + { + Span stackSpan = stackalloc byte[MaxEscapedFormatLength]; + int bytesWritten = reader.CopyString(stackSpan); + source = stackSpan.Slice(0, bytesWritten); + } + + if (!JsonHelpers.TryParseAsIso(source, out DateOnly value)) + { + ThrowHelper.ThrowFormatException(DataType.DateOnly); + } + + return value; + } + + /// + public override void Write( + Utf8JsonWriter writer, + DateOnly value, + JsonSerializerOptions options + ) + { +#if NET8_0_OR_GREATER + Span buffer = stackalloc byte[FormatLength]; +#else + Span buffer = stackalloc char[FormatLength]; +#endif + // ReSharper disable once RedundantAssignment + bool formattedSuccessfully = value.TryFormat( + buffer, + out int charsWritten, + "O".AsSpan(), + CultureInfo.InvariantCulture + ); + Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); + writer.WriteStringValue(buffer); + } + + /// + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + DateOnly value, + JsonSerializerOptions options + ) + { +#if NET8_0_OR_GREATER + Span buffer = stackalloc byte[FormatLength]; +#else + Span buffer = stackalloc char[FormatLength]; +#endif + // ReSharper disable once RedundantAssignment + bool formattedSuccessfully = value.TryFormat( + buffer, + out int charsWritten, + "O".AsSpan(), + CultureInfo.InvariantCulture + ); + Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); + writer.WritePropertyName(buffer); + } + } + + internal static class JsonConstants + { + // The maximum number of fraction digits the Json DateTime parser allows + public const int DateTimeParseNumFractionDigits = 16; + + // In the worst case, an ASCII character represented as a single utf-8 byte could expand 6x when escaped. + public const int MaxExpansionFactorWhileEscaping = 6; + + // The largest fraction expressible by TimeSpan and DateTime formats + public const int MaxDateTimeFraction = 9_999_999; + + // TimeSpan and DateTime formats allow exactly up to many digits for specifying the fraction after the seconds. + public const int DateTimeNumFractionDigits = 7; + + public const byte UtcOffsetToken = (byte)'Z'; + + public const byte TimePrefix = (byte)'T'; + + public const byte Period = (byte)'.'; + + public const byte Hyphen = (byte)'-'; + + public const byte Colon = (byte)':'; + + public const byte Plus = (byte)'+'; + } + + // ReSharper disable SuggestVarOrType_Elsewhere + // ReSharper disable SuggestVarOrType_SimpleTypes + // ReSharper disable SuggestVarOrType_BuiltInTypes + + internal static class JsonHelpers + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsInRangeInclusive(int value, int lowerBound, int upperBound) => + (uint)(value - lowerBound) <= (uint)(upperBound - lowerBound); + + public static bool IsDigit(byte value) => (uint)(value - '0') <= '9' - '0'; + + [StructLayout(LayoutKind.Auto)] + private struct DateTimeParseData + { + public int Year; + public int Month; + public int Day; + public bool IsCalendarDateOnly; + public int Hour; + public int Minute; + public int Second; + public int Fraction; // This value should never be greater than 9_999_999. + public int OffsetHours; + public int OffsetMinutes; + + // ReSharper disable once NotAccessedField.Local + public byte OffsetToken; + } + + public static bool TryParseAsIso(ReadOnlySpan source, out DateOnly value) + { + if ( + TryParseDateTimeOffset(source, out DateTimeParseData parseData) + && parseData.IsCalendarDateOnly + && TryCreateDateTime(parseData, DateTimeKind.Unspecified, out DateTime dateTime) + ) + { + value = DateOnly.FromDateTime(dateTime); + return true; + } + + value = default; + return false; + } + + /// + /// ISO 8601 date time parser (ISO 8601-1:2019). + /// + /// The date/time to parse in UTF-8 format. + /// The parsed for the given . + /// + /// Supports extended calendar date (5.2.2.1) and complete (5.4.2.1) calendar date/time of day + /// representations with optional specification of seconds and fractional seconds. + /// + /// Times can be explicitly specified as UTC ("Z" - 5.3.3) or offsets from UTC ("+/-hh:mm" 5.3.4.2). + /// If unspecified they are considered to be local per spec. + /// + /// Examples: (TZD is either "Z" or hh:mm offset from UTC) + /// + /// YYYY-MM-DD (e.g. 1997-07-16) + /// YYYY-MM-DDThh:mm (e.g. 1997-07-16T19:20) + /// YYYY-MM-DDThh:mm:ss (e.g. 1997-07-16T19:20:30) + /// YYYY-MM-DDThh:mm:ss.s (e.g. 1997-07-16T19:20:30.45) + /// YYYY-MM-DDThh:mmTZD (e.g. 1997-07-16T19:20+01:00) + /// YYYY-MM-DDThh:mm:ssTZD (e.g. 1997-07-16T19:20:3001:00) + /// YYYY-MM-DDThh:mm:ss.sTZD (e.g. 1997-07-16T19:20:30.45Z) + /// + /// Generally speaking we always require the "extended" option when one exists (3.1.3.5). + /// The extended variants have separator characters between components ('-', ':', '.', etc.). + /// Spaces are not permitted. + /// + /// "true" if successfully parsed. + private static bool TryParseDateTimeOffset( + ReadOnlySpan source, + out DateTimeParseData parseData + ) + { + parseData = default; + + // too short datetime + Debug.Assert(source.Length >= 10); + + // Parse the calendar date + // ----------------------- + // ISO 8601-1:2019 5.2.2.1b "Calendar date complete extended format" + // [dateX] = [year]["-"][month]["-"][day] + // [year] = [YYYY] [0000 - 9999] (4.3.2) + // [month] = [MM] [01 - 12] (4.3.3) + // [day] = [DD] [01 - 28, 29, 30, 31] (4.3.4) + // + // Note: 5.2.2.2 "Representations with reduced precision" allows for + // just [year]["-"][month] (a) and just [year] (b), but we currently + // don't permit it. + + { + uint digit1 = source[0] - (uint)'0'; + uint digit2 = source[1] - (uint)'0'; + uint digit3 = source[2] - (uint)'0'; + uint digit4 = source[3] - (uint)'0'; + + if (digit1 > 9 || digit2 > 9 || digit3 > 9 || digit4 > 9) + { + return false; + } + + parseData.Year = (int)(digit1 * 1000 + digit2 * 100 + digit3 * 10 + digit4); + } + + if ( + source[4] != JsonConstants.Hyphen + || !TryGetNextTwoDigits(source.Slice(start: 5, length: 2), ref parseData.Month) + || source[7] != JsonConstants.Hyphen + || !TryGetNextTwoDigits(source.Slice(start: 8, length: 2), ref parseData.Day) + ) + { + return false; + } + + // We now have YYYY-MM-DD [dateX] + // ReSharper disable once ConvertIfStatementToSwitchStatement + if (source.Length == 10) + { + parseData.IsCalendarDateOnly = true; + return true; + } + + // Parse the time of day + // --------------------- + // + // ISO 8601-1:2019 5.3.1.2b "Local time of day complete extended format" + // [timeX] = ["T"][hour][":"][min][":"][sec] + // [hour] = [hh] [00 - 23] (4.3.8a) + // [minute] = [mm] [00 - 59] (4.3.9a) + // [sec] = [ss] [00 - 59, 60 with a leap second] (4.3.10a) + // + // ISO 8601-1:2019 5.3.3 "UTC of day" + // [timeX]["Z"] + // + // ISO 8601-1:2019 5.3.4.2 "Local time of day with the time shift between + // local timescale and UTC" (Extended format) + // + // [shiftX] = ["+"|"-"][hour][":"][min] + // + // Notes: + // + // "T" is optional per spec, but _only_ when times are used alone. In our + // case, we're reading out a complete date & time and as such require "T". + // (5.4.2.1b). + // + // For [timeX] We allow seconds to be omitted per 5.3.1.3a "Representations + // with reduced precision". 5.3.1.3b allows just specifying the hour, but + // we currently don't permit this. + // + // Decimal fractions are allowed for hours, minutes and seconds (5.3.14). + // We only allow fractions for seconds currently. Lower order components + // can't follow, i.e. you can have T23.3, but not T23.3:04. There must be + // one digit, but the max number of digits is implementation defined. We + // currently allow up to 16 digits of fractional seconds only. While we + // support 16 fractional digits we only parse the first seven, anything + // past that is considered a zero. This is to stay compatible with the + // DateTime implementation which is limited to this resolution. + + if (source.Length < 16) + { + // Source does not have enough characters for YYYY-MM-DDThh:mm + return false; + } + + // Parse THH:MM (e.g. "T10:32") + if ( + source[10] != JsonConstants.TimePrefix + || source[13] != JsonConstants.Colon + || !TryGetNextTwoDigits(source.Slice(start: 11, length: 2), ref parseData.Hour) + || !TryGetNextTwoDigits(source.Slice(start: 14, length: 2), ref parseData.Minute) + ) + { + return false; + } + + // We now have YYYY-MM-DDThh:mm + Debug.Assert(source.Length >= 16); + if (source.Length == 16) + { + return true; + } + + byte curByte = source[16]; + int sourceIndex = 17; + + // Either a TZD ['Z'|'+'|'-'] or a seconds separator [':'] is valid at this point + switch (curByte) + { + case JsonConstants.UtcOffsetToken: + parseData.OffsetToken = JsonConstants.UtcOffsetToken; + return sourceIndex == source.Length; + case JsonConstants.Plus: + case JsonConstants.Hyphen: + parseData.OffsetToken = curByte; + return ParseOffset(ref parseData, source.Slice(sourceIndex)); + case JsonConstants.Colon: + break; + default: + return false; + } + + // Try reading the seconds + if ( + source.Length < 19 + || !TryGetNextTwoDigits(source.Slice(start: 17, length: 2), ref parseData.Second) + ) + { + return false; + } + + // We now have YYYY-MM-DDThh:mm:ss + Debug.Assert(source.Length >= 19); + if (source.Length == 19) + { + return true; + } + + curByte = source[19]; + sourceIndex = 20; + + // Either a TZD ['Z'|'+'|'-'] or a seconds decimal fraction separator ['.'] is valid at this point + switch (curByte) + { + case JsonConstants.UtcOffsetToken: + parseData.OffsetToken = JsonConstants.UtcOffsetToken; + return sourceIndex == source.Length; + case JsonConstants.Plus: + case JsonConstants.Hyphen: + parseData.OffsetToken = curByte; + return ParseOffset(ref parseData, source.Slice(sourceIndex)); + case JsonConstants.Period: + break; + default: + return false; + } + + // Source does not have enough characters for second fractions (i.e. ".s") + // YYYY-MM-DDThh:mm:ss.s + if (source.Length < 21) + { + return false; + } + + // Parse fraction. This value should never be greater than 9_999_999 + int numDigitsRead = 0; + int fractionEnd = Math.Min( + sourceIndex + JsonConstants.DateTimeParseNumFractionDigits, + source.Length + ); + + while (sourceIndex < fractionEnd && IsDigit(curByte = source[sourceIndex])) + { + if (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) + { + parseData.Fraction = parseData.Fraction * 10 + (int)(curByte - (uint)'0'); + numDigitsRead++; + } + + sourceIndex++; + } + + if (parseData.Fraction != 0) + { + while (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) + { + parseData.Fraction *= 10; + numDigitsRead++; + } + } + + // We now have YYYY-MM-DDThh:mm:ss.s + Debug.Assert(sourceIndex <= source.Length); + if (sourceIndex == source.Length) + { + return true; + } + + curByte = source[sourceIndex++]; + + // TZD ['Z'|'+'|'-'] is valid at this point + switch (curByte) + { + case JsonConstants.UtcOffsetToken: + parseData.OffsetToken = JsonConstants.UtcOffsetToken; + return sourceIndex == source.Length; + case JsonConstants.Plus: + case JsonConstants.Hyphen: + parseData.OffsetToken = curByte; + return ParseOffset(ref parseData, source.Slice(sourceIndex)); + default: + return false; + } + + static bool ParseOffset(ref DateTimeParseData parseData, ReadOnlySpan offsetData) + { + // Parse the hours for the offset + if ( + offsetData.Length < 2 + || !TryGetNextTwoDigits(offsetData.Slice(0, 2), ref parseData.OffsetHours) + ) + { + return false; + } + + // We now have YYYY-MM-DDThh:mm:ss.s+|-hh + + if (offsetData.Length == 2) + { + // Just hours offset specified + return true; + } + + // Ensure we have enough for ":mm" + return offsetData.Length == 5 + && offsetData[2] == JsonConstants.Colon + && TryGetNextTwoDigits(offsetData.Slice(3), ref parseData.OffsetMinutes); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // ReSharper disable once RedundantAssignment + private static bool TryGetNextTwoDigits(ReadOnlySpan source, ref int value) + { + Debug.Assert(source.Length == 2); + + uint digit1 = source[0] - (uint)'0'; + uint digit2 = source[1] - (uint)'0'; + + if (digit1 > 9 || digit2 > 9) + { + value = 0; + return false; + } + + value = (int)(digit1 * 10 + digit2); + return true; + } + + // The following methods are borrowed verbatim from src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Helpers.cs + + /// + /// Overflow-safe DateTime factory. + /// + private static bool TryCreateDateTime( + DateTimeParseData parseData, + DateTimeKind kind, + out DateTime value + ) + { + if (parseData.Year == 0) + { + value = default; + return false; + } + + Debug.Assert(parseData.Year <= 9999); // All of our callers to date parse the year from fixed 4-digit fields so this value is trusted. + + if ((uint)parseData.Month - 1 >= 12) + { + value = default; + return false; + } + + uint dayMinusOne = (uint)parseData.Day - 1; + if ( + dayMinusOne >= 28 + && dayMinusOne >= DateTime.DaysInMonth(parseData.Year, parseData.Month) + ) + { + value = default; + return false; + } + + if ((uint)parseData.Hour > 23) + { + value = default; + return false; + } + + if ((uint)parseData.Minute > 59) + { + value = default; + return false; + } + + // This needs to allow leap seconds when appropriate. + // See https://github.com/dotnet/runtime/issues/30135. + if ((uint)parseData.Second > 59) + { + value = default; + return false; + } + + Debug.Assert(parseData.Fraction is >= 0 and <= JsonConstants.MaxDateTimeFraction); // All of our callers to date parse the fraction from fixed 7-digit fields so this value is trusted. + + ReadOnlySpan days = DateTime.IsLeapYear(parseData.Year) + ? DaysToMonth366 + : DaysToMonth365; + int yearMinusOne = parseData.Year - 1; + int totalDays = + yearMinusOne * 365 + + yearMinusOne / 4 + - yearMinusOne / 100 + + yearMinusOne / 400 + + days[parseData.Month - 1] + + parseData.Day + - 1; + long ticks = totalDays * TimeSpan.TicksPerDay; + int totalSeconds = parseData.Hour * 3600 + parseData.Minute * 60 + parseData.Second; + ticks += totalSeconds * TimeSpan.TicksPerSecond; + ticks += parseData.Fraction; + value = new DateTime(ticks: ticks, kind: kind); + return true; + } + + private static ReadOnlySpan DaysToMonth365 => + [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; + private static ReadOnlySpan DaysToMonth366 => + [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; + } + + internal static class ThrowHelper + { + private const string ExceptionSourceValueToRethrowAsJsonException = + "System.Text.Json.Rethrowable"; + + [DoesNotReturn] + public static void ThrowInvalidOperationException_ExpectedString(JsonTokenType tokenType) + { + throw GetInvalidOperationException("string", tokenType); + } + + public static void ThrowFormatException(DataType dataType) + { + throw new FormatException(SR.Format(SR.UnsupportedFormat, dataType)) + { + Source = ExceptionSourceValueToRethrowAsJsonException, + }; + } + + private static global::System.Exception GetInvalidOperationException( + string message, + JsonTokenType tokenType + ) + { + return GetInvalidOperationException(SR.Format(SR.InvalidCast, tokenType, message)); + } + + private static InvalidOperationException GetInvalidOperationException(string message) + { + return new InvalidOperationException(message) + { + Source = ExceptionSourceValueToRethrowAsJsonException, + }; + } + } + + internal static class Utf8JsonReaderExtensions + { + internal static int ValueLength(this Utf8JsonReader reader) => + reader.HasValueSequence + ? checked((int)reader.ValueSequence.Length) + : reader.ValueSpan.Length; + } + + internal enum DataType + { + TimeOnly, + DateOnly, + } + + [SuppressMessage("ReSharper", "InconsistentNaming")] + internal static class SR + { + private static readonly bool s_usingResourceKeys = + AppContext.TryGetSwitch( + "System.Resources.UseSystemResourceKeys", + out bool usingResourceKeys + ) && usingResourceKeys; + + public static string UnsupportedFormat => Strings.UnsupportedFormat; + + public static string InvalidCast => Strings.InvalidCast; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static string Format(string resourceFormat, object? p1) => + s_usingResourceKeys + ? string.Join(", ", resourceFormat, p1) + : string.Format(resourceFormat, p1); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static string Format(string resourceFormat, object? p1, object? p2) => + s_usingResourceKeys + ? string.Join(", ", resourceFormat, p1, p2) + : string.Format(resourceFormat, p1, p2); + } + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute( + "System.Resources.Tools.StronglyTypedResourceBuilder", + "17.0.0.0" + )] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings + { + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode" + )] + internal Strings() { } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute( + global::System.ComponentModel.EditorBrowsableState.Advanced + )] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if (object.ReferenceEquals(resourceMan, null)) + { + global::System.Resources.ResourceManager temp = + new global::System.Resources.ResourceManager( + "System.Text.Json.Resources.Strings", + typeof(Strings).Assembly + ); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute( + global::System.ComponentModel.EditorBrowsableState.Advanced + )] + internal static global::System.Globalization.CultureInfo Culture + { + get { return resourceCulture; } + set { resourceCulture = value; } + } + + /// + /// Looks up a localized string similar to Cannot get the value of a token type '{0}' as a {1}.. + /// + internal static string InvalidCast + { + get { return ResourceManager.GetString("InvalidCast", resourceCulture); } + } + + /// + /// Looks up a localized string similar to The JSON value is not in a supported {0} format.. + /// + internal static string UnsupportedFormat + { + get { return ResourceManager.GetString("UnsupportedFormat", resourceCulture); } + } + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/DateTimeSerializer.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/DateTimeSerializer.cs new file mode 100644 index 000000000000..eef4f0ef1823 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/DateTimeSerializer.cs @@ -0,0 +1,22 @@ +using global::System.Globalization; +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +namespace Seed.CsharpNamespaceConflict.Core; + +internal class DateTimeSerializer : JsonConverter +{ + public override DateTime Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(Constants.DateTimeFormat)); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/EmptyRequest.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/EmptyRequest.cs new file mode 100644 index 000000000000..e1a74011ab72 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/EmptyRequest.cs @@ -0,0 +1,11 @@ +using global::System.Net.Http; + +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// The request object to send without a request body. +/// +internal record EmptyRequest : BaseRequest +{ + internal override HttpContent? CreateContent() => null; +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/EncodingCache.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/EncodingCache.cs new file mode 100644 index 000000000000..a9b47d4b2cbf --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/EncodingCache.cs @@ -0,0 +1,11 @@ +using global::System.Text; + +namespace Seed.CsharpNamespaceConflict.Core; + +internal static class EncodingCache +{ + internal static readonly Encoding Utf8NoBom = new UTF8Encoding( + encoderShouldEmitUTF8Identifier: false, + throwOnInvalidBytes: true + ); +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Extensions.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Extensions.cs new file mode 100644 index 000000000000..18a9f20a38f2 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Extensions.cs @@ -0,0 +1,55 @@ +using global::System.Diagnostics.CodeAnalysis; +using global::System.Runtime.Serialization; + +namespace Seed.CsharpNamespaceConflict.Core; + +internal static class Extensions +{ + public static string Stringify(this Enum value) + { + var field = value.GetType().GetField(value.ToString()); + if (field is not null) + { + var attribute = (EnumMemberAttribute?) + global::System.Attribute.GetCustomAttribute(field, typeof(EnumMemberAttribute)); + return attribute?.Value ?? value.ToString(); + } + return value.ToString(); + } + + /// + /// Asserts that a condition is true, throwing an exception with the specified message if it is false. + /// + /// The condition to assert. + /// The exception message if the assertion fails. + /// Thrown when the condition is false. + internal static void Assert(this object value, bool condition, string message) + { + if (!condition) + { + throw new global::System.Exception(message); + } + } + + /// + /// Asserts that a value is not null, throwing an exception with the specified message if it is null. + /// + /// The type of the value to assert. + /// The value to assert is not null. + /// The exception message if the assertion fails. + /// The non-null value. + /// Thrown when the value is null. + internal static TValue Assert( + this object _unused, + [NotNull] TValue? value, + string message + ) + where TValue : class + { + if (value is null) + { + throw new global::System.Exception(message); + } + return value; + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/FormUrlEncoder.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/FormUrlEncoder.cs new file mode 100644 index 000000000000..8b0e51205289 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/FormUrlEncoder.cs @@ -0,0 +1,33 @@ +using global::System.Net.Http; + +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// Encodes an object into a form URL-encoded content. +/// +public static class FormUrlEncoder +{ + /// + /// Encodes an object into a form URL-encoded content using Deep Object notation. + /// + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + internal static FormUrlEncodedContent EncodeAsDeepObject(object value) => + new(QueryStringConverter.ToDeepObject(value)); + + /// + /// Encodes an object into a form URL-encoded content using Exploded Form notation. + /// + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + internal static FormUrlEncodedContent EncodeAsExplodedForm(object value) => + new(QueryStringConverter.ToExplodedForm(value)); + + /// + /// Encodes an object into a form URL-encoded content using Form notation without exploding parameters. + /// + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + internal static FormUrlEncodedContent EncodeAsForm(object value) => + new(QueryStringConverter.ToForm(value)); +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/HeaderValue.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/HeaderValue.cs new file mode 100644 index 000000000000..45d0dad0e768 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/HeaderValue.cs @@ -0,0 +1,52 @@ +namespace Seed.CsharpNamespaceConflict.Core; + +internal sealed class HeaderValue +{ + private readonly Func> _resolver; + + public HeaderValue(string value) + { + _resolver = () => new global::System.Threading.Tasks.ValueTask(value); + } + + public HeaderValue(Func value) + { + _resolver = () => new global::System.Threading.Tasks.ValueTask(value()); + } + + public HeaderValue(Func> value) + { + _resolver = value; + } + + public HeaderValue(Func> value) + { + _resolver = () => new global::System.Threading.Tasks.ValueTask(value()); + } + + public static implicit operator HeaderValue(string value) => new(value); + + public static implicit operator HeaderValue(Func value) => new(value); + + public static implicit operator HeaderValue( + Func> value + ) => new(value); + + public static implicit operator HeaderValue( + Func> value + ) => new(value); + + public static HeaderValue FromString(string value) => new(value); + + public static HeaderValue FromFunc(Func value) => new(value); + + public static HeaderValue FromValueTaskFunc( + Func> value + ) => new(value); + + public static HeaderValue FromTaskFunc( + Func> value + ) => new(value); + + internal global::System.Threading.Tasks.ValueTask ResolveAsync() => _resolver(); +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Headers.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Headers.cs new file mode 100644 index 000000000000..633c91e1f7e4 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Headers.cs @@ -0,0 +1,28 @@ +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// Represents the headers sent with the request. +/// +internal sealed class Headers : Dictionary +{ + internal Headers() { } + + /// + /// Initializes a new instance of the Headers class with the specified value. + /// + /// + internal Headers(Dictionary value) + { + foreach (var kvp in value) + { + this[kvp.Key] = kvp.Value; + } + } + + /// + /// Initializes a new instance of the Headers class with the specified value. + /// + /// + internal Headers(IEnumerable> value) + : base(value.ToDictionary(e => e.Key, e => e.Value)) { } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/HeadersBuilder.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/HeadersBuilder.cs new file mode 100644 index 000000000000..542757c953b9 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/HeadersBuilder.cs @@ -0,0 +1,197 @@ +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// Fluent builder for constructing HTTP headers with support for merging from multiple sources. +/// Provides a clean API for building headers with proper precedence handling. +/// +internal static class HeadersBuilder +{ + /// + /// Fluent builder for constructing HTTP headers. + /// + public sealed class Builder + { + private readonly Dictionary _headers; + + /// + /// Initializes a new instance with default capacity. + /// Uses case-insensitive header name comparison. + /// + public Builder() + { + _headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + /// + /// Initializes a new instance with the specified initial capacity. + /// Uses case-insensitive header name comparison. + /// + public Builder(int capacity) + { + _headers = new Dictionary( + capacity, + StringComparer.OrdinalIgnoreCase + ); + } + + /// + /// Adds a header with the specified key and value. + /// If a header with the same key already exists, it will be overwritten. + /// Null values are ignored. + /// + /// The header name. + /// The header value. Null values are ignored. + /// This builder instance for method chaining. + public Builder Add(string key, string? value) + { + if (value is not null) + { + _headers[key] = (value); + } + return this; + } + + /// + /// Adds a header with the specified key and object value. + /// The value will be converted to string using ValueConvert for consistent serialization. + /// If a header with the same key already exists, it will be overwritten. + /// Null values are ignored. + /// + /// The header name. + /// The header value. Null values are ignored. + /// This builder instance for method chaining. + public Builder Add(string key, object? value) + { + if (value is null) + { + return this; + } + + // Use ValueConvert for consistent serialization across headers, query params, and path params + var stringValue = ValueConvert.ToString(value); + if (stringValue is not null) + { + _headers[key] = (stringValue); + } + return this; + } + + /// + /// Adds multiple headers from a Headers dictionary. + /// HeaderValue instances are stored and will be resolved when BuildAsync() is called. + /// Overwrites any existing headers with the same key. + /// Null entries are ignored. + /// + /// The headers to add. Null is treated as empty. + /// This builder instance for method chaining. + public Builder Add(Headers? headers) + { + if (headers is null) + { + return this; + } + + foreach (var header in headers) + { + _headers[header.Key] = header.Value; + } + + return this; + } + + /// + /// Adds multiple headers from a Headers dictionary, excluding the Authorization header. + /// This is useful for endpoints that don't require authentication, to avoid triggering + /// lazy auth token resolution. + /// HeaderValue instances are stored and will be resolved when BuildAsync() is called. + /// Overwrites any existing headers with the same key. + /// Null entries are ignored. + /// + /// The headers to add. Null is treated as empty. + /// This builder instance for method chaining. + public Builder AddWithoutAuth(Headers? headers) + { + if (headers is null) + { + return this; + } + + foreach (var header in headers) + { + if (header.Key.Equals("Authorization", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + _headers[header.Key] = header.Value; + } + + return this; + } + + /// + /// Adds multiple headers from a key-value pair collection. + /// Overwrites any existing headers with the same key. + /// Null values are ignored. + /// + /// The headers to add. Null is treated as empty. + /// This builder instance for method chaining. + public Builder Add(IEnumerable>? headers) + { + if (headers is null) + { + return this; + } + + foreach (var header in headers) + { + if (header.Value is not null) + { + _headers[header.Key] = (header.Value); + } + } + + return this; + } + + /// + /// Adds multiple headers from a dictionary. + /// Overwrites any existing headers with the same key. + /// + /// The headers to add. Null is treated as empty. + /// This builder instance for method chaining. + public Builder Add(Dictionary? headers) + { + if (headers is null) + { + return this; + } + + foreach (var header in headers) + { + _headers[header.Key] = (header.Value); + } + + return this; + } + + /// + /// Asynchronously builds the final headers dictionary containing all merged headers. + /// Resolves all HeaderValue instances that may contain async operations. + /// Returns a case-insensitive dictionary. + /// + /// A task that represents the asynchronous operation, containing a case-insensitive dictionary of headers. + public async global::System.Threading.Tasks.Task> BuildAsync() + { + var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var kvp in _headers) + { + var value = await kvp.Value.ResolveAsync().ConfigureAwait(false); + if (value is not null) + { + headers[kvp.Key] = value; + } + } + return headers; + } + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/HttpContentExtensions.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/HttpContentExtensions.cs new file mode 100644 index 000000000000..e5068e04e3b8 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/HttpContentExtensions.cs @@ -0,0 +1,20 @@ +#if !NET5_0_OR_GREATER +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// Polyfill extension providing a ReadAsStringAsync(CancellationToken) overload +/// for target frameworks older than .NET 5, where only the parameterless +/// ReadAsStringAsync() is available. +/// +internal static class HttpContentExtensions +{ + internal static Task ReadAsStringAsync( + this HttpContent httpContent, + CancellationToken cancellationToken + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return httpContent.ReadAsStringAsync(); + } +} +#endif diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/HttpMethodExtensions.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/HttpMethodExtensions.cs new file mode 100644 index 000000000000..2a199e0cf03f --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/HttpMethodExtensions.cs @@ -0,0 +1,8 @@ +using global::System.Net.Http; + +namespace Seed.CsharpNamespaceConflict.Core; + +internal static class HttpMethodExtensions +{ + public static readonly HttpMethod Patch = new("PATCH"); +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/IIsRetryableContent.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/IIsRetryableContent.cs new file mode 100644 index 000000000000..3282430d83b3 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/IIsRetryableContent.cs @@ -0,0 +1,6 @@ +namespace Seed.CsharpNamespaceConflict.Core; + +public interface IIsRetryableContent +{ + public bool IsRetryable { get; } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/IRequestOptions.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/IRequestOptions.cs new file mode 100644 index 000000000000..21ef1b7d61f5 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/IRequestOptions.cs @@ -0,0 +1,83 @@ +namespace Seed.CsharpNamespaceConflict.Core; + +internal interface IRequestOptions +{ + /// + /// The Base URL for the API. + /// + public string? BaseUrl { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// The http client used to make requests. + /// + public HttpClient? HttpClient { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// Additional headers to be sent with the request. + /// Headers previously set with matching keys will be overwritten. + /// + public IEnumerable> AdditionalHeaders { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// The max number of retries to attempt. + /// + public int? MaxRetries { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// The timeout for the request. + /// + public TimeSpan? Timeout { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// Additional query parameters sent with the request. + /// + public IEnumerable> AdditionalQueryParameters { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// Additional body properties sent with the request. + /// This is only applied to JSON requests. + /// + public object? AdditionalBodyProperties { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/JsonAccessAttribute.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/JsonAccessAttribute.cs new file mode 100644 index 000000000000..f65d65314c10 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/JsonAccessAttribute.cs @@ -0,0 +1,15 @@ +namespace Seed.CsharpNamespaceConflict.Core; + +[global::System.AttributeUsage( + global::System.AttributeTargets.Property | global::System.AttributeTargets.Field +)] +internal class JsonAccessAttribute(JsonAccessType accessType) : global::System.Attribute +{ + internal JsonAccessType AccessType { get; init; } = accessType; +} + +internal enum JsonAccessType +{ + ReadOnly, + WriteOnly, +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/JsonConfiguration.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/JsonConfiguration.cs new file mode 100644 index 000000000000..60679be2d78a --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/JsonConfiguration.cs @@ -0,0 +1,275 @@ +using global::System.Reflection; +using global::System.Text.Encodings.Web; +using global::System.Text.Json; +using global::System.Text.Json.Nodes; +using global::System.Text.Json.Serialization; +using global::System.Text.Json.Serialization.Metadata; + +namespace Seed.CsharpNamespaceConflict.Core; + +internal static partial class JsonOptions +{ + internal static readonly JsonSerializerOptions JsonSerializerOptions; + internal static readonly JsonSerializerOptions JsonSerializerOptionsRelaxedEscaping; + + static JsonOptions() + { + var options = new JsonSerializerOptions + { + Converters = + { + new DateTimeSerializer(), +#if USE_PORTABLE_DATE_ONLY + new DateOnlyConverter(), +#endif + new OneOfSerializer(), + new OptionalJsonConverterFactory(), + }, +#if DEBUG + WriteIndented = true, +#endif + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = + { + NullableOptionalModifier, + JsonAccessAndIgnoreModifier, + HandleExtensionDataFields, + }, + }, + }; + ConfigureJsonSerializerOptions(options); + JsonSerializerOptions = options; + + var relaxedOptions = new JsonSerializerOptions(options) + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; + JsonSerializerOptionsRelaxedEscaping = relaxedOptions; + } + + private static void NullableOptionalModifier(JsonTypeInfo typeInfo) + { + if (typeInfo.Kind != JsonTypeInfoKind.Object) + return; + + foreach (var property in typeInfo.Properties) + { + var propertyInfo = property.AttributeProvider as global::System.Reflection.PropertyInfo; + + if (propertyInfo is null) + continue; + + // Check for ReadOnly JsonAccessAttribute - it overrides Optional/Nullable behavior + var jsonAccessAttribute = propertyInfo.GetCustomAttribute(); + if (jsonAccessAttribute?.AccessType == JsonAccessType.ReadOnly) + { + // ReadOnly means "never serialize", which completely overrides Optional/Nullable. + // Skip Optional/Nullable processing since JsonAccessAndIgnoreModifier + // will set ShouldSerialize = false anyway. + continue; + } + // Note: WriteOnly doesn't conflict with Optional/Nullable since it only + // affects deserialization (Set), not serialization (ShouldSerialize) + + var isOptionalType = + property.PropertyType.IsGenericType + && property.PropertyType.GetGenericTypeDefinition() == typeof(Optional<>); + + var hasOptionalAttribute = + propertyInfo.GetCustomAttribute() is not null; + var hasNullableAttribute = + propertyInfo.GetCustomAttribute() is not null; + + if (isOptionalType && hasOptionalAttribute) + { + var originalGetter = property.Get; + if (originalGetter is not null) + { + var capturedIsNullable = hasNullableAttribute; + + property.ShouldSerialize = (obj, value) => + { + var optionalValue = originalGetter(obj); + if (optionalValue is not IOptional optional) + return false; + + if (!optional.IsDefined) + return false; + + if (!capturedIsNullable) + { + var innerValue = optional.GetBoxedValue(); + if (innerValue is null) + return false; + } + + return true; + }; + } + } + else if (hasNullableAttribute) + { + // Force serialization of nullable properties even when null + property.ShouldSerialize = (obj, value) => true; + } + } + } + + private static void JsonAccessAndIgnoreModifier(JsonTypeInfo typeInfo) + { + if (typeInfo.Kind != JsonTypeInfoKind.Object) + return; + + foreach (var propertyInfo in typeInfo.Properties) + { + var jsonAccessAttribute = propertyInfo + .AttributeProvider?.GetCustomAttributes(typeof(JsonAccessAttribute), true) + .OfType() + .FirstOrDefault(); + + if (jsonAccessAttribute is not null) + { + propertyInfo.IsRequired = false; + switch (jsonAccessAttribute.AccessType) + { + case JsonAccessType.ReadOnly: + propertyInfo.ShouldSerialize = (_, _) => false; + break; + case JsonAccessType.WriteOnly: + propertyInfo.Set = null; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + var jsonIgnoreAttribute = propertyInfo + .AttributeProvider?.GetCustomAttributes(typeof(JsonIgnoreAttribute), true) + .OfType() + .FirstOrDefault(); + + if (jsonIgnoreAttribute is not null) + { + propertyInfo.IsRequired = false; + } + } + } + + private static void HandleExtensionDataFields(JsonTypeInfo typeInfo) + { + if ( + typeInfo.Kind == JsonTypeInfoKind.Object + && typeInfo.Properties.All(prop => !prop.IsExtensionData) + ) + { + var extensionProp = typeInfo + .Type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic) + .FirstOrDefault(prop => + prop.GetCustomAttribute() is not null + ); + + if (extensionProp is not null) + { + var jsonPropertyInfo = typeInfo.CreateJsonPropertyInfo( + extensionProp.FieldType, + extensionProp.Name + ); + jsonPropertyInfo.Get = extensionProp.GetValue; + jsonPropertyInfo.Set = extensionProp.SetValue; + jsonPropertyInfo.IsExtensionData = true; + typeInfo.Properties.Add(jsonPropertyInfo); + } + } + } + + static partial void ConfigureJsonSerializerOptions(JsonSerializerOptions defaultOptions); +} + +internal static class JsonUtils +{ + internal static string Serialize(T obj) => + JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptions); + + internal static string Serialize(object obj, global::System.Type type) => + JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptions); + + internal static string SerializeRelaxedEscaping(T obj) => + JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptionsRelaxedEscaping); + + internal static string SerializeRelaxedEscaping(object obj, global::System.Type type) => + JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptionsRelaxedEscaping); + + internal static JsonElement SerializeToElement(T obj) => + JsonSerializer.SerializeToElement(obj, JsonOptions.JsonSerializerOptions); + + internal static JsonElement SerializeToElement(object obj, global::System.Type type) => + JsonSerializer.SerializeToElement(obj, type, JsonOptions.JsonSerializerOptions); + + internal static JsonDocument SerializeToDocument(T obj) => + JsonSerializer.SerializeToDocument(obj, JsonOptions.JsonSerializerOptions); + + internal static JsonNode? SerializeToNode(T obj) => + JsonSerializer.SerializeToNode(obj, JsonOptions.JsonSerializerOptions); + + internal static byte[] SerializeToUtf8Bytes(T obj) => + JsonSerializer.SerializeToUtf8Bytes(obj, JsonOptions.JsonSerializerOptions); + + internal static string SerializeWithAdditionalProperties( + T obj, + object? additionalProperties = null + ) + { + if (additionalProperties is null) + { + return Serialize(obj); + } + var additionalPropertiesJsonNode = SerializeToNode(additionalProperties); + if (additionalPropertiesJsonNode is not JsonObject additionalPropertiesJsonObject) + { + throw new InvalidOperationException( + "The additional properties must serialize to a JSON object." + ); + } + var jsonNode = SerializeToNode(obj); + if (jsonNode is not JsonObject jsonObject) + { + throw new InvalidOperationException( + "The serialized object must be a JSON object to add properties." + ); + } + MergeJsonObjects(jsonObject, additionalPropertiesJsonObject); + return jsonObject.ToJsonString(JsonOptions.JsonSerializerOptions); + } + + private static void MergeJsonObjects(JsonObject baseObject, JsonObject overrideObject) + { + foreach (var property in overrideObject) + { + if (!baseObject.TryGetPropertyValue(property.Key, out JsonNode? existingValue)) + { + baseObject[property.Key] = property.Value is not null + ? JsonNode.Parse(property.Value.ToJsonString()) + : null; + continue; + } + if ( + existingValue is JsonObject nestedBaseObject + && property.Value is JsonObject nestedOverrideObject + ) + { + // If both values are objects, recursively merge them. + MergeJsonObjects(nestedBaseObject, nestedOverrideObject); + continue; + } + // Otherwise, the overrideObject takes precedence. + baseObject[property.Key] = property.Value is not null + ? JsonNode.Parse(property.Value.ToJsonString()) + : null; + } + } + + internal static T Deserialize(string json) => + JsonSerializer.Deserialize(json, JsonOptions.JsonSerializerOptions)!; +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/JsonRequest.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/JsonRequest.cs new file mode 100644 index 000000000000..58c35665ad68 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/JsonRequest.cs @@ -0,0 +1,36 @@ +using global::System.Net.Http; + +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// The request object to be sent for JSON APIs. +/// +internal record JsonRequest : BaseRequest +{ + internal object? Body { get; init; } + + internal override HttpContent? CreateContent() + { + if (Body is null && Options?.AdditionalBodyProperties is null) + { + return null; + } + + var (encoding, charset, mediaType) = ParseContentTypeOrDefault( + ContentType, + Utf8NoBom, + "application/json" + ); + var content = new StringContent( + JsonUtils.SerializeWithAdditionalProperties(Body, Options?.AdditionalBodyProperties), + encoding, + mediaType + ); + if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) + { + content.Headers.ContentType.CharSet = ""; + } + + return content; + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/MultipartFormRequest.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/MultipartFormRequest.cs new file mode 100644 index 000000000000..b775e2998f65 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/MultipartFormRequest.cs @@ -0,0 +1,294 @@ +using global::System.Net.Http; +using global::System.Net.Http.Headers; + +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// The request object to be sent for multipart form data. +/// +internal record MultipartFormRequest : BaseRequest +{ + private readonly List> _partAdders = []; + + internal void AddJsonPart(string name, object? value) => AddJsonPart(name, value, null); + + internal void AddJsonPart(string name, object? value, string? contentType) + { + if (value is null) + { + return; + } + + _partAdders.Add(form => + { + var (encoding, charset, mediaType) = ParseContentTypeOrDefault( + contentType, + Utf8NoBom, + "application/json" + ); + var content = new StringContent(JsonUtils.Serialize(value), encoding, mediaType); + if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) + { + content.Headers.ContentType.CharSet = ""; + } + + form.Add(content, name); + }); + } + + internal void AddJsonParts(string name, IEnumerable? value) => + AddJsonParts(name, value, null); + + internal void AddJsonParts(string name, IEnumerable? value, string? contentType) + { + if (value is null) + { + return; + } + + foreach (var item in value) + { + AddJsonPart(name, item, contentType); + } + } + + internal void AddJsonParts(string name, IEnumerable? value) => + AddJsonParts(name, value, null); + + internal void AddJsonParts(string name, IEnumerable? value, string? contentType) + { + if (value is null) + { + return; + } + + foreach (var item in value) + { + AddJsonPart(name, item, contentType); + } + } + + internal void AddStringPart(string name, object? value) => AddStringPart(name, value, null); + + internal void AddStringPart(string name, object? value, string? contentType) + { + if (value is null) + { + return; + } + + AddStringPart(name, ValueConvert.ToString(value), contentType); + } + + internal void AddStringPart(string name, string? value) => AddStringPart(name, value, null); + + internal void AddStringPart(string name, string? value, string? contentType) + { + if (value is null) + { + return; + } + + _partAdders.Add(form => + { + var (encoding, charset, mediaType) = ParseContentTypeOrDefault( + contentType, + Utf8NoBom, + "text/plain" + ); + var content = new StringContent(value, encoding, mediaType); + if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) + { + content.Headers.ContentType.CharSet = ""; + } + + form.Add(content, name); + }); + } + + internal void AddStringParts(string name, IEnumerable? value) => + AddStringParts(name, value, null); + + internal void AddStringParts(string name, IEnumerable? value, string? contentType) + { + if (value is null) + { + return; + } + + AddStringPart(name, ValueConvert.ToString(value), contentType); + } + + internal void AddStringParts(string name, IEnumerable? value) => + AddStringParts(name, value, null); + + internal void AddStringParts(string name, IEnumerable? value, string? contentType) + { + if (value is null) + { + return; + } + + foreach (var item in value) + { + AddStringPart(name, item, contentType); + } + } + + internal void AddStreamPart(string name, Stream? stream, string? fileName) => + AddStreamPart(name, stream, fileName, null); + + internal void AddStreamPart(string name, Stream? stream, string? fileName, string? contentType) + { + if (stream is null) + { + return; + } + + _partAdders.Add(form => + { + var content = new StreamContent(stream) + { + Headers = + { + ContentType = MediaTypeHeaderValue.Parse( + contentType ?? "application/octet-stream" + ), + }, + }; + + if (fileName is not null) + { + form.Add(content, name, fileName); + } + else + { + form.Add(content, name); + } + }); + } + + internal void AddFileParameterPart(string name, Stream? stream) => + AddStreamPart(name, stream, null, null); + + internal void AddFileParameterPart(string name, FileParameter? file) => + AddFileParameterPart(name, file, null); + + internal void AddFileParameterPart( + string name, + FileParameter? file, + string? fallbackContentType + ) => + AddStreamPart(name, file?.Stream, file?.FileName, file?.ContentType ?? fallbackContentType); + + internal void AddFileParameterParts(string name, IEnumerable? files) => + AddFileParameterParts(name, files, null); + + internal void AddFileParameterParts( + string name, + IEnumerable? files, + string? fallbackContentType + ) + { + if (files is null) + { + return; + } + + foreach (var file in files) + { + AddFileParameterPart(name, file, fallbackContentType); + } + } + + internal void AddFormEncodedPart(string name, object? value) => + AddFormEncodedPart(name, value, null); + + internal void AddFormEncodedPart(string name, object? value, string? contentType) + { + if (value is null) + { + return; + } + + _partAdders.Add(form => + { + var content = FormUrlEncoder.EncodeAsForm(value); + if (!string.IsNullOrEmpty(contentType)) + { + content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + + form.Add(content, name); + }); + } + + internal void AddFormEncodedParts(string name, IEnumerable? value) => + AddFormEncodedParts(name, value, null); + + internal void AddFormEncodedParts(string name, IEnumerable? value, string? contentType) + { + if (value is null) + { + return; + } + + foreach (var item in value) + { + AddFormEncodedPart(name, item, contentType); + } + } + + internal void AddExplodedFormEncodedPart(string name, object? value) => + AddExplodedFormEncodedPart(name, value, null); + + internal void AddExplodedFormEncodedPart(string name, object? value, string? contentType) + { + if (value is null) + { + return; + } + + _partAdders.Add(form => + { + var content = FormUrlEncoder.EncodeAsExplodedForm(value); + if (!string.IsNullOrEmpty(contentType)) + { + content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + + form.Add(content, name); + }); + } + + internal void AddExplodedFormEncodedParts(string name, IEnumerable? value) => + AddExplodedFormEncodedParts(name, value, null); + + internal void AddExplodedFormEncodedParts( + string name, + IEnumerable? value, + string? contentType + ) + { + if (value is null) + { + return; + } + + foreach (var item in value) + { + AddExplodedFormEncodedPart(name, item, contentType); + } + } + + internal override HttpContent CreateContent() + { + var form = new MultipartFormDataContent(); + foreach (var adder in _partAdders) + { + adder(form); + } + + return form; + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/NullableAttribute.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/NullableAttribute.cs new file mode 100644 index 000000000000..1b8c9ca6f0b2 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/NullableAttribute.cs @@ -0,0 +1,18 @@ +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// Marks a property as nullable in the OpenAPI specification. +/// When applied to Optional properties, this indicates that null values should be +/// written to JSON when the optional is defined with null. +/// +/// +/// For regular (required) properties: +/// - Without [Nullable]: null values are invalid (omit from JSON at runtime) +/// - With [Nullable]: null values are written to JSON +/// +/// For Optional properties (also marked with [Optional]): +/// - Without [Nullable]: Optional.Of(null) → omit from JSON (runtime edge case) +/// - With [Nullable]: Optional.Of(null) → write null to JSON +/// +[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] +public class NullableAttribute : global::System.Attribute { } diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/OneOfSerializer.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/OneOfSerializer.cs new file mode 100644 index 000000000000..f8bc65337662 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/OneOfSerializer.cs @@ -0,0 +1,145 @@ +using global::System.Reflection; +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using OneOf; + +namespace Seed.CsharpNamespaceConflict.Core; + +internal class OneOfSerializer : JsonConverter +{ + public override IOneOf? Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType is JsonTokenType.Null) + return default; + + foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) + { + try + { + var readerCopy = reader; + var result = JsonSerializer.Deserialize(ref readerCopy, type, options); + reader.Skip(); + return (IOneOf)cast.Invoke(null, [result])!; + } + catch (JsonException) { } + } + + throw new JsonException( + $"Cannot deserialize into one of the supported types for {typeToConvert}" + ); + } + + public override void Write(Utf8JsonWriter writer, IOneOf value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value.Value, options); + } + + public override IOneOf ReadAsPropertyName( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = reader.GetString(); + if (stringValue == null) + throw new JsonException("Cannot deserialize null property name into OneOf type"); + + // Try to deserialize the string value into one of the supported types + foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) + { + try + { + // For primitive types, try direct conversion + if (type == typeof(string)) + { + return (IOneOf)cast.Invoke(null, [stringValue])!; + } + + // For other types, try to deserialize from JSON string + var result = JsonSerializer.Deserialize($"\"{stringValue}\"", type, options); + if (result != null) + { + return (IOneOf)cast.Invoke(null, [result])!; + } + } + catch { } + } + + // If no type-specific deserialization worked, default to string if available + var stringType = GetOneOfTypes(typeToConvert).FirstOrDefault(t => t.type == typeof(string)); + if (stringType != default) + { + return (IOneOf)stringType.cast.Invoke(null, [stringValue])!; + } + + throw new JsonException( + $"Cannot deserialize dictionary key '{stringValue}' into one of the supported types for {typeToConvert}" + ); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + IOneOf value, + JsonSerializerOptions options + ) + { + // Serialize the underlying value to a string suitable for use as a dictionary key + var stringValue = value.Value?.ToString() ?? "null"; + writer.WritePropertyName(stringValue); + } + + private static (global::System.Type type, MethodInfo cast)[] GetOneOfTypes( + global::System.Type typeToConvert + ) + { + var type = typeToConvert; + if (Nullable.GetUnderlyingType(type) is { } underlyingType) + { + type = underlyingType; + } + + var casts = type.GetRuntimeMethods() + .Where(m => m.IsSpecialName && m.Name == "op_Implicit") + .ToArray(); + while (type is not null) + { + if ( + type.IsGenericType + && (type.Name.StartsWith("OneOf`") || type.Name.StartsWith("OneOfBase`")) + ) + { + var genericArguments = type.GetGenericArguments(); + if (genericArguments.Length == 1) + { + return [(genericArguments[0], casts[0])]; + } + + // if object type is present, make sure it is last + var indexOfObjectType = Array.IndexOf(genericArguments, typeof(object)); + if (indexOfObjectType != -1 && genericArguments.Length - 1 != indexOfObjectType) + { + genericArguments = genericArguments + .OrderBy(t => t == typeof(object) ? 1 : 0) + .ToArray(); + } + + return genericArguments + .Select(t => (t, casts.First(c => c.GetParameters()[0].ParameterType == t))) + .ToArray(); + } + + type = type.BaseType; + } + + throw new InvalidOperationException($"{type} isn't OneOf or OneOfBase"); + } + + public override bool CanConvert(global::System.Type typeToConvert) + { + return typeof(IOneOf).IsAssignableFrom(typeToConvert); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Optional.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Optional.cs new file mode 100644 index 000000000000..26dbc0987d33 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Optional.cs @@ -0,0 +1,474 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// Non-generic interface for Optional types to enable reflection-free checks. +/// +public interface IOptional +{ + /// + /// Returns true if the value is defined (set), even if the value is null. + /// + bool IsDefined { get; } + + /// + /// Gets the boxed value. Returns null if undefined or if the value is null. + /// + object? GetBoxedValue(); +} + +/// +/// Represents a field that can be "not set" (undefined) vs "explicitly set" (defined). +/// Use this for HTTP PATCH requests where you need to distinguish between: +/// +/// Undefined: Don't send this field (leave it unchanged on the server) +/// Defined with null: Send null (clear the field on the server) +/// Defined with value: Send the value (update the field on the server) +/// +/// +/// The type of the value. Use nullable types (T?) for fields that can be null. +/// +/// For nullable string fields, use Optional<string?>: +/// +/// public class UpdateUserRequest +/// { +/// public Optional<string?> Name { get; set; } = Optional<string?>.Undefined; +/// } +/// +/// var request = new UpdateUserRequest +/// { +/// Name = "John" // Will send: { "name": "John" } +/// }; +/// +/// var request2 = new UpdateUserRequest +/// { +/// Name = Optional<string?>.Of(null) // Will send: { "name": null } +/// }; +/// +/// var request3 = new UpdateUserRequest(); // Will send: {} (name not included) +/// +/// +public readonly struct Optional : IOptional, IEquatable> +{ + private readonly T _value; + private readonly bool _isDefined; + + private Optional(T value, bool isDefined) + { + _value = value; + _isDefined = isDefined; + } + + /// + /// Creates an undefined value - the field will not be included in the HTTP request. + /// Use this as the default value for optional fields. + /// + /// + /// + /// public Optional<string?> Email { get; set; } = Optional<string?>.Undefined; + /// + /// + public static Optional Undefined => new(default!, false); + + /// + /// Creates a defined value - the field will be included in the HTTP request. + /// The value can be null if T is a nullable type. + /// + /// The value to set. Can be null if T is nullable (e.g., string?, int?). + /// + /// + /// // Set to a value + /// request.Name = Optional<string?>.Of("John"); + /// + /// // Set to null (clears the field) + /// request.Email = Optional<string?>.Of(null); + /// + /// // Or use implicit conversion + /// request.Name = "John"; // Same as Of("John") + /// request.Email = null; // Same as Of(null) + /// + /// + public static Optional Of(T value) => new(value, true); + + /// + /// Returns true if the field is defined (set), even if the value is null. + /// Use this to determine if the field should be included in the HTTP request. + /// + /// + /// + /// if (request.Name.IsDefined) + /// { + /// requestBody["name"] = request.Name.Value; // Include in request (can be null) + /// } + /// + /// + public bool IsDefined => _isDefined; + + /// + /// Returns true if the field is undefined (not set). + /// Use this to check if the field should be excluded from the HTTP request. + /// + /// + /// + /// if (request.Email.IsUndefined) + /// { + /// // Don't include email in the request - leave it unchanged + /// } + /// + /// + public bool IsUndefined => !_isDefined; + + /// + /// Gets the value. The value may be null if T is a nullable type. + /// + /// Thrown if the value is undefined. + /// + /// Always check before accessing Value, or use instead. + /// + /// + /// + /// if (request.Name.IsDefined) + /// { + /// string? name = request.Name.Value; // Safe - can be null if Optional<string?> + /// } + /// + /// // Or check for null explicitly + /// if (request.Email.IsDefined && request.Email.Value is null) + /// { + /// // Email is explicitly set to null (clear it) + /// } + /// + /// + public T Value + { + get + { + if (!_isDefined) + throw new InvalidOperationException("Optional value is undefined"); + return _value; + } + } + + /// + /// Gets the value if defined, otherwise returns the specified default value. + /// Note: If the value is defined as null, this returns null (not the default). + /// + /// The value to return if undefined. + /// The actual value if defined (can be null), otherwise the default value. + /// + /// + /// string name = request.Name.GetValueOrDefault("Anonymous"); + /// // If Name is undefined: returns "Anonymous" + /// // If Name is Of(null): returns null + /// // If Name is Of("John"): returns "John" + /// + /// + public T GetValueOrDefault(T defaultValue = default!) + { + return _isDefined ? _value : defaultValue; + } + + /// + /// Tries to get the value. Returns true if the value is defined (even if null). + /// + /// + /// When this method returns, contains the value if defined, or default(T) if undefined. + /// The value may be null if T is nullable. + /// + /// True if the value is defined; otherwise, false. + /// + /// + /// if (request.Email.TryGetValue(out var email)) + /// { + /// requestBody["email"] = email; // email can be null + /// } + /// else + /// { + /// // Email is undefined - don't include in request + /// } + /// + /// + public bool TryGetValue(out T value) + { + if (_isDefined) + { + value = _value; + return true; + } + value = default!; + return false; + } + + /// + /// Implicitly converts a value to Optional<T>.Of(value). + /// This allows natural assignment: request.Name = "John" instead of request.Name = Optional<string?>.Of("John"). + /// + /// The value to convert (can be null if T is nullable). + public static implicit operator Optional(T value) => Of(value); + + /// + /// Returns a string representation of this Optional value. + /// + /// "Undefined" if not set, or "Defined(value)" if set. + public override string ToString() => _isDefined ? $"Defined({_value})" : "Undefined"; + + /// + /// Gets the boxed value. Returns null if undefined or if the value is null. + /// + public object? GetBoxedValue() + { + if (!_isDefined) + return null; + return _value; + } + + /// + public bool Equals(Optional other) => + _isDefined == other._isDefined && EqualityComparer.Default.Equals(_value, other._value); + + /// + public override bool Equals(object? obj) => obj is Optional other && Equals(other); + + /// + public override int GetHashCode() + { + if (!_isDefined) + return 0; + unchecked + { + int hash = 17; + hash = hash * 31 + 1; // _isDefined = true + hash = hash * 31 + (_value is null ? 0 : _value.GetHashCode()); + return hash; + } + } + + /// + /// Determines whether two Optional values are equal. + /// + /// The first Optional to compare. + /// The second Optional to compare. + /// True if the Optional values are equal; otherwise, false. + public static bool operator ==(Optional left, Optional right) => left.Equals(right); + + /// + /// Determines whether two Optional values are not equal. + /// + /// The first Optional to compare. + /// The second Optional to compare. + /// True if the Optional values are not equal; otherwise, false. + public static bool operator !=(Optional left, Optional right) => !left.Equals(right); +} + +/// +/// Extension methods for Optional to simplify common operations. +/// +public static class OptionalExtensions +{ + /// + /// Adds the value to a dictionary if the optional is defined (even if the value is null). + /// This is useful for building JSON request payloads where null values should be included. + /// + /// The type of the optional value. + /// The optional value to add. + /// The dictionary to add to. + /// The key to use in the dictionary. + /// + /// + /// var dict = new Dictionary<string, object?>(); + /// request.Name.AddTo(dict, "name"); // Adds only if Name.IsDefined + /// request.Email.AddTo(dict, "email"); // Adds only if Email.IsDefined + /// + /// + public static void AddTo( + this Optional optional, + Dictionary dictionary, + string key + ) + { + if (optional.IsDefined) + { + dictionary[key] = optional.Value; + } + } + + /// + /// Executes an action if the optional is defined. + /// + /// The type of the optional value. + /// The optional value. + /// The action to execute with the value. + /// + /// + /// request.Name.IfDefined(name => Console.WriteLine($"Name: {name}")); + /// + /// + public static void IfDefined(this Optional optional, Action action) + { + if (optional.IsDefined) + { + action(optional.Value); + } + } + + /// + /// Maps the value to a new type if the optional is defined, otherwise returns undefined. + /// + /// The type of the original value. + /// The type to map to. + /// The optional value to map. + /// The mapping function. + /// An optional containing the mapped value if defined, otherwise undefined. + /// + /// + /// Optional<string?> name = Optional<string?>.Of("John"); + /// Optional<int> length = name.Map(n => n?.Length ?? 0); // Optional.Of(4) + /// + /// + public static Optional Map( + this Optional optional, + Func mapper + ) + { + return optional.IsDefined + ? Optional.Of(mapper(optional.Value)) + : Optional.Undefined; + } + + /// + /// Adds a nullable value to a dictionary only if it is not null. + /// This is useful for regular nullable properties where null means "omit from request". + /// + /// The type of the value (must be a reference type or Nullable). + /// The nullable value to add. + /// The dictionary to add to. + /// The key to use in the dictionary. + /// + /// + /// var dict = new Dictionary<string, object?>(); + /// request.Description.AddIfNotNull(dict, "description"); // Only adds if not null + /// request.Score.AddIfNotNull(dict, "score"); // Only adds if not null + /// + /// + public static void AddIfNotNull( + this T? value, + Dictionary dictionary, + string key + ) + where T : class + { + if (value is not null) + { + dictionary[key] = value; + } + } + + /// + /// Adds a nullable value type to a dictionary only if it has a value. + /// This is useful for regular nullable properties where null means "omit from request". + /// + /// The underlying value type. + /// The nullable value to add. + /// The dictionary to add to. + /// The key to use in the dictionary. + /// + /// + /// var dict = new Dictionary<string, object?>(); + /// request.Age.AddIfNotNull(dict, "age"); // Only adds if HasValue + /// request.Score.AddIfNotNull(dict, "score"); // Only adds if HasValue + /// + /// + public static void AddIfNotNull( + this T? value, + Dictionary dictionary, + string key + ) + where T : struct + { + if (value.HasValue) + { + dictionary[key] = value.Value; + } + } +} + +/// +/// JSON converter factory for Optional that handles undefined vs null correctly. +/// Uses a TypeInfoResolver to conditionally include/exclude properties based on Optional.IsDefined. +/// +public class OptionalJsonConverterFactory : JsonConverterFactory +{ + public override bool CanConvert(global::System.Type typeToConvert) + { + if (!typeToConvert.IsGenericType) + return false; + + return typeToConvert.GetGenericTypeDefinition() == typeof(Optional<>); + } + + public override JsonConverter? CreateConverter( + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + var valueType = typeToConvert.GetGenericArguments()[0]; + var converterType = typeof(OptionalJsonConverter<>).MakeGenericType(valueType); + return (JsonConverter?)global::System.Activator.CreateInstance(converterType); + } +} + +/// +/// JSON converter for Optional that unwraps the value during serialization. +/// The actual property skipping is handled by the OptionalTypeInfoResolver. +/// +public class OptionalJsonConverter : JsonConverter> +{ + public override Optional Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType == JsonTokenType.Null) + { + return Optional.Of(default!); + } + + var value = JsonSerializer.Deserialize(ref reader, options); + return Optional.Of(value!); + } + + public override void Write( + Utf8JsonWriter writer, + Optional value, + JsonSerializerOptions options + ) + { + // This will be called by the serializer + // We need to unwrap and serialize the inner value + // The TypeInfoResolver will handle skipping undefined values + + if (value.IsUndefined) + { + // This shouldn't be called for undefined values due to ShouldSerialize + // But if it is, write null and let the resolver filter it + writer.WriteNullValue(); + return; + } + + // Get the inner value + var innerValue = value.Value; + + // Write null directly if the value is null (don't use JsonSerializer.Serialize for null) + if (innerValue is null) + { + writer.WriteNullValue(); + return; + } + + // Serialize the unwrapped value + JsonSerializer.Serialize(writer, innerValue, options); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/OptionalAttribute.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/OptionalAttribute.cs new file mode 100644 index 000000000000..a1ba2467d065 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/OptionalAttribute.cs @@ -0,0 +1,17 @@ +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// Marks a property as optional in the OpenAPI specification. +/// Optional properties use the Optional type and can be undefined (not present in JSON). +/// +/// +/// Properties marked with [Optional] should use the Optional type: +/// - Undefined: Optional.Undefined → omitted from JSON +/// - Defined: Optional.Of(value) → written to JSON +/// +/// Combine with [Nullable] to allow null values: +/// - [Optional, Nullable] Optional → can be undefined, null, or a value +/// - [Optional] Optional → can be undefined or a value (null is invalid) +/// +[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] +public class OptionalAttribute : global::System.Attribute { } diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/AdditionalProperties.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/AdditionalProperties.cs new file mode 100644 index 000000000000..64bf7fc4e0e4 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/AdditionalProperties.cs @@ -0,0 +1,353 @@ +using global::System.Collections; +using global::System.Collections.ObjectModel; +using global::System.Text.Json; +using global::System.Text.Json.Nodes; +using Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict; + +public record ReadOnlyAdditionalProperties : ReadOnlyAdditionalProperties +{ + internal ReadOnlyAdditionalProperties() { } + + internal ReadOnlyAdditionalProperties(IDictionary properties) + : base(properties) { } +} + +public record ReadOnlyAdditionalProperties : IReadOnlyDictionary +{ + private readonly Dictionary _extensionData = new(); + private readonly Dictionary _convertedCache = new(); + + internal ReadOnlyAdditionalProperties() + { + _extensionData = new Dictionary(); + _convertedCache = new Dictionary(); + } + + internal ReadOnlyAdditionalProperties(IDictionary properties) + { + _extensionData = new Dictionary(properties.Count); + _convertedCache = new Dictionary(properties.Count); + foreach (var kvp in properties) + { + if (kvp.Value is JsonElement element) + { + _extensionData.Add(kvp.Key, element); + } + else + { + _extensionData[kvp.Key] = JsonUtils.SerializeToElement(kvp.Value); + } + + _convertedCache[kvp.Key] = kvp.Value; + } + } + + private static T ConvertToT(JsonElement value) + { + if (typeof(T) == typeof(JsonElement)) + { + return (T)(object)value; + } + + return value.Deserialize(JsonOptions.JsonSerializerOptions)!; + } + + internal void CopyFromExtensionData(IDictionary extensionData) + { + _extensionData.Clear(); + _convertedCache.Clear(); + foreach (var kvp in extensionData) + { + _extensionData[kvp.Key] = kvp.Value; + if (kvp.Value is T value) + { + _convertedCache[kvp.Key] = value; + } + } + } + + private T GetCached(string key) + { + if (_convertedCache.TryGetValue(key, out var cached)) + { + return cached; + } + + var value = ConvertToT(_extensionData[key]); + _convertedCache[key] = value; + return value; + } + + public IEnumerator> GetEnumerator() + { + return _extensionData + .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) + .GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public int Count => _extensionData.Count; + + public bool ContainsKey(string key) => _extensionData.ContainsKey(key); + + public bool TryGetValue(string key, out T value) + { + if (_convertedCache.TryGetValue(key, out value!)) + { + return true; + } + + if (_extensionData.TryGetValue(key, out var element)) + { + value = ConvertToT(element); + _convertedCache[key] = value; + return true; + } + + return false; + } + + public T this[string key] => GetCached(key); + + public IEnumerable Keys => _extensionData.Keys; + + public IEnumerable Values => Keys.Select(GetCached); +} + +public record AdditionalProperties : AdditionalProperties +{ + public AdditionalProperties() { } + + public AdditionalProperties(IDictionary properties) + : base(properties) { } +} + +public record AdditionalProperties : IDictionary +{ + private readonly Dictionary _extensionData; + private readonly Dictionary _convertedCache; + + public AdditionalProperties() + { + _extensionData = new Dictionary(); + _convertedCache = new Dictionary(); + } + + public AdditionalProperties(IDictionary properties) + { + _extensionData = new Dictionary(properties.Count); + _convertedCache = new Dictionary(properties.Count); + foreach (var kvp in properties) + { + _extensionData[kvp.Key] = kvp.Value; + _convertedCache[kvp.Key] = kvp.Value; + } + } + + private static T ConvertToT(object? extensionDataValue) + { + return extensionDataValue switch + { + T value => value, + JsonElement jsonElement => jsonElement.Deserialize( + JsonOptions.JsonSerializerOptions + )!, + JsonNode jsonNode => jsonNode.Deserialize(JsonOptions.JsonSerializerOptions)!, + _ => JsonUtils + .SerializeToElement(extensionDataValue) + .Deserialize(JsonOptions.JsonSerializerOptions)!, + }; + } + + internal void CopyFromExtensionData(IDictionary extensionData) + { + _extensionData.Clear(); + _convertedCache.Clear(); + foreach (var kvp in extensionData) + { + _extensionData[kvp.Key] = kvp.Value; + if (kvp.Value is T value) + { + _convertedCache[kvp.Key] = value; + } + } + } + + internal void CopyToExtensionData(IDictionary extensionData) + { + extensionData.Clear(); + foreach (var kvp in _extensionData) + { + extensionData[kvp.Key] = kvp.Value; + } + } + + public JsonObject ToJsonObject() => + ( + JsonUtils.SerializeToNode(_extensionData) + ?? throw new InvalidOperationException( + "Failed to serialize AdditionalProperties to JSON Node." + ) + ).AsObject(); + + public JsonNode ToJsonNode() => + JsonUtils.SerializeToNode(_extensionData) + ?? throw new InvalidOperationException( + "Failed to serialize AdditionalProperties to JSON Node." + ); + + public JsonElement ToJsonElement() => JsonUtils.SerializeToElement(_extensionData); + + public JsonDocument ToJsonDocument() => JsonUtils.SerializeToDocument(_extensionData); + + public IReadOnlyDictionary ToJsonElementDictionary() + { + return new ReadOnlyDictionary( + _extensionData.ToDictionary( + kvp => kvp.Key, + kvp => + { + if (kvp.Value is JsonElement jsonElement) + { + return jsonElement; + } + + return JsonUtils.SerializeToElement(kvp.Value); + } + ) + ); + } + + public ICollection Keys => _extensionData.Keys; + + public ICollection Values + { + get + { + var values = new T[_extensionData.Count]; + var i = 0; + foreach (var key in Keys) + { + values[i++] = GetCached(key); + } + + return values; + } + } + + private T GetCached(string key) + { + if (_convertedCache.TryGetValue(key, out var value)) + { + return value; + } + + value = ConvertToT(_extensionData[key]); + _convertedCache.Add(key, value); + return value; + } + + private void SetCached(string key, T value) + { + _extensionData[key] = value; + _convertedCache[key] = value; + } + + private void AddCached(string key, T value) + { + _extensionData.Add(key, value); + _convertedCache.Add(key, value); + } + + private bool RemoveCached(string key) + { + var isRemoved = _extensionData.Remove(key); + _convertedCache.Remove(key); + return isRemoved; + } + + public int Count => _extensionData.Count; + public bool IsReadOnly => false; + + public T this[string key] + { + get => GetCached(key); + set => SetCached(key, value); + } + + public void Add(string key, T value) => AddCached(key, value); + + public void Add(KeyValuePair item) => AddCached(item.Key, item.Value); + + public bool Remove(string key) => RemoveCached(key); + + public bool Remove(KeyValuePair item) => RemoveCached(item.Key); + + public bool ContainsKey(string key) => _extensionData.ContainsKey(key); + + public bool Contains(KeyValuePair item) + { + return _extensionData.ContainsKey(item.Key) + && EqualityComparer.Default.Equals(GetCached(item.Key), item.Value); + } + + public bool TryGetValue(string key, out T value) + { + if (_convertedCache.TryGetValue(key, out value!)) + { + return true; + } + + if (_extensionData.TryGetValue(key, out var extensionDataValue)) + { + value = ConvertToT(extensionDataValue); + _convertedCache[key] = value; + return true; + } + + return false; + } + + public void Clear() + { + _extensionData.Clear(); + _convertedCache.Clear(); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (array is null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (arrayIndex < 0 || arrayIndex > array.Length) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + + if (array.Length - arrayIndex < _extensionData.Count) + { + throw new ArgumentException( + "The array does not have enough space to copy the elements." + ); + } + + foreach (var kvp in _extensionData) + { + array[arrayIndex++] = new KeyValuePair(kvp.Key, GetCached(kvp.Key)); + } + } + + public IEnumerator> GetEnumerator() + { + return _extensionData + .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) + .GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/ClientOptions.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/ClientOptions.cs new file mode 100644 index 000000000000..ef01fb5b34ea --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/ClientOptions.cs @@ -0,0 +1,84 @@ +using global::Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict; + +[Serializable] +public partial class ClientOptions +{ + /// + /// The http headers sent with the request. + /// + internal Headers Headers { get; init; } = new(); + + /// + /// The Base URL for the API. + /// + public string BaseUrl { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = ""; + + /// + /// The http client used to make requests. + /// + public HttpClient HttpClient { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = new HttpClient(); + + /// + /// Additional headers to be sent with HTTP requests. + /// Headers with matching keys will be overwritten by headers set on the request. + /// + public IEnumerable> AdditionalHeaders { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = []; + + /// + /// The max number of retries to attempt. + /// + public int MaxRetries { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = 2; + + /// + /// The timeout for the request. + /// + public TimeSpan Timeout { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = TimeSpan.FromSeconds(30); + + /// + /// Clones this and returns a new instance + /// + internal ClientOptions Clone() + { + return new ClientOptions + { + BaseUrl = BaseUrl, + HttpClient = HttpClient, + MaxRetries = MaxRetries, + Timeout = Timeout, + Headers = new Headers(new Dictionary(Headers)), + AdditionalHeaders = AdditionalHeaders, + }; + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/FileParameter.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/FileParameter.cs new file mode 100644 index 000000000000..75a525c49044 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/FileParameter.cs @@ -0,0 +1,63 @@ +namespace Seed.CsharpNamespaceConflict; + +/// +/// File parameter for uploading files. +/// +public record FileParameter : IDisposable +#if NET6_0_OR_GREATER + , IAsyncDisposable +#endif +{ + private bool _disposed; + + /// + /// The name of the file to be uploaded. + /// + public string? FileName { get; set; } + + /// + /// The content type of the file to be uploaded. + /// + public string? ContentType { get; set; } + + /// + /// The content of the file to be uploaded. + /// + public required Stream Stream { get; set; } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + if (disposing) + { + Stream.Dispose(); + } + + _disposed = true; + } + +#if NET6_0_OR_GREATER + /// + public async ValueTask DisposeAsync() + { + if (!_disposed) + { + await Stream.DisposeAsync().ConfigureAwait(false); + _disposed = true; + } + + GC.SuppressFinalize(this); + } +#endif + + public static implicit operator FileParameter(Stream stream) => new() { Stream = stream }; +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/RawResponse.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/RawResponse.cs new file mode 100644 index 000000000000..06f9cd2cfb05 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/RawResponse.cs @@ -0,0 +1,24 @@ +using global::System.Net; + +namespace Seed.CsharpNamespaceConflict; + +/// +/// Contains HTTP response metadata including status code, URL, and headers. +/// +public record RawResponse +{ + /// + /// The HTTP status code of the response. + /// + public required HttpStatusCode StatusCode { get; init; } + + /// + /// The request URL that generated this response. + /// + public required Uri Url { get; init; } + + /// + /// The HTTP response headers. + /// + public required Core.ResponseHeaders Headers { get; init; } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/RequestOptions.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/RequestOptions.cs new file mode 100644 index 000000000000..d8e2cdb0d894 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/RequestOptions.cs @@ -0,0 +1,86 @@ +using global::Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict; + +[Serializable] +public partial class RequestOptions : IRequestOptions +{ + /// + /// The Base URL for the API. + /// + public string? BaseUrl { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// The http client used to make requests. + /// + public HttpClient? HttpClient { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// Additional headers to be sent with the request. + /// Headers previously set with matching keys will be overwritten. + /// + public IEnumerable> AdditionalHeaders { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = []; + + /// + /// The max number of retries to attempt. + /// + public int? MaxRetries { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// The timeout for the request. + /// + public TimeSpan? Timeout { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// Additional query parameters sent with the request. + /// + public IEnumerable> AdditionalQueryParameters { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = Enumerable.Empty>(); + + /// + /// Additional body properties sent with the request. + /// This is only applied to JSON requests. + /// + public object? AdditionalBodyProperties { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/SeedApiException.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/SeedApiException.cs new file mode 100644 index 000000000000..2815c4a303e0 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/SeedApiException.cs @@ -0,0 +1,22 @@ +namespace Seed.CsharpNamespaceConflict; + +/// +/// This exception type will be thrown for any non-2XX API responses. +/// +public class SeedApiException( + string message, + int statusCode, + object body, + Exception? innerException = null +) : SeedException(message, innerException) +{ + /// + /// The error code of the response that triggered the exception. + /// + public int StatusCode => statusCode; + + /// + /// The body of the response that triggered the exception. + /// + public object Body => body; +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/SeedException.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/SeedException.cs new file mode 100644 index 000000000000..2b4b9f42f94c --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/SeedException.cs @@ -0,0 +1,7 @@ +namespace Seed.CsharpNamespaceConflict; + +/// +/// Base exception class for all exceptions thrown by the SDK. +/// +public class SeedException(string message, Exception? innerException = null) + : Exception(message, innerException); diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/Version.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/Version.cs new file mode 100644 index 000000000000..411783512bbd --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/Version.cs @@ -0,0 +1,7 @@ +namespace Seed.CsharpNamespaceConflict; + +[Serializable] +internal class Version +{ + public const string Current = "0.0.1"; +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/WithRawResponse.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/WithRawResponse.cs new file mode 100644 index 000000000000..b8ee5dd8fda1 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/WithRawResponse.cs @@ -0,0 +1,18 @@ +namespace Seed.CsharpNamespaceConflict; + +/// +/// Wraps a parsed response value with its raw HTTP response metadata. +/// +/// The type of the parsed response data. +public readonly struct WithRawResponse +{ + /// + /// The parsed response data. + /// + public required T Data { get; init; } + + /// + /// The raw HTTP response metadata. + /// + public required RawResponse RawResponse { get; init; } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/WithRawResponseTask.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/WithRawResponseTask.cs new file mode 100644 index 000000000000..a170282b1f1b --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/Public/WithRawResponseTask.cs @@ -0,0 +1,144 @@ +using global::System.Runtime.CompilerServices; + +namespace Seed.CsharpNamespaceConflict; + +/// +/// A task-like type that wraps Task<WithRawResponse<T>> and provides dual-mode awaiting: +/// - Direct await yields just T (zero-allocation path for common case) +/// - .WithRawResponse() yields WithRawResponse<T> (when raw response metadata is needed) +/// +/// The type of the parsed response data. +public readonly struct WithRawResponseTask +{ + private readonly global::System.Threading.Tasks.Task> _task; + + /// + /// Creates a new WithRawResponseTask wrapping the given task. + /// + public WithRawResponseTask(global::System.Threading.Tasks.Task> task) + { + _task = task; + } + + /// + /// Returns the underlying task that yields both the data and raw response metadata. + /// + public global::System.Threading.Tasks.Task> WithRawResponse() => _task; + + /// + /// Gets the custom awaiter that unwraps to just T when awaited. + /// + public Awaiter GetAwaiter() => new(_task.GetAwaiter()); + + /// + /// Configures the awaiter to continue on the captured context or not. + /// + public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) => + new(_task.ConfigureAwait(continueOnCapturedContext)); + + /// + /// Implicitly converts WithRawResponseTask<T> to global::System.Threading.Tasks.Task<T> for backward compatibility. + /// The resulting task will yield just the data when awaited. + /// + public static implicit operator global::System.Threading.Tasks.Task( + WithRawResponseTask task + ) + { + return task._task.ContinueWith( + t => t.Result.Data, + TaskContinuationOptions.ExecuteSynchronously + ); + } + + /// + /// Custom awaiter that unwraps WithRawResponse<T> to just T. + /// + public readonly struct Awaiter : ICriticalNotifyCompletion + { + private readonly TaskAwaiter> _awaiter; + + internal Awaiter(TaskAwaiter> awaiter) + { + _awaiter = awaiter; + } + + /// + /// Gets whether the underlying task has completed. + /// + public bool IsCompleted => _awaiter.IsCompleted; + + /// + /// Gets the result, unwrapping to just the data. + /// + public T GetResult() => _awaiter.GetResult().Data; + + /// + /// Schedules the continuation action. + /// + public void OnCompleted(global::System.Action continuation) => + _awaiter.OnCompleted(continuation); + + /// + /// Schedules the continuation action without capturing the execution context. + /// + public void UnsafeOnCompleted(global::System.Action continuation) => + _awaiter.UnsafeOnCompleted(continuation); + } + + /// + /// Awaitable type returned by ConfigureAwait that unwraps to just T. + /// + public readonly struct ConfiguredTaskAwaitable + { + private readonly ConfiguredTaskAwaitable> _configuredTask; + + internal ConfiguredTaskAwaitable(ConfiguredTaskAwaitable> configuredTask) + { + _configuredTask = configuredTask; + } + + /// + /// Gets the configured awaiter that unwraps to just T. + /// + public ConfiguredAwaiter GetAwaiter() => new(_configuredTask.GetAwaiter()); + + /// + /// Custom configured awaiter that unwraps WithRawResponse<T> to just T. + /// + public readonly struct ConfiguredAwaiter : ICriticalNotifyCompletion + { + private readonly ConfiguredTaskAwaitable< + WithRawResponse + >.ConfiguredTaskAwaiter _awaiter; + + internal ConfiguredAwaiter( + ConfiguredTaskAwaitable>.ConfiguredTaskAwaiter awaiter + ) + { + _awaiter = awaiter; + } + + /// + /// Gets whether the underlying task has completed. + /// + public bool IsCompleted => _awaiter.IsCompleted; + + /// + /// Gets the result, unwrapping to just the data. + /// + public T GetResult() => _awaiter.GetResult().Data; + + /// + /// Schedules the continuation action. + /// + public void OnCompleted(global::System.Action continuation) => + _awaiter.OnCompleted(continuation); + + /// + /// Schedules the continuation action without capturing the execution context. + /// + public void UnsafeOnCompleted(global::System.Action continuation) => + _awaiter.UnsafeOnCompleted(continuation); + } + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/QueryStringBuilder.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/QueryStringBuilder.cs new file mode 100644 index 000000000000..28aa876cc7ca --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/QueryStringBuilder.cs @@ -0,0 +1,484 @@ +using global::System.Buffers; +using global::System.Runtime.CompilerServices; +#if !NET6_0_OR_GREATER +using global::System.Text; +#endif + +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// High-performance query string builder with cross-platform optimizations. +/// Uses span-based APIs on .NET 6+ and StringBuilder fallback for older targets. +/// +internal static class QueryStringBuilder +{ +#if NET8_0_OR_GREATER + private static readonly SearchValues UnreservedChars = SearchValues.Create( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~" + ); +#else + private const string UnreservedChars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~"; +#endif + +#if NET7_0_OR_GREATER + private static ReadOnlySpan UpperHexChars => "0123456789ABCDEF"u8; +#else + private static readonly byte[] UpperHexChars = + { + (byte)'0', + (byte)'1', + (byte)'2', + (byte)'3', + (byte)'4', + (byte)'5', + (byte)'6', + (byte)'7', + (byte)'8', + (byte)'9', + (byte)'A', + (byte)'B', + (byte)'C', + (byte)'D', + (byte)'E', + (byte)'F', + }; +#endif + + /// + /// Builds a query string from the provided parameters. + /// +#if NET6_0_OR_GREATER + public static string Build(ReadOnlySpan> parameters) + { + if (parameters.IsEmpty) + return string.Empty; + + var estimatedLength = EstimateLength(parameters); + if (estimatedLength == 0) + return string.Empty; + + var bufferSize = Math.Min(estimatedLength * 3, 8192); + var buffer = ArrayPool.Shared.Rent(bufferSize); + + try + { + var written = BuildCore(parameters, buffer); + return new string(buffer.AsSpan(0, written)); + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } + + private static int EstimateLength(ReadOnlySpan> parameters) + { + var estimatedLength = 0; + foreach (var kvp in parameters) + { + estimatedLength += kvp.Key.Length + kvp.Value.Length + 2; + } + return estimatedLength; + } +#endif + + /// + /// Builds a query string from the provided parameters. + /// + public static string Build(IEnumerable> parameters) + { +#if NET6_0_OR_GREATER + // Try to get span access for collections that support it + if (parameters is ICollection> collection) + { + if (collection.Count == 0) + return string.Empty; + + var array = ArrayPool>.Shared.Rent(collection.Count); + try + { + collection.CopyTo(array, 0); + return Build(array.AsSpan(0, collection.Count)); + } + finally + { + ArrayPool>.Shared.Return(array); + } + } + + // Fallback for non-collection enumerables + using var enumerator = parameters.GetEnumerator(); + if (!enumerator.MoveNext()) + return string.Empty; + + var buffer = ArrayPool.Shared.Rent(4096); + try + { + var position = 0; + var first = true; + + do + { + var kvp = enumerator.Current; + + // Ensure capacity (worst case: 3x for encoding + separators) + var required = (kvp.Key.Length + kvp.Value.Length + 2) * 3; + if (position + required > buffer.Length) + { + var newBuffer = ArrayPool.Shared.Rent(buffer.Length * 2); + buffer.AsSpan(0, position).CopyTo(newBuffer); + ArrayPool.Shared.Return(buffer); + buffer = newBuffer; + } + + buffer[position++] = first ? '?' : '&'; + first = false; + + position += EncodeComponent(kvp.Key.AsSpan(), buffer.AsSpan(position)); + buffer[position++] = '='; + position += EncodeComponent(kvp.Value.AsSpan(), buffer.AsSpan(position)); + } while (enumerator.MoveNext()); + + return first ? string.Empty : new string(buffer.AsSpan(0, position)); + } + finally + { + ArrayPool.Shared.Return(buffer); + } +#else + // netstandard2.0 / net462 fallback using StringBuilder + var sb = new StringBuilder(); + var first = true; + + foreach (var kvp in parameters) + { + sb.Append(first ? '?' : '&'); + first = false; + + AppendEncoded(sb, kvp.Key); + sb.Append('='); + AppendEncoded(sb, kvp.Value); + } + + return sb.ToString(); +#endif + } + +#if NET6_0_OR_GREATER + private static int BuildCore( + ReadOnlySpan> parameters, + Span buffer + ) + { + var position = 0; + var first = true; + + foreach (var kvp in parameters) + { + buffer[position++] = first ? '?' : '&'; + first = false; + + position += EncodeComponent(kvp.Key.AsSpan(), buffer.Slice(position)); + buffer[position++] = '='; + position += EncodeComponent(kvp.Value.AsSpan(), buffer.Slice(position)); + } + + return position; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int EncodeComponent(ReadOnlySpan input, Span output) + { + if (!NeedsEncoding(input)) + { + input.CopyTo(output); + return input.Length; + } + + return EncodeSlow(input, output); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool NeedsEncoding(ReadOnlySpan value) + { +#if NET8_0_OR_GREATER + return value.ContainsAnyExcept(UnreservedChars); +#else + foreach (var c in value) + { + if (!IsUnreserved(c)) + return true; + } + return false; +#endif + } + + private static int EncodeSlow(ReadOnlySpan input, Span output) + { + var position = 0; + + foreach (var c in input) + { + if (IsUnreserved(c)) + { + output[position++] = c; + } + else if (c == ' ') + { + output[position++] = '%'; + output[position++] = '2'; + output[position++] = '0'; + } +#if NET7_0_OR_GREATER + else if (char.IsAscii(c)) +#else + else if (c <= 127) +#endif + { + position += EncodeAscii((byte)c, output.Slice(position)); + } + else + { + position += EncodeUtf8(c, output.Slice(position)); + } + } + + return position; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int EncodeAscii(byte value, Span output) + { + output[0] = '%'; + output[1] = (char)UpperHexChars[value >> 4]; + output[2] = (char)UpperHexChars[value & 0xF]; + return 3; + } + + private static int EncodeUtf8(char c, Span output) + { + Span utf8Bytes = stackalloc byte[4]; + Span singleChar = stackalloc char[1] { c }; + var byteCount = global::System.Text.Encoding.UTF8.GetBytes(singleChar, utf8Bytes); + + var position = 0; + for (var i = 0; i < byteCount; i++) + { + output[position++] = '%'; + output[position++] = (char)UpperHexChars[utf8Bytes[i] >> 4]; + output[position++] = (char)UpperHexChars[utf8Bytes[i] & 0xF]; + } + + return position; + } +#else + // netstandard2.0 / net462 StringBuilder-based encoding + private static void AppendEncoded(StringBuilder sb, string value) + { + foreach (var c in value) + { + if (IsUnreserved(c)) + { + sb.Append(c); + } + else if (c == ' ') + { + sb.Append("%20"); + } + else if (c <= 127) + { + AppendPercentEncoded(sb, (byte)c); + } + else + { + var bytes = Encoding.UTF8.GetBytes(new[] { c }); + foreach (var b in bytes) + { + AppendPercentEncoded(sb, b); + } + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void AppendPercentEncoded(StringBuilder sb, byte value) + { + sb.Append('%'); + sb.Append((char)UpperHexChars[value >> 4]); + sb.Append((char)UpperHexChars[value & 0xF]); + } +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsUnreserved(char c) + { +#if NET8_0_OR_GREATER + return UnreservedChars.Contains(c); +#elif NET7_0_OR_GREATER + return char.IsAsciiLetterOrDigit(c) || c is '-' or '_' or '.' or '~'; +#else + return (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') + || c == '-' + || c == '_' + || c == '.' + || c == '~'; +#endif + } + + /// + /// Fluent builder for constructing query strings with support for simple parameters and deep object notation. + /// + public sealed class Builder + { + private readonly List> _params; + + /// + /// Initializes a new instance with default capacity. + /// + public Builder() + { + _params = new List>(); + } + + /// + /// Initializes a new instance with the specified initial capacity. + /// + public Builder(int capacity) + { + _params = new List>(capacity); + } + + /// + /// Adds a simple parameter. For collections, adds multiple key-value pairs (one per element). + /// + public Builder Add(string key, object? value) + { + if (value is null) + { + return this; + } + + // Handle string separately since it implements IEnumerable + if (value is string stringValue) + { + _params.Add(new KeyValuePair(key, stringValue)); + return this; + } + + // Handle collections (arrays, lists, etc.) - add each element as a separate key-value pair + if ( + value + is global::System.Collections.IEnumerable enumerable + and not global::System.Collections.IDictionary + ) + { + foreach (var item in enumerable) + { + if (item is not null) + { + _params.Add( + new KeyValuePair( + key, + ValueConvert.ToQueryStringValue(item) + ) + ); + } + } + return this; + } + + // Handle scalar values + _params.Add( + new KeyValuePair(key, ValueConvert.ToQueryStringValue(value)) + ); + return this; + } + + /// + /// Sets a parameter, removing any existing parameters with the same key before adding the new value. + /// For collections, removes all existing parameters with the key, then adds multiple key-value pairs (one per element). + /// This allows overriding parameters set earlier in the builder. + /// + public Builder Set(string key, object? value) + { + // Remove all existing parameters with this key + _params.RemoveAll(kv => kv.Key == key); + + // Add the new value(s) + return Add(key, value); + } + + /// + /// Merges additional query parameters with override semantics. + /// Groups parameters by key and calls Set() once per unique key. + /// This ensures that parameters with the same key are properly merged: + /// - If a key appears once, it's added as a single value + /// - If a key appears multiple times, all values are added as an array + /// - All parameters override any existing parameters with the same key + /// + public Builder MergeAdditional( + global::System.Collections.Generic.IEnumerable>? additionalParameters + ) + { + if (additionalParameters is null) + { + return this; + } + + // Group by key to handle multiple values for the same key correctly + var grouped = additionalParameters + .GroupBy(kv => kv.Key) + .Select(g => new global::System.Collections.Generic.KeyValuePair( + g.Key, + g.Count() == 1 ? (object)g.First().Value : g.Select(kv => kv.Value).ToArray() + )); + + foreach (var param in grouped) + { + Set(param.Key, param.Value); + } + + return this; + } + + /// + /// Adds a complex object using deep object notation with a prefix. + /// Deep object notation nests properties with brackets: prefix[key][nested]=value + /// + public Builder AddDeepObject(string prefix, object? value) + { + if (value is not null) + { + _params.AddRange(QueryStringConverter.ToDeepObject(prefix, value)); + } + return this; + } + + /// + /// Adds a complex object using exploded form notation with an optional prefix. + /// Exploded form flattens properties: prefix[key]=value (no deep nesting). + /// + public Builder AddExploded(string prefix, object? value) + { + if (value is not null) + { + _params.AddRange(QueryStringConverter.ToExplodedForm(prefix, value)); + } + return this; + } + + /// + /// Builds the final query string. + /// + public string Build() + { + return QueryStringBuilder.Build(_params); + } + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/QueryStringConverter.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/QueryStringConverter.cs new file mode 100644 index 000000000000..88bb974ff60c --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/QueryStringConverter.cs @@ -0,0 +1,259 @@ +using global::System.Text.Json; + +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// Converts an object into a query string collection. +/// +internal static class QueryStringConverter +{ + /// + /// Converts an object into a query string collection using Deep Object notation with a prefix. + /// + /// The prefix to prepend to all keys (e.g., "session_settings"). Pass empty string for no prefix. + /// Object to form URL-encode. Can be an object, array of objects, or dictionary. + /// Throws when passing in a string or primitive value. + /// A collection of key value pairs. The keys and values are not URL encoded. + internal static IEnumerable> ToDeepObject( + string prefix, + object value + ) + { + var queryCollection = new List>(); + var json = JsonUtils.SerializeToElement(value); + JsonToDeepObject(json, prefix, queryCollection); + return queryCollection; + } + + /// + /// Converts an object into a query string collection using Deep Object notation. + /// + /// Object to form URL-encode. Can be an object, array of objects, or dictionary. + /// Throws when passing in a string or primitive value. + /// A collection of key value pairs. The keys and values are not URL encoded. + internal static IEnumerable> ToDeepObject(object value) + { + return ToDeepObject("", value); + } + + /// + /// Converts an object into a query string collection using Exploded Form notation with a prefix. + /// + /// The prefix to prepend to all keys. Pass empty string for no prefix. + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + /// A collection of key value pairs. The keys and values are not URL encoded. + internal static IEnumerable> ToExplodedForm( + string prefix, + object value + ) + { + var queryCollection = new List>(); + var json = JsonUtils.SerializeToElement(value); + AssertRootJson(json); + JsonToFormExploded(json, prefix, queryCollection); + return queryCollection; + } + + /// + /// Converts an object into a query string collection using Exploded Form notation. + /// + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + /// A collection of key value pairs. The keys and values are not URL encoded. + internal static IEnumerable> ToExplodedForm(object value) + { + return ToExplodedForm("", value); + } + + /// + /// Converts an object into a query string collection using Form notation without exploding parameters. + /// + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + /// A collection of key value pairs. The keys and values are not URL encoded. + internal static IEnumerable> ToForm(object value) + { + var queryCollection = new List>(); + var json = JsonUtils.SerializeToElement(value); + AssertRootJson(json); + JsonToForm(json, "", queryCollection); + return queryCollection; + } + + private static void AssertRootJson(JsonElement json) + { + switch (json.ValueKind) + { + case JsonValueKind.Object: + break; + case JsonValueKind.Array: + case JsonValueKind.Undefined: + case JsonValueKind.String: + case JsonValueKind.Number: + case JsonValueKind.True: + case JsonValueKind.False: + case JsonValueKind.Null: + default: + throw new global::System.Exception( + $"Only objects can be converted to query string collections. Given type is {json.ValueKind}." + ); + } + } + + private static void JsonToForm( + JsonElement element, + string prefix, + List> parameters + ) + { + switch (element.ValueKind) + { + case JsonValueKind.Object: + foreach (var property in element.EnumerateObject()) + { + var newPrefix = string.IsNullOrEmpty(prefix) + ? property.Name + : $"{prefix}[{property.Name}]"; + + JsonToForm(property.Value, newPrefix, parameters); + } + break; + case JsonValueKind.Array: + var arrayValues = element.EnumerateArray().Select(ValueToString).ToArray(); + parameters.Add( + new KeyValuePair(prefix, string.Join(",", arrayValues)) + ); + break; + case JsonValueKind.Null: + break; + case JsonValueKind.Undefined: + case JsonValueKind.String: + case JsonValueKind.Number: + case JsonValueKind.True: + case JsonValueKind.False: + default: + parameters.Add(new KeyValuePair(prefix, ValueToString(element))); + break; + } + } + + private static void JsonToFormExploded( + JsonElement element, + string prefix, + List> parameters + ) + { + switch (element.ValueKind) + { + case JsonValueKind.Object: + foreach (var property in element.EnumerateObject()) + { + var newPrefix = string.IsNullOrEmpty(prefix) + ? property.Name + : $"{prefix}[{property.Name}]"; + + JsonToFormExploded(property.Value, newPrefix, parameters); + } + + break; + case JsonValueKind.Array: + foreach (var item in element.EnumerateArray()) + { + if ( + item.ValueKind != JsonValueKind.Object + && item.ValueKind != JsonValueKind.Array + ) + { + parameters.Add( + new KeyValuePair(prefix, ValueToString(item)) + ); + } + else + { + JsonToFormExploded(item, prefix, parameters); + } + } + + break; + case JsonValueKind.Null: + break; + case JsonValueKind.Undefined: + case JsonValueKind.String: + case JsonValueKind.Number: + case JsonValueKind.True: + case JsonValueKind.False: + default: + parameters.Add(new KeyValuePair(prefix, ValueToString(element))); + break; + } + } + + private static void JsonToDeepObject( + JsonElement element, + string prefix, + List> parameters + ) + { + switch (element.ValueKind) + { + case JsonValueKind.Object: + foreach (var property in element.EnumerateObject()) + { + var newPrefix = string.IsNullOrEmpty(prefix) + ? property.Name + : $"{prefix}[{property.Name}]"; + + JsonToDeepObject(property.Value, newPrefix, parameters); + } + + break; + case JsonValueKind.Array: + var index = 0; + foreach (var item in element.EnumerateArray()) + { + var newPrefix = $"{prefix}[{index++}]"; + + if ( + item.ValueKind != JsonValueKind.Object + && item.ValueKind != JsonValueKind.Array + ) + { + parameters.Add( + new KeyValuePair(newPrefix, ValueToString(item)) + ); + } + else + { + JsonToDeepObject(item, newPrefix, parameters); + } + } + + break; + case JsonValueKind.Null: + case JsonValueKind.Undefined: + // Skip null and undefined values - don't add parameters for them + break; + case JsonValueKind.String: + case JsonValueKind.Number: + case JsonValueKind.True: + case JsonValueKind.False: + default: + parameters.Add(new KeyValuePair(prefix, ValueToString(element))); + break; + } + } + + private static string ValueToString(JsonElement element) + { + return element.ValueKind switch + { + JsonValueKind.String => element.GetString() ?? "", + JsonValueKind.Number => element.GetRawText(), + JsonValueKind.True => "true", + JsonValueKind.False => "false", + JsonValueKind.Null => "", + _ => element.GetRawText(), + }; + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/RawClient.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/RawClient.cs new file mode 100644 index 000000000000..c70e6842497a --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/RawClient.cs @@ -0,0 +1,344 @@ +using global::System.Net.Http; +using global::System.Net.Http.Headers; +using global::System.Text; +using SystemTask = global::System.Threading.Tasks.Task; + +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// Utility class for making raw HTTP requests to the API. +/// +internal partial class RawClient(ClientOptions clientOptions) +{ + private const int MaxRetryDelayMs = 60000; + private const double JitterFactor = 0.2; +#if NET6_0_OR_GREATER + // Use Random.Shared for thread-safe random number generation on .NET 6+ +#else + private static readonly object JitterLock = new(); + private static readonly Random JitterRandom = new(); +#endif + internal int BaseRetryDelay { get; set; } = 1000; + + /// + /// The client options applied on every request. + /// + internal readonly ClientOptions Options = clientOptions; + + internal async global::System.Threading.Tasks.Task SendRequestAsync( + global::Seed.CsharpNamespaceConflict.Core.BaseRequest request, + CancellationToken cancellationToken = default + ) + { + // Apply the request timeout. + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var timeout = request.Options?.Timeout ?? Options.Timeout; + cts.CancelAfter(timeout); + + var httpRequest = await CreateHttpRequestAsync(request).ConfigureAwait(false); + // Send the request. + return await SendWithRetriesAsync(httpRequest, request.Options, cts.Token) + .ConfigureAwait(false); + } + + internal async global::System.Threading.Tasks.Task SendRequestAsync( + HttpRequestMessage request, + IRequestOptions? options, + CancellationToken cancellationToken = default + ) + { + // Apply the request timeout. + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var timeout = options?.Timeout ?? Options.Timeout; + cts.CancelAfter(timeout); + + // Send the request. + return await SendWithRetriesAsync(request, options, cts.Token).ConfigureAwait(false); + } + + private static async global::System.Threading.Tasks.Task CloneRequestAsync( + HttpRequestMessage request + ) + { + var clonedRequest = new HttpRequestMessage(request.Method, request.RequestUri); + clonedRequest.Version = request.Version; + + if (request.Content != null) + { + switch (request.Content) + { + case MultipartContent oldMultipartFormContent: + var originalBoundary = + oldMultipartFormContent + .Headers.ContentType?.Parameters.First(p => + p.Name.Equals("boundary", StringComparison.OrdinalIgnoreCase) + ) + .Value?.Trim('"') + ?? Guid.NewGuid().ToString(); + var newMultipartContent = oldMultipartFormContent switch + { + MultipartFormDataContent => new MultipartFormDataContent(originalBoundary), + _ => new MultipartContent(), + }; + foreach (var content in oldMultipartFormContent) + { + var ms = new MemoryStream(); + await content.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + var newPart = new StreamContent(ms); + foreach (var header in content.Headers) + { + newPart.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + newMultipartContent.Add(newPart); + } + + clonedRequest.Content = newMultipartContent; + break; + default: + var bodyStream = new MemoryStream(); + await request.Content.CopyToAsync(bodyStream).ConfigureAwait(false); + bodyStream.Position = 0; + var clonedContent = new StreamContent(bodyStream); + foreach (var header in request.Content.Headers) + { + clonedContent.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + clonedRequest.Content = clonedContent; + break; + } + } + + foreach (var header in request.Headers) + { + clonedRequest.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + return clonedRequest; + } + + /// + /// Sends the request with retries, unless the request content is not retryable, + /// such as stream requests and multipart form data with stream content. + /// + private async global::System.Threading.Tasks.Task SendWithRetriesAsync( + HttpRequestMessage request, + IRequestOptions? options, + CancellationToken cancellationToken + ) + { + var httpClient = options?.HttpClient ?? Options.HttpClient; + var maxRetries = options?.MaxRetries ?? Options.MaxRetries; + var response = await httpClient + .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken) + .ConfigureAwait(false); + var isRetryableContent = IsRetryableContent(request); + + if (!isRetryableContent) + { + return new global::Seed.CsharpNamespaceConflict.Core.ApiResponse + { + StatusCode = (int)response.StatusCode, + Raw = response, + }; + } + + for (var i = 0; i < maxRetries; i++) + { + if (!ShouldRetry(response)) + { + break; + } + + var delayMs = GetRetryDelayFromHeaders(response, i); + await SystemTask.Delay(delayMs, cancellationToken).ConfigureAwait(false); + using var retryRequest = await CloneRequestAsync(request).ConfigureAwait(false); + response = await httpClient + .SendAsync( + retryRequest, + HttpCompletionOption.ResponseHeadersRead, + cancellationToken + ) + .ConfigureAwait(false); + } + + return new global::Seed.CsharpNamespaceConflict.Core.ApiResponse + { + StatusCode = (int)response.StatusCode, + Raw = response, + }; + } + + private static bool ShouldRetry(HttpResponseMessage response) + { + var statusCode = (int)response.StatusCode; + return statusCode is 408 or 429 or >= 500; + } + + private static int AddPositiveJitter(int delayMs) + { +#if NET6_0_OR_GREATER + var random = Random.Shared.NextDouble(); +#else + double random; + lock (JitterLock) + { + random = JitterRandom.NextDouble(); + } +#endif + var jitterMultiplier = 1 + random * JitterFactor; + return (int)(delayMs * jitterMultiplier); + } + + private static int AddSymmetricJitter(int delayMs) + { +#if NET6_0_OR_GREATER + var random = Random.Shared.NextDouble(); +#else + double random; + lock (JitterLock) + { + random = JitterRandom.NextDouble(); + } +#endif + var jitterMultiplier = 1 + (random - 0.5) * JitterFactor; + return (int)(delayMs * jitterMultiplier); + } + + private int GetRetryDelayFromHeaders(HttpResponseMessage response, int retryAttempt) + { + if (response.Headers.TryGetValues("Retry-After", out var retryAfterValues)) + { + var retryAfter = retryAfterValues.FirstOrDefault(); + if (!string.IsNullOrEmpty(retryAfter)) + { + if (int.TryParse(retryAfter, out var retryAfterSeconds) && retryAfterSeconds > 0) + { + return Math.Min(retryAfterSeconds * 1000, MaxRetryDelayMs); + } + + if (DateTimeOffset.TryParse(retryAfter, out var retryAfterDate)) + { + var delay = (int)(retryAfterDate - DateTimeOffset.UtcNow).TotalMilliseconds; + if (delay > 0) + { + return Math.Min(delay, MaxRetryDelayMs); + } + } + } + } + + if (response.Headers.TryGetValues("X-RateLimit-Reset", out var rateLimitResetValues)) + { + var rateLimitReset = rateLimitResetValues.FirstOrDefault(); + if ( + !string.IsNullOrEmpty(rateLimitReset) + && long.TryParse(rateLimitReset, out var resetTime) + ) + { + var resetDateTime = DateTimeOffset.FromUnixTimeSeconds(resetTime); + var delay = (int)(resetDateTime - DateTimeOffset.UtcNow).TotalMilliseconds; + if (delay > 0) + { + return AddPositiveJitter(Math.Min(delay, MaxRetryDelayMs)); + } + } + } + + var exponentialDelay = Math.Min(BaseRetryDelay * (1 << retryAttempt), MaxRetryDelayMs); + return AddSymmetricJitter(exponentialDelay); + } + + private static bool IsRetryableContent(HttpRequestMessage request) + { + return request.Content switch + { + IIsRetryableContent c => c.IsRetryable, + StreamContent => false, + MultipartContent content => !content.Any(c => c is StreamContent), + _ => true, + }; + } + + internal async global::System.Threading.Tasks.Task CreateHttpRequestAsync( + global::Seed.CsharpNamespaceConflict.Core.BaseRequest request + ) + { + var url = BuildUrl(request); + var httpRequest = new HttpRequestMessage(request.Method, url); + httpRequest.Content = request.CreateContent(); + SetHeaders(httpRequest, request.Headers); + + return httpRequest; + } + + private string BuildUrl(global::Seed.CsharpNamespaceConflict.Core.BaseRequest request) + { + var baseUrl = request.Options?.BaseUrl ?? request.BaseUrl ?? Options.BaseUrl; + + var trimmedBaseUrl = baseUrl.TrimEnd('/'); + var trimmedBasePath = request.Path.TrimStart('/'); + var url = $"{trimmedBaseUrl}/{trimmedBasePath}"; + + // Append query string if present + if (!string.IsNullOrEmpty(request.QueryString)) + { + return url + request.QueryString; + } + + return url; + } + + private void SetHeaders(HttpRequestMessage httpRequest, Dictionary? headers) + { + if (headers is null) + { + return; + } + + foreach (var kv in headers) + { + if (kv.Value is null) + { + continue; + } + + httpRequest.Headers.TryAddWithoutValidation(kv.Key, kv.Value); + } + } + + private static (Encoding encoding, string? charset, string mediaType) ParseContentTypeOrDefault( + string? contentType, + Encoding encodingFallback, + string mediaTypeFallback + ) + { + var encoding = encodingFallback; + var mediaType = mediaTypeFallback; + string? charset = null; + if (string.IsNullOrEmpty(contentType)) + { + return (encoding, charset, mediaType); + } + + if (!MediaTypeHeaderValue.TryParse(contentType, out var mediaTypeHeaderValue)) + { + return (encoding, charset, mediaType); + } + + if (!string.IsNullOrEmpty(mediaTypeHeaderValue.CharSet)) + { + charset = mediaTypeHeaderValue.CharSet; + encoding = Encoding.GetEncoding(mediaTypeHeaderValue.CharSet); + } + + if (!string.IsNullOrEmpty(mediaTypeHeaderValue.MediaType)) + { + mediaType = mediaTypeHeaderValue.MediaType; + } + + return (encoding, charset, mediaType); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/RawResponse.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/RawResponse.cs new file mode 100644 index 000000000000..97304ddc1150 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/RawResponse.cs @@ -0,0 +1,24 @@ +using global::System.Net; + +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// Contains HTTP response metadata including status code, URL, and headers. +/// +public record RawResponse +{ + /// + /// The HTTP status code of the response. + /// + public required HttpStatusCode StatusCode { get; init; } + + /// + /// The request URL that generated this response. + /// + public required Uri Url { get; init; } + + /// + /// The HTTP response headers. + /// + public required Core.ResponseHeaders Headers { get; init; } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/ResponseHeaders.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/ResponseHeaders.cs new file mode 100644 index 000000000000..e6a28db48e9c --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/ResponseHeaders.cs @@ -0,0 +1,108 @@ +using global::System.Collections; +using global::System.Net.Http.Headers; + +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// Represents HTTP response headers with case-insensitive lookup. +/// +public readonly struct ResponseHeaders : IEnumerable +{ + private readonly HttpResponseHeaders? _headers; + private readonly HttpContentHeaders? _contentHeaders; + + private ResponseHeaders(HttpResponseHeaders headers, HttpContentHeaders? contentHeaders) + { + _headers = headers; + _contentHeaders = contentHeaders; + } + + /// + /// Gets the Content-Type header value, if present. + /// + public string? ContentType => _contentHeaders?.ContentType?.ToString(); + + /// + /// Gets the Content-Length header value, if present. + /// + public long? ContentLength => _contentHeaders?.ContentLength; + + /// + /// Creates a ResponseHeaders instance from an HttpResponseMessage. + /// + public static ResponseHeaders FromHttpResponseMessage(HttpResponseMessage response) + { + return new ResponseHeaders(response.Headers, response.Content?.Headers); + } + + /// + /// Tries to get a single header value. Returns the first value if multiple values exist. + /// + public bool TryGetValue(string name, out string? value) + { + if (TryGetValues(name, out var values) && values is not null) + { + value = values.FirstOrDefault(); + return true; + } + + value = null; + return false; + } + + /// + /// Tries to get all values for a header. + /// + public bool TryGetValues(string name, out IEnumerable? values) + { + if (_headers?.TryGetValues(name, out values) == true) + { + return true; + } + + if (_contentHeaders?.TryGetValues(name, out values) == true) + { + return true; + } + + values = null; + return false; + } + + /// + /// Checks if the headers contain a specific header name. + /// + public bool Contains(string name) + { + return _headers?.Contains(name) == true || _contentHeaders?.Contains(name) == true; + } + + /// + /// Gets an enumerator for all headers. + /// + public IEnumerator GetEnumerator() + { + if (_headers is not null) + { + foreach (var header in _headers) + { + yield return new HttpHeader(header.Key, string.Join(", ", header.Value)); + } + } + + if (_contentHeaders is not null) + { + foreach (var header in _contentHeaders) + { + yield return new HttpHeader(header.Key, string.Join(", ", header.Value)); + } + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} + +/// +/// Represents a single HTTP header. +/// +public readonly record struct HttpHeader(string Name, string Value); diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/StreamRequest.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/StreamRequest.cs new file mode 100644 index 000000000000..214418bf7873 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/StreamRequest.cs @@ -0,0 +1,29 @@ +using global::System.Net.Http; +using global::System.Net.Http.Headers; + +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// The request object to be sent for streaming uploads. +/// +internal record StreamRequest : BaseRequest +{ + internal Stream? Body { get; init; } + + internal override HttpContent? CreateContent() + { + if (Body is null) + { + return null; + } + + var content = new StreamContent(Body) + { + Headers = + { + ContentType = MediaTypeHeaderValue.Parse(ContentType ?? "application/octet-stream"), + }, + }; + return content; + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/StringEnum.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/StringEnum.cs new file mode 100644 index 000000000000..f45da7c83a5c --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/StringEnum.cs @@ -0,0 +1,6 @@ +namespace Seed.CsharpNamespaceConflict.Core; + +public interface IStringEnum : IEquatable +{ + public string Value { get; } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/StringEnumExtensions.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/StringEnumExtensions.cs new file mode 100644 index 000000000000..0abbbfbcf465 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/StringEnumExtensions.cs @@ -0,0 +1,6 @@ +namespace Seed.CsharpNamespaceConflict.Core; + +internal static class StringEnumExtensions +{ + public static string Stringify(this IStringEnum stringEnum) => stringEnum.Value; +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/ValueConvert.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/ValueConvert.cs new file mode 100644 index 000000000000..818fba57a2cd --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Core/ValueConvert.cs @@ -0,0 +1,114 @@ +using global::System.Globalization; + +namespace Seed.CsharpNamespaceConflict.Core; + +/// +/// Convert values to string for path and query parameters. +/// +public static class ValueConvert +{ + internal static string ToPathParameterString(T value) => ToString(value); + + internal static string ToPathParameterString(bool v) => ToString(v); + + internal static string ToPathParameterString(int v) => ToString(v); + + internal static string ToPathParameterString(long v) => ToString(v); + + internal static string ToPathParameterString(float v) => ToString(v); + + internal static string ToPathParameterString(double v) => ToString(v); + + internal static string ToPathParameterString(decimal v) => ToString(v); + + internal static string ToPathParameterString(short v) => ToString(v); + + internal static string ToPathParameterString(ushort v) => ToString(v); + + internal static string ToPathParameterString(uint v) => ToString(v); + + internal static string ToPathParameterString(ulong v) => ToString(v); + + internal static string ToPathParameterString(string v) => ToString(v); + + internal static string ToPathParameterString(char v) => ToString(v); + + internal static string ToPathParameterString(Guid v) => ToString(v); + + internal static string ToQueryStringValue(T value) => value is null ? "" : ToString(value); + + internal static string ToQueryStringValue(bool v) => ToString(v); + + internal static string ToQueryStringValue(int v) => ToString(v); + + internal static string ToQueryStringValue(long v) => ToString(v); + + internal static string ToQueryStringValue(float v) => ToString(v); + + internal static string ToQueryStringValue(double v) => ToString(v); + + internal static string ToQueryStringValue(decimal v) => ToString(v); + + internal static string ToQueryStringValue(short v) => ToString(v); + + internal static string ToQueryStringValue(ushort v) => ToString(v); + + internal static string ToQueryStringValue(uint v) => ToString(v); + + internal static string ToQueryStringValue(ulong v) => ToString(v); + + internal static string ToQueryStringValue(string v) => v is null ? "" : v; + + internal static string ToQueryStringValue(char v) => ToString(v); + + internal static string ToQueryStringValue(Guid v) => ToString(v); + + internal static string ToString(T value) + { + return value switch + { + null => "null", + string str => str, + true => "true", + false => "false", + int i => ToString(i), + long l => ToString(l), + float f => ToString(f), + double d => ToString(d), + decimal dec => ToString(dec), + short s => ToString(s), + ushort u => ToString(u), + uint u => ToString(u), + ulong u => ToString(u), + char c => ToString(c), + Guid guid => ToString(guid), + _ => JsonUtils.SerializeRelaxedEscaping(value, value.GetType()).Trim('"'), + }; + } + + internal static string ToString(bool v) => v ? "true" : "false"; + + internal static string ToString(int v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(long v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(float v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(double v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(decimal v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(short v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(ushort v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(uint v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(ulong v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(char v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(string v) => v; + + internal static string ToString(Guid v) => v.ToString("D"); +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/ISeed.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/ISeed.cs new file mode 100644 index 000000000000..cd8578255f04 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/ISeed.cs @@ -0,0 +1,6 @@ +namespace Seed.CsharpNamespaceConflict; + +public partial interface ISeed +{ + public ITasktestClient Tasktest { get; } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Seed.CsharpNamespaceConflict.Custom.props b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Seed.CsharpNamespaceConflict.Custom.props new file mode 100644 index 000000000000..17a84cada530 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Seed.CsharpNamespaceConflict.Custom.props @@ -0,0 +1,20 @@ + + + + diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Seed.CsharpNamespaceConflict.csproj b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Seed.CsharpNamespaceConflict.csproj new file mode 100644 index 000000000000..18b7a504a0e2 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Seed.CsharpNamespaceConflict.csproj @@ -0,0 +1,61 @@ + + + net462;net8.0;netstandard2.0 + enable + 12 + enable + 0.0.1 + $(Version) + $(Version) + README.md + https://github.com/csharp-namespace-conflict/fern + true + + + + false + + + $(DefineConstants);USE_PORTABLE_DATE_ONLY + true + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + <_Parameter1>Seed.CsharpNamespaceConflict.Test + + + + + diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Seed.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Seed.cs new file mode 100644 index 000000000000..7012fd217ad7 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Seed.cs @@ -0,0 +1,33 @@ +using global::Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict; + +public partial class Seed : ISeed +{ + private readonly RawClient _client; + + public Seed(ClientOptions? clientOptions = null) + { + clientOptions ??= new ClientOptions(); + var platformHeaders = new Headers( + new Dictionary() + { + { "X-Fern-Language", "C#" }, + { "X-Fern-SDK-Name", "Seed.CsharpNamespaceConflict" }, + { "X-Fern-SDK-Version", Version.Current }, + { "User-Agent", "Ferncsharp-namespace-conflict/0.0.1" }, + } + ); + foreach (var header in platformHeaders) + { + if (!clientOptions.Headers.ContainsKey(header.Key)) + { + clientOptions.Headers[header.Key] = header.Value; + } + } + _client = new RawClient(clientOptions); + Tasktest = new TasktestClient(_client); + } + + public ITasktestClient Tasktest { get; } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Tasktest/ITasktestClient.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Tasktest/ITasktestClient.cs new file mode 100644 index 000000000000..f21f50370a8b --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Tasktest/ITasktestClient.cs @@ -0,0 +1,9 @@ +namespace Seed.CsharpNamespaceConflict; + +public partial interface ITasktestClient +{ + System.Threading.Tasks.Task HelloAsync( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ); +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Tasktest/TasktestClient.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Tasktest/TasktestClient.cs new file mode 100644 index 000000000000..91a03a638624 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Tasktest/TasktestClient.cs @@ -0,0 +1,55 @@ +using global::Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict; + +public partial class TasktestClient : ITasktestClient +{ + private readonly RawClient _client; + + internal TasktestClient(RawClient client) + { + _client = client; + } + + /// + /// await client.Tasktest.HelloAsync(); + /// + public async System.Threading.Tasks.Task HelloAsync( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + var _headers = await new global::Seed.CsharpNamespaceConflict.Core.HeadersBuilder.Builder() + .Add(_client.Options.Headers) + .Add(_client.Options.AdditionalHeaders) + .Add(options?.AdditionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + var response = await _client + .SendRequestAsync( + new JsonRequest + { + Method = HttpMethod.Get, + Path = "hello", + Headers = _headers, + Options = options, + }, + cancellationToken + ) + .ConfigureAwait(false); + if (response.StatusCode is >= 200 and < 400) + { + return; + } + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + throw new SeedApiException( + $"Error with status code {response.StatusCode}", + response.StatusCode, + responseBody + ); + } + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Tasktest/Types/Task.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Tasktest/Types/Task.cs new file mode 100644 index 000000000000..1f1e76ad4681 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/Seed.CsharpNamespaceConflict/Tasktest/Types/Task.cs @@ -0,0 +1,28 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using global::Seed.CsharpNamespaceConflict.Core; + +namespace Seed.CsharpNamespaceConflict; + +[Serializable] +public record Task : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/SeedApi.DynamicSnippets/Example0.cs b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/SeedApi.DynamicSnippets/Example0.cs new file mode 100644 index 000000000000..2a44a7c2c8e6 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/SeedApi.DynamicSnippets/Example0.cs @@ -0,0 +1,17 @@ +using global::Seed.CsharpNamespaceConflict; + +namespace Usage; + +public class Example0 +{ + public async global::System.Threading.Tasks.Task Do() { + var client = new global::Seed.CsharpNamespaceConflict.Seed( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.Tasktest.HelloAsync(); + } + +} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj new file mode 100644 index 000000000000..3417db2e58e2 --- /dev/null +++ b/seed/csharp-sdk/csharp-namespace-conflict/client-class-name-matches-namespace-root/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + 12 + enable + enable + + + + + + \ No newline at end of file diff --git a/seed/csharp-sdk/seed.yml b/seed/csharp-sdk/seed.yml index d4f3f5fdd0ca..0814f800dcdd 100644 --- a/seed/csharp-sdk/seed.yml +++ b/seed/csharp-sdk/seed.yml @@ -264,6 +264,12 @@ fixtures: explicit-namespaces: true experimental-fully-qualified-namespaces: true outputFolder: namespace-client-collision + csharp-namespace-conflict: + - customConfig: + namespace: Seed.CsharpNamespaceConflict + client-class-name: Seed + experimental-fully-qualified-namespaces: true + outputFolder: client-class-name-matches-namespace-root csharp-system-collision: - customConfig: client-class-name: System @@ -317,7 +323,6 @@ fixtures: redact-response-body-on-error: true outputFolder: redact-response-body-on-error allowedFailures: - - csharp-namespace-collision:namespace-client-collision - enum:forward-compatible-enums - enum:plain-enums - examples:no-custom-config From db6d68602ff01d31957fbef1e8cc4dc762013c80 Mon Sep 17 00:00:00 2001 From: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Date: Tue, 17 Mar 2026 15:21:45 -0400 Subject: [PATCH 05/11] chore(changelog): Improve TypeScript SDK 3.56.0 changelog with naming config samples (#13644) Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- generators/typescript/sdk/versions.yml | 41 ++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/generators/typescript/sdk/versions.yml b/generators/typescript/sdk/versions.yml index 7198fb0a90cc..57c6ba59a081 100644 --- a/generators/typescript/sdk/versions.yml +++ b/generators/typescript/sdk/versions.yml @@ -112,11 +112,42 @@ - version: 3.56.0 changelogEntry: - summary: | - Add a `naming` configuration object that allows users to independently control the - namespace export and PascalCase class/type names (client, error, timeout error, - environment, environment URLs, version). When individual overrides are not provided, - they are derived from the namespace value using the existing `${PascalCase(namespace)}Suffix` - pattern. The existing `namespaceExport` config continues to work for backwards compatibility. + Add a `naming` configuration object that gives you fine-grained control over the + exported namespace and generated class names (client, error, timeout error, + environment, environment URLs, version). + + You can use it as a simple string shorthand to set the namespace: + ```yaml + # generators.yml + groups: + ts-sdk: + generators: + - name: fernapi/fern-typescript-node-sdk + config: + naming: acme + ``` + + Or as a full object to override individual names: + ```yaml + # generators.yml + groups: + ts-sdk: + generators: + - name: fernapi/fern-typescript-node-sdk + config: + naming: + namespace: acme + client: AcmeApiClient + error: AcmeApiError + environment: AcmeRegion + ``` + + When individual overrides are not provided, they are derived from the namespace + using the `PascalCase(namespace) + Suffix` pattern (e.g. `namespace: acme` produces + `AcmeClient`, `AcmeError`, `AcmeTimeoutError`, `AcmeEnvironment`, `AcmeEnvironmentUrls`, + and `AcmeVersion` by default). + + The existing `namespaceExport` config continues to work for backwards compatibility. type: feat createdAt: "2026-03-13" irVersion: 65 From 63d295705e6c2630946bfd8c960a31d95e6f6ba2 Mon Sep 17 00:00:00 2001 From: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Date: Tue, 17 Mar 2026 15:32:46 -0400 Subject: [PATCH 06/11] feat(cli): add `x-fern-sdk-method-name` support for AsyncAPI v2 publish/subscribe operations (#13641) Co-authored-by: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../src/2.x/channel/ChannelConverter2_X.ts | 12 +- .../src/extensions/x-fern-sdk-method-name.ts | 29 + .../src/asyncapi/fernExtensions.ts | 20 +- .../src/asyncapi/v2/parseAsyncAPIV2.ts | 10 +- .../asyncapi-v2-x-fern-sdk-method-name.json | 168 +++ .../asyncapi-v2-x-fern-sdk-method-name.json | 125 ++ .../asyncapi.yml | 32 + .../fern/fern.config.json | 4 + .../fern/generators.yml | 4 + .../asyncapi-v2-x-fern-sdk-method-name.json | 1114 ++++++++++++++++ .../asyncapi-v2-x-fern-sdk-method-name.json | 1123 +++++++++++++++++ .../asyncapi.yml | 32 + .../fern/fern.config.json | 4 + .../fern/generators.yml | 4 + packages/cli/cli/versions.yml | 12 +- .../.fern/metadata.json | 8 - .../.github/workflows/ci.yml | 52 - .../src/SeedApi.DynamicSnippets/Example0.cs | 17 - .../SeedApi.DynamicSnippets.csproj | 13 - .../Core/HeadersBuilderTests.cs | 326 ----- .../Core/Json/AdditionalPropertiesTests.cs | 365 ------ .../Core/Json/DateOnlyJsonTests.cs | 76 -- .../Core/Json/DateTimeJsonTests.cs | 110 -- .../Core/Json/JsonAccessAttributeTests.cs | 160 --- .../Core/QueryStringBuilderTests.cs | 560 -------- .../Core/QueryStringConverterTests.cs | 158 --- .../Core/RawClientTests/MultipartFormTests.cs | 1121 ---------------- .../RawClientTests/QueryParameterTests.cs | 108 -- .../Core/RawClientTests/RetriesTests.cs | 406 ------ .../Core/WithRawResponseTests.cs | 269 ---- ...dCsharpNamespaceConflict.Test.Custom.props | 6 - .../SeedCsharpNamespaceConflict.Test.csproj | 39 - .../TestClient.cs | 6 - .../Unit/MockServer/BaseMockServerTest.cs | 37 - .../Unit/MockServer/Tasktest/HelloTest.cs | 19 - .../Utils/AdditionalPropertiesComparer.cs | 126 -- .../Utils/JsonAssert.cs | 19 - .../Utils/JsonElementComparer.cs | 236 ---- .../Utils/NUnitExtensions.cs | 30 - .../Utils/OneOfComparer.cs | 86 -- .../Utils/OptionalComparer.cs | 104 -- .../Utils/ReadOnlyMemoryComparer.cs | 87 -- .../A/Aa/Types/A.cs | 26 - .../A/Aa/Types/B.cs | 26 - .../A/Aa/Types/SubTestType.cs | 32 - .../B/Types/TestType.cs | 32 - .../Core/ApiResponse.cs | 13 - .../Core/BaseRequest.cs | 67 - .../Core/CollectionItemSerializer.cs | 89 -- .../Core/Constants.cs | 7 - .../Core/DateOnlyConverter.cs | 747 ----------- .../Core/DateTimeSerializer.cs | 22 - .../Core/EmptyRequest.cs | 11 - .../Core/EncodingCache.cs | 11 - .../Core/Extensions.cs | 55 - .../Core/FormUrlEncoder.cs | 33 - .../Core/HeaderValue.cs | 52 - .../Core/Headers.cs | 28 - .../Core/HeadersBuilder.cs | 197 --- .../Core/HttpContentExtensions.cs | 20 - .../Core/HttpMethodExtensions.cs | 8 - .../Core/IIsRetryableContent.cs | 6 - .../Core/IRequestOptions.cs | 83 -- .../Core/JsonAccessAttribute.cs | 15 - .../Core/JsonConfiguration.cs | 275 ---- .../Core/JsonRequest.cs | 36 - .../Core/MultipartFormRequest.cs | 294 ----- .../Core/NullableAttribute.cs | 18 - .../Core/OneOfSerializer.cs | 145 --- .../Core/Optional.cs | 474 ------- .../Core/OptionalAttribute.cs | 17 - .../Core/Public/AdditionalProperties.cs | 353 ------ .../Core/Public/ClientOptions.cs | 84 -- .../Core/Public/FileParameter.cs | 63 - .../Core/Public/RawResponse.cs | 24 - .../Core/Public/RequestOptions.cs | 86 -- ...SeedCsharpNamespaceConflictApiException.cs | 22 - .../SeedCsharpNamespaceConflictException.cs | 7 - .../Core/Public/Version.cs | 7 - .../Core/Public/WithRawResponse.cs | 18 - .../Core/Public/WithRawResponseTask.cs | 144 --- .../Core/QueryStringBuilder.cs | 484 ------- .../Core/QueryStringConverter.cs | 259 ---- .../Core/RawClient.cs | 344 ----- .../Core/RawResponse.cs | 24 - .../Core/ResponseHeaders.cs | 108 -- .../Core/StreamRequest.cs | 29 - .../Core/StringEnum.cs | 6 - .../Core/StringEnumExtensions.cs | 6 - .../Core/ValueConvert.cs | 114 -- .../ISeedCsharpNamespaceConflictClient.cs | 6 - .../SeedCsharpNamespaceConflict.Custom.props | 20 - .../SeedCsharpNamespaceConflict.csproj | 61 - .../SeedCsharpNamespaceConflictClient.cs | 33 - .../Tasktest/ITasktestClient.cs | 9 - .../Tasktest/TasktestClient.cs | 55 - .../Tasktest/Types/Task.cs | 28 - 97 files changed, 2688 insertions(+), 9782 deletions(-) create mode 100644 packages/cli/api-importers/asyncapi-to-ir/src/extensions/x-fern-sdk-method-name.ts create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/asyncapi-v2-x-fern-sdk-method-name.json create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/asyncapi-v2-x-fern-sdk-method-name.json create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/asyncapi.yml create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/fern/fern.config.json create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/fern/generators.yml create mode 100644 packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/baseline-sdks/asyncapi-v2-x-fern-sdk-method-name.json create mode 100644 packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/asyncapi-v2-x-fern-sdk-method-name.json create mode 100644 packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/asyncapi.yml create mode 100644 packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/fern/fern.config.json create mode 100644 packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/fern/generators.yml delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/.fern/metadata.json delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/.github/workflows/ci.yml delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedApi.DynamicSnippets/Example0.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/HeadersBuilderTests.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/Json/AdditionalPropertiesTests.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/Json/DateOnlyJsonTests.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/Json/DateTimeJsonTests.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/Json/JsonAccessAttributeTests.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/QueryStringBuilderTests.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/QueryStringConverterTests.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/RawClientTests/MultipartFormTests.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/RawClientTests/QueryParameterTests.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/RawClientTests/RetriesTests.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/WithRawResponseTests.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/SeedCsharpNamespaceConflict.Test.Custom.props delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/SeedCsharpNamespaceConflict.Test.csproj delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/TestClient.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Unit/MockServer/BaseMockServerTest.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Unit/MockServer/Tasktest/HelloTest.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/AdditionalPropertiesComparer.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/JsonAssert.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/JsonElementComparer.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/NUnitExtensions.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/OneOfComparer.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/OptionalComparer.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/ReadOnlyMemoryComparer.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/A/Aa/Types/A.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/A/Aa/Types/B.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/A/Aa/Types/SubTestType.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/B/Types/TestType.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/ApiResponse.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/BaseRequest.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/CollectionItemSerializer.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Constants.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/DateOnlyConverter.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/DateTimeSerializer.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/EmptyRequest.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/EncodingCache.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Extensions.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/FormUrlEncoder.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/HeaderValue.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Headers.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/HeadersBuilder.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/HttpContentExtensions.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/HttpMethodExtensions.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/IIsRetryableContent.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/IRequestOptions.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/JsonAccessAttribute.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/JsonConfiguration.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/JsonRequest.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/MultipartFormRequest.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/NullableAttribute.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/OneOfSerializer.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Optional.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/OptionalAttribute.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/AdditionalProperties.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/ClientOptions.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/FileParameter.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/RawResponse.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/RequestOptions.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/SeedCsharpNamespaceConflictApiException.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/SeedCsharpNamespaceConflictException.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/Version.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/WithRawResponse.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/WithRawResponseTask.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/QueryStringBuilder.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/QueryStringConverter.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/RawClient.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/RawResponse.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/ResponseHeaders.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/StreamRequest.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/StringEnum.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/StringEnumExtensions.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/ValueConvert.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/ISeedCsharpNamespaceConflictClient.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/SeedCsharpNamespaceConflict.Custom.props delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/SeedCsharpNamespaceConflict.csproj delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/SeedCsharpNamespaceConflictClient.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Tasktest/ITasktestClient.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Tasktest/TasktestClient.cs delete mode 100644 seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Tasktest/Types/Task.cs diff --git a/packages/cli/api-importers/asyncapi-to-ir/src/2.x/channel/ChannelConverter2_X.ts b/packages/cli/api-importers/asyncapi-to-ir/src/2.x/channel/ChannelConverter2_X.ts index 58da5389b484..2d14739265e4 100644 --- a/packages/cli/api-importers/asyncapi-to-ir/src/2.x/channel/ChannelConverter2_X.ts +++ b/packages/cli/api-importers/asyncapi-to-ir/src/2.x/channel/ChannelConverter2_X.ts @@ -16,6 +16,7 @@ import { AbstractServerConverter } from "../../converters/AbstractServerConverte import { ParameterConverter } from "../../converters/ParameterConverter.js"; import { ChannelAddressExtension } from "../../extensions/x-fern-channel-address.js"; import { DisplayNameExtension } from "../../extensions/x-fern-display-name.js"; +import { SdkMethodNameExtension } from "../../extensions/x-fern-sdk-method-name.js"; import { AsyncAPIV2 } from "../index.js"; export declare namespace ChannelConverter2_X { @@ -249,7 +250,7 @@ export class ChannelConverter2_X extends AbstractChannelConverter { + private readonly operation: AsyncAPIV2.PublishEvent | AsyncAPIV2.SubscribeEvent | AsyncAPIV3.Operation; + public readonly key = "x-fern-sdk-method-name"; + + constructor({ breadcrumbs, operation, context }: SdkMethodNameExtension.Args) { + super({ breadcrumbs, context }); + this.operation = operation; + } + + public convert(): string | undefined { + const extensionValue = this.getExtensionValue(this.operation); + if (extensionValue == null || typeof extensionValue !== "string") { + return undefined; + } + + return extensionValue; + } +} diff --git a/packages/cli/api-importers/openapi/openapi-ir-parser/src/asyncapi/fernExtensions.ts b/packages/cli/api-importers/openapi/openapi-ir-parser/src/asyncapi/fernExtensions.ts index 75dae8dc2be7..be44ddd268a8 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-parser/src/asyncapi/fernExtensions.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-parser/src/asyncapi/fernExtensions.ts @@ -34,7 +34,25 @@ export const FernAsyncAPIExtension = { /** * The x-fern-sdk-method-name allows you to specify the SDK method name for an AsyncAPI operation. - * This extension is applied to AsyncAPI v3 operations to customize the generated method names. + * This extension is applied to AsyncAPI v2 publish/subscribe operations and AsyncAPI v3 operations + * to customize the generated method names. + * + * AsyncAPI v2 example: + * + * channels: + * /v1/realtime: + * publish: + * x-fern-sdk-method-name: send + * message: + * oneOf: + * - $ref: "#/components/messages/SessionUpdate" + * subscribe: + * x-fern-sdk-method-name: receive + * message: + * oneOf: + * - $ref: "#/components/messages/ServerEvent" + * + * AsyncAPI v3 example: * * operations: * SendMessage: diff --git a/packages/cli/api-importers/openapi/openapi-ir-parser/src/asyncapi/v2/parseAsyncAPIV2.ts b/packages/cli/api-importers/openapi/openapi-ir-parser/src/asyncapi/v2/parseAsyncAPIV2.ts index 45678974233a..06b1c4c5f18f 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-parser/src/asyncapi/v2/parseAsyncAPIV2.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-parser/src/asyncapi/v2/parseAsyncAPIV2.ts @@ -322,7 +322,10 @@ export function parseAsyncAPIV2({ origin: "client", name: "publish", body: convertSchemaWithExampleToSchema(publishSchema), - methodName: undefined // AsyncAPI v2 doesn't support operations with custom method names + methodName: + channel.publish != null + ? getExtension(channel.publish, FernAsyncAPIExtension.FERN_SDK_METHOD_NAME) + : undefined }); } if (subscribeSchema != null) { @@ -330,7 +333,10 @@ export function parseAsyncAPIV2({ origin: "server", name: "subscribe", body: convertSchemaWithExampleToSchema(subscribeSchema), - methodName: undefined // AsyncAPI v2 doesn't support operations with custom method names + methodName: + channel.subscribe != null + ? getExtension(channel.subscribe, FernAsyncAPIExtension.FERN_SDK_METHOD_NAME) + : undefined }); } parsedChannels[channelPath] = { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/asyncapi-v2-x-fern-sdk-method-name.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/asyncapi-v2-x-fern-sdk-method-name.json new file mode 100644 index 000000000000..83afbdbaf3fb --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/asyncapi-v2-x-fern-sdk-method-name.json @@ -0,0 +1,168 @@ +{ + "servers": [], + "websocketServers": [ + { + "name": "production", + "url": "wss://api.example.com" + } + ], + "tags": { + "tagsById": {} + }, + "hasEndpointsMarkedInternal": false, + "endpoints": [], + "webhooks": [], + "channels": { + "/v1/realtime": { + "audiences": [], + "handshake": { + "headers": [], + "queryParameters": [], + "pathParameters": [] + }, + "groupName": [ + "/v1/realtime" + ], + "messages": [ + { + "origin": "client", + "name": "publish", + "body": { + "allOf": [], + "properties": [ + { + "conflict": {}, + "generatedName": "v1RealtimePublishContent", + "key": "content", + "schema": { + "generatedName": "V1RealtimePublishContent", + "value": { + "schema": { + "type": "string" + }, + "generatedName": "V1RealtimePublishContent", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + } + ], + "allOfPropertyConflicts": [], + "generatedName": "V1RealtimePublish", + "groupName": [], + "additionalProperties": false, + "source": { + "file": "../asyncapi.yml", + "type": "openapi" + }, + "type": "object" + }, + "methodName": "send" + }, + { + "origin": "server", + "name": "subscribe", + "body": { + "allOf": [], + "properties": [ + { + "conflict": {}, + "generatedName": "v1RealtimeSubscribeContent", + "key": "content", + "schema": { + "generatedName": "V1RealtimeSubscribeContent", + "value": { + "schema": { + "type": "string" + }, + "generatedName": "V1RealtimeSubscribeContent", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + }, + { + "conflict": {}, + "generatedName": "v1RealtimeSubscribeTimestamp", + "key": "timestamp", + "schema": { + "generatedName": "V1RealtimeSubscribeTimestamp", + "value": { + "schema": { + "type": "string" + }, + "generatedName": "V1RealtimeSubscribeTimestamp", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + } + ], + "allOfPropertyConflicts": [], + "generatedName": "V1RealtimeSubscribe", + "groupName": [], + "additionalProperties": false, + "source": { + "file": "../asyncapi.yml", + "type": "openapi" + }, + "type": "object" + }, + "methodName": "receive" + } + ], + "servers": [ + { + "name": "production", + "url": "wss://api.example.com" + } + ], + "path": "/v1/realtime", + "examples": [ + { + "queryParameters": [], + "headers": [], + "messages": [ + { + "messageType": "publish", + "payload": { + "properties": {}, + "type": "object" + } + }, + { + "messageType": "subscribe", + "payload": { + "properties": {}, + "type": "object" + } + } + ] + } + ], + "source": { + "file": "../asyncapi.yml", + "type": "openapi" + } + } + }, + "groupedSchemas": { + "rootSchemas": {}, + "namespacedSchemas": {} + }, + "variables": {}, + "nonRequestReferencedSchemas": {}, + "securitySchemes": {}, + "globalHeaders": [], + "idempotencyHeaders": [], + "groups": {} +} \ No newline at end of file diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/asyncapi-v2-x-fern-sdk-method-name.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/asyncapi-v2-x-fern-sdk-method-name.json new file mode 100644 index 000000000000..f744da199e75 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/asyncapi-v2-x-fern-sdk-method-name.json @@ -0,0 +1,125 @@ +{ + "absoluteFilePath": "/DUMMY_PATH", + "importedDefinitions": {}, + "namedDefinitionFiles": { + "__package__.yml": { + "absoluteFilepath": "/DUMMY_PATH", + "contents": {}, + "rawContents": "{} +", + }, + "v1Realtime.yml": { + "absoluteFilepath": "/DUMMY_PATH", + "contents": { + "channel": { + "auth": false, + "examples": [ + { + "messages": [ + { + "body": {}, + "type": "publish", + }, + { + "body": {}, + "type": "subscribe", + }, + ], + }, + ], + "messages": { + "publish": { + "body": "V1RealtimePublish", + "method-name": "send", + "origin": "client", + }, + "subscribe": { + "body": "V1RealtimeSubscribe", + "method-name": "receive", + "origin": "server", + }, + }, + "path": "/v1/realtime", + "url": undefined, + }, + "types": { + "V1RealtimePublish": { + "docs": undefined, + "inline": undefined, + "properties": { + "content": "optional", + }, + "source": { + "openapi": "../asyncapi.yml", + }, + }, + "V1RealtimeSubscribe": { + "docs": undefined, + "inline": undefined, + "properties": { + "content": "optional", + "timestamp": "optional", + }, + "source": { + "openapi": "../asyncapi.yml", + }, + }, + }, + }, + "rawContents": "channel: + path: /v1/realtime + auth: false + messages: + publish: + origin: client + body: V1RealtimePublish + method-name: send + subscribe: + origin: server + body: V1RealtimeSubscribe + method-name: receive + examples: + - messages: + - type: publish + body: {} + - type: subscribe + body: {} +types: + V1RealtimePublish: + properties: + content: optional + source: + openapi: ../asyncapi.yml + V1RealtimeSubscribe: + properties: + content: optional + timestamp: optional + source: + openapi: ../asyncapi.yml +", + }, + }, + "packageMarkers": {}, + "rootApiFile": { + "contents": { + "default-environment": "production", + "default-url": "Base", + "environments": { + "production": "wss://api.example.com", + }, + "error-discrimination": { + "strategy": "status-code", + }, + "name": "api", + }, + "defaultUrl": "Base", + "rawContents": "name: api +error-discrimination: + strategy: status-code +environments: + production: wss://api.example.com +default-environment: production +default-url: Base +", + }, +} \ No newline at end of file diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/asyncapi.yml b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/asyncapi.yml new file mode 100644 index 000000000000..d26d40883e10 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/asyncapi.yml @@ -0,0 +1,32 @@ +asyncapi: 2.6.0 +info: + title: Test x-fern-sdk-method-name extension for AsyncAPI v2 + version: 1.0.0 + +servers: + production: + url: wss://api.example.com + protocol: wss + +channels: + /v1/realtime: + publish: + x-fern-sdk-method-name: send + operationId: sendEvent + message: + payload: + type: object + properties: + content: + type: string + subscribe: + x-fern-sdk-method-name: receive + operationId: receiveEvent + message: + payload: + type: object + properties: + content: + type: string + timestamp: + type: string diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/fern/fern.config.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/fern/fern.config.json new file mode 100644 index 000000000000..2d882fc1f7cb --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/fern/fern.config.json @@ -0,0 +1,4 @@ +{ + "organization": "fern", + "version": "0.0.0" +} diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/fern/generators.yml b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/fern/generators.yml new file mode 100644 index 000000000000..c8442b7d1eb1 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/fern/generators.yml @@ -0,0 +1,4 @@ +# yaml-language-server: $schema=https://schema.buildwithfern.dev/generators-yml.json +api: + specs: + - asyncapi: ../asyncapi.yml diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/baseline-sdks/asyncapi-v2-x-fern-sdk-method-name.json b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/baseline-sdks/asyncapi-v2-x-fern-sdk-method-name.json new file mode 100644 index 000000000000..3d812343acd1 --- /dev/null +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/baseline-sdks/asyncapi-v2-x-fern-sdk-method-name.json @@ -0,0 +1,1114 @@ +{ + "selfHosted": false, + "apiName": { + "originalName": "api", + "camelCase": { + "unsafeName": "api", + "safeName": "api" + }, + "snakeCase": { + "unsafeName": "api", + "safeName": "api" + }, + "screamingSnakeCase": { + "unsafeName": "API", + "safeName": "API" + }, + "pascalCase": { + "unsafeName": "API", + "safeName": "API" + } + }, + "auth": { + "requirement": "ALL", + "schemes": [] + }, + "headers": [], + "idempotencyHeaders": [], + "types": { + "type_v1Realtime:V1RealtimePublish": { + "name": { + "name": { + "originalName": "V1RealtimePublish", + "camelCase": { + "unsafeName": "v1RealtimePublish", + "safeName": "v1RealtimePublish" + }, + "snakeCase": { + "unsafeName": "v1realtime_publish", + "safeName": "v1realtime_publish" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME_PUBLISH", + "safeName": "V1REALTIME_PUBLISH" + }, + "pascalCase": { + "unsafeName": "V1RealtimePublish", + "safeName": "V1RealtimePublish" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + ], + "packagePath": [], + "file": { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + }, + "typeId": "type_v1Realtime:V1RealtimePublish" + }, + "shape": { + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "content", + "camelCase": { + "unsafeName": "content", + "safeName": "content" + }, + "snakeCase": { + "unsafeName": "content", + "safeName": "content" + }, + "screamingSnakeCase": { + "unsafeName": "CONTENT", + "safeName": "CONTENT" + }, + "pascalCase": { + "unsafeName": "Content", + "safeName": "Content" + } + }, + "wireValue": "content" + }, + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + } + ], + "extraProperties": false, + "extendedProperties": [], + "type": "object" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + }, + "type_v1Realtime:V1RealtimeSubscribe": { + "name": { + "name": { + "originalName": "V1RealtimeSubscribe", + "camelCase": { + "unsafeName": "v1RealtimeSubscribe", + "safeName": "v1RealtimeSubscribe" + }, + "snakeCase": { + "unsafeName": "v1realtime_subscribe", + "safeName": "v1realtime_subscribe" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME_SUBSCRIBE", + "safeName": "V1REALTIME_SUBSCRIBE" + }, + "pascalCase": { + "unsafeName": "V1RealtimeSubscribe", + "safeName": "V1RealtimeSubscribe" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + ], + "packagePath": [], + "file": { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + }, + "typeId": "type_v1Realtime:V1RealtimeSubscribe" + }, + "shape": { + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "content", + "camelCase": { + "unsafeName": "content", + "safeName": "content" + }, + "snakeCase": { + "unsafeName": "content", + "safeName": "content" + }, + "screamingSnakeCase": { + "unsafeName": "CONTENT", + "safeName": "CONTENT" + }, + "pascalCase": { + "unsafeName": "Content", + "safeName": "Content" + } + }, + "wireValue": "content" + }, + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": { + "name": { + "originalName": "timestamp", + "camelCase": { + "unsafeName": "timestamp", + "safeName": "timestamp" + }, + "snakeCase": { + "unsafeName": "timestamp", + "safeName": "timestamp" + }, + "screamingSnakeCase": { + "unsafeName": "TIMESTAMP", + "safeName": "TIMESTAMP" + }, + "pascalCase": { + "unsafeName": "Timestamp", + "safeName": "Timestamp" + } + }, + "wireValue": "timestamp" + }, + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + } + ], + "extraProperties": false, + "extendedProperties": [], + "type": "object" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + } + }, + "errors": {}, + "services": {}, + "constants": { + "errorInstanceIdKey": { + "name": { + "originalName": "errorInstanceId", + "camelCase": { + "unsafeName": "errorInstanceID", + "safeName": "errorInstanceID" + }, + "snakeCase": { + "unsafeName": "error_instance_id", + "safeName": "error_instance_id" + }, + "screamingSnakeCase": { + "unsafeName": "ERROR_INSTANCE_ID", + "safeName": "ERROR_INSTANCE_ID" + }, + "pascalCase": { + "unsafeName": "ErrorInstanceID", + "safeName": "ErrorInstanceID" + } + }, + "wireValue": "errorInstanceId" + } + }, + "environments": { + "defaultEnvironment": "production", + "environments": { + "environments": [ + { + "id": "production", + "name": { + "originalName": "production", + "camelCase": { + "unsafeName": "production", + "safeName": "production" + }, + "snakeCase": { + "unsafeName": "production", + "safeName": "production" + }, + "screamingSnakeCase": { + "unsafeName": "PRODUCTION", + "safeName": "PRODUCTION" + }, + "pascalCase": { + "unsafeName": "Production", + "safeName": "Production" + } + }, + "url": "wss://api.example.com" + } + ], + "type": "singleBaseUrl" + } + }, + "errorDiscriminationStrategy": { + "type": "statusCode" + }, + "pathParameters": [], + "variables": [], + "serviceTypeReferenceInfo": { + "typesReferencedOnlyByService": {}, + "sharedTypes": [ + "type_v1Realtime:V1RealtimePublish", + "type_v1Realtime:V1RealtimeSubscribe" + ] + }, + "webhookGroups": {}, + "websocketChannels": { + "channel_v1Realtime": { + "path": { + "head": "/v1/realtime", + "parts": [] + }, + "auth": false, + "name": { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + }, + "headers": [], + "pathParameters": [], + "queryParameters": [], + "messages": [ + { + "type": "publish", + "origin": "client", + "body": { + "bodyType": { + "name": { + "originalName": "V1RealtimePublish", + "camelCase": { + "unsafeName": "v1RealtimePublish", + "safeName": "v1RealtimePublish" + }, + "snakeCase": { + "unsafeName": "v1realtime_publish", + "safeName": "v1realtime_publish" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME_PUBLISH", + "safeName": "V1REALTIME_PUBLISH" + }, + "pascalCase": { + "unsafeName": "V1RealtimePublish", + "safeName": "V1RealtimePublish" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + ], + "packagePath": [], + "file": { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + }, + "typeId": "type_v1Realtime:V1RealtimePublish", + "type": "named" + }, + "type": "reference" + }, + "methodName": "send" + }, + { + "type": "subscribe", + "origin": "server", + "body": { + "bodyType": { + "name": { + "originalName": "V1RealtimeSubscribe", + "camelCase": { + "unsafeName": "v1RealtimeSubscribe", + "safeName": "v1RealtimeSubscribe" + }, + "snakeCase": { + "unsafeName": "v1realtime_subscribe", + "safeName": "v1realtime_subscribe" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME_SUBSCRIBE", + "safeName": "V1REALTIME_SUBSCRIBE" + }, + "pascalCase": { + "unsafeName": "V1RealtimeSubscribe", + "safeName": "V1RealtimeSubscribe" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + ], + "packagePath": [], + "file": { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + }, + "typeId": "type_v1Realtime:V1RealtimeSubscribe", + "type": "named" + }, + "type": "reference" + }, + "methodName": "receive" + } + ], + "examples": [ + { + "url": "/v1/realtime", + "pathParameters": [], + "headers": [], + "queryParameters": [], + "messages": [ + { + "type": "publish", + "body": { + "shape": { + "typeName": { + "typeId": "type_v1Realtime:V1RealtimePublish", + "fernFilepath": { + "allParts": [ + { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + ], + "packagePath": [], + "file": { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + }, + "name": { + "originalName": "V1RealtimePublish", + "camelCase": { + "unsafeName": "v1RealtimePublish", + "safeName": "v1RealtimePublish" + }, + "snakeCase": { + "unsafeName": "v1realtime_publish", + "safeName": "v1realtime_publish" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME_PUBLISH", + "safeName": "V1REALTIME_PUBLISH" + }, + "pascalCase": { + "unsafeName": "V1RealtimePublish", + "safeName": "V1RealtimePublish" + } + } + }, + "shape": { + "properties": [], + "type": "object" + }, + "type": "named" + }, + "jsonExample": {}, + "type": "reference" + } + }, + { + "type": "subscribe", + "body": { + "shape": { + "typeName": { + "typeId": "type_v1Realtime:V1RealtimeSubscribe", + "fernFilepath": { + "allParts": [ + { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + ], + "packagePath": [], + "file": { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + }, + "name": { + "originalName": "V1RealtimeSubscribe", + "camelCase": { + "unsafeName": "v1RealtimeSubscribe", + "safeName": "v1RealtimeSubscribe" + }, + "snakeCase": { + "unsafeName": "v1realtime_subscribe", + "safeName": "v1realtime_subscribe" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME_SUBSCRIBE", + "safeName": "V1REALTIME_SUBSCRIBE" + }, + "pascalCase": { + "unsafeName": "V1RealtimeSubscribe", + "safeName": "V1RealtimeSubscribe" + } + } + }, + "shape": { + "properties": [], + "type": "object" + }, + "type": "named" + }, + "jsonExample": {}, + "type": "reference" + } + } + ] + } + ], + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + } + }, + "dynamic": { + "version": "1.0.0", + "types": { + "type_v1Realtime:V1RealtimePublish": { + "declaration": { + "name": { + "originalName": "V1RealtimePublish", + "camelCase": { + "unsafeName": "v1RealtimePublish", + "safeName": "v1RealtimePublish" + }, + "snakeCase": { + "unsafeName": "v1realtime_publish", + "safeName": "v1realtime_publish" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME_PUBLISH", + "safeName": "V1REALTIME_PUBLISH" + }, + "pascalCase": { + "unsafeName": "V1RealtimePublish", + "safeName": "V1RealtimePublish" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + ], + "packagePath": [], + "file": { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + } + }, + "properties": [ + { + "name": { + "name": { + "originalName": "content", + "camelCase": { + "unsafeName": "content", + "safeName": "content" + }, + "snakeCase": { + "unsafeName": "content", + "safeName": "content" + }, + "screamingSnakeCase": { + "unsafeName": "CONTENT", + "safeName": "CONTENT" + }, + "pascalCase": { + "unsafeName": "Content", + "safeName": "Content" + } + }, + "wireValue": "content" + }, + "typeReference": { + "value": { + "value": "STRING", + "type": "primitive" + }, + "type": "optional" + } + } + ], + "additionalProperties": false, + "type": "object" + }, + "type_v1Realtime:V1RealtimeSubscribe": { + "declaration": { + "name": { + "originalName": "V1RealtimeSubscribe", + "camelCase": { + "unsafeName": "v1RealtimeSubscribe", + "safeName": "v1RealtimeSubscribe" + }, + "snakeCase": { + "unsafeName": "v1realtime_subscribe", + "safeName": "v1realtime_subscribe" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME_SUBSCRIBE", + "safeName": "V1REALTIME_SUBSCRIBE" + }, + "pascalCase": { + "unsafeName": "V1RealtimeSubscribe", + "safeName": "V1RealtimeSubscribe" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + ], + "packagePath": [], + "file": { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + } + }, + "properties": [ + { + "name": { + "name": { + "originalName": "content", + "camelCase": { + "unsafeName": "content", + "safeName": "content" + }, + "snakeCase": { + "unsafeName": "content", + "safeName": "content" + }, + "screamingSnakeCase": { + "unsafeName": "CONTENT", + "safeName": "CONTENT" + }, + "pascalCase": { + "unsafeName": "Content", + "safeName": "Content" + } + }, + "wireValue": "content" + }, + "typeReference": { + "value": { + "value": "STRING", + "type": "primitive" + }, + "type": "optional" + } + }, + { + "name": { + "name": { + "originalName": "timestamp", + "camelCase": { + "unsafeName": "timestamp", + "safeName": "timestamp" + }, + "snakeCase": { + "unsafeName": "timestamp", + "safeName": "timestamp" + }, + "screamingSnakeCase": { + "unsafeName": "TIMESTAMP", + "safeName": "TIMESTAMP" + }, + "pascalCase": { + "unsafeName": "Timestamp", + "safeName": "Timestamp" + } + }, + "wireValue": "timestamp" + }, + "typeReference": { + "value": { + "value": "STRING", + "type": "primitive" + }, + "type": "optional" + } + } + ], + "additionalProperties": false, + "type": "object" + } + }, + "headers": [], + "endpoints": {}, + "pathParameters": [], + "environments": { + "defaultEnvironment": "production", + "environments": { + "environments": [ + { + "id": "production", + "name": { + "originalName": "production", + "camelCase": { + "unsafeName": "production", + "safeName": "production" + }, + "snakeCase": { + "unsafeName": "production", + "safeName": "production" + }, + "screamingSnakeCase": { + "unsafeName": "PRODUCTION", + "safeName": "PRODUCTION" + }, + "pascalCase": { + "unsafeName": "Production", + "safeName": "Production" + } + }, + "url": "wss://api.example.com" + } + ], + "type": "singleBaseUrl" + } + } + }, + "apiPlayground": true, + "subpackages": { + "subpackage_v1Realtime": { + "fernFilepath": { + "allParts": [ + { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + ], + "packagePath": [], + "file": { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + }, + "name": { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v1realtime", + "safeName": "v1realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V1REALTIME", + "safeName": "V1REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + }, + "types": [ + "type_v1Realtime:V1RealtimePublish", + "type_v1Realtime:V1RealtimeSubscribe" + ], + "errors": [], + "subpackages": [], + "websocket": "channel_v1Realtime", + "hasEndpointsInTree": false + } + }, + "rootPackage": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "types": [], + "errors": [], + "subpackages": [ + "subpackage_v1Realtime" + ], + "hasEndpointsInTree": false + }, + "sdkConfig": { + "isAuthMandatory": false, + "hasStreamingEndpoints": false, + "hasPaginatedEndpoints": false, + "hasFileDownloadEndpoints": false, + "platformHeaders": { + "language": "X-Fern-Language", + "sdkName": "X-Fern-SDK-Name", + "sdkVersion": "X-Fern-SDK-Version" + } + } +} \ No newline at end of file diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/asyncapi-v2-x-fern-sdk-method-name.json b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/asyncapi-v2-x-fern-sdk-method-name.json new file mode 100644 index 000000000000..c6c46d161ab6 --- /dev/null +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/asyncapi-v2-x-fern-sdk-method-name.json @@ -0,0 +1,1123 @@ +{ + "auth": { + "requirement": "ALL", + "schemes": [] + }, + "selfHosted": false, + "types": { + "V1RealtimeSubscribe": { + "name": { + "typeId": "V1RealtimeSubscribe", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": { + "originalName": "V1RealtimeSubscribe", + "camelCase": { + "unsafeName": "v1RealtimeSubscribe", + "safeName": "v1RealtimeSubscribe" + }, + "snakeCase": { + "unsafeName": "v_1_realtime_subscribe", + "safeName": "v_1_realtime_subscribe" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME_SUBSCRIBE", + "safeName": "V_1_REALTIME_SUBSCRIBE" + }, + "pascalCase": { + "unsafeName": "V1RealtimeSubscribe", + "safeName": "V1RealtimeSubscribe" + } + } + }, + "shape": { + "properties": [ + { + "name": { + "name": { + "originalName": "content", + "camelCase": { + "unsafeName": "content", + "safeName": "content" + }, + "snakeCase": { + "unsafeName": "content", + "safeName": "content" + }, + "screamingSnakeCase": { + "unsafeName": "CONTENT", + "safeName": "CONTENT" + }, + "pascalCase": { + "unsafeName": "Content", + "safeName": "Content" + } + }, + "wireValue": "content" + }, + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "ChannelsV1RealtimeSubscribeContent_example_autogenerated": "string" + } + } + }, + { + "name": { + "name": { + "originalName": "timestamp", + "camelCase": { + "unsafeName": "timestamp", + "safeName": "timestamp" + }, + "snakeCase": { + "unsafeName": "timestamp", + "safeName": "timestamp" + }, + "screamingSnakeCase": { + "unsafeName": "TIMESTAMP", + "safeName": "TIMESTAMP" + }, + "pascalCase": { + "unsafeName": "Timestamp", + "safeName": "Timestamp" + } + }, + "wireValue": "timestamp" + }, + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "ChannelsV1RealtimeSubscribeTimestamp_example_autogenerated": "string" + } + } + } + ], + "extends": [], + "extendedProperties": [], + "extraProperties": false, + "type": "object" + }, + "autogeneratedExamples": [], + "userProvidedExamples": [], + "referencedTypes": {}, + "inline": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "V1RealtimeSubscribe_example_autogenerated": {} + } + } + }, + "V1RealtimePublish": { + "name": { + "typeId": "V1RealtimePublish", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": { + "originalName": "V1RealtimePublish", + "camelCase": { + "unsafeName": "v1RealtimePublish", + "safeName": "v1RealtimePublish" + }, + "snakeCase": { + "unsafeName": "v_1_realtime_publish", + "safeName": "v_1_realtime_publish" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME_PUBLISH", + "safeName": "V_1_REALTIME_PUBLISH" + }, + "pascalCase": { + "unsafeName": "V1RealtimePublish", + "safeName": "V1RealtimePublish" + } + } + }, + "shape": { + "properties": [ + { + "name": { + "name": { + "originalName": "content", + "camelCase": { + "unsafeName": "content", + "safeName": "content" + }, + "snakeCase": { + "unsafeName": "content", + "safeName": "content" + }, + "screamingSnakeCase": { + "unsafeName": "CONTENT", + "safeName": "CONTENT" + }, + "pascalCase": { + "unsafeName": "Content", + "safeName": "Content" + } + }, + "wireValue": "content" + }, + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "ChannelsV1RealtimePublishContent_example_autogenerated": "string" + } + } + } + ], + "extends": [], + "extendedProperties": [], + "extraProperties": false, + "type": "object" + }, + "autogeneratedExamples": [], + "userProvidedExamples": [], + "referencedTypes": {}, + "inline": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "V1RealtimePublish_example_autogenerated": {} + } + } + } + }, + "services": {}, + "errors": {}, + "webhookGroups": {}, + "websocketChannels": { + "channel_v1Realtime": { + "name": { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v_1_realtime", + "safeName": "v_1_realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME", + "safeName": "V_1_REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + }, + "baseUrl": "production", + "path": { + "head": "/v1/realtime", + "parts": [] + }, + "auth": false, + "headers": [], + "queryParameters": [], + "pathParameters": [], + "messages": [ + { + "type": "subscribe", + "displayName": "subscribe", + "origin": "server", + "body": { + "bodyType": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": { + "originalName": "V1RealtimeSubscribe", + "camelCase": { + "unsafeName": "v1RealtimeSubscribe", + "safeName": "v1RealtimeSubscribe" + }, + "snakeCase": { + "unsafeName": "v_1_realtime_subscribe", + "safeName": "v_1_realtime_subscribe" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME_SUBSCRIBE", + "safeName": "V_1_REALTIME_SUBSCRIBE" + }, + "pascalCase": { + "unsafeName": "V1RealtimeSubscribe", + "safeName": "V1RealtimeSubscribe" + } + }, + "typeId": "V1RealtimeSubscribe", + "inline": false, + "type": "named" + }, + "type": "reference" + }, + "methodName": "receive" + }, + { + "type": "publish", + "displayName": "publish", + "origin": "client", + "body": { + "bodyType": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": { + "originalName": "V1RealtimePublish", + "camelCase": { + "unsafeName": "v1RealtimePublish", + "safeName": "v1RealtimePublish" + }, + "snakeCase": { + "unsafeName": "v_1_realtime_publish", + "safeName": "v_1_realtime_publish" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME_PUBLISH", + "safeName": "V_1_REALTIME_PUBLISH" + }, + "pascalCase": { + "unsafeName": "V1RealtimePublish", + "safeName": "V1RealtimePublish" + } + }, + "typeId": "V1RealtimePublish", + "inline": false, + "type": "named" + }, + "type": "reference" + }, + "methodName": "send" + } + ], + "examples": [ + { + "pathParameters": [], + "headers": [], + "queryParameters": [], + "messages": [ + { + "type": "publish", + "body": { + "jsonExample": {}, + "shape": { + "shape": { + "properties": [ + { + "name": { + "name": { + "originalName": "content", + "camelCase": { + "unsafeName": "content", + "safeName": "content" + }, + "snakeCase": { + "unsafeName": "content", + "safeName": "content" + }, + "screamingSnakeCase": { + "unsafeName": "CONTENT", + "safeName": "CONTENT" + }, + "pascalCase": { + "unsafeName": "Content", + "safeName": "Content" + } + }, + "wireValue": "content" + }, + "originalTypeDeclaration": { + "typeId": "V1RealtimePublish", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": { + "originalName": "V1RealtimePublish", + "camelCase": { + "unsafeName": "v1RealtimePublish", + "safeName": "v1RealtimePublish" + }, + "snakeCase": { + "unsafeName": "v_1_realtime_publish", + "safeName": "v_1_realtime_publish" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME_PUBLISH", + "safeName": "V_1_REALTIME_PUBLISH" + }, + "pascalCase": { + "unsafeName": "V1RealtimePublish", + "safeName": "V1RealtimePublish" + } + } + }, + "value": { + "shape": { + "container": { + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + } + } + } + ], + "type": "object" + }, + "typeName": { + "typeId": "V1RealtimePublish", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": { + "originalName": "V1RealtimePublish", + "camelCase": { + "unsafeName": "v1RealtimePublish", + "safeName": "v1RealtimePublish" + }, + "snakeCase": { + "unsafeName": "v_1_realtime_publish", + "safeName": "v_1_realtime_publish" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME_PUBLISH", + "safeName": "V_1_REALTIME_PUBLISH" + }, + "pascalCase": { + "unsafeName": "V1RealtimePublish", + "safeName": "V1RealtimePublish" + } + } + }, + "type": "named" + }, + "type": "reference" + } + }, + { + "type": "subscribe", + "body": { + "jsonExample": {}, + "shape": { + "shape": { + "properties": [ + { + "name": { + "name": { + "originalName": "content", + "camelCase": { + "unsafeName": "content", + "safeName": "content" + }, + "snakeCase": { + "unsafeName": "content", + "safeName": "content" + }, + "screamingSnakeCase": { + "unsafeName": "CONTENT", + "safeName": "CONTENT" + }, + "pascalCase": { + "unsafeName": "Content", + "safeName": "Content" + } + }, + "wireValue": "content" + }, + "originalTypeDeclaration": { + "typeId": "V1RealtimeSubscribe", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": { + "originalName": "V1RealtimeSubscribe", + "camelCase": { + "unsafeName": "v1RealtimeSubscribe", + "safeName": "v1RealtimeSubscribe" + }, + "snakeCase": { + "unsafeName": "v_1_realtime_subscribe", + "safeName": "v_1_realtime_subscribe" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME_SUBSCRIBE", + "safeName": "V_1_REALTIME_SUBSCRIBE" + }, + "pascalCase": { + "unsafeName": "V1RealtimeSubscribe", + "safeName": "V1RealtimeSubscribe" + } + } + }, + "value": { + "shape": { + "container": { + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + } + } + }, + { + "name": { + "name": { + "originalName": "timestamp", + "camelCase": { + "unsafeName": "timestamp", + "safeName": "timestamp" + }, + "snakeCase": { + "unsafeName": "timestamp", + "safeName": "timestamp" + }, + "screamingSnakeCase": { + "unsafeName": "TIMESTAMP", + "safeName": "TIMESTAMP" + }, + "pascalCase": { + "unsafeName": "Timestamp", + "safeName": "Timestamp" + } + }, + "wireValue": "timestamp" + }, + "originalTypeDeclaration": { + "typeId": "V1RealtimeSubscribe", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": { + "originalName": "V1RealtimeSubscribe", + "camelCase": { + "unsafeName": "v1RealtimeSubscribe", + "safeName": "v1RealtimeSubscribe" + }, + "snakeCase": { + "unsafeName": "v_1_realtime_subscribe", + "safeName": "v_1_realtime_subscribe" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME_SUBSCRIBE", + "safeName": "V_1_REALTIME_SUBSCRIBE" + }, + "pascalCase": { + "unsafeName": "V1RealtimeSubscribe", + "safeName": "V1RealtimeSubscribe" + } + } + }, + "value": { + "shape": { + "container": { + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + } + } + } + ], + "type": "object" + }, + "typeName": { + "typeId": "V1RealtimeSubscribe", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": { + "originalName": "V1RealtimeSubscribe", + "camelCase": { + "unsafeName": "v1RealtimeSubscribe", + "safeName": "v1RealtimeSubscribe" + }, + "snakeCase": { + "unsafeName": "v_1_realtime_subscribe", + "safeName": "v_1_realtime_subscribe" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME_SUBSCRIBE", + "safeName": "V_1_REALTIME_SUBSCRIBE" + }, + "pascalCase": { + "unsafeName": "V1RealtimeSubscribe", + "safeName": "V1RealtimeSubscribe" + } + } + }, + "type": "named" + }, + "type": "reference" + } + } + ], + "url": "/v1/realtime" + }, + { + "pathParameters": [], + "headers": [], + "queryParameters": [], + "messages": [ + { + "type": "publish", + "body": { + "jsonExample": {}, + "shape": { + "shape": { + "properties": [ + { + "name": { + "name": { + "originalName": "content", + "camelCase": { + "unsafeName": "content", + "safeName": "content" + }, + "snakeCase": { + "unsafeName": "content", + "safeName": "content" + }, + "screamingSnakeCase": { + "unsafeName": "CONTENT", + "safeName": "CONTENT" + }, + "pascalCase": { + "unsafeName": "Content", + "safeName": "Content" + } + }, + "wireValue": "content" + }, + "originalTypeDeclaration": { + "typeId": "V1RealtimePublish", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": { + "originalName": "V1RealtimePublish", + "camelCase": { + "unsafeName": "v1RealtimePublish", + "safeName": "v1RealtimePublish" + }, + "snakeCase": { + "unsafeName": "v_1_realtime_publish", + "safeName": "v_1_realtime_publish" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME_PUBLISH", + "safeName": "V_1_REALTIME_PUBLISH" + }, + "pascalCase": { + "unsafeName": "V1RealtimePublish", + "safeName": "V1RealtimePublish" + } + } + }, + "value": { + "shape": { + "container": { + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + } + } + } + ], + "type": "object" + }, + "typeName": { + "typeId": "V1RealtimePublish", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": { + "originalName": "V1RealtimePublish", + "camelCase": { + "unsafeName": "v1RealtimePublish", + "safeName": "v1RealtimePublish" + }, + "snakeCase": { + "unsafeName": "v_1_realtime_publish", + "safeName": "v_1_realtime_publish" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME_PUBLISH", + "safeName": "V_1_REALTIME_PUBLISH" + }, + "pascalCase": { + "unsafeName": "V1RealtimePublish", + "safeName": "V1RealtimePublish" + } + } + }, + "type": "named" + }, + "type": "reference" + } + }, + { + "type": "subscribe", + "body": { + "jsonExample": {}, + "shape": { + "shape": { + "properties": [ + { + "name": { + "name": { + "originalName": "content", + "camelCase": { + "unsafeName": "content", + "safeName": "content" + }, + "snakeCase": { + "unsafeName": "content", + "safeName": "content" + }, + "screamingSnakeCase": { + "unsafeName": "CONTENT", + "safeName": "CONTENT" + }, + "pascalCase": { + "unsafeName": "Content", + "safeName": "Content" + } + }, + "wireValue": "content" + }, + "originalTypeDeclaration": { + "typeId": "V1RealtimeSubscribe", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": { + "originalName": "V1RealtimeSubscribe", + "camelCase": { + "unsafeName": "v1RealtimeSubscribe", + "safeName": "v1RealtimeSubscribe" + }, + "snakeCase": { + "unsafeName": "v_1_realtime_subscribe", + "safeName": "v_1_realtime_subscribe" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME_SUBSCRIBE", + "safeName": "V_1_REALTIME_SUBSCRIBE" + }, + "pascalCase": { + "unsafeName": "V1RealtimeSubscribe", + "safeName": "V1RealtimeSubscribe" + } + } + }, + "value": { + "shape": { + "container": { + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + } + } + }, + { + "name": { + "name": { + "originalName": "timestamp", + "camelCase": { + "unsafeName": "timestamp", + "safeName": "timestamp" + }, + "snakeCase": { + "unsafeName": "timestamp", + "safeName": "timestamp" + }, + "screamingSnakeCase": { + "unsafeName": "TIMESTAMP", + "safeName": "TIMESTAMP" + }, + "pascalCase": { + "unsafeName": "Timestamp", + "safeName": "Timestamp" + } + }, + "wireValue": "timestamp" + }, + "originalTypeDeclaration": { + "typeId": "V1RealtimeSubscribe", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": { + "originalName": "V1RealtimeSubscribe", + "camelCase": { + "unsafeName": "v1RealtimeSubscribe", + "safeName": "v1RealtimeSubscribe" + }, + "snakeCase": { + "unsafeName": "v_1_realtime_subscribe", + "safeName": "v_1_realtime_subscribe" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME_SUBSCRIBE", + "safeName": "V_1_REALTIME_SUBSCRIBE" + }, + "pascalCase": { + "unsafeName": "V1RealtimeSubscribe", + "safeName": "V1RealtimeSubscribe" + } + } + }, + "value": { + "shape": { + "container": { + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + } + } + } + ], + "type": "object" + }, + "typeName": { + "typeId": "V1RealtimeSubscribe", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": { + "originalName": "V1RealtimeSubscribe", + "camelCase": { + "unsafeName": "v1RealtimeSubscribe", + "safeName": "v1RealtimeSubscribe" + }, + "snakeCase": { + "unsafeName": "v_1_realtime_subscribe", + "safeName": "v_1_realtime_subscribe" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME_SUBSCRIBE", + "safeName": "V_1_REALTIME_SUBSCRIBE" + }, + "pascalCase": { + "unsafeName": "V1RealtimeSubscribe", + "safeName": "V1RealtimeSubscribe" + } + } + }, + "type": "named" + }, + "type": "reference" + } + } + ], + "url": "/v1/realtime" + } + ], + "v2Examples": { + "autogeneratedExamples": {}, + "userSpecifiedExamples": {} + } + } + }, + "headers": [], + "idempotencyHeaders": [], + "pathParameters": [], + "errorDiscriminationStrategy": { + "type": "statusCode" + }, + "variables": [], + "serviceTypeReferenceInfo": { + "sharedTypes": [], + "typesReferencedOnlyByService": {} + }, + "environments": { + "defaultEnvironment": "production", + "environments": { + "environments": [ + { + "id": "production", + "name": { + "originalName": "production", + "camelCase": { + "unsafeName": "production", + "safeName": "production" + }, + "snakeCase": { + "unsafeName": "production", + "safeName": "production" + }, + "screamingSnakeCase": { + "unsafeName": "PRODUCTION", + "safeName": "PRODUCTION" + }, + "pascalCase": { + "unsafeName": "Production", + "safeName": "Production" + } + }, + "url": "wss://api.example.com" + } + ], + "type": "singleBaseUrl" + } + }, + "rootPackage": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "types": [], + "errors": [], + "subpackages": [ + "subpackage_v1Realtime" + ], + "hasEndpointsInTree": false + }, + "subpackages": { + "subpackage_v1Realtime": { + "name": { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v_1_realtime", + "safeName": "v_1_realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME", + "safeName": "V_1_REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v_1_realtime", + "safeName": "v_1_realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME", + "safeName": "V_1_REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + ], + "packagePath": [], + "file": { + "originalName": "v1Realtime", + "camelCase": { + "unsafeName": "v1Realtime", + "safeName": "v1Realtime" + }, + "snakeCase": { + "unsafeName": "v_1_realtime", + "safeName": "v_1_realtime" + }, + "screamingSnakeCase": { + "unsafeName": "V_1_REALTIME", + "safeName": "V_1_REALTIME" + }, + "pascalCase": { + "unsafeName": "V1Realtime", + "safeName": "V1Realtime" + } + } + }, + "types": [], + "errors": [], + "subpackages": [], + "websocket": "channel_v1Realtime", + "hasEndpointsInTree": false + } + }, + "sdkConfig": { + "hasFileDownloadEndpoints": false, + "hasPaginatedEndpoints": false, + "hasStreamingEndpoints": false, + "isAuthMandatory": true, + "platformHeaders": { + "language": "", + "sdkName": "", + "sdkVersion": "" + } + }, + "apiName": { + "originalName": "", + "camelCase": { + "unsafeName": "", + "safeName": "" + }, + "snakeCase": { + "unsafeName": "", + "safeName": "" + }, + "screamingSnakeCase": { + "unsafeName": "", + "safeName": "" + }, + "pascalCase": { + "unsafeName": "", + "safeName": "" + } + }, + "constants": { + "errorInstanceIdKey": { + "name": { + "originalName": "errorInstanceId", + "camelCase": { + "unsafeName": "errorInstanceId", + "safeName": "errorInstanceId" + }, + "snakeCase": { + "unsafeName": "error_instance_id", + "safeName": "error_instance_id" + }, + "screamingSnakeCase": { + "unsafeName": "ERROR_INSTANCE_ID", + "safeName": "ERROR_INSTANCE_ID" + }, + "pascalCase": { + "unsafeName": "ErrorInstanceId", + "safeName": "ErrorInstanceId" + } + }, + "wireValue": "errorInstanceId" + } + } +} \ No newline at end of file diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/asyncapi.yml b/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/asyncapi.yml new file mode 100644 index 000000000000..d26d40883e10 --- /dev/null +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/asyncapi.yml @@ -0,0 +1,32 @@ +asyncapi: 2.6.0 +info: + title: Test x-fern-sdk-method-name extension for AsyncAPI v2 + version: 1.0.0 + +servers: + production: + url: wss://api.example.com + protocol: wss + +channels: + /v1/realtime: + publish: + x-fern-sdk-method-name: send + operationId: sendEvent + message: + payload: + type: object + properties: + content: + type: string + subscribe: + x-fern-sdk-method-name: receive + operationId: receiveEvent + message: + payload: + type: object + properties: + content: + type: string + timestamp: + type: string diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/fern/fern.config.json b/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/fern/fern.config.json new file mode 100644 index 000000000000..ecb7133e2645 --- /dev/null +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/fern/fern.config.json @@ -0,0 +1,4 @@ +{ + "organization": "fern", + "version": "*" +} diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/fern/generators.yml b/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/fern/generators.yml new file mode 100644 index 000000000000..c8442b7d1eb1 --- /dev/null +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/asyncapi-v2-x-fern-sdk-method-name/fern/generators.yml @@ -0,0 +1,4 @@ +# yaml-language-server: $schema=https://schema.buildwithfern.dev/generators-yml.json +api: + specs: + - asyncapi: ../asyncapi.yml diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index df158ae0bee9..a5c141d4bfb9 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,4 +1,15 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 4.32.0 + changelogEntry: + - summary: | + Add support for the `x-fern-sdk-method-name` extension on AsyncAPI v2 + `publish` and `subscribe` operations. Users can now customize the + generated SDK method name (e.g., changing `sendPublish` to `send`) + without migrating to AsyncAPI v3. + type: feat + createdAt: "2026-03-17" + irVersion: 65 + - version: 4.31.3 changelogEntry: - summary: | @@ -9,7 +20,6 @@ type: fix createdAt: "2026-03-17" irVersion: 65 - - version: 4.31.2 changelogEntry: - summary: | diff --git a/seed/csharp-sdk/csharp-namespace-conflict/.fern/metadata.json b/seed/csharp-sdk/csharp-namespace-conflict/.fern/metadata.json deleted file mode 100644 index 91d0855bee07..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/.fern/metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-csharp-sdk", - "generatorVersion": "latest", - "generatorConfig": {}, - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/csharp-sdk/csharp-namespace-conflict/.github/workflows/ci.yml b/seed/csharp-sdk/csharp-namespace-conflict/.github/workflows/ci.yml deleted file mode 100644 index 5e74575f3dd2..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/.github/workflows/ci.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -env: - DOTNET_NOLOGO: true - -jobs: - ci: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v6 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - dotnet-version: 10.x - - - name: Install tools - run: dotnet tool restore - - - name: Restore dependencies - run: dotnet restore src/SeedCsharpNamespaceConflict/SeedCsharpNamespaceConflict.csproj - - - name: Build - run: dotnet build src/SeedCsharpNamespaceConflict/SeedCsharpNamespaceConflict.csproj --no-restore -c Release - - - name: Restore test dependencies - run: dotnet restore src/SeedCsharpNamespaceConflict.Test/SeedCsharpNamespaceConflict.Test.csproj - - - name: Build tests - run: dotnet build src/SeedCsharpNamespaceConflict.Test/SeedCsharpNamespaceConflict.Test.csproj --no-restore -c Release - - - name: Test - run: dotnet test src/SeedCsharpNamespaceConflict.Test/SeedCsharpNamespaceConflict.Test.csproj --no-restore --no-build -c Release - - - name: Pack - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - run: dotnet pack src/SeedCsharpNamespaceConflict/SeedCsharpNamespaceConflict.csproj --no-build --no-restore -c Release - - - name: Publish to NuGet.org - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - env: - NUGET_API_KEY: ${{ secrets.NUGET_API_TOKEN }} - run: dotnet nuget push src/SeedCsharpNamespaceConflict/bin/Release/*.nupkg --api-key $NUGET_API_KEY --source "nuget.org" - diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedApi.DynamicSnippets/Example0.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedApi.DynamicSnippets/Example0.cs deleted file mode 100644 index 7567fdb7d27c..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedApi.DynamicSnippets/Example0.cs +++ /dev/null @@ -1,17 +0,0 @@ -using SeedCsharpNamespaceConflict; - -namespace Usage; - -public class Example0 -{ - public async System.Threading.Tasks.Task Do() { - var client = new SeedCsharpNamespaceConflictClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.Tasktest.HelloAsync(); - } - -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj deleted file mode 100644 index 3417db2e58e2..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - net8.0 - 12 - enable - enable - - - - - - \ No newline at end of file diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/HeadersBuilderTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/HeadersBuilderTests.cs deleted file mode 100644 index 92114ef283b1..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/HeadersBuilderTests.cs +++ /dev/null @@ -1,326 +0,0 @@ -using NUnit.Framework; -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict.Test.Core; - -[TestFixture] -public class HeadersBuilderTests -{ - [Test] - public async global::System.Threading.Tasks.Task Add_SimpleHeaders() - { - var headers = await new HeadersBuilder.Builder() - .Add("Content-Type", "application/json") - .Add("Authorization", "Bearer token123") - .Add("X-API-Key", "key456") - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(3)); - Assert.That(headers["Content-Type"], Is.EqualTo("application/json")); - Assert.That(headers["Authorization"], Is.EqualTo("Bearer token123")); - Assert.That(headers["X-API-Key"], Is.EqualTo("key456")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_NullValuesIgnored() - { - var headers = await new HeadersBuilder.Builder() - .Add("Header1", "value1") - .Add("Header2", null) - .Add("Header3", "value3") - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(2)); - Assert.That(headers.ContainsKey("Header1"), Is.True); - Assert.That(headers.ContainsKey("Header2"), Is.False); - Assert.That(headers.ContainsKey("Header3"), Is.True); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_OverwritesExistingHeader() - { - var headers = await new HeadersBuilder.Builder() - .Add("Content-Type", "application/json") - .Add("Content-Type", "application/xml") - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(1)); - Assert.That(headers["Content-Type"], Is.EqualTo("application/xml")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_HeadersOverload_MergesExistingHeaders() - { - var existingHeaders = new Headers( - new Dictionary { { "Header1", "value1" }, { "Header2", "value2" } } - ); - - var result = await new HeadersBuilder.Builder() - .Add("Header3", "value3") - .Add(existingHeaders) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result.Count, Is.EqualTo(3)); - Assert.That(result["Header1"], Is.EqualTo("value1")); - Assert.That(result["Header2"], Is.EqualTo("value2")); - Assert.That(result["Header3"], Is.EqualTo("value3")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_HeadersOverload_OverwritesExistingHeaders() - { - var existingHeaders = new Headers( - new Dictionary { { "Header1", "override" } } - ); - - var result = await new HeadersBuilder.Builder() - .Add("Header1", "original") - .Add("Header2", "keep") - .Add(existingHeaders) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result.Count, Is.EqualTo(2)); - Assert.That(result["Header1"], Is.EqualTo("override")); - Assert.That(result["Header2"], Is.EqualTo("keep")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_HeadersOverload_NullHeadersIgnored() - { - var result = await new HeadersBuilder.Builder() - .Add("Header1", "value1") - .Add((Headers?)null) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result.Count, Is.EqualTo(1)); - Assert.That(result["Header1"], Is.EqualTo("value1")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_KeyValuePairOverload_AddsHeaders() - { - var additionalHeaders = new List> - { - new("Header1", "value1"), - new("Header2", "value2"), - }; - - var headers = await new HeadersBuilder.Builder() - .Add("Header3", "value3") - .Add(additionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(3)); - Assert.That(headers["Header1"], Is.EqualTo("value1")); - Assert.That(headers["Header2"], Is.EqualTo("value2")); - Assert.That(headers["Header3"], Is.EqualTo("value3")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_KeyValuePairOverload_IgnoresNullValues() - { - var additionalHeaders = new List> - { - new("Header1", "value1"), - new("Header2", null), // Should be ignored - }; - - var headers = await new HeadersBuilder.Builder() - .Add(additionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(1)); - Assert.That(headers.ContainsKey("Header2"), Is.False); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_DictionaryOverload_AddsHeaders() - { - var dict = new Dictionary - { - { "Header1", "value1" }, - { "Header2", "value2" }, - }; - - var headers = await new HeadersBuilder.Builder() - .Add("Header3", "value3") - .Add(dict) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(3)); - Assert.That(headers["Header1"], Is.EqualTo("value1")); - Assert.That(headers["Header2"], Is.EqualTo("value2")); - Assert.That(headers["Header3"], Is.EqualTo("value3")); - } - - [Test] - public async global::System.Threading.Tasks.Task EmptyBuilder_ReturnsEmptyHeaders() - { - var headers = await new HeadersBuilder.Builder().BuildAsync().ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(0)); - } - - [Test] - public async global::System.Threading.Tasks.Task OnlyNullValues_ReturnsEmptyHeaders() - { - var headers = await new HeadersBuilder.Builder() - .Add("Header1", null) - .Add("Header2", null) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(0)); - } - - [Test] - public async global::System.Threading.Tasks.Task ComplexMergingScenario() - { - // Simulates real SDK usage: endpoint headers + client headers + request options - var clientHeaders = new Headers( - new Dictionary - { - { "X-Client-Version", "1.0.0" }, - { "User-Agent", "MyClient/1.0" }, - } - ); - - var clientAdditionalHeaders = new List> - { - new("X-Custom-Header", "custom-value"), - }; - - var requestOptionsHeaders = new Headers( - new Dictionary - { - { "Authorization", "Bearer user-token" }, - { "User-Agent", "MyClient/2.0" }, // Override - } - ); - - var requestAdditionalHeaders = new List> - { - new("X-Request-ID", "req-123"), - new("X-Custom-Header", "overridden-value"), // Override - }; - - var headers = await new HeadersBuilder.Builder() - .Add("Content-Type", "application/json") // Endpoint header - .Add("X-Endpoint-ID", "endpoint-1") - .Add(clientHeaders) - .Add(clientAdditionalHeaders) - .Add(requestOptionsHeaders) - .Add(requestAdditionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - - // Verify precedence - Assert.That(headers["Content-Type"], Is.EqualTo("application/json")); - Assert.That(headers["X-Endpoint-ID"], Is.EqualTo("endpoint-1")); - Assert.That(headers["X-Client-Version"], Is.EqualTo("1.0.0")); - Assert.That(headers["User-Agent"], Is.EqualTo("MyClient/2.0")); // Overridden - Assert.That(headers["Authorization"], Is.EqualTo("Bearer user-token")); - Assert.That(headers["X-Request-ID"], Is.EqualTo("req-123")); - Assert.That(headers["X-Custom-Header"], Is.EqualTo("overridden-value")); // Overridden - } - - [Test] - public async global::System.Threading.Tasks.Task Builder_WithCapacity() - { - // Test that capacity constructor works without errors - var headers = await new HeadersBuilder.Builder(capacity: 10) - .Add("Header1", "value1") - .Add("Header2", "value2") - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(2)); - Assert.That(headers["Header1"], Is.EqualTo("value1")); - Assert.That(headers["Header2"], Is.EqualTo("value2")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_HeadersOverload_ResolvesDynamicHeaderValues() - { - // Test that BuildAsync properly resolves HeaderValue instances - var existingHeaders = new Headers(); - existingHeaders["DynamicHeader"] = - (Func>)( - () => global::System.Threading.Tasks.Task.FromResult("dynamic-value") - ); - - var result = await new HeadersBuilder.Builder() - .Add("StaticHeader", "static-value") - .Add(existingHeaders) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result.Count, Is.EqualTo(2)); - Assert.That(result["StaticHeader"], Is.EqualTo("static-value")); - Assert.That(result["DynamicHeader"], Is.EqualTo("dynamic-value")); - } - - [Test] - public async global::System.Threading.Tasks.Task MultipleSyncAdds() - { - var headers1 = new Headers(new Dictionary { { "H1", "v1" } }); - var headers2 = new Headers(new Dictionary { { "H2", "v2" } }); - var headers3 = new Headers(new Dictionary { { "H3", "v3" } }); - - var result = await new HeadersBuilder.Builder() - .Add(headers1) - .Add(headers2) - .Add(headers3) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result.Count, Is.EqualTo(3)); - Assert.That(result["H1"], Is.EqualTo("v1")); - Assert.That(result["H2"], Is.EqualTo("v2")); - Assert.That(result["H3"], Is.EqualTo("v3")); - } - - [Test] - public async global::System.Threading.Tasks.Task PrecedenceOrder_LatestWins() - { - // Test that later operations override earlier ones - var headers1 = new Headers(new Dictionary { { "Key", "value1" } }); - var headers2 = new Headers(new Dictionary { { "Key", "value2" } }); - var additional = new List> { new("Key", "value3") }; - - var result = await new HeadersBuilder.Builder() - .Add("Key", "value0") - .Add(headers1) - .Add(headers2) - .Add(additional) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result["Key"], Is.EqualTo("value3")); - } - - [Test] - public async global::System.Threading.Tasks.Task CaseInsensitiveKeys() - { - // Test that header keys are case-insensitive - var headers = await new HeadersBuilder.Builder() - .Add("content-type", "application/json") - .Add("Content-Type", "application/xml") // Should overwrite - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(1)); - Assert.That(headers["content-type"], Is.EqualTo("application/xml")); - Assert.That(headers["Content-Type"], Is.EqualTo("application/xml")); - Assert.That(headers["CONTENT-TYPE"], Is.EqualTo("application/xml")); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/Json/AdditionalPropertiesTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/Json/AdditionalPropertiesTests.cs deleted file mode 100644 index 2d0253dc0127..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/Json/AdditionalPropertiesTests.cs +++ /dev/null @@ -1,365 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using NUnit.Framework; -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict.Test.Core.Json; - -[TestFixture] -public class AdditionalPropertiesTests -{ - [Test] - public void Record_OnDeserialized_ShouldPopulateAdditionalProperties() - { - // Arrange - const string json = """ - { - "id": "1", - "category": "fiction", - "title": "The Hobbit" - } - """; - - // Act - var record = JsonUtils.Deserialize(json); - - // Assert - Assert.That(record, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(record.Id, Is.EqualTo("1")); - Assert.That(record.AdditionalProperties["category"].GetString(), Is.EqualTo("fiction")); - Assert.That(record.AdditionalProperties["title"].GetString(), Is.EqualTo("The Hobbit")); - }); - } - - [Test] - public void RecordWithWriteableAdditionalProperties_OnSerialization_ShouldIncludeAdditionalProperties() - { - // Arrange - var record = new WriteableRecord - { - Id = "1", - AdditionalProperties = { ["category"] = "fiction", ["title"] = "The Hobbit" }, - }; - - // Act - var json = JsonUtils.Serialize(record); - var deserializedRecord = JsonUtils.Deserialize(json); - - // Assert - Assert.That(deserializedRecord, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(deserializedRecord.Id, Is.EqualTo("1")); - Assert.That( - deserializedRecord.AdditionalProperties["category"], - Is.InstanceOf() - ); - Assert.That( - ((JsonElement)deserializedRecord.AdditionalProperties["category"]!).GetString(), - Is.EqualTo("fiction") - ); - Assert.That( - deserializedRecord.AdditionalProperties["title"], - Is.InstanceOf() - ); - Assert.That( - ((JsonElement)deserializedRecord.AdditionalProperties["title"]!).GetString(), - Is.EqualTo("The Hobbit") - ); - }); - } - - [Test] - public void ReadOnlyAdditionalProperties_ShouldRetrieveValuesCorrectly() - { - // Arrange - var extensionData = new Dictionary - { - ["key1"] = JsonUtils.SerializeToElement("value1"), - ["key2"] = JsonUtils.SerializeToElement(123), - }; - var readOnlyProps = new ReadOnlyAdditionalProperties(); - readOnlyProps.CopyFromExtensionData(extensionData); - - // Act & Assert - Assert.That(readOnlyProps["key1"].GetString(), Is.EqualTo("value1")); - Assert.That(readOnlyProps["key2"].GetInt32(), Is.EqualTo(123)); - } - - [Test] - public void AdditionalProperties_ShouldBehaveAsDictionary() - { - // Arrange - var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; - - // Act - additionalProps["key3"] = true; - - // Assert - Assert.Multiple(() => - { - Assert.That(additionalProps["key1"], Is.EqualTo("value1")); - Assert.That(additionalProps["key2"], Is.EqualTo(123)); - Assert.That((bool)additionalProps["key3"]!, Is.True); - Assert.That(additionalProps.Count, Is.EqualTo(3)); - }); - } - - [Test] - public void AdditionalProperties_ToJsonObject_ShouldSerializeCorrectly() - { - // Arrange - var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; - - // Act - var jsonObject = additionalProps.ToJsonObject(); - - Assert.Multiple(() => - { - // Assert - Assert.That(jsonObject["key1"]!.GetValue(), Is.EqualTo("value1")); - Assert.That(jsonObject["key2"]!.GetValue(), Is.EqualTo(123)); - }); - } - - [Test] - public void AdditionalProperties_MixReadAndWrite_ShouldOverwriteDeserializedProperty() - { - // Arrange - const string json = """ - { - "id": "1", - "category": "fiction", - "title": "The Hobbit" - } - """; - var record = JsonUtils.Deserialize(json); - - // Act - record.AdditionalProperties["category"] = "non-fiction"; - - // Assert - Assert.Multiple(() => - { - Assert.That(record, Is.Not.Null); - Assert.That(record.Id, Is.EqualTo("1")); - Assert.That(record.AdditionalProperties["category"], Is.EqualTo("non-fiction")); - Assert.That(record.AdditionalProperties["title"], Is.InstanceOf()); - Assert.That( - ((JsonElement)record.AdditionalProperties["title"]!).GetString(), - Is.EqualTo("The Hobbit") - ); - }); - } - - [Test] - public void RecordWithReadonlyAdditionalPropertiesInts_OnDeserialized_ShouldPopulateAdditionalProperties() - { - // Arrange - const string json = """ - { - "extra1": 42, - "extra2": 99 - } - """; - - // Act - var record = JsonUtils.Deserialize(json); - - // Assert - Assert.That(record, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(record.AdditionalProperties["extra1"], Is.EqualTo(42)); - Assert.That(record.AdditionalProperties["extra2"], Is.EqualTo(99)); - }); - } - - [Test] - public void RecordWithAdditionalPropertiesInts_OnSerialization_ShouldIncludeAdditionalProperties() - { - // Arrange - var record = new WriteableRecordWithInts - { - AdditionalProperties = { ["extra1"] = 42, ["extra2"] = 99 }, - }; - - // Act - var json = JsonUtils.Serialize(record); - var deserializedRecord = JsonUtils.Deserialize(json); - - // Assert - Assert.That(deserializedRecord, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(deserializedRecord.AdditionalProperties["extra1"], Is.EqualTo(42)); - Assert.That(deserializedRecord.AdditionalProperties["extra2"], Is.EqualTo(99)); - }); - } - - [Test] - public void RecordWithReadonlyAdditionalPropertiesDictionaries_OnDeserialized_ShouldPopulateAdditionalProperties() - { - // Arrange - const string json = """ - { - "extra1": { "key1": true, "key2": false }, - "extra2": { "key3": true } - } - """; - - // Act - var record = JsonUtils.Deserialize(json); - - // Assert - Assert.That(record, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(record.AdditionalProperties["extra1"]["key1"], Is.True); - Assert.That(record.AdditionalProperties["extra1"]["key2"], Is.False); - Assert.That(record.AdditionalProperties["extra2"]["key3"], Is.True); - }); - } - - [Test] - public void RecordWithAdditionalPropertiesDictionaries_OnSerialization_ShouldIncludeAdditionalProperties() - { - // Arrange - var record = new WriteableRecordWithDictionaries - { - AdditionalProperties = - { - ["extra1"] = new Dictionary { { "key1", true }, { "key2", false } }, - ["extra2"] = new Dictionary { { "key3", true } }, - }, - }; - - // Act - var json = JsonUtils.Serialize(record); - var deserializedRecord = JsonUtils.Deserialize(json); - - // Assert - Assert.That(deserializedRecord, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key1"], Is.True); - Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key2"], Is.False); - Assert.That(deserializedRecord.AdditionalProperties["extra2"]["key3"], Is.True); - }); - } - - private record Record : IJsonOnDeserialized - { - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - } - - private record WriteableRecord : IJsonOnDeserialized, IJsonOnSerializing - { - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public AdditionalProperties AdditionalProperties { get; set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - - void IJsonOnSerializing.OnSerializing() - { - AdditionalProperties.CopyToExtensionData(_extensionData); - } - } - - private record RecordWithInts : IJsonOnDeserialized - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - } - - private record WriteableRecordWithInts : IJsonOnDeserialized, IJsonOnSerializing - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public AdditionalProperties AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - - void IJsonOnSerializing.OnSerializing() - { - AdditionalProperties.CopyToExtensionData(_extensionData); - } - } - - private record RecordWithDictionaries : IJsonOnDeserialized - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties< - Dictionary - > AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - } - - private record WriteableRecordWithDictionaries : IJsonOnDeserialized, IJsonOnSerializing - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public AdditionalProperties> AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - - void IJsonOnSerializing.OnSerializing() - { - AdditionalProperties.CopyToExtensionData(_extensionData); - } - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/Json/DateOnlyJsonTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/Json/DateOnlyJsonTests.cs deleted file mode 100644 index 651c7e6df267..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/Json/DateOnlyJsonTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -using NUnit.Framework; -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict.Test.Core.Json; - -[TestFixture] -public class DateOnlyJsonTests -{ - [Test] - public void SerializeDateOnly_ShouldMatchExpectedFormat() - { - (DateOnly dateOnly, string expected)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (new DateOnly(2023, 1, 1), "\"2023-01-01\""), - (new DateOnly(2023, 12, 31), "\"2023-12-31\""), - (new DateOnly(2023, 6, 15), "\"2023-06-15\""), - (new DateOnly(2023, 3, 10), "\"2023-03-10\""), - ]; - foreach (var (dateOnly, expected) in testCases) - { - var json = JsonUtils.Serialize(dateOnly); - Assert.That(json, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeDateOnly_ShouldMatchExpectedDateOnly() - { - (DateOnly expected, string json)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (new DateOnly(2023, 1, 1), "\"2023-01-01\""), - (new DateOnly(2023, 12, 31), "\"2023-12-31\""), - (new DateOnly(2023, 6, 15), "\"2023-06-15\""), - (new DateOnly(2023, 3, 10), "\"2023-03-10\""), - ]; - - foreach (var (expected, json) in testCases) - { - var dateOnly = JsonUtils.Deserialize(json); - Assert.That(dateOnly, Is.EqualTo(expected)); - } - } - - [Test] - public void SerializeNullableDateOnly_ShouldMatchExpectedFormat() - { - (DateOnly? dateOnly, string expected)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (null, "null"), - ]; - foreach (var (dateOnly, expected) in testCases) - { - var json = JsonUtils.Serialize(dateOnly); - Assert.That(json, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeNullableDateOnly_ShouldMatchExpectedDateOnly() - { - (DateOnly? expected, string json)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (null, "null"), - ]; - - foreach (var (expected, json) in testCases) - { - var dateOnly = JsonUtils.Deserialize(json); - Assert.That(dateOnly, Is.EqualTo(expected)); - } - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/Json/DateTimeJsonTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/Json/DateTimeJsonTests.cs deleted file mode 100644 index 2643f9396146..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/Json/DateTimeJsonTests.cs +++ /dev/null @@ -1,110 +0,0 @@ -using NUnit.Framework; -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict.Test.Core.Json; - -[TestFixture] -public class DateTimeJsonTests -{ - [Test] - public void SerializeDateTime_ShouldMatchExpectedFormat() - { - (DateTime dateTime, string expected)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), - ( - new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), - "\"2023-12-31T23:59:59.000Z\"" - ), - (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), - ( - new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.000Z\"" - ), - ( - new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.123Z\"" - ), - ]; - foreach (var (dateTime, expected) in testCases) - { - var json = JsonUtils.Serialize(dateTime); - Assert.That(json, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeDateTime_ShouldMatchExpectedDateTime() - { - (DateTime expected, string json)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), - ( - new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), - "\"2023-12-31T23:59:59.000Z\"" - ), - (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), - ( - new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.000Z\"" - ), - (new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), "\"2023-03-10T08:45:30Z\""), - ( - new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.123Z\"" - ), - ]; - - foreach (var (expected, json) in testCases) - { - var dateTime = JsonUtils.Deserialize(json); - Assert.That(dateTime, Is.EqualTo(expected)); - } - } - - [Test] - public void SerializeNullableDateTime_ShouldMatchExpectedFormat() - { - (DateTime? expected, string json)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (null, "null"), - ]; - - foreach (var (expected, json) in testCases) - { - var dateTime = JsonUtils.Deserialize(json); - Assert.That(dateTime, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeNullableDateTime_ShouldMatchExpectedDateTime() - { - (DateTime? expected, string json)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (null, "null"), - ]; - - foreach (var (expected, json) in testCases) - { - var dateTime = JsonUtils.Deserialize(json); - Assert.That(dateTime, Is.EqualTo(expected)); - } - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/Json/JsonAccessAttributeTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/Json/JsonAccessAttributeTests.cs deleted file mode 100644 index b9f6f9589cc3..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/Json/JsonAccessAttributeTests.cs +++ /dev/null @@ -1,160 +0,0 @@ -using global::System.Text.Json.Serialization; -using NUnit.Framework; -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict.Test.Core.Json; - -[TestFixture] -public class JsonAccessAttributeTests -{ - private class MyClass - { - [JsonPropertyName("read_only_prop")] - [JsonAccess(JsonAccessType.ReadOnly)] - public string? ReadOnlyProp { get; set; } - - [JsonPropertyName("write_only_prop")] - [JsonAccess(JsonAccessType.WriteOnly)] - public string? WriteOnlyProp { get; set; } - - [JsonPropertyName("normal_prop")] - public string? NormalProp { get; set; } - - [JsonPropertyName("read_only_nullable_list")] - [JsonAccess(JsonAccessType.ReadOnly)] - public IEnumerable? ReadOnlyNullableList { get; set; } - - [JsonPropertyName("read_only_list")] - [JsonAccess(JsonAccessType.ReadOnly)] - public IEnumerable ReadOnlyList { get; set; } = []; - - [JsonPropertyName("write_only_nullable_list")] - [JsonAccess(JsonAccessType.WriteOnly)] - public IEnumerable? WriteOnlyNullableList { get; set; } - - [JsonPropertyName("write_only_list")] - [JsonAccess(JsonAccessType.WriteOnly)] - public IEnumerable WriteOnlyList { get; set; } = []; - - [JsonPropertyName("normal_list")] - public IEnumerable NormalList { get; set; } = []; - - [JsonPropertyName("normal_nullable_list")] - public IEnumerable? NullableNormalList { get; set; } - } - - [Test] - public void JsonAccessAttribute_ShouldWorkAsExpected() - { - const string json = """ - { - "read_only_prop": "read", - "write_only_prop": "write", - "normal_prop": "normal_prop", - "read_only_nullable_list": ["item1", "item2"], - "read_only_list": ["item3", "item4"], - "write_only_nullable_list": ["item5", "item6"], - "write_only_list": ["item7", "item8"], - "normal_list": ["normal1", "normal2"], - "normal_nullable_list": ["normal1", "normal2"] - } - """; - var obj = JsonUtils.Deserialize(json); - - Assert.Multiple(() => - { - // String properties - Assert.That(obj.ReadOnlyProp, Is.EqualTo("read")); - Assert.That(obj.WriteOnlyProp, Is.Null); - Assert.That(obj.NormalProp, Is.EqualTo("normal_prop")); - - // List properties - read only - var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); - Assert.That(nullableReadOnlyList, Is.Not.Null); - Assert.That(nullableReadOnlyList, Has.Length.EqualTo(2)); - Assert.That(nullableReadOnlyList![0], Is.EqualTo("item1")); - Assert.That(nullableReadOnlyList![1], Is.EqualTo("item2")); - - var readOnlyList = obj.ReadOnlyList.ToArray(); - Assert.That(readOnlyList, Is.Not.Null); - Assert.That(readOnlyList, Has.Length.EqualTo(2)); - Assert.That(readOnlyList[0], Is.EqualTo("item3")); - Assert.That(readOnlyList[1], Is.EqualTo("item4")); - - // List properties - write only - Assert.That(obj.WriteOnlyNullableList, Is.Null); - Assert.That(obj.WriteOnlyList, Is.Not.Null); - Assert.That(obj.WriteOnlyList, Is.Empty); - - // Normal list property - var normalList = obj.NormalList.ToArray(); - Assert.That(normalList, Is.Not.Null); - Assert.That(normalList, Has.Length.EqualTo(2)); - Assert.That(normalList[0], Is.EqualTo("normal1")); - Assert.That(normalList[1], Is.EqualTo("normal2")); - }); - - // Set up values for serialization - obj.WriteOnlyProp = "write"; - obj.NormalProp = "new_value"; - obj.WriteOnlyNullableList = new List { "write1", "write2" }; - obj.WriteOnlyList = new List { "write3", "write4" }; - obj.NormalList = new List { "new_normal" }; - obj.NullableNormalList = new List { "new_normal" }; - - var serializedJson = JsonUtils.Serialize(obj); - const string expectedJson = """ - { - "write_only_prop": "write", - "normal_prop": "new_value", - "write_only_nullable_list": [ - "write1", - "write2" - ], - "write_only_list": [ - "write3", - "write4" - ], - "normal_list": [ - "new_normal" - ], - "normal_nullable_list": [ - "new_normal" - ] - } - """; - Assert.That(serializedJson, Is.EqualTo(expectedJson).IgnoreWhiteSpace); - } - - [Test] - public void JsonAccessAttribute_WithNullListsInJson_ShouldWorkAsExpected() - { - const string json = """ - { - "read_only_prop": "read", - "normal_prop": "normal_prop", - "read_only_nullable_list": null, - "read_only_list": [] - } - """; - var obj = JsonUtils.Deserialize(json); - - Assert.Multiple(() => - { - // Read-only nullable list should be null when JSON contains null - var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); - Assert.That(nullableReadOnlyList, Is.Null); - - // Read-only non-nullable list should never be null, but empty when JSON contains null - var readOnlyList = obj.ReadOnlyList.ToArray(); // This should be initialized to an empty list by default - Assert.That(readOnlyList, Is.Not.Null); - Assert.That(readOnlyList, Is.Empty); - }); - - // Serialize and verify read-only lists are not included - var serializedJson = JsonUtils.Serialize(obj); - Assert.That(serializedJson, Does.Not.Contain("read_only_prop")); - Assert.That(serializedJson, Does.Not.Contain("read_only_nullable_list")); - Assert.That(serializedJson, Does.Not.Contain("read_only_list")); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/QueryStringBuilderTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/QueryStringBuilderTests.cs deleted file mode 100644 index 1b19717f075e..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/QueryStringBuilderTests.cs +++ /dev/null @@ -1,560 +0,0 @@ -using NUnit.Framework; -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict.Test.Core; - -[TestFixture] -public class QueryStringBuilderTests -{ - [Test] - public void Build_SimpleParameters() - { - var parameters = new List> - { - new("name", "John Doe"), - new("age", "30"), - new("city", "New York"), - }; - - var result = QueryStringBuilder.Build(parameters); - - Assert.That(result, Is.EqualTo("?name=John%20Doe&age=30&city=New%20York")); - } - - [Test] - public void Build_EmptyList_ReturnsEmptyString() - { - var parameters = new List>(); - - var result = QueryStringBuilder.Build(parameters); - - Assert.That(result, Is.EqualTo(string.Empty)); - } - - [Test] - public void Build_SpecialCharacters() - { - var parameters = new List> - { - new("email", "test@example.com"), - new("url", "https://example.com/path?query=value"), - new("special", "a+b=c&d"), - }; - - var result = QueryStringBuilder.Build(parameters); - - Assert.That( - result, - Is.EqualTo( - "?email=test%40example.com&url=https%3A%2F%2Fexample.com%2Fpath%3Fquery%3Dvalue&special=a%2Bb%3Dc%26d" - ) - ); - } - - [Test] - public void Build_UnicodeCharacters() - { - var parameters = new List> { new("greeting", "Hello 世界") }; - - var result = QueryStringBuilder.Build(parameters); - - // Verify the Chinese characters are properly UTF-8 encoded - Assert.That(result, Does.StartWith("?greeting=Hello%20")); - Assert.That(result, Does.Contain("%E4%B8%96%E7%95%8C")); // 世界 - } - - [Test] - public void Build_SessionSettings_DeepObject() - { - // Simulate session settings with nested properties - var sessionSettings = new - { - custom_session_id = "my-custom-session-id", - system_prompt = "You are a helpful assistant", - variables = new Dictionary - { - { "userName", "John" }, - { "userAge", 30 }, - { "isPremium", true }, - }, - }; - - // Build query parameters list - var queryParams = new List> { new("api_key", "test_key_123") }; - - // Add session_settings with prefix using the new overload - queryParams.AddRange( - QueryStringConverter.ToDeepObject("session_settings", sessionSettings) - ); - - var result = QueryStringBuilder.Build(queryParams); - - // Verify the result contains properly formatted deep object notation - // Note: Square brackets are URL-encoded as %5B and %5D - Assert.That(result, Does.StartWith("?api_key=test_key_123")); - Assert.That( - result, - Does.Contain("session_settings%5Bcustom_session_id%5D=my-custom-session-id") - ); - Assert.That( - result, - Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20a%20helpful%20assistant") - ); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserName%5D=John")); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserAge%5D=30")); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BisPremium%5D=true")); - - // Verify it's NOT JSON encoded (no braces or quotes in the original format) - Assert.That(result, Does.Not.Contain("%7B%22")); // Not {" sequence - } - - [Test] - public void Build_ChatApiLikeParameters() - { - // Simulate what ChatApi constructor does - var sessionSettings = new - { - system_prompt = "You are helpful", - variables = new Dictionary { { "name", "Alice" } }, - }; - - var queryParams = new List>(); - - // Simple parameters - var simpleParams = new Dictionary - { - { "access_token", "token123" }, - { "config_id", "config456" }, - { "api_key", "key789" }, - }; - queryParams.AddRange(QueryStringConverter.ToExplodedForm(simpleParams)); - - // Session settings as deep object with prefix - queryParams.AddRange( - QueryStringConverter.ToDeepObject("session_settings", sessionSettings) - ); - - var result = QueryStringBuilder.Build(queryParams); - - // Verify structure (square brackets are URL-encoded) - Assert.That(result, Does.StartWith("?")); - Assert.That(result, Does.Contain("access_token=token123")); - Assert.That(result, Does.Contain("config_id=config456")); - Assert.That(result, Does.Contain("api_key=key789")); - Assert.That( - result, - Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20helpful") - ); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bname%5D=Alice")); - } - - [Test] - public void Build_ReservedCharacters_NotEncoded() - { - var parameters = new List> - { - new("path", "some-path"), - new("id", "123-456_789.test~value"), - }; - - var result = QueryStringBuilder.Build(parameters); - - // Unreserved characters: A-Z a-z 0-9 - _ . ~ - Assert.That(result, Is.EqualTo("?path=some-path&id=123-456_789.test~value")); - } - - [Test] - public void Builder_Add_SimpleParameters() - { - var result = new QueryStringBuilder.Builder() - .Add("name", "John Doe") - .Add("age", 30) - .Add("active", true) - .Build(); - - Assert.That(result, Does.Contain("name=John%20Doe")); - Assert.That(result, Does.Contain("age=30")); - Assert.That(result, Does.Contain("active=true")); - } - - [Test] - public void Builder_Add_NullValuesIgnored() - { - var result = new QueryStringBuilder.Builder() - .Add("name", "John") - .Add("middle", null) - .Add("age", 30) - .Build(); - - Assert.That(result, Does.Contain("name=John")); - Assert.That(result, Does.Contain("age=30")); - Assert.That(result, Does.Not.Contain("middle")); - } - - [Test] - public void Builder_AddDeepObject_WithPrefix() - { - var settings = new - { - custom_session_id = "id-123", - system_prompt = "You are helpful", - variables = new { name = "Alice", age = 25 }, - }; - - var result = new QueryStringBuilder.Builder() - .Add("api_key", "key123") - .AddDeepObject("session_settings", settings) - .Build(); - - Assert.That(result, Does.Contain("api_key=key123")); - Assert.That(result, Does.Contain("session_settings%5Bcustom_session_id%5D=id-123")); - Assert.That( - result, - Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20helpful") - ); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bname%5D=Alice")); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bage%5D=25")); - } - - [Test] - public void Builder_AddDeepObject_NullIgnored() - { - var result = new QueryStringBuilder.Builder() - .Add("api_key", "key123") - .AddDeepObject("settings", null) - .Build(); - - Assert.That(result, Is.EqualTo("?api_key=key123")); - Assert.That(result, Does.Not.Contain("settings")); - } - - [Test] - public void Builder_AddExploded_WithPrefix() - { - var filter = new { status = "active", type = "user" }; - - var result = new QueryStringBuilder.Builder() - .Add("api_key", "key123") - .AddExploded("filter", filter) - .Build(); - - Assert.That(result, Does.Contain("api_key=key123")); - Assert.That(result, Does.Contain("filter%5Bstatus%5D=active")); - Assert.That(result, Does.Contain("filter%5Btype%5D=user")); - } - - [Test] - public void Builder_AddExploded_NullIgnored() - { - var result = new QueryStringBuilder.Builder() - .Add("api_key", "key123") - .AddExploded("filter", null) - .Build(); - - Assert.That(result, Is.EqualTo("?api_key=key123")); - Assert.That(result, Does.Not.Contain("filter")); - } - - [Test] - public void Builder_WithCapacity() - { - // Test that capacity constructor works without errors - var result = new QueryStringBuilder.Builder(capacity: 10) - .Add("param1", "value1") - .Add("param2", "value2") - .Build(); - - Assert.That(result, Does.Contain("param1=value1")); - Assert.That(result, Does.Contain("param2=value2")); - } - - [Test] - public void Builder_ChatApiLikeUsage() - { - // Simulate real usage from ChatApi - var sessionSettings = new - { - custom_session_id = "session-123", - variables = new Dictionary - { - { "userName", "John" }, - { "userAge", 30 }, - }, - }; - - var result = new QueryStringBuilder.Builder(capacity: 16) - .Add("access_token", "token123") - .Add("allow_connection", true) - .Add("config_id", "config456") - .Add("api_key", "key789") - .AddDeepObject("session_settings", sessionSettings) - .Build(); - - Assert.That(result, Does.StartWith("?")); - Assert.That(result, Does.Contain("access_token=token123")); - Assert.That(result, Does.Contain("allow_connection=true")); - Assert.That(result, Does.Contain("config_id=config456")); - Assert.That(result, Does.Contain("api_key=key789")); - Assert.That(result, Does.Contain("session_settings%5Bcustom_session_id%5D=session-123")); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserName%5D=John")); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserAge%5D=30")); - } - - [Test] - public void Builder_EmptyBuilder_ReturnsEmptyString() - { - var result = new QueryStringBuilder.Builder().Build(); - - Assert.That(result, Is.EqualTo(string.Empty)); - } - - [Test] - public void Builder_OnlyNullValues_ReturnsEmptyString() - { - var result = new QueryStringBuilder.Builder() - .Add("param1", null) - .Add("param2", null) - .AddDeepObject("settings", null) - .Build(); - - Assert.That(result, Is.EqualTo(string.Empty)); - } - - [Test] - public void Builder_Set_OverridesSingleValue() - { - var result = new QueryStringBuilder.Builder() - .Add("foo", "original") - .Set("foo", "override") - .Build(); - - Assert.That(result, Is.EqualTo("?foo=override")); - } - - [Test] - public void Builder_Set_OverridesMultipleValues() - { - var result = new QueryStringBuilder.Builder() - .Add("foo", "value1") - .Add("foo", "value2") - .Set("foo", "override") - .Build(); - - Assert.That(result, Is.EqualTo("?foo=override")); - } - - [Test] - public void Builder_Set_WithArray_CreatesMultipleParameters() - { - var result = new QueryStringBuilder.Builder() - .Add("foo", "original") - .Set("foo", new[] { "value1", "value2" }) - .Build(); - - Assert.That(result, Is.EqualTo("?foo=value1&foo=value2")); - } - - [Test] - public void Builder_Set_WithNull_RemovesParameter() - { - var result = new QueryStringBuilder.Builder() - .Add("foo", "original") - .Add("bar", "keep") - .Set("foo", null) - .Build(); - - Assert.That(result, Is.EqualTo("?bar=keep")); - } - - [Test] - public void Builder_MergeAdditional_WithSingleValues() - { - var additional = new List> - { - new("foo", "bar"), - new("baz", "qux"), - }; - - var result = new QueryStringBuilder.Builder() - .Add("existing", "value") - .MergeAdditional(additional) - .Build(); - - Assert.That(result, Does.Contain("existing=value")); - Assert.That(result, Does.Contain("foo=bar")); - Assert.That(result, Does.Contain("baz=qux")); - } - - [Test] - public void Builder_MergeAdditional_WithDuplicateKeys_CreatesList() - { - var additional = new List> - { - new("foo", "bar1"), - new("foo", "bar2"), - new("baz", "qux"), - }; - - var result = new QueryStringBuilder.Builder() - .Add("existing", "value") - .MergeAdditional(additional) - .Build(); - - Assert.That(result, Does.Contain("existing=value")); - Assert.That(result, Does.Contain("foo=bar1")); - Assert.That(result, Does.Contain("foo=bar2")); - Assert.That(result, Does.Contain("baz=qux")); - } - - [Test] - public void Builder_MergeAdditional_OverridesExistingParameters() - { - var additional = new List> { new("foo", "override") }; - - var result = new QueryStringBuilder.Builder() - .Add("foo", "original1") - .Add("foo", "original2") - .Add("bar", "keep") - .MergeAdditional(additional) - .Build(); - - Assert.That(result, Does.Contain("bar=keep")); - Assert.That(result, Does.Contain("foo=override")); - Assert.That(result, Does.Not.Contain("original1")); - Assert.That(result, Does.Not.Contain("original2")); - } - - [Test] - public void Builder_MergeAdditional_WithDuplicates_OverridesExisting() - { - var additional = new List> - { - new("foo", "new1"), - new("foo", "new2"), - new("foo", "new3"), - }; - - var result = new QueryStringBuilder.Builder() - .Add("foo", "original1") - .Add("foo", "original2") - .Add("bar", "keep") - .MergeAdditional(additional) - .Build(); - - Assert.That(result, Does.Contain("bar=keep")); - Assert.That(result, Does.Contain("foo=new1")); - Assert.That(result, Does.Contain("foo=new2")); - Assert.That(result, Does.Contain("foo=new3")); - Assert.That(result, Does.Not.Contain("original1")); - Assert.That(result, Does.Not.Contain("original2")); - } - - [Test] - public void Builder_MergeAdditional_WithNull_NoOp() - { - var result = new QueryStringBuilder.Builder() - .Add("foo", "value") - .MergeAdditional(null) - .Build(); - - Assert.That(result, Is.EqualTo("?foo=value")); - } - - [Test] - public void Builder_MergeAdditional_WithEmptyList_NoOp() - { - var additional = new List>(); - - var result = new QueryStringBuilder.Builder() - .Add("foo", "value") - .MergeAdditional(additional) - .Build(); - - Assert.That(result, Is.EqualTo("?foo=value")); - } - - [Test] - public void Builder_MergeAdditional_RealWorldScenario() - { - // SDK generates foo=foo1&foo=foo2 - var builder = new QueryStringBuilder.Builder() - .Add("foo", "foo1") - .Add("foo", "foo2") - .Add("bar", "baz"); - - // User provides foo=override in AdditionalQueryParameters - var additional = new List> { new("foo", "override") }; - - var result = builder.MergeAdditional(additional).Build(); - - // Result should be foo=override&bar=baz (user overrides SDK) - Assert.That(result, Does.Contain("bar=baz")); - Assert.That(result, Does.Contain("foo=override")); - Assert.That(result, Does.Not.Contain("foo1")); - Assert.That(result, Does.Not.Contain("foo2")); - } - - [Test] - public void Builder_MergeAdditional_UserProvidesMultipleValues() - { - // SDK generates no foo parameter - var builder = new QueryStringBuilder.Builder().Add("bar", "baz"); - - // User provides foo=bar1&foo=bar2 in AdditionalQueryParameters - var additional = new List> - { - new("foo", "bar1"), - new("foo", "bar2"), - }; - - var result = builder.MergeAdditional(additional).Build(); - - // Result should be bar=baz&foo=bar1&foo=bar2 - Assert.That(result, Does.Contain("bar=baz")); - Assert.That(result, Does.Contain("foo=bar1")); - Assert.That(result, Does.Contain("foo=bar2")); - } - - [Test] - public void Builder_Add_WithCollection_CreatesMultipleParameters() - { - var tags = new[] { "tag1", "tag2", "tag3" }; - var result = new QueryStringBuilder.Builder().Add("tag", tags).Build(); - - Assert.That(result, Does.Contain("tag=tag1")); - Assert.That(result, Does.Contain("tag=tag2")); - Assert.That(result, Does.Contain("tag=tag3")); - } - - [Test] - public void Builder_Add_WithList_CreatesMultipleParameters() - { - var ids = new List { 1, 2, 3 }; - var result = new QueryStringBuilder.Builder().Add("id", ids).Build(); - - Assert.That(result, Does.Contain("id=1")); - Assert.That(result, Does.Contain("id=2")); - Assert.That(result, Does.Contain("id=3")); - } - - [Test] - public void Builder_Set_WithCollection_ReplacesAllPreviousValues() - { - var result = new QueryStringBuilder.Builder() - .Add("id", 1) - .Add("id", 2) - .Set("id", new[] { 10, 20, 30 }) - .Build(); - - Assert.That(result, Does.Contain("id=10")); - Assert.That(result, Does.Contain("id=20")); - Assert.That(result, Does.Contain("id=30")); - // Check that old values are not present (use word boundaries to avoid false positives with id=10) - Assert.That(result, Does.Not.Contain("id=1&")); - Assert.That(result, Does.Not.Contain("id=2&")); - Assert.That(result, Does.Not.Contain("id=1?")); - Assert.That(result, Does.Not.Contain("id=2?")); - Assert.That(result, Does.Not.EndWith("id=1")); - Assert.That(result, Does.Not.EndWith("id=2")); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/QueryStringConverterTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/QueryStringConverterTests.cs deleted file mode 100644 index 3ba833f2b803..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/QueryStringConverterTests.cs +++ /dev/null @@ -1,158 +0,0 @@ -using NUnit.Framework; -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict.Test.Core; - -[TestFixture] -public class QueryStringConverterTests -{ - [Test] - public void ToQueryStringCollection_Form() - { - var obj = new - { - Name = "John", - Age = 30, - Address = new - { - Street = "123 Main St", - City = "Anytown", - Coordinates = new[] { 39.781721f, -89.650148f }, - }, - Tags = new[] { "Developer", "Blogger" }, - }; - var result = QueryStringConverter.ToForm(obj); - var expected = new List> - { - new("Name", "John"), - new("Age", "30"), - new("Address[Street]", "123 Main St"), - new("Address[City]", "Anytown"), - new("Address[Coordinates]", "39.78172,-89.65015"), - new("Tags", "Developer,Blogger"), - }; - Assert.That(result, Is.EqualTo(expected)); - } - - [Test] - public void ToQueryStringCollection_ExplodedForm() - { - var obj = new - { - Name = "John", - Age = 30, - Address = new - { - Street = "123 Main St", - City = "Anytown", - Coordinates = new[] { 39.781721f, -89.650148f }, - }, - Tags = new[] { "Developer", "Blogger" }, - }; - var result = QueryStringConverter.ToExplodedForm(obj); - var expected = new List> - { - new("Name", "John"), - new("Age", "30"), - new("Address[Street]", "123 Main St"), - new("Address[City]", "Anytown"), - new("Address[Coordinates]", "39.78172"), - new("Address[Coordinates]", "-89.65015"), - new("Tags", "Developer"), - new("Tags", "Blogger"), - }; - Assert.That(result, Is.EqualTo(expected)); - } - - [Test] - public void ToQueryStringCollection_DeepObject() - { - var obj = new - { - Name = "John", - Age = 30, - Address = new - { - Street = "123 Main St", - City = "Anytown", - Coordinates = new[] { 39.781721f, -89.650148f }, - }, - Tags = new[] { "Developer", "Blogger" }, - }; - var result = QueryStringConverter.ToDeepObject(obj); - var expected = new List> - { - new("Name", "John"), - new("Age", "30"), - new("Address[Street]", "123 Main St"), - new("Address[City]", "Anytown"), - new("Address[Coordinates][0]", "39.78172"), - new("Address[Coordinates][1]", "-89.65015"), - new("Tags[0]", "Developer"), - new("Tags[1]", "Blogger"), - }; - Assert.That(result, Is.EqualTo(expected)); - } - - [Test] - public void ToQueryStringCollection_OnString_ThrowsException() - { - var exception = Assert.Throws(() => - QueryStringConverter.ToForm("invalid") - ); - Assert.That( - exception.Message, - Is.EqualTo( - "Only objects can be converted to query string collections. Given type is String." - ) - ); - } - - [Test] - public void ToQueryStringCollection_OnArray_ThrowsException() - { - var exception = Assert.Throws(() => - QueryStringConverter.ToForm(Array.Empty()) - ); - Assert.That( - exception.Message, - Is.EqualTo( - "Only objects can be converted to query string collections. Given type is Array." - ) - ); - } - - [Test] - public void ToQueryStringCollection_DeepObject_WithPrefix() - { - var obj = new - { - custom_session_id = "my-id", - system_prompt = "You are helpful", - variables = new { name = "Alice", age = 25 }, - }; - var result = QueryStringConverter.ToDeepObject("session_settings", obj); - var expected = new List> - { - new("session_settings[custom_session_id]", "my-id"), - new("session_settings[system_prompt]", "You are helpful"), - new("session_settings[variables][name]", "Alice"), - new("session_settings[variables][age]", "25"), - }; - Assert.That(result, Is.EqualTo(expected)); - } - - [Test] - public void ToQueryStringCollection_ExplodedForm_WithPrefix() - { - var obj = new { Name = "John", Tags = new[] { "Developer", "Blogger" } }; - var result = QueryStringConverter.ToExplodedForm("user", obj); - var expected = new List> - { - new("user[Name]", "John"), - new("user[Tags]", "Developer"), - new("user[Tags]", "Blogger"), - }; - Assert.That(result, Is.EqualTo(expected)); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/RawClientTests/MultipartFormTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/RawClientTests/MultipartFormTests.cs deleted file mode 100644 index 4466e0ea0844..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/RawClientTests/MultipartFormTests.cs +++ /dev/null @@ -1,1121 +0,0 @@ -using global::System.Net.Http; -using global::System.Text; -using global::System.Text.Json.Serialization; -using NUnit.Framework; -using SeedCsharpNamespaceConflict.Core; -using SystemTask = global::System.Threading.Tasks.Task; - -namespace SeedCsharpNamespaceConflict.Test.Core.RawClientTests; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class MultipartFormTests -{ - private static SimpleObject _simpleObject = new(); - - private static string _simpleFormEncoded = - "meta=data&Date=2023-10-01&Time=12:00:00&Duration=01:00:00&Id=1a1bb98f-47c6-407b-9481-78476affe52a&IsActive=true&Count=42&Initial=A&Values=data,2023-10-01,12:00:00,01:00:00,1a1bb98f-47c6-407b-9481-78476affe52a,true,42,A"; - - private static string _simpleExplodedFormEncoded = - "meta=data&Date=2023-10-01&Time=12:00:00&Duration=01:00:00&Id=1a1bb98f-47c6-407b-9481-78476affe52a&IsActive=true&Count=42&Initial=A&Values=data&Values=2023-10-01&Values=12:00:00&Values=01:00:00&Values=1a1bb98f-47c6-407b-9481-78476affe52a&Values=true&Values=42&Values=A"; - - private static ComplexObject _complexObject = new(); - - private static string _complexJson = """ - { - "meta": "data", - "Nested": { - "foo": "value" - }, - "NestedDictionary": { - "key": { - "foo": "value" - } - }, - "ListOfObjects": [ - { - "foo": "value" - }, - { - "foo": "value2" - } - ], - "Date": "2023-10-01", - "Time": "12:00:00", - "Duration": "01:00:00", - "Id": "1a1bb98f-47c6-407b-9481-78476affe52a", - "IsActive": true, - "Count": 42, - "Initial": "A" - } - """; - - [Test] - public async SystemTask ShouldAddStringPart() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringPart("string", partInput); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=string - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringParts() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringParts("strings", [partInput, partInput]); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask GivenNull_ShouldNotAddStringPart() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringPart("string", null); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringParts_WithNullsInList() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringParts("strings", [partInput, null, partInput]); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringPart_WithContentType() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringPart("string", partInput, "text/xml"); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/xml - Content-Disposition: form-data; name=string - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringPart_WithContentTypeAndCharset() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringPart("string", partInput, "text/xml; charset=utf-8"); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/xml; charset=utf-8 - Content-Disposition: form-data; name=string - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringParts_WithContentType() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringParts("strings", [partInput, partInput], "text/xml"); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/xml - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary} - Content-Type: text/xml - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringParts_WithContentTypeAndCharset() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringParts( - "strings", - [partInput, partInput], - "text/xml; charset=utf-8" - ); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/xml; charset=utf-8 - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary} - Content-Type: text/xml; charset=utf-8 - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithFileName() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", file); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithoutFileName() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", partInput); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithContentType() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var file = new FileParameter - { - Stream = partInput, - FileName = "test.txt", - ContentType = "text/plain", - }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", file, "ignored-fallback-content-type"); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithContentTypeAndCharset() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var file = new FileParameter - { - Stream = partInput, - FileName = "test.txt", - ContentType = "text/plain; charset=utf-8", - }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", file, "ignored-fallback-content-type"); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain; charset=utf-8 - Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithFallbackContentType() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", file, "text/plain"); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithFallbackContentTypeAndCharset() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", file, "text/plain; charset=utf-8"); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain; charset=utf-8 - Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameters() - { - var (partInput1, partExpectedString1) = GetFileParameterTestData(); - var (partInput2, partExpectedString2) = GetFileParameterTestData(); - var file1 = new FileParameter { Stream = partInput1, FileName = "test1.txt" }; - var file2 = new FileParameter { Stream = partInput2, FileName = "test2.txt" }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterParts("file", [file1, file2]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file; filename=test1.txt; filename*=utf-8''test1.txt - - {partExpectedString1} - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file; filename=test2.txt; filename*=utf-8''test2.txt - - {partExpectedString2} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameters_WithNullsInList() - { - var (partInput1, partExpectedString1) = GetFileParameterTestData(); - var (partInput2, partExpectedString2) = GetFileParameterTestData(); - var file1 = new FileParameter { Stream = partInput1, FileName = "test1.txt" }; - var file2 = new FileParameter { Stream = partInput2, FileName = "test2.txt" }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterParts("file", [file1, null, file2]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file; filename=test1.txt; filename*=utf-8''test1.txt - - {partExpectedString1} - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file; filename=test2.txt; filename*=utf-8''test2.txt - - {partExpectedString2} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask GivenNull_ShouldNotAddFileParameter() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", null); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddJsonPart_WithComplexObject() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddJsonPart("object", _complexObject); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/json - Content-Disposition: form-data; name=object - - {_complexJson} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddJsonPart_WithComplexObjectList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddJsonParts("objects", [_complexObject, _complexObject]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/json - Content-Disposition: form-data; name=objects - - {_complexJson} - --{boundary} - Content-Type: application/json - Content-Disposition: form-data; name=objects - - {_complexJson} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask GivenNull_ShouldNotAddJsonPart() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddJsonPart("object", null); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddJsonParts_WithNullsInList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddJsonParts("objects", [_complexObject, null]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/json - Content-Disposition: form-data; name=objects - - {_complexJson} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddJsonParts_WithContentType() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddJsonParts("objects", [new { }], "application/json-patch+json"); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $$""" - --{{boundary}} - Content-Type: application/json-patch+json - Content-Disposition: form-data; name=objects - - {} - --{{boundary}}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedParts_WithSimpleObject() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedPart("object", _simpleObject); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=object - - {EscapeFormEncodedString(_simpleFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedParts_WithSimpleObjectList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedParts("objects", [_simpleObject, _simpleObject]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleFormEncoded)} - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldNotAddFormEncodedParts_WithNull() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedParts("object", null); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldNotAddFormEncodedParts_WithNullsInList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedParts("objects", [_simpleObject, null]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedPart_WithContentType() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedPart( - "objects", - new { foo = "bar" }, - "application/x-www-form-urlencoded" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedPart_WithContentTypeAndCharset() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedPart( - "objects", - new { foo = "bar" }, - "application/x-www-form-urlencoded; charset=utf-8" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded; charset=utf-8 - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedParts_WithContentType() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedParts( - "objects", - [new { foo = "bar" }], - "application/x-www-form-urlencoded" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedParts_WithContentTypeAndCharset() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedParts( - "objects", - [new { foo = "bar" }], - "application/x-www-form-urlencoded; charset=utf-8" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded; charset=utf-8 - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedParts_WithSimpleObject() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedPart("object", _simpleObject); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=object - - {EscapeFormEncodedString(_simpleExplodedFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedParts_WithSimpleObjectList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedParts("objects", [_simpleObject, _simpleObject]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleExplodedFormEncoded)} - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleExplodedFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldNotAddExplodedFormEncodedParts_WithNull() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedPart("object", null); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldNotAddExplodedFormEncodedParts_WithNullsInList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedParts("objects", [_simpleObject, null]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleExplodedFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedPart_WithContentType() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedPart( - "objects", - new { foo = "bar" }, - "application/x-www-form-urlencoded" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedPart_WithContentTypeAndCharset() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedPart( - "objects", - new { foo = "bar" }, - "application/x-www-form-urlencoded; charset=utf-8" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded; charset=utf-8 - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedParts_WithContentType() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedParts( - "objects", - [new { foo = "bar" }], - "application/x-www-form-urlencoded" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedParts_WithContentTypeAndCharset() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedParts( - "objects", - [new { foo = "bar" }], - "application/x-www-form-urlencoded; charset=utf-8" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded; charset=utf-8 - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - private static string EscapeFormEncodedString(string input) - { - return string.Join( - "&", - input - .Split('&') - .Select(x => x.Split('=')) - .Select(x => $"{Uri.EscapeDataString(x[0])}={Uri.EscapeDataString(x[1])}") - ); - } - - private static string GetBoundary(MultipartFormDataContent content) - { - return content - .Headers.ContentType?.Parameters.Single(p => - p.Name.Equals("boundary", StringComparison.OrdinalIgnoreCase) - ) - .Value?.Trim('"') - ?? throw new global::System.Exception("Boundary not found"); - } - - private static SeedCsharpNamespaceConflict.Core.MultipartFormRequest CreateMultipartFormRequest() - { - return new SeedCsharpNamespaceConflict.Core.MultipartFormRequest - { - BaseUrl = "https://localhost", - Method = HttpMethod.Post, - Path = "", - }; - } - - private static (Stream partInput, string partExpectedString) GetFileParameterTestData() - { - const string partExpectedString = "file content"; - var partInput = new MemoryStream(Encoding.Default.GetBytes(partExpectedString)); - return (partInput, partExpectedString); - } - - private class SimpleObject - { - [JsonPropertyName("meta")] - public string Meta { get; set; } = "data"; - public DateOnly Date { get; set; } = DateOnly.Parse("2023-10-01"); - public TimeOnly Time { get; set; } = TimeOnly.Parse("12:00:00"); - public TimeSpan Duration { get; set; } = TimeSpan.FromHours(1); - public Guid Id { get; set; } = Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"); - public bool IsActive { get; set; } = true; - public int Count { get; set; } = 42; - public char Initial { get; set; } = 'A'; - public IEnumerable Values { get; set; } = - [ - "data", - DateOnly.Parse("2023-10-01"), - TimeOnly.Parse("12:00:00"), - TimeSpan.FromHours(1), - Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"), - true, - 42, - 'A', - ]; - } - - private class ComplexObject - { - [JsonPropertyName("meta")] - public string Meta { get; set; } = "data"; - - public object Nested { get; set; } = new { foo = "value" }; - - public Dictionary NestedDictionary { get; set; } = - new() { { "key", new { foo = "value" } } }; - - public IEnumerable ListOfObjects { get; set; } = - new List { new { foo = "value" }, new { foo = "value2" } }; - - public DateOnly Date { get; set; } = DateOnly.Parse("2023-10-01"); - public TimeOnly Time { get; set; } = TimeOnly.Parse("12:00:00"); - public TimeSpan Duration { get; set; } = TimeSpan.FromHours(1); - public Guid Id { get; set; } = Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"); - public bool IsActive { get; set; } = true; - public int Count { get; set; } = 42; - public char Initial { get; set; } = 'A'; - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/RawClientTests/QueryParameterTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/RawClientTests/QueryParameterTests.cs deleted file mode 100644 index 15639087e360..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/RawClientTests/QueryParameterTests.cs +++ /dev/null @@ -1,108 +0,0 @@ -using NUnit.Framework; -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict.Test.Core.RawClientTests; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class QueryParameterTests -{ - [Test] - public void QueryParameters_BasicParameters() - { - var queryString = new QueryStringBuilder.Builder() - .Add("foo", "bar") - .Add("baz", "qux") - .Build(); - - Assert.That(queryString, Is.EqualTo("?foo=bar&baz=qux")); - } - - [Test] - public void QueryParameters_SpecialCharacterEscaping() - { - var queryString = new QueryStringBuilder.Builder() - .Add("email", "bob+test@example.com") - .Add("%Complete", "100") - .Add("space test", "hello world") - .Build(); - - Assert.That(queryString, Does.Contain("email=bob%2Btest%40example.com")); - Assert.That(queryString, Does.Contain("%25Complete=100")); - Assert.That(queryString, Does.Contain("space%20test=hello%20world")); - } - - [Test] - public void QueryParameters_MergeAdditionalParameters() - { - var queryString = new QueryStringBuilder.Builder() - .Add("sdk", "param") - .MergeAdditional(new List> { new("user", "value") }) - .Build(); - - Assert.That(queryString, Does.Contain("sdk=param")); - Assert.That(queryString, Does.Contain("user=value")); - } - - [Test] - public void QueryParameters_AdditionalOverridesSdk() - { - var queryString = new QueryStringBuilder.Builder() - .Add("foo", "sdk_value") - .MergeAdditional(new List> { new("foo", "user_override") }) - .Build(); - - Assert.That(queryString, Does.Contain("foo=user_override")); - Assert.That(queryString, Does.Not.Contain("sdk_value")); - } - - [Test] - public void QueryParameters_AdditionalMultipleValues() - { - var queryString = new QueryStringBuilder.Builder() - .Add("foo", "sdk_value") - .MergeAdditional( - new List> { new("foo", "user1"), new("foo", "user2") } - ) - .Build(); - - Assert.That(queryString, Does.Contain("foo=user1")); - Assert.That(queryString, Does.Contain("foo=user2")); - Assert.That(queryString, Does.Not.Contain("sdk_value")); - } - - [Test] - public void QueryParameters_OnlyAdditionalParameters() - { - var queryString = new QueryStringBuilder.Builder() - .MergeAdditional( - new List> { new("foo", "bar"), new("baz", "qux") } - ) - .Build(); - - Assert.That(queryString, Does.Contain("foo=bar")); - Assert.That(queryString, Does.Contain("baz=qux")); - } - - [Test] - public void QueryParameters_EmptyAdditionalParameters() - { - var queryString = new QueryStringBuilder.Builder() - .Add("foo", "bar") - .MergeAdditional(new List>()) - .Build(); - - Assert.That(queryString, Is.EqualTo("?foo=bar")); - } - - [Test] - public void QueryParameters_NullAdditionalParameters() - { - var queryString = new QueryStringBuilder.Builder() - .Add("foo", "bar") - .MergeAdditional(null) - .Build(); - - Assert.That(queryString, Is.EqualTo("?foo=bar")); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/RawClientTests/RetriesTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/RawClientTests/RetriesTests.cs deleted file mode 100644 index ddcbf9d3f6e3..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/RawClientTests/RetriesTests.cs +++ /dev/null @@ -1,406 +0,0 @@ -using global::System.Net.Http; -using global::System.Text.Json; -using NUnit.Framework; -using SeedCsharpNamespaceConflict.Core; -using WireMock.Server; -using SystemTask = global::System.Threading.Tasks.Task; -using WireMockRequest = WireMock.RequestBuilders.Request; -using WireMockResponse = WireMock.ResponseBuilders.Response; - -namespace SeedCsharpNamespaceConflict.Test.Core.RawClientTests; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class RetriesTests -{ - private const int MaxRetries = 3; - private WireMockServer _server; - private HttpClient _httpClient; - private RawClient _rawClient; - private string _baseUrl; - - [SetUp] - public void SetUp() - { - _server = WireMockServer.Start(); - _baseUrl = _server.Url ?? ""; - _httpClient = new HttpClient { BaseAddress = new Uri(_baseUrl) }; - _rawClient = new RawClient( - new ClientOptions { HttpClient = _httpClient, MaxRetries = MaxRetries } - ) - { - BaseRetryDelay = 0, - }; - } - - [Test] - [TestCase(408)] - [TestCase(429)] - [TestCase(500)] - [TestCase(504)] - public async SystemTask SendRequestAsync_ShouldRetry_OnRetryableStatusCodes(int statusCode) - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("Retry") - .WillSetStateTo("Server Error") - .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("Retry") - .WhenStateIs("Server Error") - .WillSetStateTo("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("Retry") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new EmptyRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Get, - Path = "/test", - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - using (Assert.EnterMultipleScope()) - { - Assert.That(content, Is.EqualTo("Success")); - - Assert.That(_server.LogEntries, Has.Count.EqualTo(MaxRetries)); - } - } - - [Test] - [TestCase(400)] - [TestCase(409)] - public async SystemTask SendRequestAsync_ShouldRetry_OnNonRetryableStatusCodes(int statusCode) - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("Retry") - .WillSetStateTo("Server Error") - .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode).WithBody("Failure")); - - var request = new JsonRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Get, - Path = "/test", - Body = new { }, - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(statusCode)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Failure")); - - Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldNotRetry_WithStreamRequest() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("Retry") - .WillSetStateTo("Server Error") - .RespondWith(WireMockResponse.Create().WithStatusCode(429).WithBody("Failure")); - - var request = new StreamRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Post, - Path = "/test", - Body = new MemoryStream(), - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(429)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Failure")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldNotRetry_WithMultiPartFormRequest_WithStream() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("Retry") - .WillSetStateTo("Server Error") - .RespondWith(WireMockResponse.Create().WithStatusCode(429).WithBody("Failure")); - - var request = new SeedCsharpNamespaceConflict.Core.MultipartFormRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Post, - Path = "/test", - }; - request.AddFileParameterPart("file", new MemoryStream()); - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(429)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Failure")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldRetry_WithMultiPartFormRequest_WithoutStream() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("Retry") - .WillSetStateTo("Server Error") - .RespondWith(WireMockResponse.Create().WithStatusCode(429)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("Retry") - .WhenStateIs("Server Error") - .WillSetStateTo("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(429)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("Retry") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new SeedCsharpNamespaceConflict.Core.MultipartFormRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Post, - Path = "/test", - }; - request.AddJsonPart("object", new { }); - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(MaxRetries)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldRespectRetryAfterHeader_WithSecondsValue() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RetryAfter") - .WillSetStateTo("Success") - .RespondWith( - WireMockResponse.Create().WithStatusCode(429).WithHeader("Retry-After", "1") - ); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RetryAfter") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new EmptyRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Get, - Path = "/test", - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldRespectRetryAfterHeader_WithHttpDateValue() - { - var retryAfterDate = DateTimeOffset.UtcNow.AddSeconds(1).ToString("R"); - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RetryAfterDate") - .WillSetStateTo("Success") - .RespondWith( - WireMockResponse - .Create() - .WithStatusCode(429) - .WithHeader("Retry-After", retryAfterDate) - ); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RetryAfterDate") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new EmptyRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Get, - Path = "/test", - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldRespectXRateLimitResetHeader() - { - var resetTime = DateTimeOffset.UtcNow.AddSeconds(1).ToUnixTimeSeconds().ToString(); - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RateLimitReset") - .WillSetStateTo("Success") - .RespondWith( - WireMockResponse - .Create() - .WithStatusCode(429) - .WithHeader("X-RateLimit-Reset", resetTime) - ); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RateLimitReset") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new EmptyRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Get, - Path = "/test", - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldPreserveJsonBody_OnRetry() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("RetryWithBody") - .WillSetStateTo("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(500)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("RetryWithBody") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new JsonRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Post, - Path = "/test", - Body = new { key = "value" }, - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - using (Assert.EnterMultipleScope()) - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); - - // Verify the retried request preserved the JSON body (compare parsed to ignore formatting differences) - var retriedEntry = _server.LogEntries.ElementAt(1); - using var actualJson = JsonDocument.Parse(retriedEntry.RequestMessage.Body!); - Assert.That(actualJson.RootElement.GetProperty("key").GetString(), Is.EqualTo("value")); - } - } - - [Test] - public async SystemTask SendRequestAsync_ShouldPreserveMultipartBody_OnRetry() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("RetryMultipart") - .WillSetStateTo("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(500)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("RetryMultipart") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new SeedCsharpNamespaceConflict.Core.MultipartFormRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Post, - Path = "/test", - }; - request.AddJsonPart("object", new { key = "value" }); - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - using (Assert.EnterMultipleScope()) - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); - - // Verify the retried request preserved the multipart body (check key/value presence to ignore formatting differences) - var retriedEntry = _server.LogEntries.ElementAt(1); - Assert.That(retriedEntry.RequestMessage.Body, Does.Contain("\"key\"")); - Assert.That(retriedEntry.RequestMessage.Body, Does.Contain("\"value\"")); - } - } - - [TearDown] - public void TearDown() - { - _server.Dispose(); - _httpClient.Dispose(); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/WithRawResponseTests.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/WithRawResponseTests.cs deleted file mode 100644 index 49a1640686e1..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Core/WithRawResponseTests.cs +++ /dev/null @@ -1,269 +0,0 @@ -using global::System.Net; -using global::System.Net.Http.Headers; -using NUnit.Framework; -using SeedCsharpNamespaceConflict; -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict.Test.Core; - -[TestFixture] -public class WithRawResponseTests -{ - [Test] - public async global::System.Threading.Tasks.Task WithRawResponseTask_DirectAwait_ReturnsData() - { - // Arrange - var expectedData = "test-data"; - var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); - - // Act - var result = await task; - - // Assert - Assert.That(result, Is.EqualTo(expectedData)); - } - - [Test] - public async global::System.Threading.Tasks.Task WithRawResponseTask_WithRawResponse_ReturnsDataAndMetadata() - { - // Arrange - var expectedData = "test-data"; - var expectedStatusCode = HttpStatusCode.Created; - var task = CreateWithRawResponseTask(expectedData, expectedStatusCode); - - // Act - var result = await task.WithRawResponse(); - - // Assert - Assert.That(result.Data, Is.EqualTo(expectedData)); - Assert.That(result.RawResponse.StatusCode, Is.EqualTo(expectedStatusCode)); - Assert.That(result.RawResponse.Url, Is.Not.Null); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValue_CaseInsensitive() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Headers.Add("X-Request-Id", "12345"); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act & Assert - Assert.That(headers.TryGetValue("X-Request-Id", out var value), Is.True); - Assert.That(value, Is.EqualTo("12345")); - - Assert.That(headers.TryGetValue("x-request-id", out value), Is.True); - Assert.That(value, Is.EqualTo("12345")); - - Assert.That(headers.TryGetValue("X-REQUEST-ID", out value), Is.True); - Assert.That(value, Is.EqualTo("12345")); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValues_ReturnsMultipleValues() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Headers.Add("Set-Cookie", new[] { "cookie1=value1", "cookie2=value2" }); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var success = headers.TryGetValues("Set-Cookie", out var values); - - // Assert - Assert.That(success, Is.True); - Assert.That(values, Is.Not.Null); - Assert.That(values!.Count(), Is.EqualTo(2)); - Assert.That(values, Does.Contain("cookie1=value1")); - Assert.That(values, Does.Contain("cookie2=value2")); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_ContentType_ReturnsValue() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Content = new StringContent( - "{}", - global::System.Text.Encoding.UTF8, - "application/json" - ); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var contentType = headers.ContentType; - - // Assert - Assert.That(contentType, Is.Not.Null); - Assert.That(contentType, Does.Contain("application/json")); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_ContentLength_ReturnsValue() - { - // Arrange - var content = "test content"; - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Content = new StringContent(content); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var contentLength = headers.ContentLength; - - // Assert - Assert.That(contentLength, Is.Not.Null); - Assert.That(contentLength, Is.GreaterThan(0)); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_Contains_ReturnsTrueForExistingHeader() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Headers.Add("X-Custom-Header", "value"); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act & Assert - Assert.That(headers.Contains("X-Custom-Header"), Is.True); - Assert.That(headers.Contains("x-custom-header"), Is.True); - Assert.That(headers.Contains("NonExistent"), Is.False); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_Enumeration_IncludesAllHeaders() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Headers.Add("X-Header-1", "value1"); - response.Headers.Add("X-Header-2", "value2"); - response.Content = new StringContent("test"); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var allHeaders = headers.ToList(); - - // Assert - Assert.That(allHeaders.Count, Is.GreaterThan(0)); - Assert.That(allHeaders.Any(h => h.Name == "X-Header-1"), Is.True); - Assert.That(allHeaders.Any(h => h.Name == "X-Header-2"), Is.True); - } - - [Test] - public async global::System.Threading.Tasks.Task WithRawResponseTask_ErrorStatusCode_StillReturnsMetadata() - { - // Arrange - var expectedData = "error-data"; - var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.BadRequest); - - // Act - var result = await task.WithRawResponse(); - - // Assert - Assert.That(result.Data, Is.EqualTo(expectedData)); - Assert.That(result.RawResponse.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async global::System.Threading.Tasks.Task WithRawResponseTask_Url_IsPreserved() - { - // Arrange - var expectedUrl = new Uri("https://api.example.com/users/123"); - var task = CreateWithRawResponseTask("data", HttpStatusCode.OK, expectedUrl); - - // Act - var result = await task.WithRawResponse(); - - // Assert - Assert.That(result.RawResponse.Url, Is.EqualTo(expectedUrl)); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValue_NonExistentHeader_ReturnsFalse() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var success = headers.TryGetValue("X-NonExistent", out var value); - - // Assert - Assert.That(success, Is.False); - Assert.That(value, Is.Null); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValues_NonExistentHeader_ReturnsFalse() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var success = headers.TryGetValues("X-NonExistent", out var values); - - // Assert - Assert.That(success, Is.False); - Assert.That(values, Is.Null); - } - - [Test] - public async global::System.Threading.Tasks.Task WithRawResponseTask_ImplicitConversion_ToTask() - { - // Arrange - var expectedData = "test-data"; - var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); - - // Act - implicitly convert to Task - global::System.Threading.Tasks.Task regularTask = task; - var result = await regularTask; - - // Assert - Assert.That(result, Is.EqualTo(expectedData)); - } - - [Test] - public void WithRawResponseTask_ImplicitConversion_AssignToTaskVariable() - { - // Arrange - var expectedData = "test-data"; - var wrappedTask = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); - - // Act - assign to Task variable - global::System.Threading.Tasks.Task regularTask = wrappedTask; - - // Assert - Assert.That(regularTask, Is.Not.Null); - Assert.That(regularTask, Is.InstanceOf>()); - } - - // Helper methods - - private static WithRawResponseTask CreateWithRawResponseTask( - T data, - HttpStatusCode statusCode, - Uri? url = null - ) - { - url ??= new Uri("https://api.example.com/test"); - using var httpResponse = CreateHttpResponse(statusCode); - httpResponse.RequestMessage = new HttpRequestMessage(HttpMethod.Get, url); - - var rawResponse = new RawResponse - { - StatusCode = statusCode, - Url = url, - Headers = ResponseHeaders.FromHttpResponseMessage(httpResponse), - }; - - var withRawResponse = new WithRawResponse { Data = data, RawResponse = rawResponse }; - - var task = global::System.Threading.Tasks.Task.FromResult(withRawResponse); - return new WithRawResponseTask(task); - } - - private static HttpResponseMessage CreateHttpResponse(HttpStatusCode statusCode) - { - return new HttpResponseMessage(statusCode) { Content = new StringContent("") }; - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/SeedCsharpNamespaceConflict.Test.Custom.props b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/SeedCsharpNamespaceConflict.Test.Custom.props deleted file mode 100644 index aac9b5020d80..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/SeedCsharpNamespaceConflict.Test.Custom.props +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/SeedCsharpNamespaceConflict.Test.csproj b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/SeedCsharpNamespaceConflict.Test.csproj deleted file mode 100644 index 0b5ca4815fe8..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/SeedCsharpNamespaceConflict.Test.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - net8.0 - 12 - enable - enable - false - true - true - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/TestClient.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/TestClient.cs deleted file mode 100644 index 50fc00871a16..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/TestClient.cs +++ /dev/null @@ -1,6 +0,0 @@ -using NUnit.Framework; - -namespace SeedCsharpNamespaceConflict.Test; - -[TestFixture] -public class TestClient; diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Unit/MockServer/BaseMockServerTest.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Unit/MockServer/BaseMockServerTest.cs deleted file mode 100644 index 48f12a42bd37..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Unit/MockServer/BaseMockServerTest.cs +++ /dev/null @@ -1,37 +0,0 @@ -using NUnit.Framework; -using SeedCsharpNamespaceConflict; -using WireMock.Logging; -using WireMock.Server; -using WireMock.Settings; - -namespace SeedCsharpNamespaceConflict.Test.Unit.MockServer; - -public class BaseMockServerTest -{ - protected WireMockServer Server { get; set; } = null!; - - protected SeedCsharpNamespaceConflictClient Client { get; set; } = null!; - - protected RequestOptions RequestOptions { get; set; } = new(); - - [OneTimeSetUp] - public void GlobalSetup() - { - // Start the WireMock server - Server = WireMockServer.Start( - new WireMockServerSettings { Logger = new WireMockConsoleLogger() } - ); - - // Initialize the Client - Client = new SeedCsharpNamespaceConflictClient( - clientOptions: new ClientOptions { BaseUrl = Server.Urls[0], MaxRetries = 0 } - ); - } - - [OneTimeTearDown] - public void GlobalTeardown() - { - Server.Stop(); - Server.Dispose(); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Unit/MockServer/Tasktest/HelloTest.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Unit/MockServer/Tasktest/HelloTest.cs deleted file mode 100644 index 8987b08f8f0e..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Unit/MockServer/Tasktest/HelloTest.cs +++ /dev/null @@ -1,19 +0,0 @@ -using NUnit.Framework; -using SeedCsharpNamespaceConflict.Test.Unit.MockServer; - -namespace SeedCsharpNamespaceConflict.Test.Unit.MockServer.Tasktest; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class HelloTest : BaseMockServerTest -{ - [NUnit.Framework.Test] - public void MockServerTest() - { - Server - .Given(WireMock.RequestBuilders.Request.Create().WithPath("/hello").UsingGet()) - .RespondWith(WireMock.ResponseBuilders.Response.Create().WithStatusCode(200)); - - Assert.DoesNotThrowAsync(async () => await Client.Tasktest.HelloAsync()); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/AdditionalPropertiesComparer.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/AdditionalPropertiesComparer.cs deleted file mode 100644 index 6a8e44542c34..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/AdditionalPropertiesComparer.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System.Text.Json; -using NUnit.Framework.Constraints; -using SeedCsharpNamespaceConflict; -using SeedCsharpNamespaceConflict.Core; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle AdditionalProperties values. -/// -public static class AdditionalPropertiesComparerExtensions -{ - /// - /// Modifies the EqualConstraint to handle AdditionalProperties instances by comparing their - /// serialized JSON representations. This handles the type mismatch between native C# types - /// and JsonElement values that occur when comparing manually constructed objects with - /// deserialized objects. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingAdditionalPropertiesComparer(this EqualConstraint constraint) - { - constraint.Using( - (x, y) => - { - if (x.Count != y.Count) - { - return false; - } - - foreach (var key in x.Keys) - { - if (!y.ContainsKey(key)) - { - return false; - } - - var xElement = JsonUtils.SerializeToElement(x[key]); - var yElement = JsonUtils.SerializeToElement(y[key]); - - if (!JsonElementsAreEqual(xElement, yElement)) - { - return false; - } - } - - return true; - } - ); - - return constraint; - } - - private static bool JsonElementsAreEqual(JsonElement x, JsonElement y) - { - if (x.ValueKind != y.ValueKind) - { - return false; - } - - return x.ValueKind switch - { - JsonValueKind.Object => CompareJsonObjects(x, y), - JsonValueKind.Array => CompareJsonArrays(x, y), - JsonValueKind.String => x.GetString() == y.GetString(), - JsonValueKind.Number => x.GetDecimal() == y.GetDecimal(), - JsonValueKind.True => true, - JsonValueKind.False => true, - JsonValueKind.Null => true, - _ => false, - }; - } - - private static bool CompareJsonObjects(JsonElement x, JsonElement y) - { - var xProps = new Dictionary(); - var yProps = new Dictionary(); - - foreach (var prop in x.EnumerateObject()) - xProps[prop.Name] = prop.Value; - - foreach (var prop in y.EnumerateObject()) - yProps[prop.Name] = prop.Value; - - if (xProps.Count != yProps.Count) - { - return false; - } - - foreach (var key in xProps.Keys) - { - if (!yProps.ContainsKey(key)) - { - return false; - } - - if (!JsonElementsAreEqual(xProps[key], yProps[key])) - { - return false; - } - } - - return true; - } - - private static bool CompareJsonArrays(JsonElement x, JsonElement y) - { - var xArray = x.EnumerateArray().ToList(); - var yArray = y.EnumerateArray().ToList(); - - if (xArray.Count != yArray.Count) - { - return false; - } - - for (var i = 0; i < xArray.Count; i++) - { - if (!JsonElementsAreEqual(xArray[i], yArray[i])) - { - return false; - } - } - - return true; - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/JsonAssert.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/JsonAssert.cs deleted file mode 100644 index de5001369c6f..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/JsonAssert.cs +++ /dev/null @@ -1,19 +0,0 @@ -using global::System.Text.Json; -using NUnit.Framework; -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict.Test.Utils; - -internal static class JsonAssert -{ - /// - /// Asserts that the serialized JSON of an object equals the expected JSON string. - /// Uses JsonElement comparison for reliable deep equality of collections and union types. - /// - internal static void AreEqual(object actual, string expectedJson) - { - var actualElement = JsonUtils.SerializeToElement(actual); - var expectedElement = JsonUtils.Deserialize(expectedJson); - Assert.That(actualElement, Is.EqualTo(expectedElement).UsingJsonElementComparer()); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/JsonElementComparer.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/JsonElementComparer.cs deleted file mode 100644 index a37ef402c1ac..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/JsonElementComparer.cs +++ /dev/null @@ -1,236 +0,0 @@ -using global::System.Text.Json; -using NUnit.Framework.Constraints; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle JsonElement objects. -/// -public static class JsonElementComparerExtensions -{ - /// - /// Extension method for comparing JsonElement objects in NUnit tests. - /// Property order doesn't matter, but array order does matter. - /// Includes special handling for DateTime string formats. - /// - /// The Is.EqualTo() constraint instance. - /// A constraint that can compare JsonElements with detailed diffs. - public static EqualConstraint UsingJsonElementComparer(this EqualConstraint constraint) - { - return constraint.Using(new JsonElementComparer()); - } -} - -/// -/// Equality comparer for JsonElement with detailed reporting. -/// Property order doesn't matter, but array order does matter. -/// Now includes special handling for DateTime string formats with improved null handling. -/// -public class JsonElementComparer : IEqualityComparer -{ - private string _failurePath = string.Empty; - - /// - public bool Equals(JsonElement x, JsonElement y) - { - _failurePath = string.Empty; - return CompareJsonElements(x, y, string.Empty); - } - - /// - public int GetHashCode(JsonElement obj) - { - return JsonSerializer.Serialize(obj).GetHashCode(); - } - - private bool CompareJsonElements(JsonElement x, JsonElement y, string path) - { - // If value kinds don't match, they're not equivalent - if (x.ValueKind != y.ValueKind) - { - _failurePath = $"{path}: Expected {x.ValueKind} but got {y.ValueKind}"; - return false; - } - - switch (x.ValueKind) - { - case JsonValueKind.Object: - return CompareJsonObjects(x, y, path); - - case JsonValueKind.Array: - return CompareJsonArraysInOrder(x, y, path); - - case JsonValueKind.String: - string? xStr = x.GetString(); - string? yStr = y.GetString(); - - // Handle null strings - if (xStr is null && yStr is null) - return true; - - if (xStr is null || yStr is null) - { - _failurePath = - $"{path}: Expected {(xStr is null ? "null" : $"\"{xStr}\"")} but got {(yStr is null ? "null" : $"\"{yStr}\"")}"; - return false; - } - - // Check if they are identical strings - if (xStr == yStr) - return true; - - // Try to handle DateTime strings - if (IsLikelyDateTimeString(xStr) && IsLikelyDateTimeString(yStr)) - { - if (AreEquivalentDateTimeStrings(xStr, yStr)) - return true; - } - - _failurePath = $"{path}: Expected \"{xStr}\" but got \"{yStr}\""; - return false; - - case JsonValueKind.Number: - if (x.GetDecimal() != y.GetDecimal()) - { - _failurePath = $"{path}: Expected {x.GetDecimal()} but got {y.GetDecimal()}"; - return false; - } - - return true; - - case JsonValueKind.True: - case JsonValueKind.False: - if (x.GetBoolean() != y.GetBoolean()) - { - _failurePath = $"{path}: Expected {x.GetBoolean()} but got {y.GetBoolean()}"; - return false; - } - - return true; - - case JsonValueKind.Null: - return true; - - default: - _failurePath = $"{path}: Unsupported JsonValueKind {x.ValueKind}"; - return false; - } - } - - private bool IsLikelyDateTimeString(string? str) - { - // Simple heuristic to identify likely ISO date time strings - return str is not null - && (str.Contains("T") && (str.EndsWith("Z") || str.Contains("+") || str.Contains("-"))); - } - - private bool AreEquivalentDateTimeStrings(string str1, string str2) - { - // Try to parse both as DateTime - if (DateTime.TryParse(str1, out DateTime dt1) && DateTime.TryParse(str2, out DateTime dt2)) - { - return dt1 == dt2; - } - - return false; - } - - private bool CompareJsonObjects(JsonElement x, JsonElement y, string path) - { - // Create dictionaries for both JSON objects - var xProps = new Dictionary(); - var yProps = new Dictionary(); - - foreach (var prop in x.EnumerateObject()) - xProps[prop.Name] = prop.Value; - - foreach (var prop in y.EnumerateObject()) - yProps[prop.Name] = prop.Value; - - // Check if all properties in x exist in y - foreach (var key in xProps.Keys) - { - if (!yProps.ContainsKey(key)) - { - _failurePath = $"{path}: Missing property '{key}'"; - return false; - } - } - - // Check if y has extra properties - foreach (var key in yProps.Keys) - { - if (!xProps.ContainsKey(key)) - { - _failurePath = $"{path}: Unexpected property '{key}'"; - return false; - } - } - - // Compare each property value - foreach (var key in xProps.Keys) - { - var propPath = string.IsNullOrEmpty(path) ? key : $"{path}.{key}"; - if (!CompareJsonElements(xProps[key], yProps[key], propPath)) - { - return false; - } - } - - return true; - } - - private bool CompareJsonArraysInOrder(JsonElement x, JsonElement y, string path) - { - var xArray = x.EnumerateArray(); - var yArray = y.EnumerateArray(); - - // Count x elements - var xCount = 0; - var xElements = new List(); - foreach (var item in xArray) - { - xElements.Add(item); - xCount++; - } - - // Count y elements - var yCount = 0; - var yElements = new List(); - foreach (var item in yArray) - { - yElements.Add(item); - yCount++; - } - - // Check if counts match - if (xCount != yCount) - { - _failurePath = $"{path}: Expected {xCount} items but found {yCount}"; - return false; - } - - // Compare elements in order - for (var i = 0; i < xCount; i++) - { - var itemPath = $"{path}[{i}]"; - if (!CompareJsonElements(xElements[i], yElements[i], itemPath)) - { - return false; - } - } - - return true; - } - - /// - public override string ToString() - { - if (!string.IsNullOrEmpty(_failurePath)) - { - return $"JSON comparison failed at {_failurePath}"; - } - - return "JsonElementEqualityComparer"; - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/NUnitExtensions.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/NUnitExtensions.cs deleted file mode 100644 index 170795cb2c3e..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/NUnitExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using NUnit.Framework.Constraints; - -namespace NUnit.Framework; - -/// -/// Extensions for NUnit constraints. -/// -public static class NUnitExtensions -{ - /// - /// Modifies the EqualConstraint to use our own set of default comparers. - /// - /// - /// - public static EqualConstraint UsingDefaults(this EqualConstraint constraint) => - constraint - .UsingPropertiesComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingOneOfComparer() - .UsingJsonElementComparer() - .UsingOptionalComparer() - .UsingAdditionalPropertiesComparer(); -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/OneOfComparer.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/OneOfComparer.cs deleted file mode 100644 index 767439174363..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/OneOfComparer.cs +++ /dev/null @@ -1,86 +0,0 @@ -using NUnit.Framework.Constraints; -using OneOf; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle OneOf values. -/// -public static class EqualConstraintExtensions -{ - /// - /// Modifies the EqualConstraint to handle OneOf instances by comparing their inner values. - /// This works alongside other comparison modifiers like UsingPropertiesComparer. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingOneOfComparer(this EqualConstraint constraint) - { - // Register a comparer factory for IOneOf types - constraint.Using( - (x, y) => - { - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (x.Value is null && y.Value is null) - { - return true; - } - - if (x.Value is null) - { - return false; - } - - var propertiesComparer = new NUnitEqualityComparer(); - var tolerance = Tolerance.Default; - propertiesComparer.CompareProperties = true; - // Add OneOf comparer to handle nested OneOf values (e.g., in Lists) - propertiesComparer.ExternalComparers.Add( - new OneOfEqualityAdapter(propertiesComparer) - ); - return propertiesComparer.AreEqual(x.Value, y.Value, ref tolerance); - } - ); - - return constraint; - } - - /// - /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. - /// This enables recursive comparison of nested OneOf values. - /// - private class OneOfEqualityAdapter : EqualityAdapter - { - private readonly NUnitEqualityComparer _comparer; - - public OneOfEqualityAdapter(NUnitEqualityComparer comparer) - { - _comparer = comparer; - } - - public override bool CanCompare(object? x, object? y) - { - return x is IOneOf && y is IOneOf; - } - - public override bool AreEqual(object? x, object? y) - { - var oneOfX = (IOneOf?)x; - var oneOfY = (IOneOf?)y; - - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (oneOfX?.Value is null && oneOfY?.Value is null) - { - return true; - } - - if (oneOfX?.Value is null || oneOfY?.Value is null) - { - return false; - } - - var tolerance = Tolerance.Default; - return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); - } - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/OptionalComparer.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/OptionalComparer.cs deleted file mode 100644 index 94dd200a7dc0..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/OptionalComparer.cs +++ /dev/null @@ -1,104 +0,0 @@ -using NUnit.Framework.Constraints; -using OneOf; -using SeedCsharpNamespaceConflict.Core; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle Optional values. -/// -public static class OptionalComparerExtensions -{ - /// - /// Modifies the EqualConstraint to handle Optional instances by comparing their IsDefined state and inner values. - /// This works alongside other comparison modifiers like UsingPropertiesComparer. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingOptionalComparer(this EqualConstraint constraint) - { - // Register a comparer factory for IOptional types - constraint.Using( - (x, y) => - { - // Both must have the same IsDefined state - if (x.IsDefined != y.IsDefined) - { - return false; - } - - // If both are undefined, they're equal - if (!x.IsDefined) - { - return true; - } - - // Both are defined, compare their boxed values - var xValue = x.GetBoxedValue(); - var yValue = y.GetBoxedValue(); - - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (xValue is null && yValue is null) - { - return true; - } - - if (xValue is null || yValue is null) - { - return false; - } - - // Use NUnit's property comparer for the inner values - var propertiesComparer = new NUnitEqualityComparer(); - var tolerance = Tolerance.Default; - propertiesComparer.CompareProperties = true; - // Add OneOf comparer to handle nested OneOf values (e.g., in Lists within Optional) - propertiesComparer.ExternalComparers.Add( - new OneOfEqualityAdapter(propertiesComparer) - ); - return propertiesComparer.AreEqual(xValue, yValue, ref tolerance); - } - ); - - return constraint; - } - - /// - /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. - /// This enables recursive comparison of nested OneOf values within Optional types. - /// - private class OneOfEqualityAdapter : EqualityAdapter - { - private readonly NUnitEqualityComparer _comparer; - - public OneOfEqualityAdapter(NUnitEqualityComparer comparer) - { - _comparer = comparer; - } - - public override bool CanCompare(object? x, object? y) - { - return x is IOneOf && y is IOneOf; - } - - public override bool AreEqual(object? x, object? y) - { - var oneOfX = (IOneOf?)x; - var oneOfY = (IOneOf?)y; - - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (oneOfX?.Value is null && oneOfY?.Value is null) - { - return true; - } - - if (oneOfX?.Value is null || oneOfY?.Value is null) - { - return false; - } - - var tolerance = Tolerance.Default; - return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); - } - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/ReadOnlyMemoryComparer.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/ReadOnlyMemoryComparer.cs deleted file mode 100644 index fc0b595a5e54..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict.Test/Utils/ReadOnlyMemoryComparer.cs +++ /dev/null @@ -1,87 +0,0 @@ -using NUnit.Framework.Constraints; - -namespace NUnit.Framework; - -/// -/// Extensions for NUnit constraints. -/// -public static class ReadOnlyMemoryComparerExtensions -{ - /// - /// Extension method for comparing ReadOnlyMemory<T> in NUnit tests. - /// - /// The type of elements in the ReadOnlyMemory. - /// The Is.EqualTo() constraint instance. - /// A constraint that can compare ReadOnlyMemory<T>. - public static EqualConstraint UsingReadOnlyMemoryComparer(this EqualConstraint constraint) - where T : IComparable - { - return constraint.Using(new ReadOnlyMemoryComparer()); - } -} - -/// -/// Comparer for ReadOnlyMemory<T>. Compares sequences by value. -/// -/// -/// The type of elements in the ReadOnlyMemory. -/// -public class ReadOnlyMemoryComparer : IComparer> - where T : IComparable -{ - /// - public int Compare(ReadOnlyMemory x, ReadOnlyMemory y) - { - // Check if sequences are equal - var xSpan = x.Span; - var ySpan = y.Span; - - // Optimized case for IEquatable implementations - if (typeof(IEquatable).IsAssignableFrom(typeof(T))) - { - var areEqual = xSpan.SequenceEqual(ySpan); - if (areEqual) - { - return 0; // Sequences are equal - } - } - else - { - // Manual equality check for non-IEquatable types - if (xSpan.Length == ySpan.Length) - { - var areEqual = true; - for (var i = 0; i < xSpan.Length; i++) - { - if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) - { - areEqual = false; - break; - } - } - - if (areEqual) - { - return 0; // Sequences are equal - } - } - } - - // For non-equal sequences, we need to return a consistent ordering - // First compare lengths - if (x.Length != y.Length) - return x.Length.CompareTo(y.Length); - - // Same length but different content - compare first differing element - for (var i = 0; i < x.Length; i++) - { - if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) - { - return xSpan[i].CompareTo(ySpan[i]); - } - } - - // Should never reach here if not equal - return 0; - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/A/Aa/Types/A.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/A/Aa/Types/A.cs deleted file mode 100644 index 5bc0b2ea0597..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/A/Aa/Types/A.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using SeedCsharpNamespaceConflict; -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict.A.Aa; - -[Serializable] -public record A : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/A/Aa/Types/B.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/A/Aa/Types/B.cs deleted file mode 100644 index 7a5607a0e7a7..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/A/Aa/Types/B.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using SeedCsharpNamespaceConflict; -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict.A.Aa; - -[Serializable] -public record B : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/A/Aa/Types/SubTestType.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/A/Aa/Types/SubTestType.cs deleted file mode 100644 index e9c4baa5a30e..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/A/Aa/Types/SubTestType.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using SeedCsharpNamespaceConflict; -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict.A.Aa; - -[Serializable] -public record SubTestType : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("a")] - public required A A { get; set; } - - [JsonPropertyName("b")] - public required B B { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/B/Types/TestType.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/B/Types/TestType.cs deleted file mode 100644 index f0b7cc373909..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/B/Types/TestType.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using SeedCsharpNamespaceConflict; -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict.B; - -[Serializable] -public record TestType : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("a")] - public required SeedCsharpNamespaceConflict.A.Aa.A A { get; set; } - - [JsonPropertyName("b")] - public required SeedCsharpNamespaceConflict.A.Aa.B B { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/ApiResponse.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/ApiResponse.cs deleted file mode 100644 index 21c35aa1b772..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/ApiResponse.cs +++ /dev/null @@ -1,13 +0,0 @@ -using global::System.Net.Http; - -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// The response object returned from the API. -/// -internal record ApiResponse -{ - internal required int StatusCode { get; init; } - - internal required HttpResponseMessage Raw { get; init; } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/BaseRequest.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/BaseRequest.cs deleted file mode 100644 index b9635c0d4e08..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/BaseRequest.cs +++ /dev/null @@ -1,67 +0,0 @@ -using global::System.Net.Http; -using global::System.Net.Http.Headers; -using global::System.Text; - -namespace SeedCsharpNamespaceConflict.Core; - -internal abstract record BaseRequest -{ - internal string? BaseUrl { get; init; } - - internal required HttpMethod Method { get; init; } - - internal required string Path { get; init; } - - internal string? ContentType { get; init; } - - /// - /// The query string for this request (including the leading '?' if non-empty). - /// - internal string? QueryString { get; init; } - - internal Dictionary Headers { get; init; } = - new(StringComparer.OrdinalIgnoreCase); - - internal IRequestOptions? Options { get; init; } - - internal abstract HttpContent? CreateContent(); - - protected static ( - Encoding encoding, - string? charset, - string mediaType - ) ParseContentTypeOrDefault( - string? contentType, - Encoding encodingFallback, - string mediaTypeFallback - ) - { - var encoding = encodingFallback; - var mediaType = mediaTypeFallback; - string? charset = null; - if (string.IsNullOrEmpty(contentType)) - { - return (encoding, charset, mediaType); - } - - if (!MediaTypeHeaderValue.TryParse(contentType, out var mediaTypeHeaderValue)) - { - return (encoding, charset, mediaType); - } - - if (!string.IsNullOrEmpty(mediaTypeHeaderValue.CharSet)) - { - charset = mediaTypeHeaderValue.CharSet; - encoding = Encoding.GetEncoding(mediaTypeHeaderValue.CharSet); - } - - if (!string.IsNullOrEmpty(mediaTypeHeaderValue.MediaType)) - { - mediaType = mediaTypeHeaderValue.MediaType; - } - - return (encoding, charset, mediaType); - } - - protected static Encoding Utf8NoBom => EncodingCache.Utf8NoBom; -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/CollectionItemSerializer.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/CollectionItemSerializer.cs deleted file mode 100644 index 87a07ef25478..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/CollectionItemSerializer.cs +++ /dev/null @@ -1,89 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// Json collection converter. -/// -/// Type of item to convert. -/// Converter to use for individual items. -internal class CollectionItemSerializer - : JsonConverter> - where TConverterType : JsonConverter -{ - /// - /// Reads a json string and deserializes it into an object. - /// - /// Json reader. - /// Type to convert. - /// Serializer options. - /// Created object. - public override IEnumerable? Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType == JsonTokenType.Null) - { - return default; - } - - var jsonSerializerOptions = new JsonSerializerOptions(options); - jsonSerializerOptions.Converters.Clear(); - jsonSerializerOptions.Converters.Add(Activator.CreateInstance()); - - var returnValue = new List(); - - while (reader.TokenType != JsonTokenType.EndArray) - { - if (reader.TokenType != JsonTokenType.StartArray) - { - var item = (TDatatype)( - JsonSerializer.Deserialize(ref reader, typeof(TDatatype), jsonSerializerOptions) - ?? throw new global::System.Exception( - $"Failed to deserialize collection item of type {typeof(TDatatype)}" - ) - ); - returnValue.Add(item); - } - - reader.Read(); - } - - return returnValue; - } - - /// - /// Writes a json string. - /// - /// Json writer. - /// Value to write. - /// Serializer options. - public override void Write( - Utf8JsonWriter writer, - IEnumerable? value, - JsonSerializerOptions options - ) - { - if (value is null) - { - writer.WriteNullValue(); - return; - } - - var jsonSerializerOptions = new JsonSerializerOptions(options); - jsonSerializerOptions.Converters.Clear(); - jsonSerializerOptions.Converters.Add(Activator.CreateInstance()); - - writer.WriteStartArray(); - - foreach (var data in value) - { - JsonSerializer.Serialize(writer, data, jsonSerializerOptions); - } - - writer.WriteEndArray(); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Constants.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Constants.cs deleted file mode 100644 index 1529aa7ac4f2..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Constants.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SeedCsharpNamespaceConflict.Core; - -internal static class Constants -{ - public const string DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffK"; - public const string DateFormat = "yyyy-MM-dd"; -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/DateOnlyConverter.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/DateOnlyConverter.cs deleted file mode 100644 index 2b80caff9610..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/DateOnlyConverter.cs +++ /dev/null @@ -1,747 +0,0 @@ -// ReSharper disable All -#pragma warning disable - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using global::System.Diagnostics; -using global::System.Diagnostics.CodeAnalysis; -using global::System.Globalization; -using global::System.Runtime.CompilerServices; -using global::System.Runtime.InteropServices; -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -// ReSharper disable SuggestVarOrType_SimpleTypes -// ReSharper disable SuggestVarOrType_BuiltInTypes - -namespace SeedCsharpNamespaceConflict.Core -{ - /// - /// Custom converter for handling the data type with the System.Text.Json library. - /// - /// - /// This class backported from: - /// - /// System.Text.Json.Serialization.Converters.DateOnlyConverter - /// - public sealed class DateOnlyConverter : JsonConverter - { - private const int FormatLength = 10; // YYYY-MM-DD - - private const int MaxEscapedFormatLength = - FormatLength * JsonConstants.MaxExpansionFactorWhileEscaping; - - /// - public override DateOnly Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType != JsonTokenType.String) - { - ThrowHelper.ThrowInvalidOperationException_ExpectedString(reader.TokenType); - } - - return ReadCore(ref reader); - } - - /// - public override DateOnly ReadAsPropertyName( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); - return ReadCore(ref reader); - } - - private static DateOnly ReadCore(ref Utf8JsonReader reader) - { - if ( - !JsonHelpers.IsInRangeInclusive( - reader.ValueLength(), - FormatLength, - MaxEscapedFormatLength - ) - ) - { - ThrowHelper.ThrowFormatException(DataType.DateOnly); - } - - scoped ReadOnlySpan source; - if (!reader.HasValueSequence && !reader.ValueIsEscaped) - { - source = reader.ValueSpan; - } - else - { - Span stackSpan = stackalloc byte[MaxEscapedFormatLength]; - int bytesWritten = reader.CopyString(stackSpan); - source = stackSpan.Slice(0, bytesWritten); - } - - if (!JsonHelpers.TryParseAsIso(source, out DateOnly value)) - { - ThrowHelper.ThrowFormatException(DataType.DateOnly); - } - - return value; - } - - /// - public override void Write( - Utf8JsonWriter writer, - DateOnly value, - JsonSerializerOptions options - ) - { -#if NET8_0_OR_GREATER - Span buffer = stackalloc byte[FormatLength]; -#else - Span buffer = stackalloc char[FormatLength]; -#endif - // ReSharper disable once RedundantAssignment - bool formattedSuccessfully = value.TryFormat( - buffer, - out int charsWritten, - "O".AsSpan(), - CultureInfo.InvariantCulture - ); - Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); - writer.WriteStringValue(buffer); - } - - /// - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - DateOnly value, - JsonSerializerOptions options - ) - { -#if NET8_0_OR_GREATER - Span buffer = stackalloc byte[FormatLength]; -#else - Span buffer = stackalloc char[FormatLength]; -#endif - // ReSharper disable once RedundantAssignment - bool formattedSuccessfully = value.TryFormat( - buffer, - out int charsWritten, - "O".AsSpan(), - CultureInfo.InvariantCulture - ); - Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); - writer.WritePropertyName(buffer); - } - } - - internal static class JsonConstants - { - // The maximum number of fraction digits the Json DateTime parser allows - public const int DateTimeParseNumFractionDigits = 16; - - // In the worst case, an ASCII character represented as a single utf-8 byte could expand 6x when escaped. - public const int MaxExpansionFactorWhileEscaping = 6; - - // The largest fraction expressible by TimeSpan and DateTime formats - public const int MaxDateTimeFraction = 9_999_999; - - // TimeSpan and DateTime formats allow exactly up to many digits for specifying the fraction after the seconds. - public const int DateTimeNumFractionDigits = 7; - - public const byte UtcOffsetToken = (byte)'Z'; - - public const byte TimePrefix = (byte)'T'; - - public const byte Period = (byte)'.'; - - public const byte Hyphen = (byte)'-'; - - public const byte Colon = (byte)':'; - - public const byte Plus = (byte)'+'; - } - - // ReSharper disable SuggestVarOrType_Elsewhere - // ReSharper disable SuggestVarOrType_SimpleTypes - // ReSharper disable SuggestVarOrType_BuiltInTypes - - internal static class JsonHelpers - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsInRangeInclusive(int value, int lowerBound, int upperBound) => - (uint)(value - lowerBound) <= (uint)(upperBound - lowerBound); - - public static bool IsDigit(byte value) => (uint)(value - '0') <= '9' - '0'; - - [StructLayout(LayoutKind.Auto)] - private struct DateTimeParseData - { - public int Year; - public int Month; - public int Day; - public bool IsCalendarDateOnly; - public int Hour; - public int Minute; - public int Second; - public int Fraction; // This value should never be greater than 9_999_999. - public int OffsetHours; - public int OffsetMinutes; - - // ReSharper disable once NotAccessedField.Local - public byte OffsetToken; - } - - public static bool TryParseAsIso(ReadOnlySpan source, out DateOnly value) - { - if ( - TryParseDateTimeOffset(source, out DateTimeParseData parseData) - && parseData.IsCalendarDateOnly - && TryCreateDateTime(parseData, DateTimeKind.Unspecified, out DateTime dateTime) - ) - { - value = DateOnly.FromDateTime(dateTime); - return true; - } - - value = default; - return false; - } - - /// - /// ISO 8601 date time parser (ISO 8601-1:2019). - /// - /// The date/time to parse in UTF-8 format. - /// The parsed for the given . - /// - /// Supports extended calendar date (5.2.2.1) and complete (5.4.2.1) calendar date/time of day - /// representations with optional specification of seconds and fractional seconds. - /// - /// Times can be explicitly specified as UTC ("Z" - 5.3.3) or offsets from UTC ("+/-hh:mm" 5.3.4.2). - /// If unspecified they are considered to be local per spec. - /// - /// Examples: (TZD is either "Z" or hh:mm offset from UTC) - /// - /// YYYY-MM-DD (e.g. 1997-07-16) - /// YYYY-MM-DDThh:mm (e.g. 1997-07-16T19:20) - /// YYYY-MM-DDThh:mm:ss (e.g. 1997-07-16T19:20:30) - /// YYYY-MM-DDThh:mm:ss.s (e.g. 1997-07-16T19:20:30.45) - /// YYYY-MM-DDThh:mmTZD (e.g. 1997-07-16T19:20+01:00) - /// YYYY-MM-DDThh:mm:ssTZD (e.g. 1997-07-16T19:20:3001:00) - /// YYYY-MM-DDThh:mm:ss.sTZD (e.g. 1997-07-16T19:20:30.45Z) - /// - /// Generally speaking we always require the "extended" option when one exists (3.1.3.5). - /// The extended variants have separator characters between components ('-', ':', '.', etc.). - /// Spaces are not permitted. - /// - /// "true" if successfully parsed. - private static bool TryParseDateTimeOffset( - ReadOnlySpan source, - out DateTimeParseData parseData - ) - { - parseData = default; - - // too short datetime - Debug.Assert(source.Length >= 10); - - // Parse the calendar date - // ----------------------- - // ISO 8601-1:2019 5.2.2.1b "Calendar date complete extended format" - // [dateX] = [year]["-"][month]["-"][day] - // [year] = [YYYY] [0000 - 9999] (4.3.2) - // [month] = [MM] [01 - 12] (4.3.3) - // [day] = [DD] [01 - 28, 29, 30, 31] (4.3.4) - // - // Note: 5.2.2.2 "Representations with reduced precision" allows for - // just [year]["-"][month] (a) and just [year] (b), but we currently - // don't permit it. - - { - uint digit1 = source[0] - (uint)'0'; - uint digit2 = source[1] - (uint)'0'; - uint digit3 = source[2] - (uint)'0'; - uint digit4 = source[3] - (uint)'0'; - - if (digit1 > 9 || digit2 > 9 || digit3 > 9 || digit4 > 9) - { - return false; - } - - parseData.Year = (int)(digit1 * 1000 + digit2 * 100 + digit3 * 10 + digit4); - } - - if ( - source[4] != JsonConstants.Hyphen - || !TryGetNextTwoDigits(source.Slice(start: 5, length: 2), ref parseData.Month) - || source[7] != JsonConstants.Hyphen - || !TryGetNextTwoDigits(source.Slice(start: 8, length: 2), ref parseData.Day) - ) - { - return false; - } - - // We now have YYYY-MM-DD [dateX] - // ReSharper disable once ConvertIfStatementToSwitchStatement - if (source.Length == 10) - { - parseData.IsCalendarDateOnly = true; - return true; - } - - // Parse the time of day - // --------------------- - // - // ISO 8601-1:2019 5.3.1.2b "Local time of day complete extended format" - // [timeX] = ["T"][hour][":"][min][":"][sec] - // [hour] = [hh] [00 - 23] (4.3.8a) - // [minute] = [mm] [00 - 59] (4.3.9a) - // [sec] = [ss] [00 - 59, 60 with a leap second] (4.3.10a) - // - // ISO 8601-1:2019 5.3.3 "UTC of day" - // [timeX]["Z"] - // - // ISO 8601-1:2019 5.3.4.2 "Local time of day with the time shift between - // local timescale and UTC" (Extended format) - // - // [shiftX] = ["+"|"-"][hour][":"][min] - // - // Notes: - // - // "T" is optional per spec, but _only_ when times are used alone. In our - // case, we're reading out a complete date & time and as such require "T". - // (5.4.2.1b). - // - // For [timeX] We allow seconds to be omitted per 5.3.1.3a "Representations - // with reduced precision". 5.3.1.3b allows just specifying the hour, but - // we currently don't permit this. - // - // Decimal fractions are allowed for hours, minutes and seconds (5.3.14). - // We only allow fractions for seconds currently. Lower order components - // can't follow, i.e. you can have T23.3, but not T23.3:04. There must be - // one digit, but the max number of digits is implementation defined. We - // currently allow up to 16 digits of fractional seconds only. While we - // support 16 fractional digits we only parse the first seven, anything - // past that is considered a zero. This is to stay compatible with the - // DateTime implementation which is limited to this resolution. - - if (source.Length < 16) - { - // Source does not have enough characters for YYYY-MM-DDThh:mm - return false; - } - - // Parse THH:MM (e.g. "T10:32") - if ( - source[10] != JsonConstants.TimePrefix - || source[13] != JsonConstants.Colon - || !TryGetNextTwoDigits(source.Slice(start: 11, length: 2), ref parseData.Hour) - || !TryGetNextTwoDigits(source.Slice(start: 14, length: 2), ref parseData.Minute) - ) - { - return false; - } - - // We now have YYYY-MM-DDThh:mm - Debug.Assert(source.Length >= 16); - if (source.Length == 16) - { - return true; - } - - byte curByte = source[16]; - int sourceIndex = 17; - - // Either a TZD ['Z'|'+'|'-'] or a seconds separator [':'] is valid at this point - switch (curByte) - { - case JsonConstants.UtcOffsetToken: - parseData.OffsetToken = JsonConstants.UtcOffsetToken; - return sourceIndex == source.Length; - case JsonConstants.Plus: - case JsonConstants.Hyphen: - parseData.OffsetToken = curByte; - return ParseOffset(ref parseData, source.Slice(sourceIndex)); - case JsonConstants.Colon: - break; - default: - return false; - } - - // Try reading the seconds - if ( - source.Length < 19 - || !TryGetNextTwoDigits(source.Slice(start: 17, length: 2), ref parseData.Second) - ) - { - return false; - } - - // We now have YYYY-MM-DDThh:mm:ss - Debug.Assert(source.Length >= 19); - if (source.Length == 19) - { - return true; - } - - curByte = source[19]; - sourceIndex = 20; - - // Either a TZD ['Z'|'+'|'-'] or a seconds decimal fraction separator ['.'] is valid at this point - switch (curByte) - { - case JsonConstants.UtcOffsetToken: - parseData.OffsetToken = JsonConstants.UtcOffsetToken; - return sourceIndex == source.Length; - case JsonConstants.Plus: - case JsonConstants.Hyphen: - parseData.OffsetToken = curByte; - return ParseOffset(ref parseData, source.Slice(sourceIndex)); - case JsonConstants.Period: - break; - default: - return false; - } - - // Source does not have enough characters for second fractions (i.e. ".s") - // YYYY-MM-DDThh:mm:ss.s - if (source.Length < 21) - { - return false; - } - - // Parse fraction. This value should never be greater than 9_999_999 - int numDigitsRead = 0; - int fractionEnd = Math.Min( - sourceIndex + JsonConstants.DateTimeParseNumFractionDigits, - source.Length - ); - - while (sourceIndex < fractionEnd && IsDigit(curByte = source[sourceIndex])) - { - if (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) - { - parseData.Fraction = parseData.Fraction * 10 + (int)(curByte - (uint)'0'); - numDigitsRead++; - } - - sourceIndex++; - } - - if (parseData.Fraction != 0) - { - while (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) - { - parseData.Fraction *= 10; - numDigitsRead++; - } - } - - // We now have YYYY-MM-DDThh:mm:ss.s - Debug.Assert(sourceIndex <= source.Length); - if (sourceIndex == source.Length) - { - return true; - } - - curByte = source[sourceIndex++]; - - // TZD ['Z'|'+'|'-'] is valid at this point - switch (curByte) - { - case JsonConstants.UtcOffsetToken: - parseData.OffsetToken = JsonConstants.UtcOffsetToken; - return sourceIndex == source.Length; - case JsonConstants.Plus: - case JsonConstants.Hyphen: - parseData.OffsetToken = curByte; - return ParseOffset(ref parseData, source.Slice(sourceIndex)); - default: - return false; - } - - static bool ParseOffset(ref DateTimeParseData parseData, ReadOnlySpan offsetData) - { - // Parse the hours for the offset - if ( - offsetData.Length < 2 - || !TryGetNextTwoDigits(offsetData.Slice(0, 2), ref parseData.OffsetHours) - ) - { - return false; - } - - // We now have YYYY-MM-DDThh:mm:ss.s+|-hh - - if (offsetData.Length == 2) - { - // Just hours offset specified - return true; - } - - // Ensure we have enough for ":mm" - return offsetData.Length == 5 - && offsetData[2] == JsonConstants.Colon - && TryGetNextTwoDigits(offsetData.Slice(3), ref parseData.OffsetMinutes); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - // ReSharper disable once RedundantAssignment - private static bool TryGetNextTwoDigits(ReadOnlySpan source, ref int value) - { - Debug.Assert(source.Length == 2); - - uint digit1 = source[0] - (uint)'0'; - uint digit2 = source[1] - (uint)'0'; - - if (digit1 > 9 || digit2 > 9) - { - value = 0; - return false; - } - - value = (int)(digit1 * 10 + digit2); - return true; - } - - // The following methods are borrowed verbatim from src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Helpers.cs - - /// - /// Overflow-safe DateTime factory. - /// - private static bool TryCreateDateTime( - DateTimeParseData parseData, - DateTimeKind kind, - out DateTime value - ) - { - if (parseData.Year == 0) - { - value = default; - return false; - } - - Debug.Assert(parseData.Year <= 9999); // All of our callers to date parse the year from fixed 4-digit fields so this value is trusted. - - if ((uint)parseData.Month - 1 >= 12) - { - value = default; - return false; - } - - uint dayMinusOne = (uint)parseData.Day - 1; - if ( - dayMinusOne >= 28 - && dayMinusOne >= DateTime.DaysInMonth(parseData.Year, parseData.Month) - ) - { - value = default; - return false; - } - - if ((uint)parseData.Hour > 23) - { - value = default; - return false; - } - - if ((uint)parseData.Minute > 59) - { - value = default; - return false; - } - - // This needs to allow leap seconds when appropriate. - // See https://github.com/dotnet/runtime/issues/30135. - if ((uint)parseData.Second > 59) - { - value = default; - return false; - } - - Debug.Assert(parseData.Fraction is >= 0 and <= JsonConstants.MaxDateTimeFraction); // All of our callers to date parse the fraction from fixed 7-digit fields so this value is trusted. - - ReadOnlySpan days = DateTime.IsLeapYear(parseData.Year) - ? DaysToMonth366 - : DaysToMonth365; - int yearMinusOne = parseData.Year - 1; - int totalDays = - yearMinusOne * 365 - + yearMinusOne / 4 - - yearMinusOne / 100 - + yearMinusOne / 400 - + days[parseData.Month - 1] - + parseData.Day - - 1; - long ticks = totalDays * TimeSpan.TicksPerDay; - int totalSeconds = parseData.Hour * 3600 + parseData.Minute * 60 + parseData.Second; - ticks += totalSeconds * TimeSpan.TicksPerSecond; - ticks += parseData.Fraction; - value = new DateTime(ticks: ticks, kind: kind); - return true; - } - - private static ReadOnlySpan DaysToMonth365 => - [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; - private static ReadOnlySpan DaysToMonth366 => - [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; - } - - internal static class ThrowHelper - { - private const string ExceptionSourceValueToRethrowAsJsonException = - "System.Text.Json.Rethrowable"; - - [DoesNotReturn] - public static void ThrowInvalidOperationException_ExpectedString(JsonTokenType tokenType) - { - throw GetInvalidOperationException("string", tokenType); - } - - public static void ThrowFormatException(DataType dataType) - { - throw new FormatException(SR.Format(SR.UnsupportedFormat, dataType)) - { - Source = ExceptionSourceValueToRethrowAsJsonException, - }; - } - - private static global::System.Exception GetInvalidOperationException( - string message, - JsonTokenType tokenType - ) - { - return GetInvalidOperationException(SR.Format(SR.InvalidCast, tokenType, message)); - } - - private static InvalidOperationException GetInvalidOperationException(string message) - { - return new InvalidOperationException(message) - { - Source = ExceptionSourceValueToRethrowAsJsonException, - }; - } - } - - internal static class Utf8JsonReaderExtensions - { - internal static int ValueLength(this Utf8JsonReader reader) => - reader.HasValueSequence - ? checked((int)reader.ValueSequence.Length) - : reader.ValueSpan.Length; - } - - internal enum DataType - { - TimeOnly, - DateOnly, - } - - [SuppressMessage("ReSharper", "InconsistentNaming")] - internal static class SR - { - private static readonly bool s_usingResourceKeys = - AppContext.TryGetSwitch( - "System.Resources.UseSystemResourceKeys", - out bool usingResourceKeys - ) && usingResourceKeys; - - public static string UnsupportedFormat => Strings.UnsupportedFormat; - - public static string InvalidCast => Strings.InvalidCast; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static string Format(string resourceFormat, object? p1) => - s_usingResourceKeys - ? string.Join(", ", resourceFormat, p1) - : string.Format(resourceFormat, p1); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static string Format(string resourceFormat, object? p1, object? p2) => - s_usingResourceKeys - ? string.Join(", ", resourceFormat, p1, p2) - : string.Format(resourceFormat, p1, p2); - } - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute( - "System.Resources.Tools.StronglyTypedResourceBuilder", - "17.0.0.0" - )] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Strings - { - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( - "Microsoft.Performance", - "CA1811:AvoidUncalledPrivateCode" - )] - internal Strings() { } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute( - global::System.ComponentModel.EditorBrowsableState.Advanced - )] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if (object.ReferenceEquals(resourceMan, null)) - { - global::System.Resources.ResourceManager temp = - new global::System.Resources.ResourceManager( - "System.Text.Json.Resources.Strings", - typeof(Strings).Assembly - ); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute( - global::System.ComponentModel.EditorBrowsableState.Advanced - )] - internal static global::System.Globalization.CultureInfo Culture - { - get { return resourceCulture; } - set { resourceCulture = value; } - } - - /// - /// Looks up a localized string similar to Cannot get the value of a token type '{0}' as a {1}.. - /// - internal static string InvalidCast - { - get { return ResourceManager.GetString("InvalidCast", resourceCulture); } - } - - /// - /// Looks up a localized string similar to The JSON value is not in a supported {0} format.. - /// - internal static string UnsupportedFormat - { - get { return ResourceManager.GetString("UnsupportedFormat", resourceCulture); } - } - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/DateTimeSerializer.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/DateTimeSerializer.cs deleted file mode 100644 index c3d80bfcc7da..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/DateTimeSerializer.cs +++ /dev/null @@ -1,22 +0,0 @@ -using global::System.Globalization; -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -namespace SeedCsharpNamespaceConflict.Core; - -internal class DateTimeSerializer : JsonConverter -{ - public override DateTime Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); - } - - public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) - { - writer.WriteStringValue(value.ToString(Constants.DateTimeFormat)); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/EmptyRequest.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/EmptyRequest.cs deleted file mode 100644 index 80cf16dcf4d9..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/EmptyRequest.cs +++ /dev/null @@ -1,11 +0,0 @@ -using global::System.Net.Http; - -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// The request object to send without a request body. -/// -internal record EmptyRequest : BaseRequest -{ - internal override HttpContent? CreateContent() => null; -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/EncodingCache.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/EncodingCache.cs deleted file mode 100644 index 42210f67a848..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/EncodingCache.cs +++ /dev/null @@ -1,11 +0,0 @@ -using global::System.Text; - -namespace SeedCsharpNamespaceConflict.Core; - -internal static class EncodingCache -{ - internal static readonly Encoding Utf8NoBom = new UTF8Encoding( - encoderShouldEmitUTF8Identifier: false, - throwOnInvalidBytes: true - ); -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Extensions.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Extensions.cs deleted file mode 100644 index f4fb401158c1..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Extensions.cs +++ /dev/null @@ -1,55 +0,0 @@ -using global::System.Diagnostics.CodeAnalysis; -using global::System.Runtime.Serialization; - -namespace SeedCsharpNamespaceConflict.Core; - -internal static class Extensions -{ - public static string Stringify(this Enum value) - { - var field = value.GetType().GetField(value.ToString()); - if (field is not null) - { - var attribute = (EnumMemberAttribute?) - global::System.Attribute.GetCustomAttribute(field, typeof(EnumMemberAttribute)); - return attribute?.Value ?? value.ToString(); - } - return value.ToString(); - } - - /// - /// Asserts that a condition is true, throwing an exception with the specified message if it is false. - /// - /// The condition to assert. - /// The exception message if the assertion fails. - /// Thrown when the condition is false. - internal static void Assert(this object value, bool condition, string message) - { - if (!condition) - { - throw new global::System.Exception(message); - } - } - - /// - /// Asserts that a value is not null, throwing an exception with the specified message if it is null. - /// - /// The type of the value to assert. - /// The value to assert is not null. - /// The exception message if the assertion fails. - /// The non-null value. - /// Thrown when the value is null. - internal static TValue Assert( - this object _unused, - [NotNull] TValue? value, - string message - ) - where TValue : class - { - if (value is null) - { - throw new global::System.Exception(message); - } - return value; - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/FormUrlEncoder.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/FormUrlEncoder.cs deleted file mode 100644 index 46d13ef4ea09..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/FormUrlEncoder.cs +++ /dev/null @@ -1,33 +0,0 @@ -using global::System.Net.Http; - -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// Encodes an object into a form URL-encoded content. -/// -public static class FormUrlEncoder -{ - /// - /// Encodes an object into a form URL-encoded content using Deep Object notation. - /// - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - internal static FormUrlEncodedContent EncodeAsDeepObject(object value) => - new(QueryStringConverter.ToDeepObject(value)); - - /// - /// Encodes an object into a form URL-encoded content using Exploded Form notation. - /// - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - internal static FormUrlEncodedContent EncodeAsExplodedForm(object value) => - new(QueryStringConverter.ToExplodedForm(value)); - - /// - /// Encodes an object into a form URL-encoded content using Form notation without exploding parameters. - /// - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - internal static FormUrlEncodedContent EncodeAsForm(object value) => - new(QueryStringConverter.ToForm(value)); -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/HeaderValue.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/HeaderValue.cs deleted file mode 100644 index fc9338f23443..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/HeaderValue.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace SeedCsharpNamespaceConflict.Core; - -internal sealed class HeaderValue -{ - private readonly Func> _resolver; - - public HeaderValue(string value) - { - _resolver = () => new global::System.Threading.Tasks.ValueTask(value); - } - - public HeaderValue(Func value) - { - _resolver = () => new global::System.Threading.Tasks.ValueTask(value()); - } - - public HeaderValue(Func> value) - { - _resolver = value; - } - - public HeaderValue(Func> value) - { - _resolver = () => new global::System.Threading.Tasks.ValueTask(value()); - } - - public static implicit operator HeaderValue(string value) => new(value); - - public static implicit operator HeaderValue(Func value) => new(value); - - public static implicit operator HeaderValue( - Func> value - ) => new(value); - - public static implicit operator HeaderValue( - Func> value - ) => new(value); - - public static HeaderValue FromString(string value) => new(value); - - public static HeaderValue FromFunc(Func value) => new(value); - - public static HeaderValue FromValueTaskFunc( - Func> value - ) => new(value); - - public static HeaderValue FromTaskFunc( - Func> value - ) => new(value); - - internal global::System.Threading.Tasks.ValueTask ResolveAsync() => _resolver(); -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Headers.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Headers.cs deleted file mode 100644 index 6cf231ef6c77..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Headers.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// Represents the headers sent with the request. -/// -internal sealed class Headers : Dictionary -{ - internal Headers() { } - - /// - /// Initializes a new instance of the Headers class with the specified value. - /// - /// - internal Headers(Dictionary value) - { - foreach (var kvp in value) - { - this[kvp.Key] = kvp.Value; - } - } - - /// - /// Initializes a new instance of the Headers class with the specified value. - /// - /// - internal Headers(IEnumerable> value) - : base(value.ToDictionary(e => e.Key, e => e.Value)) { } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/HeadersBuilder.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/HeadersBuilder.cs deleted file mode 100644 index 45eba76c4fa1..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/HeadersBuilder.cs +++ /dev/null @@ -1,197 +0,0 @@ -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// Fluent builder for constructing HTTP headers with support for merging from multiple sources. -/// Provides a clean API for building headers with proper precedence handling. -/// -internal static class HeadersBuilder -{ - /// - /// Fluent builder for constructing HTTP headers. - /// - public sealed class Builder - { - private readonly Dictionary _headers; - - /// - /// Initializes a new instance with default capacity. - /// Uses case-insensitive header name comparison. - /// - public Builder() - { - _headers = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - - /// - /// Initializes a new instance with the specified initial capacity. - /// Uses case-insensitive header name comparison. - /// - public Builder(int capacity) - { - _headers = new Dictionary( - capacity, - StringComparer.OrdinalIgnoreCase - ); - } - - /// - /// Adds a header with the specified key and value. - /// If a header with the same key already exists, it will be overwritten. - /// Null values are ignored. - /// - /// The header name. - /// The header value. Null values are ignored. - /// This builder instance for method chaining. - public Builder Add(string key, string? value) - { - if (value is not null) - { - _headers[key] = (value); - } - return this; - } - - /// - /// Adds a header with the specified key and object value. - /// The value will be converted to string using ValueConvert for consistent serialization. - /// If a header with the same key already exists, it will be overwritten. - /// Null values are ignored. - /// - /// The header name. - /// The header value. Null values are ignored. - /// This builder instance for method chaining. - public Builder Add(string key, object? value) - { - if (value is null) - { - return this; - } - - // Use ValueConvert for consistent serialization across headers, query params, and path params - var stringValue = ValueConvert.ToString(value); - if (stringValue is not null) - { - _headers[key] = (stringValue); - } - return this; - } - - /// - /// Adds multiple headers from a Headers dictionary. - /// HeaderValue instances are stored and will be resolved when BuildAsync() is called. - /// Overwrites any existing headers with the same key. - /// Null entries are ignored. - /// - /// The headers to add. Null is treated as empty. - /// This builder instance for method chaining. - public Builder Add(Headers? headers) - { - if (headers is null) - { - return this; - } - - foreach (var header in headers) - { - _headers[header.Key] = header.Value; - } - - return this; - } - - /// - /// Adds multiple headers from a Headers dictionary, excluding the Authorization header. - /// This is useful for endpoints that don't require authentication, to avoid triggering - /// lazy auth token resolution. - /// HeaderValue instances are stored and will be resolved when BuildAsync() is called. - /// Overwrites any existing headers with the same key. - /// Null entries are ignored. - /// - /// The headers to add. Null is treated as empty. - /// This builder instance for method chaining. - public Builder AddWithoutAuth(Headers? headers) - { - if (headers is null) - { - return this; - } - - foreach (var header in headers) - { - if (header.Key.Equals("Authorization", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - _headers[header.Key] = header.Value; - } - - return this; - } - - /// - /// Adds multiple headers from a key-value pair collection. - /// Overwrites any existing headers with the same key. - /// Null values are ignored. - /// - /// The headers to add. Null is treated as empty. - /// This builder instance for method chaining. - public Builder Add(IEnumerable>? headers) - { - if (headers is null) - { - return this; - } - - foreach (var header in headers) - { - if (header.Value is not null) - { - _headers[header.Key] = (header.Value); - } - } - - return this; - } - - /// - /// Adds multiple headers from a dictionary. - /// Overwrites any existing headers with the same key. - /// - /// The headers to add. Null is treated as empty. - /// This builder instance for method chaining. - public Builder Add(Dictionary? headers) - { - if (headers is null) - { - return this; - } - - foreach (var header in headers) - { - _headers[header.Key] = (header.Value); - } - - return this; - } - - /// - /// Asynchronously builds the final headers dictionary containing all merged headers. - /// Resolves all HeaderValue instances that may contain async operations. - /// Returns a case-insensitive dictionary. - /// - /// A task that represents the asynchronous operation, containing a case-insensitive dictionary of headers. - public async global::System.Threading.Tasks.Task> BuildAsync() - { - var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var kvp in _headers) - { - var value = await kvp.Value.ResolveAsync().ConfigureAwait(false); - if (value is not null) - { - headers[kvp.Key] = value; - } - } - return headers; - } - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/HttpContentExtensions.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/HttpContentExtensions.cs deleted file mode 100644 index 82271d3cef50..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/HttpContentExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -#if !NET5_0_OR_GREATER -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// Polyfill extension providing a ReadAsStringAsync(CancellationToken) overload -/// for target frameworks older than .NET 5, where only the parameterless -/// ReadAsStringAsync() is available. -/// -internal static class HttpContentExtensions -{ - internal static Task ReadAsStringAsync( - this HttpContent httpContent, - CancellationToken cancellationToken - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return httpContent.ReadAsStringAsync(); - } -} -#endif diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/HttpMethodExtensions.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/HttpMethodExtensions.cs deleted file mode 100644 index ae3f3e5bc1c0..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/HttpMethodExtensions.cs +++ /dev/null @@ -1,8 +0,0 @@ -using global::System.Net.Http; - -namespace SeedCsharpNamespaceConflict.Core; - -internal static class HttpMethodExtensions -{ - public static readonly HttpMethod Patch = new("PATCH"); -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/IIsRetryableContent.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/IIsRetryableContent.cs deleted file mode 100644 index 25f69dce31e7..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/IIsRetryableContent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace SeedCsharpNamespaceConflict.Core; - -public interface IIsRetryableContent -{ - public bool IsRetryable { get; } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/IRequestOptions.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/IRequestOptions.cs deleted file mode 100644 index 196a25a72a40..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/IRequestOptions.cs +++ /dev/null @@ -1,83 +0,0 @@ -namespace SeedCsharpNamespaceConflict.Core; - -internal interface IRequestOptions -{ - /// - /// The Base URL for the API. - /// - public string? BaseUrl { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// The http client used to make requests. - /// - public HttpClient? HttpClient { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// Additional headers to be sent with the request. - /// Headers previously set with matching keys will be overwritten. - /// - public IEnumerable> AdditionalHeaders { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// The max number of retries to attempt. - /// - public int? MaxRetries { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// The timeout for the request. - /// - public TimeSpan? Timeout { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// Additional query parameters sent with the request. - /// - public IEnumerable> AdditionalQueryParameters { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// Additional body properties sent with the request. - /// This is only applied to JSON requests. - /// - public object? AdditionalBodyProperties { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/JsonAccessAttribute.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/JsonAccessAttribute.cs deleted file mode 100644 index bf8f553cbb0a..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/JsonAccessAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace SeedCsharpNamespaceConflict.Core; - -[global::System.AttributeUsage( - global::System.AttributeTargets.Property | global::System.AttributeTargets.Field -)] -internal class JsonAccessAttribute(JsonAccessType accessType) : global::System.Attribute -{ - internal JsonAccessType AccessType { get; init; } = accessType; -} - -internal enum JsonAccessType -{ - ReadOnly, - WriteOnly, -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/JsonConfiguration.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/JsonConfiguration.cs deleted file mode 100644 index 1cf0be80d3a3..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/JsonConfiguration.cs +++ /dev/null @@ -1,275 +0,0 @@ -using global::System.Reflection; -using global::System.Text.Encodings.Web; -using global::System.Text.Json; -using global::System.Text.Json.Nodes; -using global::System.Text.Json.Serialization; -using global::System.Text.Json.Serialization.Metadata; - -namespace SeedCsharpNamespaceConflict.Core; - -internal static partial class JsonOptions -{ - internal static readonly JsonSerializerOptions JsonSerializerOptions; - internal static readonly JsonSerializerOptions JsonSerializerOptionsRelaxedEscaping; - - static JsonOptions() - { - var options = new JsonSerializerOptions - { - Converters = - { - new DateTimeSerializer(), -#if USE_PORTABLE_DATE_ONLY - new DateOnlyConverter(), -#endif - new OneOfSerializer(), - new OptionalJsonConverterFactory(), - }, -#if DEBUG - WriteIndented = true, -#endif - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - TypeInfoResolver = new DefaultJsonTypeInfoResolver - { - Modifiers = - { - NullableOptionalModifier, - JsonAccessAndIgnoreModifier, - HandleExtensionDataFields, - }, - }, - }; - ConfigureJsonSerializerOptions(options); - JsonSerializerOptions = options; - - var relaxedOptions = new JsonSerializerOptions(options) - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - }; - JsonSerializerOptionsRelaxedEscaping = relaxedOptions; - } - - private static void NullableOptionalModifier(JsonTypeInfo typeInfo) - { - if (typeInfo.Kind != JsonTypeInfoKind.Object) - return; - - foreach (var property in typeInfo.Properties) - { - var propertyInfo = property.AttributeProvider as global::System.Reflection.PropertyInfo; - - if (propertyInfo is null) - continue; - - // Check for ReadOnly JsonAccessAttribute - it overrides Optional/Nullable behavior - var jsonAccessAttribute = propertyInfo.GetCustomAttribute(); - if (jsonAccessAttribute?.AccessType == JsonAccessType.ReadOnly) - { - // ReadOnly means "never serialize", which completely overrides Optional/Nullable. - // Skip Optional/Nullable processing since JsonAccessAndIgnoreModifier - // will set ShouldSerialize = false anyway. - continue; - } - // Note: WriteOnly doesn't conflict with Optional/Nullable since it only - // affects deserialization (Set), not serialization (ShouldSerialize) - - var isOptionalType = - property.PropertyType.IsGenericType - && property.PropertyType.GetGenericTypeDefinition() == typeof(Optional<>); - - var hasOptionalAttribute = - propertyInfo.GetCustomAttribute() is not null; - var hasNullableAttribute = - propertyInfo.GetCustomAttribute() is not null; - - if (isOptionalType && hasOptionalAttribute) - { - var originalGetter = property.Get; - if (originalGetter is not null) - { - var capturedIsNullable = hasNullableAttribute; - - property.ShouldSerialize = (obj, value) => - { - var optionalValue = originalGetter(obj); - if (optionalValue is not IOptional optional) - return false; - - if (!optional.IsDefined) - return false; - - if (!capturedIsNullable) - { - var innerValue = optional.GetBoxedValue(); - if (innerValue is null) - return false; - } - - return true; - }; - } - } - else if (hasNullableAttribute) - { - // Force serialization of nullable properties even when null - property.ShouldSerialize = (obj, value) => true; - } - } - } - - private static void JsonAccessAndIgnoreModifier(JsonTypeInfo typeInfo) - { - if (typeInfo.Kind != JsonTypeInfoKind.Object) - return; - - foreach (var propertyInfo in typeInfo.Properties) - { - var jsonAccessAttribute = propertyInfo - .AttributeProvider?.GetCustomAttributes(typeof(JsonAccessAttribute), true) - .OfType() - .FirstOrDefault(); - - if (jsonAccessAttribute is not null) - { - propertyInfo.IsRequired = false; - switch (jsonAccessAttribute.AccessType) - { - case JsonAccessType.ReadOnly: - propertyInfo.ShouldSerialize = (_, _) => false; - break; - case JsonAccessType.WriteOnly: - propertyInfo.Set = null; - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - var jsonIgnoreAttribute = propertyInfo - .AttributeProvider?.GetCustomAttributes(typeof(JsonIgnoreAttribute), true) - .OfType() - .FirstOrDefault(); - - if (jsonIgnoreAttribute is not null) - { - propertyInfo.IsRequired = false; - } - } - } - - private static void HandleExtensionDataFields(JsonTypeInfo typeInfo) - { - if ( - typeInfo.Kind == JsonTypeInfoKind.Object - && typeInfo.Properties.All(prop => !prop.IsExtensionData) - ) - { - var extensionProp = typeInfo - .Type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic) - .FirstOrDefault(prop => - prop.GetCustomAttribute() is not null - ); - - if (extensionProp is not null) - { - var jsonPropertyInfo = typeInfo.CreateJsonPropertyInfo( - extensionProp.FieldType, - extensionProp.Name - ); - jsonPropertyInfo.Get = extensionProp.GetValue; - jsonPropertyInfo.Set = extensionProp.SetValue; - jsonPropertyInfo.IsExtensionData = true; - typeInfo.Properties.Add(jsonPropertyInfo); - } - } - } - - static partial void ConfigureJsonSerializerOptions(JsonSerializerOptions defaultOptions); -} - -internal static class JsonUtils -{ - internal static string Serialize(T obj) => - JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptions); - - internal static string Serialize(object obj, global::System.Type type) => - JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptions); - - internal static string SerializeRelaxedEscaping(T obj) => - JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptionsRelaxedEscaping); - - internal static string SerializeRelaxedEscaping(object obj, global::System.Type type) => - JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptionsRelaxedEscaping); - - internal static JsonElement SerializeToElement(T obj) => - JsonSerializer.SerializeToElement(obj, JsonOptions.JsonSerializerOptions); - - internal static JsonElement SerializeToElement(object obj, global::System.Type type) => - JsonSerializer.SerializeToElement(obj, type, JsonOptions.JsonSerializerOptions); - - internal static JsonDocument SerializeToDocument(T obj) => - JsonSerializer.SerializeToDocument(obj, JsonOptions.JsonSerializerOptions); - - internal static JsonNode? SerializeToNode(T obj) => - JsonSerializer.SerializeToNode(obj, JsonOptions.JsonSerializerOptions); - - internal static byte[] SerializeToUtf8Bytes(T obj) => - JsonSerializer.SerializeToUtf8Bytes(obj, JsonOptions.JsonSerializerOptions); - - internal static string SerializeWithAdditionalProperties( - T obj, - object? additionalProperties = null - ) - { - if (additionalProperties is null) - { - return Serialize(obj); - } - var additionalPropertiesJsonNode = SerializeToNode(additionalProperties); - if (additionalPropertiesJsonNode is not JsonObject additionalPropertiesJsonObject) - { - throw new InvalidOperationException( - "The additional properties must serialize to a JSON object." - ); - } - var jsonNode = SerializeToNode(obj); - if (jsonNode is not JsonObject jsonObject) - { - throw new InvalidOperationException( - "The serialized object must be a JSON object to add properties." - ); - } - MergeJsonObjects(jsonObject, additionalPropertiesJsonObject); - return jsonObject.ToJsonString(JsonOptions.JsonSerializerOptions); - } - - private static void MergeJsonObjects(JsonObject baseObject, JsonObject overrideObject) - { - foreach (var property in overrideObject) - { - if (!baseObject.TryGetPropertyValue(property.Key, out JsonNode? existingValue)) - { - baseObject[property.Key] = property.Value is not null - ? JsonNode.Parse(property.Value.ToJsonString()) - : null; - continue; - } - if ( - existingValue is JsonObject nestedBaseObject - && property.Value is JsonObject nestedOverrideObject - ) - { - // If both values are objects, recursively merge them. - MergeJsonObjects(nestedBaseObject, nestedOverrideObject); - continue; - } - // Otherwise, the overrideObject takes precedence. - baseObject[property.Key] = property.Value is not null - ? JsonNode.Parse(property.Value.ToJsonString()) - : null; - } - } - - internal static T Deserialize(string json) => - JsonSerializer.Deserialize(json, JsonOptions.JsonSerializerOptions)!; -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/JsonRequest.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/JsonRequest.cs deleted file mode 100644 index a59e3f9355c9..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/JsonRequest.cs +++ /dev/null @@ -1,36 +0,0 @@ -using global::System.Net.Http; - -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// The request object to be sent for JSON APIs. -/// -internal record JsonRequest : BaseRequest -{ - internal object? Body { get; init; } - - internal override HttpContent? CreateContent() - { - if (Body is null && Options?.AdditionalBodyProperties is null) - { - return null; - } - - var (encoding, charset, mediaType) = ParseContentTypeOrDefault( - ContentType, - Utf8NoBom, - "application/json" - ); - var content = new StringContent( - JsonUtils.SerializeWithAdditionalProperties(Body, Options?.AdditionalBodyProperties), - encoding, - mediaType - ); - if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) - { - content.Headers.ContentType.CharSet = ""; - } - - return content; - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/MultipartFormRequest.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/MultipartFormRequest.cs deleted file mode 100644 index 2e20fc1ad18a..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/MultipartFormRequest.cs +++ /dev/null @@ -1,294 +0,0 @@ -using global::System.Net.Http; -using global::System.Net.Http.Headers; - -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// The request object to be sent for multipart form data. -/// -internal record MultipartFormRequest : BaseRequest -{ - private readonly List> _partAdders = []; - - internal void AddJsonPart(string name, object? value) => AddJsonPart(name, value, null); - - internal void AddJsonPart(string name, object? value, string? contentType) - { - if (value is null) - { - return; - } - - _partAdders.Add(form => - { - var (encoding, charset, mediaType) = ParseContentTypeOrDefault( - contentType, - Utf8NoBom, - "application/json" - ); - var content = new StringContent(JsonUtils.Serialize(value), encoding, mediaType); - if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) - { - content.Headers.ContentType.CharSet = ""; - } - - form.Add(content, name); - }); - } - - internal void AddJsonParts(string name, IEnumerable? value) => - AddJsonParts(name, value, null); - - internal void AddJsonParts(string name, IEnumerable? value, string? contentType) - { - if (value is null) - { - return; - } - - foreach (var item in value) - { - AddJsonPart(name, item, contentType); - } - } - - internal void AddJsonParts(string name, IEnumerable? value) => - AddJsonParts(name, value, null); - - internal void AddJsonParts(string name, IEnumerable? value, string? contentType) - { - if (value is null) - { - return; - } - - foreach (var item in value) - { - AddJsonPart(name, item, contentType); - } - } - - internal void AddStringPart(string name, object? value) => AddStringPart(name, value, null); - - internal void AddStringPart(string name, object? value, string? contentType) - { - if (value is null) - { - return; - } - - AddStringPart(name, ValueConvert.ToString(value), contentType); - } - - internal void AddStringPart(string name, string? value) => AddStringPart(name, value, null); - - internal void AddStringPart(string name, string? value, string? contentType) - { - if (value is null) - { - return; - } - - _partAdders.Add(form => - { - var (encoding, charset, mediaType) = ParseContentTypeOrDefault( - contentType, - Utf8NoBom, - "text/plain" - ); - var content = new StringContent(value, encoding, mediaType); - if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) - { - content.Headers.ContentType.CharSet = ""; - } - - form.Add(content, name); - }); - } - - internal void AddStringParts(string name, IEnumerable? value) => - AddStringParts(name, value, null); - - internal void AddStringParts(string name, IEnumerable? value, string? contentType) - { - if (value is null) - { - return; - } - - AddStringPart(name, ValueConvert.ToString(value), contentType); - } - - internal void AddStringParts(string name, IEnumerable? value) => - AddStringParts(name, value, null); - - internal void AddStringParts(string name, IEnumerable? value, string? contentType) - { - if (value is null) - { - return; - } - - foreach (var item in value) - { - AddStringPart(name, item, contentType); - } - } - - internal void AddStreamPart(string name, Stream? stream, string? fileName) => - AddStreamPart(name, stream, fileName, null); - - internal void AddStreamPart(string name, Stream? stream, string? fileName, string? contentType) - { - if (stream is null) - { - return; - } - - _partAdders.Add(form => - { - var content = new StreamContent(stream) - { - Headers = - { - ContentType = MediaTypeHeaderValue.Parse( - contentType ?? "application/octet-stream" - ), - }, - }; - - if (fileName is not null) - { - form.Add(content, name, fileName); - } - else - { - form.Add(content, name); - } - }); - } - - internal void AddFileParameterPart(string name, Stream? stream) => - AddStreamPart(name, stream, null, null); - - internal void AddFileParameterPart(string name, FileParameter? file) => - AddFileParameterPart(name, file, null); - - internal void AddFileParameterPart( - string name, - FileParameter? file, - string? fallbackContentType - ) => - AddStreamPart(name, file?.Stream, file?.FileName, file?.ContentType ?? fallbackContentType); - - internal void AddFileParameterParts(string name, IEnumerable? files) => - AddFileParameterParts(name, files, null); - - internal void AddFileParameterParts( - string name, - IEnumerable? files, - string? fallbackContentType - ) - { - if (files is null) - { - return; - } - - foreach (var file in files) - { - AddFileParameterPart(name, file, fallbackContentType); - } - } - - internal void AddFormEncodedPart(string name, object? value) => - AddFormEncodedPart(name, value, null); - - internal void AddFormEncodedPart(string name, object? value, string? contentType) - { - if (value is null) - { - return; - } - - _partAdders.Add(form => - { - var content = FormUrlEncoder.EncodeAsForm(value); - if (!string.IsNullOrEmpty(contentType)) - { - content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); - } - - form.Add(content, name); - }); - } - - internal void AddFormEncodedParts(string name, IEnumerable? value) => - AddFormEncodedParts(name, value, null); - - internal void AddFormEncodedParts(string name, IEnumerable? value, string? contentType) - { - if (value is null) - { - return; - } - - foreach (var item in value) - { - AddFormEncodedPart(name, item, contentType); - } - } - - internal void AddExplodedFormEncodedPart(string name, object? value) => - AddExplodedFormEncodedPart(name, value, null); - - internal void AddExplodedFormEncodedPart(string name, object? value, string? contentType) - { - if (value is null) - { - return; - } - - _partAdders.Add(form => - { - var content = FormUrlEncoder.EncodeAsExplodedForm(value); - if (!string.IsNullOrEmpty(contentType)) - { - content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); - } - - form.Add(content, name); - }); - } - - internal void AddExplodedFormEncodedParts(string name, IEnumerable? value) => - AddExplodedFormEncodedParts(name, value, null); - - internal void AddExplodedFormEncodedParts( - string name, - IEnumerable? value, - string? contentType - ) - { - if (value is null) - { - return; - } - - foreach (var item in value) - { - AddExplodedFormEncodedPart(name, item, contentType); - } - } - - internal override HttpContent CreateContent() - { - var form = new MultipartFormDataContent(); - foreach (var adder in _partAdders) - { - adder(form); - } - - return form; - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/NullableAttribute.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/NullableAttribute.cs deleted file mode 100644 index abdad1f407b6..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/NullableAttribute.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// Marks a property as nullable in the OpenAPI specification. -/// When applied to Optional properties, this indicates that null values should be -/// written to JSON when the optional is defined with null. -/// -/// -/// For regular (required) properties: -/// - Without [Nullable]: null values are invalid (omit from JSON at runtime) -/// - With [Nullable]: null values are written to JSON -/// -/// For Optional properties (also marked with [Optional]): -/// - Without [Nullable]: Optional.Of(null) → omit from JSON (runtime edge case) -/// - With [Nullable]: Optional.Of(null) → write null to JSON -/// -[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] -public class NullableAttribute : global::System.Attribute { } diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/OneOfSerializer.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/OneOfSerializer.cs deleted file mode 100644 index af3fe82996eb..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/OneOfSerializer.cs +++ /dev/null @@ -1,145 +0,0 @@ -using global::System.Reflection; -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using OneOf; - -namespace SeedCsharpNamespaceConflict.Core; - -internal class OneOfSerializer : JsonConverter -{ - public override IOneOf? Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType is JsonTokenType.Null) - return default; - - foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) - { - try - { - var readerCopy = reader; - var result = JsonSerializer.Deserialize(ref readerCopy, type, options); - reader.Skip(); - return (IOneOf)cast.Invoke(null, [result])!; - } - catch (JsonException) { } - } - - throw new JsonException( - $"Cannot deserialize into one of the supported types for {typeToConvert}" - ); - } - - public override void Write(Utf8JsonWriter writer, IOneOf value, JsonSerializerOptions options) - { - JsonSerializer.Serialize(writer, value.Value, options); - } - - public override IOneOf ReadAsPropertyName( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = reader.GetString(); - if (stringValue == null) - throw new JsonException("Cannot deserialize null property name into OneOf type"); - - // Try to deserialize the string value into one of the supported types - foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) - { - try - { - // For primitive types, try direct conversion - if (type == typeof(string)) - { - return (IOneOf)cast.Invoke(null, [stringValue])!; - } - - // For other types, try to deserialize from JSON string - var result = JsonSerializer.Deserialize($"\"{stringValue}\"", type, options); - if (result != null) - { - return (IOneOf)cast.Invoke(null, [result])!; - } - } - catch { } - } - - // If no type-specific deserialization worked, default to string if available - var stringType = GetOneOfTypes(typeToConvert).FirstOrDefault(t => t.type == typeof(string)); - if (stringType != default) - { - return (IOneOf)stringType.cast.Invoke(null, [stringValue])!; - } - - throw new JsonException( - $"Cannot deserialize dictionary key '{stringValue}' into one of the supported types for {typeToConvert}" - ); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - IOneOf value, - JsonSerializerOptions options - ) - { - // Serialize the underlying value to a string suitable for use as a dictionary key - var stringValue = value.Value?.ToString() ?? "null"; - writer.WritePropertyName(stringValue); - } - - private static (global::System.Type type, MethodInfo cast)[] GetOneOfTypes( - global::System.Type typeToConvert - ) - { - var type = typeToConvert; - if (Nullable.GetUnderlyingType(type) is { } underlyingType) - { - type = underlyingType; - } - - var casts = type.GetRuntimeMethods() - .Where(m => m.IsSpecialName && m.Name == "op_Implicit") - .ToArray(); - while (type is not null) - { - if ( - type.IsGenericType - && (type.Name.StartsWith("OneOf`") || type.Name.StartsWith("OneOfBase`")) - ) - { - var genericArguments = type.GetGenericArguments(); - if (genericArguments.Length == 1) - { - return [(genericArguments[0], casts[0])]; - } - - // if object type is present, make sure it is last - var indexOfObjectType = Array.IndexOf(genericArguments, typeof(object)); - if (indexOfObjectType != -1 && genericArguments.Length - 1 != indexOfObjectType) - { - genericArguments = genericArguments - .OrderBy(t => t == typeof(object) ? 1 : 0) - .ToArray(); - } - - return genericArguments - .Select(t => (t, casts.First(c => c.GetParameters()[0].ParameterType == t))) - .ToArray(); - } - - type = type.BaseType; - } - - throw new InvalidOperationException($"{type} isn't OneOf or OneOfBase"); - } - - public override bool CanConvert(global::System.Type typeToConvert) - { - return typeof(IOneOf).IsAssignableFrom(typeToConvert); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Optional.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Optional.cs deleted file mode 100644 index 3599004d7e37..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Optional.cs +++ /dev/null @@ -1,474 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// Non-generic interface for Optional types to enable reflection-free checks. -/// -public interface IOptional -{ - /// - /// Returns true if the value is defined (set), even if the value is null. - /// - bool IsDefined { get; } - - /// - /// Gets the boxed value. Returns null if undefined or if the value is null. - /// - object? GetBoxedValue(); -} - -/// -/// Represents a field that can be "not set" (undefined) vs "explicitly set" (defined). -/// Use this for HTTP PATCH requests where you need to distinguish between: -/// -/// Undefined: Don't send this field (leave it unchanged on the server) -/// Defined with null: Send null (clear the field on the server) -/// Defined with value: Send the value (update the field on the server) -/// -/// -/// The type of the value. Use nullable types (T?) for fields that can be null. -/// -/// For nullable string fields, use Optional<string?>: -/// -/// public class UpdateUserRequest -/// { -/// public Optional<string?> Name { get; set; } = Optional<string?>.Undefined; -/// } -/// -/// var request = new UpdateUserRequest -/// { -/// Name = "John" // Will send: { "name": "John" } -/// }; -/// -/// var request2 = new UpdateUserRequest -/// { -/// Name = Optional<string?>.Of(null) // Will send: { "name": null } -/// }; -/// -/// var request3 = new UpdateUserRequest(); // Will send: {} (name not included) -/// -/// -public readonly struct Optional : IOptional, IEquatable> -{ - private readonly T _value; - private readonly bool _isDefined; - - private Optional(T value, bool isDefined) - { - _value = value; - _isDefined = isDefined; - } - - /// - /// Creates an undefined value - the field will not be included in the HTTP request. - /// Use this as the default value for optional fields. - /// - /// - /// - /// public Optional<string?> Email { get; set; } = Optional<string?>.Undefined; - /// - /// - public static Optional Undefined => new(default!, false); - - /// - /// Creates a defined value - the field will be included in the HTTP request. - /// The value can be null if T is a nullable type. - /// - /// The value to set. Can be null if T is nullable (e.g., string?, int?). - /// - /// - /// // Set to a value - /// request.Name = Optional<string?>.Of("John"); - /// - /// // Set to null (clears the field) - /// request.Email = Optional<string?>.Of(null); - /// - /// // Or use implicit conversion - /// request.Name = "John"; // Same as Of("John") - /// request.Email = null; // Same as Of(null) - /// - /// - public static Optional Of(T value) => new(value, true); - - /// - /// Returns true if the field is defined (set), even if the value is null. - /// Use this to determine if the field should be included in the HTTP request. - /// - /// - /// - /// if (request.Name.IsDefined) - /// { - /// requestBody["name"] = request.Name.Value; // Include in request (can be null) - /// } - /// - /// - public bool IsDefined => _isDefined; - - /// - /// Returns true if the field is undefined (not set). - /// Use this to check if the field should be excluded from the HTTP request. - /// - /// - /// - /// if (request.Email.IsUndefined) - /// { - /// // Don't include email in the request - leave it unchanged - /// } - /// - /// - public bool IsUndefined => !_isDefined; - - /// - /// Gets the value. The value may be null if T is a nullable type. - /// - /// Thrown if the value is undefined. - /// - /// Always check before accessing Value, or use instead. - /// - /// - /// - /// if (request.Name.IsDefined) - /// { - /// string? name = request.Name.Value; // Safe - can be null if Optional<string?> - /// } - /// - /// // Or check for null explicitly - /// if (request.Email.IsDefined && request.Email.Value is null) - /// { - /// // Email is explicitly set to null (clear it) - /// } - /// - /// - public T Value - { - get - { - if (!_isDefined) - throw new InvalidOperationException("Optional value is undefined"); - return _value; - } - } - - /// - /// Gets the value if defined, otherwise returns the specified default value. - /// Note: If the value is defined as null, this returns null (not the default). - /// - /// The value to return if undefined. - /// The actual value if defined (can be null), otherwise the default value. - /// - /// - /// string name = request.Name.GetValueOrDefault("Anonymous"); - /// // If Name is undefined: returns "Anonymous" - /// // If Name is Of(null): returns null - /// // If Name is Of("John"): returns "John" - /// - /// - public T GetValueOrDefault(T defaultValue = default!) - { - return _isDefined ? _value : defaultValue; - } - - /// - /// Tries to get the value. Returns true if the value is defined (even if null). - /// - /// - /// When this method returns, contains the value if defined, or default(T) if undefined. - /// The value may be null if T is nullable. - /// - /// True if the value is defined; otherwise, false. - /// - /// - /// if (request.Email.TryGetValue(out var email)) - /// { - /// requestBody["email"] = email; // email can be null - /// } - /// else - /// { - /// // Email is undefined - don't include in request - /// } - /// - /// - public bool TryGetValue(out T value) - { - if (_isDefined) - { - value = _value; - return true; - } - value = default!; - return false; - } - - /// - /// Implicitly converts a value to Optional<T>.Of(value). - /// This allows natural assignment: request.Name = "John" instead of request.Name = Optional<string?>.Of("John"). - /// - /// The value to convert (can be null if T is nullable). - public static implicit operator Optional(T value) => Of(value); - - /// - /// Returns a string representation of this Optional value. - /// - /// "Undefined" if not set, or "Defined(value)" if set. - public override string ToString() => _isDefined ? $"Defined({_value})" : "Undefined"; - - /// - /// Gets the boxed value. Returns null if undefined or if the value is null. - /// - public object? GetBoxedValue() - { - if (!_isDefined) - return null; - return _value; - } - - /// - public bool Equals(Optional other) => - _isDefined == other._isDefined && EqualityComparer.Default.Equals(_value, other._value); - - /// - public override bool Equals(object? obj) => obj is Optional other && Equals(other); - - /// - public override int GetHashCode() - { - if (!_isDefined) - return 0; - unchecked - { - int hash = 17; - hash = hash * 31 + 1; // _isDefined = true - hash = hash * 31 + (_value is null ? 0 : _value.GetHashCode()); - return hash; - } - } - - /// - /// Determines whether two Optional values are equal. - /// - /// The first Optional to compare. - /// The second Optional to compare. - /// True if the Optional values are equal; otherwise, false. - public static bool operator ==(Optional left, Optional right) => left.Equals(right); - - /// - /// Determines whether two Optional values are not equal. - /// - /// The first Optional to compare. - /// The second Optional to compare. - /// True if the Optional values are not equal; otherwise, false. - public static bool operator !=(Optional left, Optional right) => !left.Equals(right); -} - -/// -/// Extension methods for Optional to simplify common operations. -/// -public static class OptionalExtensions -{ - /// - /// Adds the value to a dictionary if the optional is defined (even if the value is null). - /// This is useful for building JSON request payloads where null values should be included. - /// - /// The type of the optional value. - /// The optional value to add. - /// The dictionary to add to. - /// The key to use in the dictionary. - /// - /// - /// var dict = new Dictionary<string, object?>(); - /// request.Name.AddTo(dict, "name"); // Adds only if Name.IsDefined - /// request.Email.AddTo(dict, "email"); // Adds only if Email.IsDefined - /// - /// - public static void AddTo( - this Optional optional, - Dictionary dictionary, - string key - ) - { - if (optional.IsDefined) - { - dictionary[key] = optional.Value; - } - } - - /// - /// Executes an action if the optional is defined. - /// - /// The type of the optional value. - /// The optional value. - /// The action to execute with the value. - /// - /// - /// request.Name.IfDefined(name => Console.WriteLine($"Name: {name}")); - /// - /// - public static void IfDefined(this Optional optional, Action action) - { - if (optional.IsDefined) - { - action(optional.Value); - } - } - - /// - /// Maps the value to a new type if the optional is defined, otherwise returns undefined. - /// - /// The type of the original value. - /// The type to map to. - /// The optional value to map. - /// The mapping function. - /// An optional containing the mapped value if defined, otherwise undefined. - /// - /// - /// Optional<string?> name = Optional<string?>.Of("John"); - /// Optional<int> length = name.Map(n => n?.Length ?? 0); // Optional.Of(4) - /// - /// - public static Optional Map( - this Optional optional, - Func mapper - ) - { - return optional.IsDefined - ? Optional.Of(mapper(optional.Value)) - : Optional.Undefined; - } - - /// - /// Adds a nullable value to a dictionary only if it is not null. - /// This is useful for regular nullable properties where null means "omit from request". - /// - /// The type of the value (must be a reference type or Nullable). - /// The nullable value to add. - /// The dictionary to add to. - /// The key to use in the dictionary. - /// - /// - /// var dict = new Dictionary<string, object?>(); - /// request.Description.AddIfNotNull(dict, "description"); // Only adds if not null - /// request.Score.AddIfNotNull(dict, "score"); // Only adds if not null - /// - /// - public static void AddIfNotNull( - this T? value, - Dictionary dictionary, - string key - ) - where T : class - { - if (value is not null) - { - dictionary[key] = value; - } - } - - /// - /// Adds a nullable value type to a dictionary only if it has a value. - /// This is useful for regular nullable properties where null means "omit from request". - /// - /// The underlying value type. - /// The nullable value to add. - /// The dictionary to add to. - /// The key to use in the dictionary. - /// - /// - /// var dict = new Dictionary<string, object?>(); - /// request.Age.AddIfNotNull(dict, "age"); // Only adds if HasValue - /// request.Score.AddIfNotNull(dict, "score"); // Only adds if HasValue - /// - /// - public static void AddIfNotNull( - this T? value, - Dictionary dictionary, - string key - ) - where T : struct - { - if (value.HasValue) - { - dictionary[key] = value.Value; - } - } -} - -/// -/// JSON converter factory for Optional that handles undefined vs null correctly. -/// Uses a TypeInfoResolver to conditionally include/exclude properties based on Optional.IsDefined. -/// -public class OptionalJsonConverterFactory : JsonConverterFactory -{ - public override bool CanConvert(global::System.Type typeToConvert) - { - if (!typeToConvert.IsGenericType) - return false; - - return typeToConvert.GetGenericTypeDefinition() == typeof(Optional<>); - } - - public override JsonConverter? CreateConverter( - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - var valueType = typeToConvert.GetGenericArguments()[0]; - var converterType = typeof(OptionalJsonConverter<>).MakeGenericType(valueType); - return (JsonConverter?)global::System.Activator.CreateInstance(converterType); - } -} - -/// -/// JSON converter for Optional that unwraps the value during serialization. -/// The actual property skipping is handled by the OptionalTypeInfoResolver. -/// -public class OptionalJsonConverter : JsonConverter> -{ - public override Optional Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType == JsonTokenType.Null) - { - return Optional.Of(default!); - } - - var value = JsonSerializer.Deserialize(ref reader, options); - return Optional.Of(value!); - } - - public override void Write( - Utf8JsonWriter writer, - Optional value, - JsonSerializerOptions options - ) - { - // This will be called by the serializer - // We need to unwrap and serialize the inner value - // The TypeInfoResolver will handle skipping undefined values - - if (value.IsUndefined) - { - // This shouldn't be called for undefined values due to ShouldSerialize - // But if it is, write null and let the resolver filter it - writer.WriteNullValue(); - return; - } - - // Get the inner value - var innerValue = value.Value; - - // Write null directly if the value is null (don't use JsonSerializer.Serialize for null) - if (innerValue is null) - { - writer.WriteNullValue(); - return; - } - - // Serialize the unwrapped value - JsonSerializer.Serialize(writer, innerValue, options); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/OptionalAttribute.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/OptionalAttribute.cs deleted file mode 100644 index f73d768e7339..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/OptionalAttribute.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// Marks a property as optional in the OpenAPI specification. -/// Optional properties use the Optional type and can be undefined (not present in JSON). -/// -/// -/// Properties marked with [Optional] should use the Optional type: -/// - Undefined: Optional.Undefined → omitted from JSON -/// - Defined: Optional.Of(value) → written to JSON -/// -/// Combine with [Nullable] to allow null values: -/// - [Optional, Nullable] Optional → can be undefined, null, or a value -/// - [Optional] Optional → can be undefined or a value (null is invalid) -/// -[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] -public class OptionalAttribute : global::System.Attribute { } diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/AdditionalProperties.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/AdditionalProperties.cs deleted file mode 100644 index 8775e0f7a715..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/AdditionalProperties.cs +++ /dev/null @@ -1,353 +0,0 @@ -using global::System.Collections; -using global::System.Collections.ObjectModel; -using global::System.Text.Json; -using global::System.Text.Json.Nodes; -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict; - -public record ReadOnlyAdditionalProperties : ReadOnlyAdditionalProperties -{ - internal ReadOnlyAdditionalProperties() { } - - internal ReadOnlyAdditionalProperties(IDictionary properties) - : base(properties) { } -} - -public record ReadOnlyAdditionalProperties : IReadOnlyDictionary -{ - private readonly Dictionary _extensionData = new(); - private readonly Dictionary _convertedCache = new(); - - internal ReadOnlyAdditionalProperties() - { - _extensionData = new Dictionary(); - _convertedCache = new Dictionary(); - } - - internal ReadOnlyAdditionalProperties(IDictionary properties) - { - _extensionData = new Dictionary(properties.Count); - _convertedCache = new Dictionary(properties.Count); - foreach (var kvp in properties) - { - if (kvp.Value is JsonElement element) - { - _extensionData.Add(kvp.Key, element); - } - else - { - _extensionData[kvp.Key] = JsonUtils.SerializeToElement(kvp.Value); - } - - _convertedCache[kvp.Key] = kvp.Value; - } - } - - private static T ConvertToT(JsonElement value) - { - if (typeof(T) == typeof(JsonElement)) - { - return (T)(object)value; - } - - return value.Deserialize(JsonOptions.JsonSerializerOptions)!; - } - - internal void CopyFromExtensionData(IDictionary extensionData) - { - _extensionData.Clear(); - _convertedCache.Clear(); - foreach (var kvp in extensionData) - { - _extensionData[kvp.Key] = kvp.Value; - if (kvp.Value is T value) - { - _convertedCache[kvp.Key] = value; - } - } - } - - private T GetCached(string key) - { - if (_convertedCache.TryGetValue(key, out var cached)) - { - return cached; - } - - var value = ConvertToT(_extensionData[key]); - _convertedCache[key] = value; - return value; - } - - public IEnumerator> GetEnumerator() - { - return _extensionData - .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) - .GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public int Count => _extensionData.Count; - - public bool ContainsKey(string key) => _extensionData.ContainsKey(key); - - public bool TryGetValue(string key, out T value) - { - if (_convertedCache.TryGetValue(key, out value!)) - { - return true; - } - - if (_extensionData.TryGetValue(key, out var element)) - { - value = ConvertToT(element); - _convertedCache[key] = value; - return true; - } - - return false; - } - - public T this[string key] => GetCached(key); - - public IEnumerable Keys => _extensionData.Keys; - - public IEnumerable Values => Keys.Select(GetCached); -} - -public record AdditionalProperties : AdditionalProperties -{ - public AdditionalProperties() { } - - public AdditionalProperties(IDictionary properties) - : base(properties) { } -} - -public record AdditionalProperties : IDictionary -{ - private readonly Dictionary _extensionData; - private readonly Dictionary _convertedCache; - - public AdditionalProperties() - { - _extensionData = new Dictionary(); - _convertedCache = new Dictionary(); - } - - public AdditionalProperties(IDictionary properties) - { - _extensionData = new Dictionary(properties.Count); - _convertedCache = new Dictionary(properties.Count); - foreach (var kvp in properties) - { - _extensionData[kvp.Key] = kvp.Value; - _convertedCache[kvp.Key] = kvp.Value; - } - } - - private static T ConvertToT(object? extensionDataValue) - { - return extensionDataValue switch - { - T value => value, - JsonElement jsonElement => jsonElement.Deserialize( - JsonOptions.JsonSerializerOptions - )!, - JsonNode jsonNode => jsonNode.Deserialize(JsonOptions.JsonSerializerOptions)!, - _ => JsonUtils - .SerializeToElement(extensionDataValue) - .Deserialize(JsonOptions.JsonSerializerOptions)!, - }; - } - - internal void CopyFromExtensionData(IDictionary extensionData) - { - _extensionData.Clear(); - _convertedCache.Clear(); - foreach (var kvp in extensionData) - { - _extensionData[kvp.Key] = kvp.Value; - if (kvp.Value is T value) - { - _convertedCache[kvp.Key] = value; - } - } - } - - internal void CopyToExtensionData(IDictionary extensionData) - { - extensionData.Clear(); - foreach (var kvp in _extensionData) - { - extensionData[kvp.Key] = kvp.Value; - } - } - - public JsonObject ToJsonObject() => - ( - JsonUtils.SerializeToNode(_extensionData) - ?? throw new InvalidOperationException( - "Failed to serialize AdditionalProperties to JSON Node." - ) - ).AsObject(); - - public JsonNode ToJsonNode() => - JsonUtils.SerializeToNode(_extensionData) - ?? throw new InvalidOperationException( - "Failed to serialize AdditionalProperties to JSON Node." - ); - - public JsonElement ToJsonElement() => JsonUtils.SerializeToElement(_extensionData); - - public JsonDocument ToJsonDocument() => JsonUtils.SerializeToDocument(_extensionData); - - public IReadOnlyDictionary ToJsonElementDictionary() - { - return new ReadOnlyDictionary( - _extensionData.ToDictionary( - kvp => kvp.Key, - kvp => - { - if (kvp.Value is JsonElement jsonElement) - { - return jsonElement; - } - - return JsonUtils.SerializeToElement(kvp.Value); - } - ) - ); - } - - public ICollection Keys => _extensionData.Keys; - - public ICollection Values - { - get - { - var values = new T[_extensionData.Count]; - var i = 0; - foreach (var key in Keys) - { - values[i++] = GetCached(key); - } - - return values; - } - } - - private T GetCached(string key) - { - if (_convertedCache.TryGetValue(key, out var value)) - { - return value; - } - - value = ConvertToT(_extensionData[key]); - _convertedCache.Add(key, value); - return value; - } - - private void SetCached(string key, T value) - { - _extensionData[key] = value; - _convertedCache[key] = value; - } - - private void AddCached(string key, T value) - { - _extensionData.Add(key, value); - _convertedCache.Add(key, value); - } - - private bool RemoveCached(string key) - { - var isRemoved = _extensionData.Remove(key); - _convertedCache.Remove(key); - return isRemoved; - } - - public int Count => _extensionData.Count; - public bool IsReadOnly => false; - - public T this[string key] - { - get => GetCached(key); - set => SetCached(key, value); - } - - public void Add(string key, T value) => AddCached(key, value); - - public void Add(KeyValuePair item) => AddCached(item.Key, item.Value); - - public bool Remove(string key) => RemoveCached(key); - - public bool Remove(KeyValuePair item) => RemoveCached(item.Key); - - public bool ContainsKey(string key) => _extensionData.ContainsKey(key); - - public bool Contains(KeyValuePair item) - { - return _extensionData.ContainsKey(item.Key) - && EqualityComparer.Default.Equals(GetCached(item.Key), item.Value); - } - - public bool TryGetValue(string key, out T value) - { - if (_convertedCache.TryGetValue(key, out value!)) - { - return true; - } - - if (_extensionData.TryGetValue(key, out var extensionDataValue)) - { - value = ConvertToT(extensionDataValue); - _convertedCache[key] = value; - return true; - } - - return false; - } - - public void Clear() - { - _extensionData.Clear(); - _convertedCache.Clear(); - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - if (array is null) - { - throw new ArgumentNullException(nameof(array)); - } - - if (arrayIndex < 0 || arrayIndex > array.Length) - { - throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - } - - if (array.Length - arrayIndex < _extensionData.Count) - { - throw new ArgumentException( - "The array does not have enough space to copy the elements." - ); - } - - foreach (var kvp in _extensionData) - { - array[arrayIndex++] = new KeyValuePair(kvp.Key, GetCached(kvp.Key)); - } - } - - public IEnumerator> GetEnumerator() - { - return _extensionData - .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) - .GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/ClientOptions.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/ClientOptions.cs deleted file mode 100644 index 726efba48715..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/ClientOptions.cs +++ /dev/null @@ -1,84 +0,0 @@ -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict; - -[Serializable] -public partial class ClientOptions -{ - /// - /// The http headers sent with the request. - /// - internal Headers Headers { get; init; } = new(); - - /// - /// The Base URL for the API. - /// - public string BaseUrl { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = ""; - - /// - /// The http client used to make requests. - /// - public HttpClient HttpClient { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = new HttpClient(); - - /// - /// Additional headers to be sent with HTTP requests. - /// Headers with matching keys will be overwritten by headers set on the request. - /// - public IEnumerable> AdditionalHeaders { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = []; - - /// - /// The max number of retries to attempt. - /// - public int MaxRetries { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = 2; - - /// - /// The timeout for the request. - /// - public TimeSpan Timeout { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = TimeSpan.FromSeconds(30); - - /// - /// Clones this and returns a new instance - /// - internal ClientOptions Clone() - { - return new ClientOptions - { - BaseUrl = BaseUrl, - HttpClient = HttpClient, - MaxRetries = MaxRetries, - Timeout = Timeout, - Headers = new Headers(new Dictionary(Headers)), - AdditionalHeaders = AdditionalHeaders, - }; - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/FileParameter.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/FileParameter.cs deleted file mode 100644 index 7c0f68a08014..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/FileParameter.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace SeedCsharpNamespaceConflict; - -/// -/// File parameter for uploading files. -/// -public record FileParameter : IDisposable -#if NET6_0_OR_GREATER - , IAsyncDisposable -#endif -{ - private bool _disposed; - - /// - /// The name of the file to be uploaded. - /// - public string? FileName { get; set; } - - /// - /// The content type of the file to be uploaded. - /// - public string? ContentType { get; set; } - - /// - /// The content of the file to be uploaded. - /// - public required Stream Stream { get; set; } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - protected virtual void Dispose(bool disposing) - { - if (_disposed) - return; - if (disposing) - { - Stream.Dispose(); - } - - _disposed = true; - } - -#if NET6_0_OR_GREATER - /// - public async ValueTask DisposeAsync() - { - if (!_disposed) - { - await Stream.DisposeAsync().ConfigureAwait(false); - _disposed = true; - } - - GC.SuppressFinalize(this); - } -#endif - - public static implicit operator FileParameter(Stream stream) => new() { Stream = stream }; -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/RawResponse.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/RawResponse.cs deleted file mode 100644 index 29dceb02a162..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/RawResponse.cs +++ /dev/null @@ -1,24 +0,0 @@ -using global::System.Net; - -namespace SeedCsharpNamespaceConflict; - -/// -/// Contains HTTP response metadata including status code, URL, and headers. -/// -public record RawResponse -{ - /// - /// The HTTP status code of the response. - /// - public required HttpStatusCode StatusCode { get; init; } - - /// - /// The request URL that generated this response. - /// - public required Uri Url { get; init; } - - /// - /// The HTTP response headers. - /// - public required Core.ResponseHeaders Headers { get; init; } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/RequestOptions.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/RequestOptions.cs deleted file mode 100644 index cf9279549578..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/RequestOptions.cs +++ /dev/null @@ -1,86 +0,0 @@ -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict; - -[Serializable] -public partial class RequestOptions : IRequestOptions -{ - /// - /// The Base URL for the API. - /// - public string? BaseUrl { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// The http client used to make requests. - /// - public HttpClient? HttpClient { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// Additional headers to be sent with the request. - /// Headers previously set with matching keys will be overwritten. - /// - public IEnumerable> AdditionalHeaders { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = []; - - /// - /// The max number of retries to attempt. - /// - public int? MaxRetries { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// The timeout for the request. - /// - public TimeSpan? Timeout { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// Additional query parameters sent with the request. - /// - public IEnumerable> AdditionalQueryParameters { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = Enumerable.Empty>(); - - /// - /// Additional body properties sent with the request. - /// This is only applied to JSON requests. - /// - public object? AdditionalBodyProperties { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/SeedCsharpNamespaceConflictApiException.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/SeedCsharpNamespaceConflictApiException.cs deleted file mode 100644 index 081ad75284da..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/SeedCsharpNamespaceConflictApiException.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace SeedCsharpNamespaceConflict; - -/// -/// This exception type will be thrown for any non-2XX API responses. -/// -public class SeedCsharpNamespaceConflictApiException( - string message, - int statusCode, - object body, - Exception? innerException = null -) : SeedCsharpNamespaceConflictException(message, innerException) -{ - /// - /// The error code of the response that triggered the exception. - /// - public int StatusCode => statusCode; - - /// - /// The body of the response that triggered the exception. - /// - public object Body => body; -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/SeedCsharpNamespaceConflictException.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/SeedCsharpNamespaceConflictException.cs deleted file mode 100644 index 89dbc55c2ad6..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/SeedCsharpNamespaceConflictException.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SeedCsharpNamespaceConflict; - -/// -/// Base exception class for all exceptions thrown by the SDK. -/// -public class SeedCsharpNamespaceConflictException(string message, Exception? innerException = null) - : Exception(message, innerException); diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/Version.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/Version.cs deleted file mode 100644 index 90181780f0a4..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/Version.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SeedCsharpNamespaceConflict; - -[Serializable] -internal class Version -{ - public const string Current = "0.0.1"; -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/WithRawResponse.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/WithRawResponse.cs deleted file mode 100644 index aa858fac8b15..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/WithRawResponse.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace SeedCsharpNamespaceConflict; - -/// -/// Wraps a parsed response value with its raw HTTP response metadata. -/// -/// The type of the parsed response data. -public readonly struct WithRawResponse -{ - /// - /// The parsed response data. - /// - public required T Data { get; init; } - - /// - /// The raw HTTP response metadata. - /// - public required RawResponse RawResponse { get; init; } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/WithRawResponseTask.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/WithRawResponseTask.cs deleted file mode 100644 index decf3a6c7fa5..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/Public/WithRawResponseTask.cs +++ /dev/null @@ -1,144 +0,0 @@ -using global::System.Runtime.CompilerServices; - -namespace SeedCsharpNamespaceConflict; - -/// -/// A task-like type that wraps Task<WithRawResponse<T>> and provides dual-mode awaiting: -/// - Direct await yields just T (zero-allocation path for common case) -/// - .WithRawResponse() yields WithRawResponse<T> (when raw response metadata is needed) -/// -/// The type of the parsed response data. -public readonly struct WithRawResponseTask -{ - private readonly global::System.Threading.Tasks.Task> _task; - - /// - /// Creates a new WithRawResponseTask wrapping the given task. - /// - public WithRawResponseTask(global::System.Threading.Tasks.Task> task) - { - _task = task; - } - - /// - /// Returns the underlying task that yields both the data and raw response metadata. - /// - public global::System.Threading.Tasks.Task> WithRawResponse() => _task; - - /// - /// Gets the custom awaiter that unwraps to just T when awaited. - /// - public Awaiter GetAwaiter() => new(_task.GetAwaiter()); - - /// - /// Configures the awaiter to continue on the captured context or not. - /// - public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) => - new(_task.ConfigureAwait(continueOnCapturedContext)); - - /// - /// Implicitly converts WithRawResponseTask<T> to global::System.Threading.Tasks.Task<T> for backward compatibility. - /// The resulting task will yield just the data when awaited. - /// - public static implicit operator global::System.Threading.Tasks.Task( - WithRawResponseTask task - ) - { - return task._task.ContinueWith( - t => t.Result.Data, - TaskContinuationOptions.ExecuteSynchronously - ); - } - - /// - /// Custom awaiter that unwraps WithRawResponse<T> to just T. - /// - public readonly struct Awaiter : ICriticalNotifyCompletion - { - private readonly TaskAwaiter> _awaiter; - - internal Awaiter(TaskAwaiter> awaiter) - { - _awaiter = awaiter; - } - - /// - /// Gets whether the underlying task has completed. - /// - public bool IsCompleted => _awaiter.IsCompleted; - - /// - /// Gets the result, unwrapping to just the data. - /// - public T GetResult() => _awaiter.GetResult().Data; - - /// - /// Schedules the continuation action. - /// - public void OnCompleted(global::System.Action continuation) => - _awaiter.OnCompleted(continuation); - - /// - /// Schedules the continuation action without capturing the execution context. - /// - public void UnsafeOnCompleted(global::System.Action continuation) => - _awaiter.UnsafeOnCompleted(continuation); - } - - /// - /// Awaitable type returned by ConfigureAwait that unwraps to just T. - /// - public readonly struct ConfiguredTaskAwaitable - { - private readonly ConfiguredTaskAwaitable> _configuredTask; - - internal ConfiguredTaskAwaitable(ConfiguredTaskAwaitable> configuredTask) - { - _configuredTask = configuredTask; - } - - /// - /// Gets the configured awaiter that unwraps to just T. - /// - public ConfiguredAwaiter GetAwaiter() => new(_configuredTask.GetAwaiter()); - - /// - /// Custom configured awaiter that unwraps WithRawResponse<T> to just T. - /// - public readonly struct ConfiguredAwaiter : ICriticalNotifyCompletion - { - private readonly ConfiguredTaskAwaitable< - WithRawResponse - >.ConfiguredTaskAwaiter _awaiter; - - internal ConfiguredAwaiter( - ConfiguredTaskAwaitable>.ConfiguredTaskAwaiter awaiter - ) - { - _awaiter = awaiter; - } - - /// - /// Gets whether the underlying task has completed. - /// - public bool IsCompleted => _awaiter.IsCompleted; - - /// - /// Gets the result, unwrapping to just the data. - /// - public T GetResult() => _awaiter.GetResult().Data; - - /// - /// Schedules the continuation action. - /// - public void OnCompleted(global::System.Action continuation) => - _awaiter.OnCompleted(continuation); - - /// - /// Schedules the continuation action without capturing the execution context. - /// - public void UnsafeOnCompleted(global::System.Action continuation) => - _awaiter.UnsafeOnCompleted(continuation); - } - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/QueryStringBuilder.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/QueryStringBuilder.cs deleted file mode 100644 index f95f9a502437..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/QueryStringBuilder.cs +++ /dev/null @@ -1,484 +0,0 @@ -using global::System.Buffers; -using global::System.Runtime.CompilerServices; -#if !NET6_0_OR_GREATER -using global::System.Text; -#endif - -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// High-performance query string builder with cross-platform optimizations. -/// Uses span-based APIs on .NET 6+ and StringBuilder fallback for older targets. -/// -internal static class QueryStringBuilder -{ -#if NET8_0_OR_GREATER - private static readonly SearchValues UnreservedChars = SearchValues.Create( - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~" - ); -#else - private const string UnreservedChars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~"; -#endif - -#if NET7_0_OR_GREATER - private static ReadOnlySpan UpperHexChars => "0123456789ABCDEF"u8; -#else - private static readonly byte[] UpperHexChars = - { - (byte)'0', - (byte)'1', - (byte)'2', - (byte)'3', - (byte)'4', - (byte)'5', - (byte)'6', - (byte)'7', - (byte)'8', - (byte)'9', - (byte)'A', - (byte)'B', - (byte)'C', - (byte)'D', - (byte)'E', - (byte)'F', - }; -#endif - - /// - /// Builds a query string from the provided parameters. - /// -#if NET6_0_OR_GREATER - public static string Build(ReadOnlySpan> parameters) - { - if (parameters.IsEmpty) - return string.Empty; - - var estimatedLength = EstimateLength(parameters); - if (estimatedLength == 0) - return string.Empty; - - var bufferSize = Math.Min(estimatedLength * 3, 8192); - var buffer = ArrayPool.Shared.Rent(bufferSize); - - try - { - var written = BuildCore(parameters, buffer); - return new string(buffer.AsSpan(0, written)); - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } - - private static int EstimateLength(ReadOnlySpan> parameters) - { - var estimatedLength = 0; - foreach (var kvp in parameters) - { - estimatedLength += kvp.Key.Length + kvp.Value.Length + 2; - } - return estimatedLength; - } -#endif - - /// - /// Builds a query string from the provided parameters. - /// - public static string Build(IEnumerable> parameters) - { -#if NET6_0_OR_GREATER - // Try to get span access for collections that support it - if (parameters is ICollection> collection) - { - if (collection.Count == 0) - return string.Empty; - - var array = ArrayPool>.Shared.Rent(collection.Count); - try - { - collection.CopyTo(array, 0); - return Build(array.AsSpan(0, collection.Count)); - } - finally - { - ArrayPool>.Shared.Return(array); - } - } - - // Fallback for non-collection enumerables - using var enumerator = parameters.GetEnumerator(); - if (!enumerator.MoveNext()) - return string.Empty; - - var buffer = ArrayPool.Shared.Rent(4096); - try - { - var position = 0; - var first = true; - - do - { - var kvp = enumerator.Current; - - // Ensure capacity (worst case: 3x for encoding + separators) - var required = (kvp.Key.Length + kvp.Value.Length + 2) * 3; - if (position + required > buffer.Length) - { - var newBuffer = ArrayPool.Shared.Rent(buffer.Length * 2); - buffer.AsSpan(0, position).CopyTo(newBuffer); - ArrayPool.Shared.Return(buffer); - buffer = newBuffer; - } - - buffer[position++] = first ? '?' : '&'; - first = false; - - position += EncodeComponent(kvp.Key.AsSpan(), buffer.AsSpan(position)); - buffer[position++] = '='; - position += EncodeComponent(kvp.Value.AsSpan(), buffer.AsSpan(position)); - } while (enumerator.MoveNext()); - - return first ? string.Empty : new string(buffer.AsSpan(0, position)); - } - finally - { - ArrayPool.Shared.Return(buffer); - } -#else - // netstandard2.0 / net462 fallback using StringBuilder - var sb = new StringBuilder(); - var first = true; - - foreach (var kvp in parameters) - { - sb.Append(first ? '?' : '&'); - first = false; - - AppendEncoded(sb, kvp.Key); - sb.Append('='); - AppendEncoded(sb, kvp.Value); - } - - return sb.ToString(); -#endif - } - -#if NET6_0_OR_GREATER - private static int BuildCore( - ReadOnlySpan> parameters, - Span buffer - ) - { - var position = 0; - var first = true; - - foreach (var kvp in parameters) - { - buffer[position++] = first ? '?' : '&'; - first = false; - - position += EncodeComponent(kvp.Key.AsSpan(), buffer.Slice(position)); - buffer[position++] = '='; - position += EncodeComponent(kvp.Value.AsSpan(), buffer.Slice(position)); - } - - return position; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int EncodeComponent(ReadOnlySpan input, Span output) - { - if (!NeedsEncoding(input)) - { - input.CopyTo(output); - return input.Length; - } - - return EncodeSlow(input, output); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool NeedsEncoding(ReadOnlySpan value) - { -#if NET8_0_OR_GREATER - return value.ContainsAnyExcept(UnreservedChars); -#else - foreach (var c in value) - { - if (!IsUnreserved(c)) - return true; - } - return false; -#endif - } - - private static int EncodeSlow(ReadOnlySpan input, Span output) - { - var position = 0; - - foreach (var c in input) - { - if (IsUnreserved(c)) - { - output[position++] = c; - } - else if (c == ' ') - { - output[position++] = '%'; - output[position++] = '2'; - output[position++] = '0'; - } -#if NET7_0_OR_GREATER - else if (char.IsAscii(c)) -#else - else if (c <= 127) -#endif - { - position += EncodeAscii((byte)c, output.Slice(position)); - } - else - { - position += EncodeUtf8(c, output.Slice(position)); - } - } - - return position; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int EncodeAscii(byte value, Span output) - { - output[0] = '%'; - output[1] = (char)UpperHexChars[value >> 4]; - output[2] = (char)UpperHexChars[value & 0xF]; - return 3; - } - - private static int EncodeUtf8(char c, Span output) - { - Span utf8Bytes = stackalloc byte[4]; - Span singleChar = stackalloc char[1] { c }; - var byteCount = global::System.Text.Encoding.UTF8.GetBytes(singleChar, utf8Bytes); - - var position = 0; - for (var i = 0; i < byteCount; i++) - { - output[position++] = '%'; - output[position++] = (char)UpperHexChars[utf8Bytes[i] >> 4]; - output[position++] = (char)UpperHexChars[utf8Bytes[i] & 0xF]; - } - - return position; - } -#else - // netstandard2.0 / net462 StringBuilder-based encoding - private static void AppendEncoded(StringBuilder sb, string value) - { - foreach (var c in value) - { - if (IsUnreserved(c)) - { - sb.Append(c); - } - else if (c == ' ') - { - sb.Append("%20"); - } - else if (c <= 127) - { - AppendPercentEncoded(sb, (byte)c); - } - else - { - var bytes = Encoding.UTF8.GetBytes(new[] { c }); - foreach (var b in bytes) - { - AppendPercentEncoded(sb, b); - } - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void AppendPercentEncoded(StringBuilder sb, byte value) - { - sb.Append('%'); - sb.Append((char)UpperHexChars[value >> 4]); - sb.Append((char)UpperHexChars[value & 0xF]); - } -#endif - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsUnreserved(char c) - { -#if NET8_0_OR_GREATER - return UnreservedChars.Contains(c); -#elif NET7_0_OR_GREATER - return char.IsAsciiLetterOrDigit(c) || c is '-' or '_' or '.' or '~'; -#else - return (c >= 'A' && c <= 'Z') - || (c >= 'a' && c <= 'z') - || (c >= '0' && c <= '9') - || c == '-' - || c == '_' - || c == '.' - || c == '~'; -#endif - } - - /// - /// Fluent builder for constructing query strings with support for simple parameters and deep object notation. - /// - public sealed class Builder - { - private readonly List> _params; - - /// - /// Initializes a new instance with default capacity. - /// - public Builder() - { - _params = new List>(); - } - - /// - /// Initializes a new instance with the specified initial capacity. - /// - public Builder(int capacity) - { - _params = new List>(capacity); - } - - /// - /// Adds a simple parameter. For collections, adds multiple key-value pairs (one per element). - /// - public Builder Add(string key, object? value) - { - if (value is null) - { - return this; - } - - // Handle string separately since it implements IEnumerable - if (value is string stringValue) - { - _params.Add(new KeyValuePair(key, stringValue)); - return this; - } - - // Handle collections (arrays, lists, etc.) - add each element as a separate key-value pair - if ( - value - is global::System.Collections.IEnumerable enumerable - and not global::System.Collections.IDictionary - ) - { - foreach (var item in enumerable) - { - if (item is not null) - { - _params.Add( - new KeyValuePair( - key, - ValueConvert.ToQueryStringValue(item) - ) - ); - } - } - return this; - } - - // Handle scalar values - _params.Add( - new KeyValuePair(key, ValueConvert.ToQueryStringValue(value)) - ); - return this; - } - - /// - /// Sets a parameter, removing any existing parameters with the same key before adding the new value. - /// For collections, removes all existing parameters with the key, then adds multiple key-value pairs (one per element). - /// This allows overriding parameters set earlier in the builder. - /// - public Builder Set(string key, object? value) - { - // Remove all existing parameters with this key - _params.RemoveAll(kv => kv.Key == key); - - // Add the new value(s) - return Add(key, value); - } - - /// - /// Merges additional query parameters with override semantics. - /// Groups parameters by key and calls Set() once per unique key. - /// This ensures that parameters with the same key are properly merged: - /// - If a key appears once, it's added as a single value - /// - If a key appears multiple times, all values are added as an array - /// - All parameters override any existing parameters with the same key - /// - public Builder MergeAdditional( - global::System.Collections.Generic.IEnumerable>? additionalParameters - ) - { - if (additionalParameters is null) - { - return this; - } - - // Group by key to handle multiple values for the same key correctly - var grouped = additionalParameters - .GroupBy(kv => kv.Key) - .Select(g => new global::System.Collections.Generic.KeyValuePair( - g.Key, - g.Count() == 1 ? (object)g.First().Value : g.Select(kv => kv.Value).ToArray() - )); - - foreach (var param in grouped) - { - Set(param.Key, param.Value); - } - - return this; - } - - /// - /// Adds a complex object using deep object notation with a prefix. - /// Deep object notation nests properties with brackets: prefix[key][nested]=value - /// - public Builder AddDeepObject(string prefix, object? value) - { - if (value is not null) - { - _params.AddRange(QueryStringConverter.ToDeepObject(prefix, value)); - } - return this; - } - - /// - /// Adds a complex object using exploded form notation with an optional prefix. - /// Exploded form flattens properties: prefix[key]=value (no deep nesting). - /// - public Builder AddExploded(string prefix, object? value) - { - if (value is not null) - { - _params.AddRange(QueryStringConverter.ToExplodedForm(prefix, value)); - } - return this; - } - - /// - /// Builds the final query string. - /// - public string Build() - { - return QueryStringBuilder.Build(_params); - } - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/QueryStringConverter.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/QueryStringConverter.cs deleted file mode 100644 index 47b25db9f1f4..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/QueryStringConverter.cs +++ /dev/null @@ -1,259 +0,0 @@ -using global::System.Text.Json; - -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// Converts an object into a query string collection. -/// -internal static class QueryStringConverter -{ - /// - /// Converts an object into a query string collection using Deep Object notation with a prefix. - /// - /// The prefix to prepend to all keys (e.g., "session_settings"). Pass empty string for no prefix. - /// Object to form URL-encode. Can be an object, array of objects, or dictionary. - /// Throws when passing in a string or primitive value. - /// A collection of key value pairs. The keys and values are not URL encoded. - internal static IEnumerable> ToDeepObject( - string prefix, - object value - ) - { - var queryCollection = new List>(); - var json = JsonUtils.SerializeToElement(value); - JsonToDeepObject(json, prefix, queryCollection); - return queryCollection; - } - - /// - /// Converts an object into a query string collection using Deep Object notation. - /// - /// Object to form URL-encode. Can be an object, array of objects, or dictionary. - /// Throws when passing in a string or primitive value. - /// A collection of key value pairs. The keys and values are not URL encoded. - internal static IEnumerable> ToDeepObject(object value) - { - return ToDeepObject("", value); - } - - /// - /// Converts an object into a query string collection using Exploded Form notation with a prefix. - /// - /// The prefix to prepend to all keys. Pass empty string for no prefix. - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - /// A collection of key value pairs. The keys and values are not URL encoded. - internal static IEnumerable> ToExplodedForm( - string prefix, - object value - ) - { - var queryCollection = new List>(); - var json = JsonUtils.SerializeToElement(value); - AssertRootJson(json); - JsonToFormExploded(json, prefix, queryCollection); - return queryCollection; - } - - /// - /// Converts an object into a query string collection using Exploded Form notation. - /// - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - /// A collection of key value pairs. The keys and values are not URL encoded. - internal static IEnumerable> ToExplodedForm(object value) - { - return ToExplodedForm("", value); - } - - /// - /// Converts an object into a query string collection using Form notation without exploding parameters. - /// - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - /// A collection of key value pairs. The keys and values are not URL encoded. - internal static IEnumerable> ToForm(object value) - { - var queryCollection = new List>(); - var json = JsonUtils.SerializeToElement(value); - AssertRootJson(json); - JsonToForm(json, "", queryCollection); - return queryCollection; - } - - private static void AssertRootJson(JsonElement json) - { - switch (json.ValueKind) - { - case JsonValueKind.Object: - break; - case JsonValueKind.Array: - case JsonValueKind.Undefined: - case JsonValueKind.String: - case JsonValueKind.Number: - case JsonValueKind.True: - case JsonValueKind.False: - case JsonValueKind.Null: - default: - throw new global::System.Exception( - $"Only objects can be converted to query string collections. Given type is {json.ValueKind}." - ); - } - } - - private static void JsonToForm( - JsonElement element, - string prefix, - List> parameters - ) - { - switch (element.ValueKind) - { - case JsonValueKind.Object: - foreach (var property in element.EnumerateObject()) - { - var newPrefix = string.IsNullOrEmpty(prefix) - ? property.Name - : $"{prefix}[{property.Name}]"; - - JsonToForm(property.Value, newPrefix, parameters); - } - break; - case JsonValueKind.Array: - var arrayValues = element.EnumerateArray().Select(ValueToString).ToArray(); - parameters.Add( - new KeyValuePair(prefix, string.Join(",", arrayValues)) - ); - break; - case JsonValueKind.Null: - break; - case JsonValueKind.Undefined: - case JsonValueKind.String: - case JsonValueKind.Number: - case JsonValueKind.True: - case JsonValueKind.False: - default: - parameters.Add(new KeyValuePair(prefix, ValueToString(element))); - break; - } - } - - private static void JsonToFormExploded( - JsonElement element, - string prefix, - List> parameters - ) - { - switch (element.ValueKind) - { - case JsonValueKind.Object: - foreach (var property in element.EnumerateObject()) - { - var newPrefix = string.IsNullOrEmpty(prefix) - ? property.Name - : $"{prefix}[{property.Name}]"; - - JsonToFormExploded(property.Value, newPrefix, parameters); - } - - break; - case JsonValueKind.Array: - foreach (var item in element.EnumerateArray()) - { - if ( - item.ValueKind != JsonValueKind.Object - && item.ValueKind != JsonValueKind.Array - ) - { - parameters.Add( - new KeyValuePair(prefix, ValueToString(item)) - ); - } - else - { - JsonToFormExploded(item, prefix, parameters); - } - } - - break; - case JsonValueKind.Null: - break; - case JsonValueKind.Undefined: - case JsonValueKind.String: - case JsonValueKind.Number: - case JsonValueKind.True: - case JsonValueKind.False: - default: - parameters.Add(new KeyValuePair(prefix, ValueToString(element))); - break; - } - } - - private static void JsonToDeepObject( - JsonElement element, - string prefix, - List> parameters - ) - { - switch (element.ValueKind) - { - case JsonValueKind.Object: - foreach (var property in element.EnumerateObject()) - { - var newPrefix = string.IsNullOrEmpty(prefix) - ? property.Name - : $"{prefix}[{property.Name}]"; - - JsonToDeepObject(property.Value, newPrefix, parameters); - } - - break; - case JsonValueKind.Array: - var index = 0; - foreach (var item in element.EnumerateArray()) - { - var newPrefix = $"{prefix}[{index++}]"; - - if ( - item.ValueKind != JsonValueKind.Object - && item.ValueKind != JsonValueKind.Array - ) - { - parameters.Add( - new KeyValuePair(newPrefix, ValueToString(item)) - ); - } - else - { - JsonToDeepObject(item, newPrefix, parameters); - } - } - - break; - case JsonValueKind.Null: - case JsonValueKind.Undefined: - // Skip null and undefined values - don't add parameters for them - break; - case JsonValueKind.String: - case JsonValueKind.Number: - case JsonValueKind.True: - case JsonValueKind.False: - default: - parameters.Add(new KeyValuePair(prefix, ValueToString(element))); - break; - } - } - - private static string ValueToString(JsonElement element) - { - return element.ValueKind switch - { - JsonValueKind.String => element.GetString() ?? "", - JsonValueKind.Number => element.GetRawText(), - JsonValueKind.True => "true", - JsonValueKind.False => "false", - JsonValueKind.Null => "", - _ => element.GetRawText(), - }; - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/RawClient.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/RawClient.cs deleted file mode 100644 index 0f459df70122..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/RawClient.cs +++ /dev/null @@ -1,344 +0,0 @@ -using global::System.Net.Http; -using global::System.Net.Http.Headers; -using global::System.Text; -using SystemTask = global::System.Threading.Tasks.Task; - -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// Utility class for making raw HTTP requests to the API. -/// -internal partial class RawClient(ClientOptions clientOptions) -{ - private const int MaxRetryDelayMs = 60000; - private const double JitterFactor = 0.2; -#if NET6_0_OR_GREATER - // Use Random.Shared for thread-safe random number generation on .NET 6+ -#else - private static readonly object JitterLock = new(); - private static readonly Random JitterRandom = new(); -#endif - internal int BaseRetryDelay { get; set; } = 1000; - - /// - /// The client options applied on every request. - /// - internal readonly ClientOptions Options = clientOptions; - - internal async global::System.Threading.Tasks.Task SendRequestAsync( - global::SeedCsharpNamespaceConflict.Core.BaseRequest request, - CancellationToken cancellationToken = default - ) - { - // Apply the request timeout. - using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var timeout = request.Options?.Timeout ?? Options.Timeout; - cts.CancelAfter(timeout); - - var httpRequest = await CreateHttpRequestAsync(request).ConfigureAwait(false); - // Send the request. - return await SendWithRetriesAsync(httpRequest, request.Options, cts.Token) - .ConfigureAwait(false); - } - - internal async global::System.Threading.Tasks.Task SendRequestAsync( - HttpRequestMessage request, - IRequestOptions? options, - CancellationToken cancellationToken = default - ) - { - // Apply the request timeout. - using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var timeout = options?.Timeout ?? Options.Timeout; - cts.CancelAfter(timeout); - - // Send the request. - return await SendWithRetriesAsync(request, options, cts.Token).ConfigureAwait(false); - } - - private static async global::System.Threading.Tasks.Task CloneRequestAsync( - HttpRequestMessage request - ) - { - var clonedRequest = new HttpRequestMessage(request.Method, request.RequestUri); - clonedRequest.Version = request.Version; - - if (request.Content != null) - { - switch (request.Content) - { - case MultipartContent oldMultipartFormContent: - var originalBoundary = - oldMultipartFormContent - .Headers.ContentType?.Parameters.First(p => - p.Name.Equals("boundary", StringComparison.OrdinalIgnoreCase) - ) - .Value?.Trim('"') - ?? Guid.NewGuid().ToString(); - var newMultipartContent = oldMultipartFormContent switch - { - MultipartFormDataContent => new MultipartFormDataContent(originalBoundary), - _ => new MultipartContent(), - }; - foreach (var content in oldMultipartFormContent) - { - var ms = new MemoryStream(); - await content.CopyToAsync(ms).ConfigureAwait(false); - ms.Position = 0; - var newPart = new StreamContent(ms); - foreach (var header in content.Headers) - { - newPart.Headers.TryAddWithoutValidation(header.Key, header.Value); - } - - newMultipartContent.Add(newPart); - } - - clonedRequest.Content = newMultipartContent; - break; - default: - var bodyStream = new MemoryStream(); - await request.Content.CopyToAsync(bodyStream).ConfigureAwait(false); - bodyStream.Position = 0; - var clonedContent = new StreamContent(bodyStream); - foreach (var header in request.Content.Headers) - { - clonedContent.Headers.TryAddWithoutValidation(header.Key, header.Value); - } - - clonedRequest.Content = clonedContent; - break; - } - } - - foreach (var header in request.Headers) - { - clonedRequest.Headers.TryAddWithoutValidation(header.Key, header.Value); - } - - return clonedRequest; - } - - /// - /// Sends the request with retries, unless the request content is not retryable, - /// such as stream requests and multipart form data with stream content. - /// - private async global::System.Threading.Tasks.Task SendWithRetriesAsync( - HttpRequestMessage request, - IRequestOptions? options, - CancellationToken cancellationToken - ) - { - var httpClient = options?.HttpClient ?? Options.HttpClient; - var maxRetries = options?.MaxRetries ?? Options.MaxRetries; - var response = await httpClient - .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken) - .ConfigureAwait(false); - var isRetryableContent = IsRetryableContent(request); - - if (!isRetryableContent) - { - return new global::SeedCsharpNamespaceConflict.Core.ApiResponse - { - StatusCode = (int)response.StatusCode, - Raw = response, - }; - } - - for (var i = 0; i < maxRetries; i++) - { - if (!ShouldRetry(response)) - { - break; - } - - var delayMs = GetRetryDelayFromHeaders(response, i); - await SystemTask.Delay(delayMs, cancellationToken).ConfigureAwait(false); - using var retryRequest = await CloneRequestAsync(request).ConfigureAwait(false); - response = await httpClient - .SendAsync( - retryRequest, - HttpCompletionOption.ResponseHeadersRead, - cancellationToken - ) - .ConfigureAwait(false); - } - - return new global::SeedCsharpNamespaceConflict.Core.ApiResponse - { - StatusCode = (int)response.StatusCode, - Raw = response, - }; - } - - private static bool ShouldRetry(HttpResponseMessage response) - { - var statusCode = (int)response.StatusCode; - return statusCode is 408 or 429 or >= 500; - } - - private static int AddPositiveJitter(int delayMs) - { -#if NET6_0_OR_GREATER - var random = Random.Shared.NextDouble(); -#else - double random; - lock (JitterLock) - { - random = JitterRandom.NextDouble(); - } -#endif - var jitterMultiplier = 1 + random * JitterFactor; - return (int)(delayMs * jitterMultiplier); - } - - private static int AddSymmetricJitter(int delayMs) - { -#if NET6_0_OR_GREATER - var random = Random.Shared.NextDouble(); -#else - double random; - lock (JitterLock) - { - random = JitterRandom.NextDouble(); - } -#endif - var jitterMultiplier = 1 + (random - 0.5) * JitterFactor; - return (int)(delayMs * jitterMultiplier); - } - - private int GetRetryDelayFromHeaders(HttpResponseMessage response, int retryAttempt) - { - if (response.Headers.TryGetValues("Retry-After", out var retryAfterValues)) - { - var retryAfter = retryAfterValues.FirstOrDefault(); - if (!string.IsNullOrEmpty(retryAfter)) - { - if (int.TryParse(retryAfter, out var retryAfterSeconds) && retryAfterSeconds > 0) - { - return Math.Min(retryAfterSeconds * 1000, MaxRetryDelayMs); - } - - if (DateTimeOffset.TryParse(retryAfter, out var retryAfterDate)) - { - var delay = (int)(retryAfterDate - DateTimeOffset.UtcNow).TotalMilliseconds; - if (delay > 0) - { - return Math.Min(delay, MaxRetryDelayMs); - } - } - } - } - - if (response.Headers.TryGetValues("X-RateLimit-Reset", out var rateLimitResetValues)) - { - var rateLimitReset = rateLimitResetValues.FirstOrDefault(); - if ( - !string.IsNullOrEmpty(rateLimitReset) - && long.TryParse(rateLimitReset, out var resetTime) - ) - { - var resetDateTime = DateTimeOffset.FromUnixTimeSeconds(resetTime); - var delay = (int)(resetDateTime - DateTimeOffset.UtcNow).TotalMilliseconds; - if (delay > 0) - { - return AddPositiveJitter(Math.Min(delay, MaxRetryDelayMs)); - } - } - } - - var exponentialDelay = Math.Min(BaseRetryDelay * (1 << retryAttempt), MaxRetryDelayMs); - return AddSymmetricJitter(exponentialDelay); - } - - private static bool IsRetryableContent(HttpRequestMessage request) - { - return request.Content switch - { - IIsRetryableContent c => c.IsRetryable, - StreamContent => false, - MultipartContent content => !content.Any(c => c is StreamContent), - _ => true, - }; - } - - internal async global::System.Threading.Tasks.Task CreateHttpRequestAsync( - global::SeedCsharpNamespaceConflict.Core.BaseRequest request - ) - { - var url = BuildUrl(request); - var httpRequest = new HttpRequestMessage(request.Method, url); - httpRequest.Content = request.CreateContent(); - SetHeaders(httpRequest, request.Headers); - - return httpRequest; - } - - private string BuildUrl(global::SeedCsharpNamespaceConflict.Core.BaseRequest request) - { - var baseUrl = request.Options?.BaseUrl ?? request.BaseUrl ?? Options.BaseUrl; - - var trimmedBaseUrl = baseUrl.TrimEnd('/'); - var trimmedBasePath = request.Path.TrimStart('/'); - var url = $"{trimmedBaseUrl}/{trimmedBasePath}"; - - // Append query string if present - if (!string.IsNullOrEmpty(request.QueryString)) - { - return url + request.QueryString; - } - - return url; - } - - private void SetHeaders(HttpRequestMessage httpRequest, Dictionary? headers) - { - if (headers is null) - { - return; - } - - foreach (var kv in headers) - { - if (kv.Value is null) - { - continue; - } - - httpRequest.Headers.TryAddWithoutValidation(kv.Key, kv.Value); - } - } - - private static (Encoding encoding, string? charset, string mediaType) ParseContentTypeOrDefault( - string? contentType, - Encoding encodingFallback, - string mediaTypeFallback - ) - { - var encoding = encodingFallback; - var mediaType = mediaTypeFallback; - string? charset = null; - if (string.IsNullOrEmpty(contentType)) - { - return (encoding, charset, mediaType); - } - - if (!MediaTypeHeaderValue.TryParse(contentType, out var mediaTypeHeaderValue)) - { - return (encoding, charset, mediaType); - } - - if (!string.IsNullOrEmpty(mediaTypeHeaderValue.CharSet)) - { - charset = mediaTypeHeaderValue.CharSet; - encoding = Encoding.GetEncoding(mediaTypeHeaderValue.CharSet); - } - - if (!string.IsNullOrEmpty(mediaTypeHeaderValue.MediaType)) - { - mediaType = mediaTypeHeaderValue.MediaType; - } - - return (encoding, charset, mediaType); - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/RawResponse.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/RawResponse.cs deleted file mode 100644 index 71475d5a4a52..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/RawResponse.cs +++ /dev/null @@ -1,24 +0,0 @@ -using global::System.Net; - -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// Contains HTTP response metadata including status code, URL, and headers. -/// -public record RawResponse -{ - /// - /// The HTTP status code of the response. - /// - public required HttpStatusCode StatusCode { get; init; } - - /// - /// The request URL that generated this response. - /// - public required Uri Url { get; init; } - - /// - /// The HTTP response headers. - /// - public required Core.ResponseHeaders Headers { get; init; } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/ResponseHeaders.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/ResponseHeaders.cs deleted file mode 100644 index 9142ff67ddc3..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/ResponseHeaders.cs +++ /dev/null @@ -1,108 +0,0 @@ -using global::System.Collections; -using global::System.Net.Http.Headers; - -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// Represents HTTP response headers with case-insensitive lookup. -/// -public readonly struct ResponseHeaders : IEnumerable -{ - private readonly HttpResponseHeaders? _headers; - private readonly HttpContentHeaders? _contentHeaders; - - private ResponseHeaders(HttpResponseHeaders headers, HttpContentHeaders? contentHeaders) - { - _headers = headers; - _contentHeaders = contentHeaders; - } - - /// - /// Gets the Content-Type header value, if present. - /// - public string? ContentType => _contentHeaders?.ContentType?.ToString(); - - /// - /// Gets the Content-Length header value, if present. - /// - public long? ContentLength => _contentHeaders?.ContentLength; - - /// - /// Creates a ResponseHeaders instance from an HttpResponseMessage. - /// - public static ResponseHeaders FromHttpResponseMessage(HttpResponseMessage response) - { - return new ResponseHeaders(response.Headers, response.Content?.Headers); - } - - /// - /// Tries to get a single header value. Returns the first value if multiple values exist. - /// - public bool TryGetValue(string name, out string? value) - { - if (TryGetValues(name, out var values) && values is not null) - { - value = values.FirstOrDefault(); - return true; - } - - value = null; - return false; - } - - /// - /// Tries to get all values for a header. - /// - public bool TryGetValues(string name, out IEnumerable? values) - { - if (_headers?.TryGetValues(name, out values) == true) - { - return true; - } - - if (_contentHeaders?.TryGetValues(name, out values) == true) - { - return true; - } - - values = null; - return false; - } - - /// - /// Checks if the headers contain a specific header name. - /// - public bool Contains(string name) - { - return _headers?.Contains(name) == true || _contentHeaders?.Contains(name) == true; - } - - /// - /// Gets an enumerator for all headers. - /// - public IEnumerator GetEnumerator() - { - if (_headers is not null) - { - foreach (var header in _headers) - { - yield return new HttpHeader(header.Key, string.Join(", ", header.Value)); - } - } - - if (_contentHeaders is not null) - { - foreach (var header in _contentHeaders) - { - yield return new HttpHeader(header.Key, string.Join(", ", header.Value)); - } - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); -} - -/// -/// Represents a single HTTP header. -/// -public readonly record struct HttpHeader(string Name, string Value); diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/StreamRequest.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/StreamRequest.cs deleted file mode 100644 index 0c3d2a1ae11d..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/StreamRequest.cs +++ /dev/null @@ -1,29 +0,0 @@ -using global::System.Net.Http; -using global::System.Net.Http.Headers; - -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// The request object to be sent for streaming uploads. -/// -internal record StreamRequest : BaseRequest -{ - internal Stream? Body { get; init; } - - internal override HttpContent? CreateContent() - { - if (Body is null) - { - return null; - } - - var content = new StreamContent(Body) - { - Headers = - { - ContentType = MediaTypeHeaderValue.Parse(ContentType ?? "application/octet-stream"), - }, - }; - return content; - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/StringEnum.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/StringEnum.cs deleted file mode 100644 index 93660010d08a..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/StringEnum.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace SeedCsharpNamespaceConflict.Core; - -public interface IStringEnum : IEquatable -{ - public string Value { get; } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/StringEnumExtensions.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/StringEnumExtensions.cs deleted file mode 100644 index 0f42fbb8a2f3..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/StringEnumExtensions.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace SeedCsharpNamespaceConflict.Core; - -internal static class StringEnumExtensions -{ - public static string Stringify(this IStringEnum stringEnum) => stringEnum.Value; -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/ValueConvert.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/ValueConvert.cs deleted file mode 100644 index 03791e807436..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Core/ValueConvert.cs +++ /dev/null @@ -1,114 +0,0 @@ -using global::System.Globalization; - -namespace SeedCsharpNamespaceConflict.Core; - -/// -/// Convert values to string for path and query parameters. -/// -public static class ValueConvert -{ - internal static string ToPathParameterString(T value) => ToString(value); - - internal static string ToPathParameterString(bool v) => ToString(v); - - internal static string ToPathParameterString(int v) => ToString(v); - - internal static string ToPathParameterString(long v) => ToString(v); - - internal static string ToPathParameterString(float v) => ToString(v); - - internal static string ToPathParameterString(double v) => ToString(v); - - internal static string ToPathParameterString(decimal v) => ToString(v); - - internal static string ToPathParameterString(short v) => ToString(v); - - internal static string ToPathParameterString(ushort v) => ToString(v); - - internal static string ToPathParameterString(uint v) => ToString(v); - - internal static string ToPathParameterString(ulong v) => ToString(v); - - internal static string ToPathParameterString(string v) => ToString(v); - - internal static string ToPathParameterString(char v) => ToString(v); - - internal static string ToPathParameterString(Guid v) => ToString(v); - - internal static string ToQueryStringValue(T value) => value is null ? "" : ToString(value); - - internal static string ToQueryStringValue(bool v) => ToString(v); - - internal static string ToQueryStringValue(int v) => ToString(v); - - internal static string ToQueryStringValue(long v) => ToString(v); - - internal static string ToQueryStringValue(float v) => ToString(v); - - internal static string ToQueryStringValue(double v) => ToString(v); - - internal static string ToQueryStringValue(decimal v) => ToString(v); - - internal static string ToQueryStringValue(short v) => ToString(v); - - internal static string ToQueryStringValue(ushort v) => ToString(v); - - internal static string ToQueryStringValue(uint v) => ToString(v); - - internal static string ToQueryStringValue(ulong v) => ToString(v); - - internal static string ToQueryStringValue(string v) => v is null ? "" : v; - - internal static string ToQueryStringValue(char v) => ToString(v); - - internal static string ToQueryStringValue(Guid v) => ToString(v); - - internal static string ToString(T value) - { - return value switch - { - null => "null", - string str => str, - true => "true", - false => "false", - int i => ToString(i), - long l => ToString(l), - float f => ToString(f), - double d => ToString(d), - decimal dec => ToString(dec), - short s => ToString(s), - ushort u => ToString(u), - uint u => ToString(u), - ulong u => ToString(u), - char c => ToString(c), - Guid guid => ToString(guid), - _ => JsonUtils.SerializeRelaxedEscaping(value, value.GetType()).Trim('"'), - }; - } - - internal static string ToString(bool v) => v ? "true" : "false"; - - internal static string ToString(int v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(long v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(float v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(double v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(decimal v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(short v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(ushort v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(uint v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(ulong v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(char v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(string v) => v; - - internal static string ToString(Guid v) => v.ToString("D"); -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/ISeedCsharpNamespaceConflictClient.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/ISeedCsharpNamespaceConflictClient.cs deleted file mode 100644 index 0f18ab0a73c0..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/ISeedCsharpNamespaceConflictClient.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace SeedCsharpNamespaceConflict; - -public partial interface ISeedCsharpNamespaceConflictClient -{ - public ITasktestClient Tasktest { get; } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/SeedCsharpNamespaceConflict.Custom.props b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/SeedCsharpNamespaceConflict.Custom.props deleted file mode 100644 index 17a84cada530..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/SeedCsharpNamespaceConflict.Custom.props +++ /dev/null @@ -1,20 +0,0 @@ - - - - diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/SeedCsharpNamespaceConflict.csproj b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/SeedCsharpNamespaceConflict.csproj deleted file mode 100644 index 05d9e6656ffb..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/SeedCsharpNamespaceConflict.csproj +++ /dev/null @@ -1,61 +0,0 @@ - - - net462;net8.0;netstandard2.0 - enable - 12 - enable - 0.0.1 - $(Version) - $(Version) - README.md - https://github.com/csharp-namespace-conflict/fern - true - - - - false - - - $(DefineConstants);USE_PORTABLE_DATE_ONLY - true - - - - - - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - - - - - <_Parameter1>SeedCsharpNamespaceConflict.Test - - - - - diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/SeedCsharpNamespaceConflictClient.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/SeedCsharpNamespaceConflictClient.cs deleted file mode 100644 index 5538ff377f17..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/SeedCsharpNamespaceConflictClient.cs +++ /dev/null @@ -1,33 +0,0 @@ -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict; - -public partial class SeedCsharpNamespaceConflictClient : ISeedCsharpNamespaceConflictClient -{ - private readonly RawClient _client; - - public SeedCsharpNamespaceConflictClient(ClientOptions? clientOptions = null) - { - clientOptions ??= new ClientOptions(); - var platformHeaders = new Headers( - new Dictionary() - { - { "X-Fern-Language", "C#" }, - { "X-Fern-SDK-Name", "SeedCsharpNamespaceConflict" }, - { "X-Fern-SDK-Version", Version.Current }, - { "User-Agent", "Ferncsharp-namespace-conflict/0.0.1" }, - } - ); - foreach (var header in platformHeaders) - { - if (!clientOptions.Headers.ContainsKey(header.Key)) - { - clientOptions.Headers[header.Key] = header.Value; - } - } - _client = new RawClient(clientOptions); - Tasktest = new TasktestClient(_client); - } - - public ITasktestClient Tasktest { get; } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Tasktest/ITasktestClient.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Tasktest/ITasktestClient.cs deleted file mode 100644 index abbbc43a5f24..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Tasktest/ITasktestClient.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace SeedCsharpNamespaceConflict; - -public partial interface ITasktestClient -{ - System.Threading.Tasks.Task HelloAsync( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ); -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Tasktest/TasktestClient.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Tasktest/TasktestClient.cs deleted file mode 100644 index 0701ced9bc7c..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Tasktest/TasktestClient.cs +++ /dev/null @@ -1,55 +0,0 @@ -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict; - -public partial class TasktestClient : ITasktestClient -{ - private readonly RawClient _client; - - internal TasktestClient(RawClient client) - { - _client = client; - } - - /// - /// await client.Tasktest.HelloAsync(); - /// - public async System.Threading.Tasks.Task HelloAsync( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - var _headers = await new SeedCsharpNamespaceConflict.Core.HeadersBuilder.Builder() - .Add(_client.Options.Headers) - .Add(_client.Options.AdditionalHeaders) - .Add(options?.AdditionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - var response = await _client - .SendRequestAsync( - new JsonRequest - { - Method = HttpMethod.Get, - Path = "hello", - Headers = _headers, - Options = options, - }, - cancellationToken - ) - .ConfigureAwait(false); - if (response.StatusCode is >= 200 and < 400) - { - return; - } - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - throw new SeedCsharpNamespaceConflictApiException( - $"Error with status code {response.StatusCode}", - response.StatusCode, - responseBody - ); - } - } -} diff --git a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Tasktest/Types/Task.cs b/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Tasktest/Types/Task.cs deleted file mode 100644 index 0c6b43be5cbf..000000000000 --- a/seed/csharp-sdk/csharp-namespace-conflict/src/SeedCsharpNamespaceConflict/Tasktest/Types/Task.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using SeedCsharpNamespaceConflict.Core; - -namespace SeedCsharpNamespaceConflict; - -[Serializable] -public record Task : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} From 8e409927a298f44fbcd2be81aec158433b9e511c Mon Sep 17 00:00:00 2001 From: Jon Syla Date: Tue, 17 Mar 2026 15:42:58 -0400 Subject: [PATCH 07/11] fix(cli): downgrade OpenAPI v2.0 docs check from error to warning (#13630) * Downgrade OpenAPI v2.0 check from error to warning Co-Authored-By: jon * Add changelog entry for Swagger v2 warning downgrade (4.31.2) Co-Authored-By: jon --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- packages/cli/cli/versions.yml | 10 ++++++++++ .../no-openapi-v2-in-docs/no-openapi-v2-in-docs.ts | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index a5c141d4bfb9..fd633771ce74 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,4 +1,14 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 4.32.1 + changelogEntry: + - summary: | + Downgrade the `no-openapi-v2-in-docs` validation rule from an error to a + warning. Fern supports OpenAPI 2.0 (Swagger) specs in docs generation, so + `fern check` no longer fails when Swagger 2.0 specs are present. + type: fix + createdAt: "2026-03-17" + irVersion: 65 + - version: 4.32.0 changelogEntry: - summary: | diff --git a/packages/cli/yaml/docs-validator/src/rules/no-openapi-v2-in-docs/no-openapi-v2-in-docs.ts b/packages/cli/yaml/docs-validator/src/rules/no-openapi-v2-in-docs/no-openapi-v2-in-docs.ts index 460c95d05a4f..ab171aacbe02 100644 --- a/packages/cli/yaml/docs-validator/src/rules/no-openapi-v2-in-docs/no-openapi-v2-in-docs.ts +++ b/packages/cli/yaml/docs-validator/src/rules/no-openapi-v2-in-docs/no-openapi-v2-in-docs.ts @@ -33,9 +33,9 @@ export const NoOpenApiV2InDocsRule: Rule = { (contents.includes('"swagger":"2.0"') || contents.includes('"swagger": "2.0"')); if (isOpenApiV2Yaml || isOpenApiV2Json) { violations.push({ - severity: "error", - name: "OpenAPI v2.0 not supported", - message: `OpenAPI version 2.0 (Swagger) is not supported in docs generation. Please upgrade to OpenAPI 3.0 or later.`, + severity: "warning", + name: "OpenAPI v2.0 detected", + message: `OpenAPI version 2.0 (Swagger) detected. Consider upgrading to OpenAPI 3.0 or later.`, relativeFilepath: relativePath }); } From 05fd8469fb3c662a7ff914ee78ae14ba5332bb5f Mon Sep 17 00:00:00 2001 From: Alex McKinney Date: Tue, 17 Mar 2026 15:51:37 -0400 Subject: [PATCH 08/11] feat(csharp): Add support for google.protobuf.Empty (#13647) --- .../src/proto/CsharpProtobufTypeMapper.ts | 4 +- .../endpoint/grpc/GrpcEndpointGenerator.ts | 4 +- generators/csharp/sdk/versions.yml | 11 + .../type__CheckResponse.json | 29 + .../csharp-grpc-proto-exhaustive.json | 179 ++++ .../csharp-grpc-proto-exhaustive.json | 906 ++++++++++++++++++ .../proto/data/v1/data.proto | 7 + .../src/SeedApi/CheckResponse.cs | 66 ++ .../src/SeedApi/UpdateResponse.cs | 2 +- .../include-exception-handler/README.md | 10 +- .../proto/data/v1/data.proto | 7 + .../include-exception-handler/reference.md | 25 + .../include-exception-handler/snippet.json | 12 + .../SeedApi/DataService/DataServiceClient.cs | 65 ++ .../SeedApi/DataService/IDataServiceClient.cs | 5 + .../src/SeedApi/Types/CheckResponse.cs | 66 ++ .../src/SeedApi/Types/UpdateResponse.cs | 2 +- .../no-custom-config/README.md | 10 +- .../no-custom-config/proto/data/v1/data.proto | 7 + .../no-custom-config/reference.md | 25 + .../no-custom-config/snippet.json | 12 + .../SeedApi/DataService/DataServiceClient.cs | 60 ++ .../SeedApi/DataService/IDataServiceClient.cs | 5 + .../src/SeedApi/Types/CheckResponse.cs | 66 ++ .../src/SeedApi/Types/UpdateResponse.cs | 2 +- .../package-id/README.md | 10 +- .../package-id/proto/data/v1/data.proto | 7 + .../package-id/reference.md | 25 + .../package-id/snippet.json | 12 + .../SeedApi/DataService/DataServiceClient.cs | 60 ++ .../SeedApi/DataService/IDataServiceClient.cs | 5 + .../src/SeedApi/Types/CheckResponse.cs | 66 ++ .../src/SeedApi/Types/UpdateResponse.cs | 2 +- .../read-only-memory/README.md | 10 +- .../read-only-memory/proto/data/v1/data.proto | 7 + .../read-only-memory/reference.md | 25 + .../read-only-memory/snippet.json | 12 + .../SeedApi/DataService/DataServiceClient.cs | 60 ++ .../SeedApi/DataService/IDataServiceClient.cs | 5 + .../src/SeedApi/Types/CheckResponse.cs | 66 ++ .../src/SeedApi/Types/UpdateResponse.cs | 2 +- .../proto/data/v1/data.proto | 7 + 42 files changed, 1940 insertions(+), 28 deletions(-) create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/csharp-grpc-proto-exhaustive/type__CheckResponse.json create mode 100644 seed/csharp-model/csharp-grpc-proto-exhaustive/src/SeedApi/CheckResponse.cs create mode 100644 seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/src/SeedApi/Types/CheckResponse.cs create mode 100644 seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/src/SeedApi/Types/CheckResponse.cs create mode 100644 seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/src/SeedApi/Types/CheckResponse.cs create mode 100644 seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/src/SeedApi/Types/CheckResponse.cs diff --git a/generators/csharp/base/src/proto/CsharpProtobufTypeMapper.ts b/generators/csharp/base/src/proto/CsharpProtobufTypeMapper.ts index 859fdb44bf11..a092f9c6f1aa 100644 --- a/generators/csharp/base/src/proto/CsharpProtobufTypeMapper.ts +++ b/generators/csharp/base/src/proto/CsharpProtobufTypeMapper.ts @@ -1040,10 +1040,10 @@ class FromProtoPropertyMapper extends WithGeneration { }): ast.CodeBlock { switch (primitive.v1) { case "DATE_TIME": - return this.csharp.codeblock(`${propertyName}.ToDateTime()`); + return this.csharp.codeblock(`${propertyName}?.ToDateTime()`); case "BASE_64": // Proto bytes fields are ByteString; SDK exposes byte[]. - return this.csharp.codeblock(`${propertyName}.ToByteArray()`); + return this.csharp.codeblock(`${propertyName}?.ToByteArray()`); case "DATE": case "INTEGER": case "LONG": diff --git a/generators/csharp/sdk/src/endpoint/grpc/GrpcEndpointGenerator.ts b/generators/csharp/sdk/src/endpoint/grpc/GrpcEndpointGenerator.ts index 5decad8049f3..12104b5e364e 100644 --- a/generators/csharp/sdk/src/endpoint/grpc/GrpcEndpointGenerator.ts +++ b/generators/csharp/sdk/src/endpoint/grpc/GrpcEndpointGenerator.ts @@ -211,7 +211,9 @@ export class GrpcEndpointGenerator extends AbstractEndpointGenerator { grpcClientInfo: GrpcClientInfo; }): ast.CodeBlock { const mapToProtoRequest = - request != null ? this.getToProtoMethodInvocation({ request }) : this.csharp.codeblock("null"); + request != null + ? this.getToProtoMethodInvocation({ request }) + : this.csharp.codeblock("new Google.Protobuf.WellKnownTypes.Empty()"); return this.csharp.codeblock((writer) => { writer.write("var call = "); writer.writeNode( diff --git a/generators/csharp/sdk/versions.yml b/generators/csharp/sdk/versions.yml index 4a9a060b3a9e..8680a8e4faf7 100644 --- a/generators/csharp/sdk/versions.yml +++ b/generators/csharp/sdk/versions.yml @@ -1,4 +1,15 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 2.32.0 + changelogEntry: + - summary: | + Add support for the `google.protobuf.Empty` well-known type for gRPC endpoints. + type: feat + - summary: | + Add more permissive handling for optional `google.protobuf.Timestamp` response properties. + type: fix + createdAt: "2026-03-17" + irVersion: 65 + - version: 2.31.1 changelogEntry: - summary: | diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/csharp-grpc-proto-exhaustive/type__CheckResponse.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/csharp-grpc-proto-exhaustive/type__CheckResponse.json new file mode 100644 index 000000000000..c6044e39d692 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/csharp-grpc-proto-exhaustive/type__CheckResponse.json @@ -0,0 +1,29 @@ +{ + "type": "object", + "properties": { + "created_at": { + "oneOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + }, + "updated_at": { + "oneOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/csharp-grpc-proto-exhaustive.json b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/csharp-grpc-proto-exhaustive.json index 032a4a55749c..bd49d4c870fe 100644 --- a/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/csharp-grpc-proto-exhaustive.json +++ b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/csharp-grpc-proto-exhaustive.json @@ -142,6 +142,105 @@ } ] }, + "type_:CheckResponse": { + "type": "object", + "declaration": { + "name": { + "originalName": "CheckResponse", + "camelCase": { + "unsafeName": "checkResponse", + "safeName": "checkResponse" + }, + "snakeCase": { + "unsafeName": "check_response", + "safeName": "check_response" + }, + "screamingSnakeCase": { + "unsafeName": "CHECK_RESPONSE", + "safeName": "CHECK_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CheckResponse", + "safeName": "CheckResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "name": { + "originalName": "created_at", + "camelCase": { + "unsafeName": "createdAt", + "safeName": "createdAt" + }, + "snakeCase": { + "unsafeName": "created_at", + "safeName": "created_at" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_AT", + "safeName": "CREATED_AT" + }, + "pascalCase": { + "unsafeName": "CreatedAt", + "safeName": "CreatedAt" + } + }, + "wireValue": "created_at" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "name": { + "originalName": "updated_at", + "camelCase": { + "unsafeName": "updatedAt", + "safeName": "updatedAt" + }, + "snakeCase": { + "unsafeName": "updated_at", + "safeName": "updated_at" + }, + "screamingSnakeCase": { + "unsafeName": "UPDATED_AT", + "safeName": "UPDATED_AT" + }, + "pascalCase": { + "unsafeName": "UpdatedAt", + "safeName": "UpdatedAt" + } + }, + "wireValue": "updated_at" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, "type_:Column": { "type": "object", "declaration": { @@ -3208,6 +3307,86 @@ }, "headers": [], "endpoints": { + "endpoint_dataService.Check": { + "auth": null, + "declaration": { + "name": { + "originalName": "Check", + "camelCase": { + "unsafeName": "check", + "safeName": "check" + }, + "snakeCase": { + "unsafeName": "check", + "safeName": "check" + }, + "screamingSnakeCase": { + "unsafeName": "CHECK", + "safeName": "CHECK" + }, + "pascalCase": { + "unsafeName": "Check", + "safeName": "Check" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "dataService", + "camelCase": { + "unsafeName": "dataService", + "safeName": "dataService" + }, + "snakeCase": { + "unsafeName": "data_service", + "safeName": "data_service" + }, + "screamingSnakeCase": { + "unsafeName": "DATA_SERVICE", + "safeName": "DATA_SERVICE" + }, + "pascalCase": { + "unsafeName": "DataService", + "safeName": "DataService" + } + } + ], + "packagePath": [], + "file": { + "originalName": "dataService", + "camelCase": { + "unsafeName": "dataService", + "safeName": "dataService" + }, + "snakeCase": { + "unsafeName": "data_service", + "safeName": "data_service" + }, + "screamingSnakeCase": { + "unsafeName": "DATA_SERVICE", + "safeName": "DATA_SERVICE" + }, + "pascalCase": { + "unsafeName": "DataService", + "safeName": "DataService" + } + } + } + }, + "location": { + "method": "POST", + "path": "/data.v1.DataService/Check" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": null + }, + "response": { + "type": "json" + }, + "examples": null + }, "endpoint_dataService.Create": { "auth": null, "declaration": { diff --git a/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/csharp-grpc-proto-exhaustive.json b/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/csharp-grpc-proto-exhaustive.json index dd90d5ce3c49..a8ad939fca57 100644 --- a/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/csharp-grpc-proto-exhaustive.json +++ b/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/csharp-grpc-proto-exhaustive.json @@ -243,6 +243,177 @@ "availability": null, "docs": null }, + "type_:CheckResponse": { + "inline": null, + "name": { + "name": { + "originalName": "CheckResponse", + "camelCase": { + "unsafeName": "checkResponse", + "safeName": "checkResponse" + }, + "snakeCase": { + "unsafeName": "check_response", + "safeName": "check_response" + }, + "screamingSnakeCase": { + "unsafeName": "CHECK_RESPONSE", + "safeName": "CHECK_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CheckResponse", + "safeName": "CheckResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CheckResponse" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "created_at", + "camelCase": { + "unsafeName": "createdAt", + "safeName": "createdAt" + }, + "snakeCase": { + "unsafeName": "created_at", + "safeName": "created_at" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_AT", + "safeName": "CREATED_AT" + }, + "pascalCase": { + "unsafeName": "CreatedAt", + "safeName": "CreatedAt" + } + }, + "wireValue": "created_at" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "updated_at", + "camelCase": { + "unsafeName": "updatedAt", + "safeName": "updatedAt" + }, + "snakeCase": { + "unsafeName": "updated_at", + "safeName": "updated_at" + }, + "screamingSnakeCase": { + "unsafeName": "UPDATED_AT", + "safeName": "UPDATED_AT" + }, + "pascalCase": { + "unsafeName": "UpdatedAt", + "safeName": "UpdatedAt" + } + }, + "wireValue": "updated_at" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": null, + "proto": {} + }, + "source": { + "type": "proto", + "value": { + "type": "userDefined", + "file": { + "filepath": "proto/data/v1/data.proto", + "packageName": "data.v1", + "options": { + "csharp": { + "namespace": "Data.V1.Grpc" + } + } + }, + "name": { + "originalName": "CheckResponse", + "camelCase": { + "unsafeName": "checkResponse", + "safeName": "checkResponse" + }, + "snakeCase": { + "unsafeName": "check_response", + "safeName": "check_response" + }, + "screamingSnakeCase": { + "unsafeName": "CHECK_RESPONSE", + "safeName": "CHECK_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CheckResponse", + "safeName": "CheckResponse" + } + } + } + }, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, "type_:Column": { "inline": null, "name": { @@ -6055,6 +6226,560 @@ } }, "endpoints": [ + { + "id": "endpoint_dataService.Check", + "name": { + "originalName": "Check", + "camelCase": { + "unsafeName": "check", + "safeName": "check" + }, + "snakeCase": { + "unsafeName": "check", + "safeName": "check" + }, + "screamingSnakeCase": { + "unsafeName": "CHECK", + "safeName": "CHECK" + }, + "pascalCase": { + "unsafeName": "Check", + "safeName": "Check" + } + }, + "displayName": null, + "auth": false, + "security": null, + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "POST", + "basePath": null, + "path": { + "head": "/data.v1.DataService/Check", + "parts": [] + }, + "fullPath": { + "head": "data.v1.DataService/Check", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": null, + "v2RequestBodies": null, + "sdkRequest": null, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": { + "originalName": "CheckResponse", + "camelCase": { + "unsafeName": "checkResponse", + "safeName": "checkResponse" + }, + "snakeCase": { + "unsafeName": "check_response", + "safeName": "check_response" + }, + "screamingSnakeCase": { + "unsafeName": "CHECK_RESPONSE", + "safeName": "CHECK_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CheckResponse", + "safeName": "CheckResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CheckResponse", + "default": null, + "inline": null + }, + "docs": "OK", + "v2Examples": null + } + }, + "status-code": 200, + "isWildcardStatusCode": null, + "docs": "OK" + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [ + { + "example": { + "id": "fdd31271", + "name": null, + "url": "/data.v1.DataService/Check", + "rootPathParameters": [], + "endpointPathParameters": [], + "servicePathParameters": [], + "endpointHeaders": [], + "serviceHeaders": [], + "queryParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:CheckResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "CheckResponse", + "camelCase": { + "unsafeName": "checkResponse", + "safeName": "checkResponse" + }, + "snakeCase": { + "unsafeName": "check_response", + "safeName": "check_response" + }, + "screamingSnakeCase": { + "unsafeName": "CHECK_RESPONSE", + "safeName": "CHECK_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CheckResponse", + "safeName": "CheckResponse" + } + }, + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "created_at", + "camelCase": { + "unsafeName": "createdAt", + "safeName": "createdAt" + }, + "snakeCase": { + "unsafeName": "created_at", + "safeName": "created_at" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_AT", + "safeName": "CREATED_AT" + }, + "pascalCase": { + "unsafeName": "CreatedAt", + "safeName": "CreatedAt" + } + }, + "wireValue": "created_at" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "originalTypeDeclaration": { + "typeId": "type_:CheckResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "CheckResponse", + "camelCase": { + "unsafeName": "checkResponse", + "safeName": "checkResponse" + }, + "snakeCase": { + "unsafeName": "check_response", + "safeName": "check_response" + }, + "screamingSnakeCase": { + "unsafeName": "CHECK_RESPONSE", + "safeName": "CHECK_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CheckResponse", + "safeName": "CheckResponse" + } + }, + "displayName": null + }, + "propertyAccess": null + }, + { + "name": { + "name": { + "originalName": "updated_at", + "camelCase": { + "unsafeName": "updatedAt", + "safeName": "updatedAt" + }, + "snakeCase": { + "unsafeName": "updated_at", + "safeName": "updated_at" + }, + "screamingSnakeCase": { + "unsafeName": "UPDATED_AT", + "safeName": "UPDATED_AT" + }, + "pascalCase": { + "unsafeName": "UpdatedAt", + "safeName": "UpdatedAt" + } + }, + "wireValue": "updated_at" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "originalTypeDeclaration": { + "typeId": "type_:CheckResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "CheckResponse", + "camelCase": { + "unsafeName": "checkResponse", + "safeName": "checkResponse" + }, + "snakeCase": { + "unsafeName": "check_response", + "safeName": "check_response" + }, + "screamingSnakeCase": { + "unsafeName": "CHECK_RESPONSE", + "safeName": "CHECK_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CheckResponse", + "safeName": "CheckResponse" + } + }, + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "created_at": "2024-01-15T09:30:00Z", + "updated_at": "2024-01-15T09:30:00Z" + } + } + } + }, + "docs": null + }, + "codeSamples": null + } + ], + "autogeneratedExamples": [ + { + "example": { + "id": "f0c4b214", + "url": "/data.v1.DataService/Check", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "created_at", + "camelCase": { + "unsafeName": "createdAt", + "safeName": "createdAt" + }, + "snakeCase": { + "unsafeName": "created_at", + "safeName": "created_at" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_AT", + "safeName": "CREATED_AT" + }, + "pascalCase": { + "unsafeName": "CreatedAt", + "safeName": "CreatedAt" + } + }, + "wireValue": "created_at" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "CheckResponse", + "camelCase": { + "unsafeName": "checkResponse", + "safeName": "checkResponse" + }, + "snakeCase": { + "unsafeName": "check_response", + "safeName": "check_response" + }, + "screamingSnakeCase": { + "unsafeName": "CHECK_RESPONSE", + "safeName": "CHECK_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CheckResponse", + "safeName": "CheckResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CheckResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "propertyAccess": null + }, + { + "name": { + "name": { + "originalName": "updated_at", + "camelCase": { + "unsafeName": "updatedAt", + "safeName": "updatedAt" + }, + "snakeCase": { + "unsafeName": "updated_at", + "safeName": "updated_at" + }, + "screamingSnakeCase": { + "unsafeName": "UPDATED_AT", + "safeName": "UPDATED_AT" + }, + "pascalCase": { + "unsafeName": "UpdatedAt", + "safeName": "UpdatedAt" + } + }, + "wireValue": "updated_at" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "CheckResponse", + "camelCase": { + "unsafeName": "checkResponse", + "safeName": "checkResponse" + }, + "snakeCase": { + "unsafeName": "check_response", + "safeName": "check_response" + }, + "screamingSnakeCase": { + "unsafeName": "CHECK_RESPONSE", + "safeName": "CHECK_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CheckResponse", + "safeName": "CheckResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CheckResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": { + "originalName": "CheckResponse", + "camelCase": { + "unsafeName": "checkResponse", + "safeName": "checkResponse" + }, + "snakeCase": { + "unsafeName": "check_response", + "safeName": "check_response" + }, + "screamingSnakeCase": { + "unsafeName": "CHECK_RESPONSE", + "safeName": "CHECK_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CheckResponse", + "safeName": "CheckResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CheckResponse" + } + }, + "jsonExample": { + "created_at": "2024-01-15T09:30:00Z", + "updated_at": "2024-01-15T09:30:00Z" + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "retries": null, + "apiPlayground": null, + "responseHeaders": [], + "availability": null, + "docs": null + }, { "id": "endpoint_dataService.Create", "name": { @@ -32668,6 +33393,7 @@ "typesReferencedOnlyByService": { "service_dataService": [ "type_:AspectRatio", + "type_:CheckResponse", "type_:Column", "type_:CreateResponse", "type_:DeleteResponse", @@ -32848,6 +33574,105 @@ } ] }, + "type_:CheckResponse": { + "type": "object", + "declaration": { + "name": { + "originalName": "CheckResponse", + "camelCase": { + "unsafeName": "checkResponse", + "safeName": "checkResponse" + }, + "snakeCase": { + "unsafeName": "check_response", + "safeName": "check_response" + }, + "screamingSnakeCase": { + "unsafeName": "CHECK_RESPONSE", + "safeName": "CHECK_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CheckResponse", + "safeName": "CheckResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "name": { + "originalName": "created_at", + "camelCase": { + "unsafeName": "createdAt", + "safeName": "createdAt" + }, + "snakeCase": { + "unsafeName": "created_at", + "safeName": "created_at" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_AT", + "safeName": "CREATED_AT" + }, + "pascalCase": { + "unsafeName": "CreatedAt", + "safeName": "CreatedAt" + } + }, + "wireValue": "created_at" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "name": { + "originalName": "updated_at", + "camelCase": { + "unsafeName": "updatedAt", + "safeName": "updatedAt" + }, + "snakeCase": { + "unsafeName": "updated_at", + "safeName": "updated_at" + }, + "screamingSnakeCase": { + "unsafeName": "UPDATED_AT", + "safeName": "UPDATED_AT" + }, + "pascalCase": { + "unsafeName": "UpdatedAt", + "safeName": "UpdatedAt" + } + }, + "wireValue": "updated_at" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, "type_:Column": { "type": "object", "declaration": { @@ -35914,6 +36739,86 @@ }, "headers": [], "endpoints": { + "endpoint_dataService.Check": { + "auth": null, + "declaration": { + "name": { + "originalName": "Check", + "camelCase": { + "unsafeName": "check", + "safeName": "check" + }, + "snakeCase": { + "unsafeName": "check", + "safeName": "check" + }, + "screamingSnakeCase": { + "unsafeName": "CHECK", + "safeName": "CHECK" + }, + "pascalCase": { + "unsafeName": "Check", + "safeName": "Check" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "dataService", + "camelCase": { + "unsafeName": "dataService", + "safeName": "dataService" + }, + "snakeCase": { + "unsafeName": "data_service", + "safeName": "data_service" + }, + "screamingSnakeCase": { + "unsafeName": "DATA_SERVICE", + "safeName": "DATA_SERVICE" + }, + "pascalCase": { + "unsafeName": "DataService", + "safeName": "DataService" + } + } + ], + "packagePath": [], + "file": { + "originalName": "dataService", + "camelCase": { + "unsafeName": "dataService", + "safeName": "dataService" + }, + "snakeCase": { + "unsafeName": "data_service", + "safeName": "data_service" + }, + "screamingSnakeCase": { + "unsafeName": "DATA_SERVICE", + "safeName": "DATA_SERVICE" + }, + "pascalCase": { + "unsafeName": "DataService", + "safeName": "DataService" + } + } + } + }, + "location": { + "method": "POST", + "path": "/data.v1.DataService/Check" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": null + }, + "response": { + "type": "json" + }, + "examples": null + }, "endpoint_dataService.Create": { "auth": null, "declaration": { @@ -38637,6 +39542,7 @@ "service": "service_", "types": [ "type_:AspectRatio", + "type_:CheckResponse", "type_:Column", "type_:CreateResponse", "type_:DeleteResponse", diff --git a/seed/csharp-model/csharp-grpc-proto-exhaustive/proto/data/v1/data.proto b/seed/csharp-model/csharp-grpc-proto-exhaustive/proto/data/v1/data.proto index 4f302a5b1c95..a19a2141a882 100644 --- a/seed/csharp-model/csharp-grpc-proto-exhaustive/proto/data/v1/data.proto +++ b/seed/csharp-model/csharp-grpc-proto-exhaustive/proto/data/v1/data.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package data.v1; import "google/protobuf/any.proto"; +import "google/protobuf/empty.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "google/api/annotations.proto"; @@ -262,7 +263,13 @@ message DescribeResponse { uint32 total_count = 4; } +message CheckResponse { + google.protobuf.Timestamp created_at = 1; + google.protobuf.Timestamp updated_at = 2; +} + service DataService { + rpc Check(google.protobuf.Empty) returns (CheckResponse); rpc Upload(UploadRequest) returns (UploadResponse); rpc Delete(DeleteRequest) returns (DeleteResponse); rpc Query(QueryRequest) returns (QueryResponse); diff --git a/seed/csharp-model/csharp-grpc-proto-exhaustive/src/SeedApi/CheckResponse.cs b/seed/csharp-model/csharp-grpc-proto-exhaustive/src/SeedApi/CheckResponse.cs new file mode 100644 index 000000000000..2d6e0357b4c4 --- /dev/null +++ b/seed/csharp-model/csharp-grpc-proto-exhaustive/src/SeedApi/CheckResponse.cs @@ -0,0 +1,66 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using SeedApi.Core; +using ProtoDataV1Grpc = Data.V1.Grpc; +using WellKnownProto = Google.Protobuf.WellKnownTypes; + +namespace SeedApi; + +[Serializable] +public record CheckResponse : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("created_at")] + public DateTime? CreatedAt { get; set; } + + [JsonPropertyName("updated_at")] + public DateTime? UpdatedAt { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + /// + /// Returns a new CheckResponse type from its Protobuf-equivalent representation. + /// + internal static CheckResponse FromProto(ProtoDataV1Grpc.CheckResponse value) + { + return new CheckResponse + { + CreatedAt = value.CreatedAt?.ToDateTime(), + UpdatedAt = value.UpdatedAt?.ToDateTime(), + }; + } + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + /// Maps the CheckResponse type into its Protobuf-equivalent representation. + /// + internal ProtoDataV1Grpc.CheckResponse ToProto() + { + var result = new ProtoDataV1Grpc.CheckResponse(); + if (CreatedAt != null) + { + result.CreatedAt = WellKnownProto.Timestamp.FromDateTime( + CreatedAt.Value.ToUniversalTime() + ); + } + if (UpdatedAt != null) + { + result.UpdatedAt = WellKnownProto.Timestamp.FromDateTime( + UpdatedAt.Value.ToUniversalTime() + ); + } + return result; + } + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/csharp-grpc-proto-exhaustive/src/SeedApi/UpdateResponse.cs b/seed/csharp-model/csharp-grpc-proto-exhaustive/src/SeedApi/UpdateResponse.cs index 9a583442a7e7..4af77fcc82b7 100644 --- a/seed/csharp-model/csharp-grpc-proto-exhaustive/src/SeedApi/UpdateResponse.cs +++ b/seed/csharp-model/csharp-grpc-proto-exhaustive/src/SeedApi/UpdateResponse.cs @@ -38,7 +38,7 @@ internal static UpdateResponse FromProto(ProtoDataV1Grpc.UpdateResponse value) { return new UpdateResponse { - UpdatedAt = value.UpdatedAt.ToDateTime(), + UpdatedAt = value.UpdatedAt?.ToDateTime(), IndexType = value.IndexType switch { ProtoDataV1Grpc.IndexType.Invalid => SeedApi.IndexType.IndexTypeInvalid, diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/README.md b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/README.md index fb55bd59b407..58c55f2c0e0d 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/README.md +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/README.md @@ -41,7 +41,7 @@ Instantiate and use the client with the following: using SeedApi; var client = new SeedApiClient(); -await client.DataService.CreateAsync(new CreateRequest { Name = "name" }); +await client.DataService.CheckAsync(); ``` ## Exception Handling @@ -53,7 +53,7 @@ will be thrown. using SeedApi; try { - var response = await client.DataService.CreateAsync(...); + var response = await client.DataService.CheckAsync(...); } catch (SeedApiApiException e) { System.Console.WriteLine(e.Body); System.Console.WriteLine(e.StatusCode); @@ -77,7 +77,7 @@ A request is deemed retryable when any of the following HTTP status codes is ret Use the `MaxRetries` request option to configure this behavior. ```csharp -var response = await client.DataService.CreateAsync( +var response = await client.DataService.CheckAsync( ..., new RequestOptions { MaxRetries: 0 // Override MaxRetries at the request level @@ -90,7 +90,7 @@ var response = await client.DataService.CreateAsync( The SDK defaults to a 30 second timeout. Use the `Timeout` option to configure this behavior. ```csharp -var response = await client.DataService.CreateAsync( +var response = await client.DataService.CheckAsync( ..., new RequestOptions { Timeout: TimeSpan.FromSeconds(3) // Override timeout to 3s @@ -103,7 +103,7 @@ var response = await client.DataService.CreateAsync( If you would like to send additional headers as part of the request, use the `AdditionalHeaders` request option. ```csharp -var response = await client.DataService.CreateAsync( +var response = await client.DataService.CheckAsync( ..., new RequestOptions { AdditionalHeaders = new Dictionary diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/proto/data/v1/data.proto b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/proto/data/v1/data.proto index 4f302a5b1c95..a19a2141a882 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/proto/data/v1/data.proto +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/proto/data/v1/data.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package data.v1; import "google/protobuf/any.proto"; +import "google/protobuf/empty.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "google/api/annotations.proto"; @@ -262,7 +263,13 @@ message DescribeResponse { uint32 total_count = 4; } +message CheckResponse { + google.protobuf.Timestamp created_at = 1; + google.protobuf.Timestamp updated_at = 2; +} + service DataService { + rpc Check(google.protobuf.Empty) returns (CheckResponse); rpc Upload(UploadRequest) returns (UploadResponse); rpc Delete(DeleteRequest) returns (DeleteResponse); rpc Query(QueryRequest) returns (QueryResponse); diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/reference.md b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/reference.md index 49ca7fcf6303..0be0c5ebe40e 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/reference.md +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/reference.md @@ -25,6 +25,31 @@ await client.PostFooAsync(); ## DataService +
client.DataService.CheckAsync() -> WithRawResponseTask<CheckResponse> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.DataService.CheckAsync(); +``` +
+
+
+
+ + +
+
+
+
client.DataService.CreateAsync(CreateRequest { ... }) -> WithRawResponseTask<CreateResponse>
diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/snippet.json b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/snippet.json index f2c2b5e9fcbc..1aeb6586f4ce 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/snippet.json +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/snippet.json @@ -1,6 +1,18 @@ { "types": {}, "endpoints": [ + { + "example_identifier": null, + "id": { + "path": "/data.v1.DataService/Check", + "method": "POST", + "identifier_override": "endpoint_dataService.Check" + }, + "snippet": { + "type": "csharp", + "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.DataService.CheckAsync();\n" + } + }, { "example_identifier": null, "id": { diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/src/SeedApi/DataService/DataServiceClient.cs b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/src/SeedApi/DataService/DataServiceClient.cs index a6d10fe116d1..83f2a8a70309 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/src/SeedApi/DataService/DataServiceClient.cs +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/src/SeedApi/DataService/DataServiceClient.cs @@ -27,6 +27,71 @@ internal DataServiceClient(RawClient client) } } + /// + /// await client.DataService.CheckAsync(); + /// + public async Task CheckAsync( + GrpcRequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + return await _client + .Options.ExceptionHandler.TryCatchAsync(async () => + { + try + { + var metadata = new global::Grpc.Core.Metadata(); + foreach (var header in _client.Options.Headers) + { + var value = await header.Value.ResolveAsync().ConfigureAwait(false); + metadata.Add(header.Key, value); + } + if (_client.Options.AdditionalHeaders != null) + { + foreach (var header in _client.Options.AdditionalHeaders) + { + if (header.Value != null) + metadata.Add(header.Key, header.Value); + } + } + if (options?.AdditionalHeaders != null) + { + foreach (var header in options.AdditionalHeaders) + { + if (header.Value != null) + metadata.Add(header.Key, header.Value); + } + } + + var callOptions = _grpc.CreateCallOptions( + metadata, + options ?? new GrpcRequestOptions(), + cancellationToken + ); + var call = _dataService.CheckAsync( + new Google.Protobuf.WellKnownTypes.Empty(), + callOptions + ); + var response = await call.ConfigureAwait(false); + return CheckResponse.FromProto(response); + } + catch (RpcException rpc) + { + var statusCode = (int)rpc.StatusCode; + throw new SeedApiApiException( + $"Error with gRPC status code {statusCode}", + statusCode, + rpc.Message + ); + } + catch (Exception e) + { + throw new SeedApiException("Error", e); + } + }) + .ConfigureAwait(false); + } + /// /// await client.DataService.CreateAsync(new CreateRequest { Name = "name" }); /// diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/src/SeedApi/DataService/IDataServiceClient.cs b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/src/SeedApi/DataService/IDataServiceClient.cs index 572af69cadb5..9f6c264179ce 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/src/SeedApi/DataService/IDataServiceClient.cs +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/src/SeedApi/DataService/IDataServiceClient.cs @@ -2,6 +2,11 @@ namespace SeedApi; public partial interface IDataServiceClient { + Task CheckAsync( + GrpcRequestOptions? options = null, + CancellationToken cancellationToken = default + ); + Task CreateAsync( CreateRequest request, GrpcRequestOptions? options = null, diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/src/SeedApi/Types/CheckResponse.cs b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/src/SeedApi/Types/CheckResponse.cs new file mode 100644 index 000000000000..2d6e0357b4c4 --- /dev/null +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/src/SeedApi/Types/CheckResponse.cs @@ -0,0 +1,66 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using SeedApi.Core; +using ProtoDataV1Grpc = Data.V1.Grpc; +using WellKnownProto = Google.Protobuf.WellKnownTypes; + +namespace SeedApi; + +[Serializable] +public record CheckResponse : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("created_at")] + public DateTime? CreatedAt { get; set; } + + [JsonPropertyName("updated_at")] + public DateTime? UpdatedAt { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + /// + /// Returns a new CheckResponse type from its Protobuf-equivalent representation. + /// + internal static CheckResponse FromProto(ProtoDataV1Grpc.CheckResponse value) + { + return new CheckResponse + { + CreatedAt = value.CreatedAt?.ToDateTime(), + UpdatedAt = value.UpdatedAt?.ToDateTime(), + }; + } + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + /// Maps the CheckResponse type into its Protobuf-equivalent representation. + /// + internal ProtoDataV1Grpc.CheckResponse ToProto() + { + var result = new ProtoDataV1Grpc.CheckResponse(); + if (CreatedAt != null) + { + result.CreatedAt = WellKnownProto.Timestamp.FromDateTime( + CreatedAt.Value.ToUniversalTime() + ); + } + if (UpdatedAt != null) + { + result.UpdatedAt = WellKnownProto.Timestamp.FromDateTime( + UpdatedAt.Value.ToUniversalTime() + ); + } + return result; + } + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/src/SeedApi/Types/UpdateResponse.cs b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/src/SeedApi/Types/UpdateResponse.cs index 9a583442a7e7..4af77fcc82b7 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/src/SeedApi/Types/UpdateResponse.cs +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/include-exception-handler/src/SeedApi/Types/UpdateResponse.cs @@ -38,7 +38,7 @@ internal static UpdateResponse FromProto(ProtoDataV1Grpc.UpdateResponse value) { return new UpdateResponse { - UpdatedAt = value.UpdatedAt.ToDateTime(), + UpdatedAt = value.UpdatedAt?.ToDateTime(), IndexType = value.IndexType switch { ProtoDataV1Grpc.IndexType.Invalid => SeedApi.IndexType.IndexTypeInvalid, diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/README.md b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/README.md index fb55bd59b407..58c55f2c0e0d 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/README.md +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/README.md @@ -41,7 +41,7 @@ Instantiate and use the client with the following: using SeedApi; var client = new SeedApiClient(); -await client.DataService.CreateAsync(new CreateRequest { Name = "name" }); +await client.DataService.CheckAsync(); ``` ## Exception Handling @@ -53,7 +53,7 @@ will be thrown. using SeedApi; try { - var response = await client.DataService.CreateAsync(...); + var response = await client.DataService.CheckAsync(...); } catch (SeedApiApiException e) { System.Console.WriteLine(e.Body); System.Console.WriteLine(e.StatusCode); @@ -77,7 +77,7 @@ A request is deemed retryable when any of the following HTTP status codes is ret Use the `MaxRetries` request option to configure this behavior. ```csharp -var response = await client.DataService.CreateAsync( +var response = await client.DataService.CheckAsync( ..., new RequestOptions { MaxRetries: 0 // Override MaxRetries at the request level @@ -90,7 +90,7 @@ var response = await client.DataService.CreateAsync( The SDK defaults to a 30 second timeout. Use the `Timeout` option to configure this behavior. ```csharp -var response = await client.DataService.CreateAsync( +var response = await client.DataService.CheckAsync( ..., new RequestOptions { Timeout: TimeSpan.FromSeconds(3) // Override timeout to 3s @@ -103,7 +103,7 @@ var response = await client.DataService.CreateAsync( If you would like to send additional headers as part of the request, use the `AdditionalHeaders` request option. ```csharp -var response = await client.DataService.CreateAsync( +var response = await client.DataService.CheckAsync( ..., new RequestOptions { AdditionalHeaders = new Dictionary diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/proto/data/v1/data.proto b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/proto/data/v1/data.proto index 4f302a5b1c95..a19a2141a882 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/proto/data/v1/data.proto +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/proto/data/v1/data.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package data.v1; import "google/protobuf/any.proto"; +import "google/protobuf/empty.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "google/api/annotations.proto"; @@ -262,7 +263,13 @@ message DescribeResponse { uint32 total_count = 4; } +message CheckResponse { + google.protobuf.Timestamp created_at = 1; + google.protobuf.Timestamp updated_at = 2; +} + service DataService { + rpc Check(google.protobuf.Empty) returns (CheckResponse); rpc Upload(UploadRequest) returns (UploadResponse); rpc Delete(DeleteRequest) returns (DeleteResponse); rpc Query(QueryRequest) returns (QueryResponse); diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/reference.md b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/reference.md index 49ca7fcf6303..0be0c5ebe40e 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/reference.md +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/reference.md @@ -25,6 +25,31 @@ await client.PostFooAsync();
## DataService +
client.DataService.CheckAsync() -> WithRawResponseTask<CheckResponse> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.DataService.CheckAsync(); +``` +
+
+
+
+ + +
+
+
+
client.DataService.CreateAsync(CreateRequest { ... }) -> WithRawResponseTask<CreateResponse>
diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/snippet.json b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/snippet.json index f2c2b5e9fcbc..1aeb6586f4ce 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/snippet.json +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/snippet.json @@ -1,6 +1,18 @@ { "types": {}, "endpoints": [ + { + "example_identifier": null, + "id": { + "path": "/data.v1.DataService/Check", + "method": "POST", + "identifier_override": "endpoint_dataService.Check" + }, + "snippet": { + "type": "csharp", + "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.DataService.CheckAsync();\n" + } + }, { "example_identifier": null, "id": { diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/src/SeedApi/DataService/DataServiceClient.cs b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/src/SeedApi/DataService/DataServiceClient.cs index 85a690d98fa7..8056ee054886 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/src/SeedApi/DataService/DataServiceClient.cs +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/src/SeedApi/DataService/DataServiceClient.cs @@ -19,6 +19,66 @@ internal DataServiceClient(RawClient client) _dataService = new DataService.DataServiceClient(_grpc.Channel); } + /// + /// await client.DataService.CheckAsync(); + /// + public async Task CheckAsync( + GrpcRequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + try + { + var metadata = new global::Grpc.Core.Metadata(); + foreach (var header in _client.Options.Headers) + { + var value = await header.Value.ResolveAsync().ConfigureAwait(false); + metadata.Add(header.Key, value); + } + if (_client.Options.AdditionalHeaders != null) + { + foreach (var header in _client.Options.AdditionalHeaders) + { + if (header.Value != null) + metadata.Add(header.Key, header.Value); + } + } + if (options?.AdditionalHeaders != null) + { + foreach (var header in options.AdditionalHeaders) + { + if (header.Value != null) + metadata.Add(header.Key, header.Value); + } + } + + var callOptions = _grpc.CreateCallOptions( + metadata, + options ?? new GrpcRequestOptions(), + cancellationToken + ); + var call = _dataService.CheckAsync( + new Google.Protobuf.WellKnownTypes.Empty(), + callOptions + ); + var response = await call.ConfigureAwait(false); + return CheckResponse.FromProto(response); + } + catch (RpcException rpc) + { + var statusCode = (int)rpc.StatusCode; + throw new SeedApiApiException( + $"Error with gRPC status code {statusCode}", + statusCode, + rpc.Message + ); + } + catch (Exception e) + { + throw new SeedApiException("Error", e); + } + } + /// /// await client.DataService.CreateAsync(new CreateRequest { Name = "name" }); /// diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/src/SeedApi/DataService/IDataServiceClient.cs b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/src/SeedApi/DataService/IDataServiceClient.cs index 572af69cadb5..9f6c264179ce 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/src/SeedApi/DataService/IDataServiceClient.cs +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/src/SeedApi/DataService/IDataServiceClient.cs @@ -2,6 +2,11 @@ namespace SeedApi; public partial interface IDataServiceClient { + Task CheckAsync( + GrpcRequestOptions? options = null, + CancellationToken cancellationToken = default + ); + Task CreateAsync( CreateRequest request, GrpcRequestOptions? options = null, diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/src/SeedApi/Types/CheckResponse.cs b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/src/SeedApi/Types/CheckResponse.cs new file mode 100644 index 000000000000..2d6e0357b4c4 --- /dev/null +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/src/SeedApi/Types/CheckResponse.cs @@ -0,0 +1,66 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using SeedApi.Core; +using ProtoDataV1Grpc = Data.V1.Grpc; +using WellKnownProto = Google.Protobuf.WellKnownTypes; + +namespace SeedApi; + +[Serializable] +public record CheckResponse : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("created_at")] + public DateTime? CreatedAt { get; set; } + + [JsonPropertyName("updated_at")] + public DateTime? UpdatedAt { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + /// + /// Returns a new CheckResponse type from its Protobuf-equivalent representation. + /// + internal static CheckResponse FromProto(ProtoDataV1Grpc.CheckResponse value) + { + return new CheckResponse + { + CreatedAt = value.CreatedAt?.ToDateTime(), + UpdatedAt = value.UpdatedAt?.ToDateTime(), + }; + } + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + /// Maps the CheckResponse type into its Protobuf-equivalent representation. + /// + internal ProtoDataV1Grpc.CheckResponse ToProto() + { + var result = new ProtoDataV1Grpc.CheckResponse(); + if (CreatedAt != null) + { + result.CreatedAt = WellKnownProto.Timestamp.FromDateTime( + CreatedAt.Value.ToUniversalTime() + ); + } + if (UpdatedAt != null) + { + result.UpdatedAt = WellKnownProto.Timestamp.FromDateTime( + UpdatedAt.Value.ToUniversalTime() + ); + } + return result; + } + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/src/SeedApi/Types/UpdateResponse.cs b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/src/SeedApi/Types/UpdateResponse.cs index 9a583442a7e7..4af77fcc82b7 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/src/SeedApi/Types/UpdateResponse.cs +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/no-custom-config/src/SeedApi/Types/UpdateResponse.cs @@ -38,7 +38,7 @@ internal static UpdateResponse FromProto(ProtoDataV1Grpc.UpdateResponse value) { return new UpdateResponse { - UpdatedAt = value.UpdatedAt.ToDateTime(), + UpdatedAt = value.UpdatedAt?.ToDateTime(), IndexType = value.IndexType switch { ProtoDataV1Grpc.IndexType.Invalid => SeedApi.IndexType.IndexTypeInvalid, diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/README.md b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/README.md index b45b127cb11c..b8a01fc06d44 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/README.md +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/README.md @@ -41,7 +41,7 @@ Instantiate and use the client with the following: using SeedApi; var client = new SeedApiClient(); -await client.DataService.CreateAsync(new CreateRequest { Name = "name" }); +await client.DataService.CheckAsync(); ``` ## Exception Handling @@ -53,7 +53,7 @@ will be thrown. using SeedApi; try { - var response = await client.DataService.CreateAsync(...); + var response = await client.DataService.CheckAsync(...); } catch (SeedApiApiException e) { System.Console.WriteLine(e.Body); System.Console.WriteLine(e.StatusCode); @@ -77,7 +77,7 @@ A request is deemed retryable when any of the following HTTP status codes is ret Use the `MaxRetries` request option to configure this behavior. ```csharp -var response = await client.DataService.CreateAsync( +var response = await client.DataService.CheckAsync( ..., new RequestOptions { MaxRetries: 0 // Override MaxRetries at the request level @@ -90,7 +90,7 @@ var response = await client.DataService.CreateAsync( The SDK defaults to a 30 second timeout. Use the `Timeout` option to configure this behavior. ```csharp -var response = await client.DataService.CreateAsync( +var response = await client.DataService.CheckAsync( ..., new RequestOptions { Timeout: TimeSpan.FromSeconds(3) // Override timeout to 3s @@ -103,7 +103,7 @@ var response = await client.DataService.CreateAsync( If you would like to send additional headers as part of the request, use the `AdditionalHeaders` request option. ```csharp -var response = await client.DataService.CreateAsync( +var response = await client.DataService.CheckAsync( ..., new RequestOptions { AdditionalHeaders = new Dictionary diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/proto/data/v1/data.proto b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/proto/data/v1/data.proto index 4f302a5b1c95..a19a2141a882 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/proto/data/v1/data.proto +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/proto/data/v1/data.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package data.v1; import "google/protobuf/any.proto"; +import "google/protobuf/empty.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "google/api/annotations.proto"; @@ -262,7 +263,13 @@ message DescribeResponse { uint32 total_count = 4; } +message CheckResponse { + google.protobuf.Timestamp created_at = 1; + google.protobuf.Timestamp updated_at = 2; +} + service DataService { + rpc Check(google.protobuf.Empty) returns (CheckResponse); rpc Upload(UploadRequest) returns (UploadResponse); rpc Delete(DeleteRequest) returns (DeleteResponse); rpc Query(QueryRequest) returns (QueryResponse); diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/reference.md b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/reference.md index 49ca7fcf6303..0be0c5ebe40e 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/reference.md +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/reference.md @@ -25,6 +25,31 @@ await client.PostFooAsync();
## DataService +
client.DataService.CheckAsync() -> WithRawResponseTask<CheckResponse> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.DataService.CheckAsync(); +``` +
+
+
+
+ + +
+
+
+
client.DataService.CreateAsync(CreateRequest { ... }) -> WithRawResponseTask<CreateResponse>
diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/snippet.json b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/snippet.json index f2c2b5e9fcbc..1aeb6586f4ce 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/snippet.json +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/snippet.json @@ -1,6 +1,18 @@ { "types": {}, "endpoints": [ + { + "example_identifier": null, + "id": { + "path": "/data.v1.DataService/Check", + "method": "POST", + "identifier_override": "endpoint_dataService.Check" + }, + "snippet": { + "type": "csharp", + "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.DataService.CheckAsync();\n" + } + }, { "example_identifier": null, "id": { diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/src/SeedApi/DataService/DataServiceClient.cs b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/src/SeedApi/DataService/DataServiceClient.cs index 85a690d98fa7..8056ee054886 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/src/SeedApi/DataService/DataServiceClient.cs +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/src/SeedApi/DataService/DataServiceClient.cs @@ -19,6 +19,66 @@ internal DataServiceClient(RawClient client) _dataService = new DataService.DataServiceClient(_grpc.Channel); } + /// + /// await client.DataService.CheckAsync(); + /// + public async Task CheckAsync( + GrpcRequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + try + { + var metadata = new global::Grpc.Core.Metadata(); + foreach (var header in _client.Options.Headers) + { + var value = await header.Value.ResolveAsync().ConfigureAwait(false); + metadata.Add(header.Key, value); + } + if (_client.Options.AdditionalHeaders != null) + { + foreach (var header in _client.Options.AdditionalHeaders) + { + if (header.Value != null) + metadata.Add(header.Key, header.Value); + } + } + if (options?.AdditionalHeaders != null) + { + foreach (var header in options.AdditionalHeaders) + { + if (header.Value != null) + metadata.Add(header.Key, header.Value); + } + } + + var callOptions = _grpc.CreateCallOptions( + metadata, + options ?? new GrpcRequestOptions(), + cancellationToken + ); + var call = _dataService.CheckAsync( + new Google.Protobuf.WellKnownTypes.Empty(), + callOptions + ); + var response = await call.ConfigureAwait(false); + return CheckResponse.FromProto(response); + } + catch (RpcException rpc) + { + var statusCode = (int)rpc.StatusCode; + throw new SeedApiApiException( + $"Error with gRPC status code {statusCode}", + statusCode, + rpc.Message + ); + } + catch (Exception e) + { + throw new SeedApiException("Error", e); + } + } + /// /// await client.DataService.CreateAsync(new CreateRequest { Name = "name" }); /// diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/src/SeedApi/DataService/IDataServiceClient.cs b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/src/SeedApi/DataService/IDataServiceClient.cs index 572af69cadb5..9f6c264179ce 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/src/SeedApi/DataService/IDataServiceClient.cs +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/src/SeedApi/DataService/IDataServiceClient.cs @@ -2,6 +2,11 @@ namespace SeedApi; public partial interface IDataServiceClient { + Task CheckAsync( + GrpcRequestOptions? options = null, + CancellationToken cancellationToken = default + ); + Task CreateAsync( CreateRequest request, GrpcRequestOptions? options = null, diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/src/SeedApi/Types/CheckResponse.cs b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/src/SeedApi/Types/CheckResponse.cs new file mode 100644 index 000000000000..2d6e0357b4c4 --- /dev/null +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/src/SeedApi/Types/CheckResponse.cs @@ -0,0 +1,66 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using SeedApi.Core; +using ProtoDataV1Grpc = Data.V1.Grpc; +using WellKnownProto = Google.Protobuf.WellKnownTypes; + +namespace SeedApi; + +[Serializable] +public record CheckResponse : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("created_at")] + public DateTime? CreatedAt { get; set; } + + [JsonPropertyName("updated_at")] + public DateTime? UpdatedAt { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + /// + /// Returns a new CheckResponse type from its Protobuf-equivalent representation. + /// + internal static CheckResponse FromProto(ProtoDataV1Grpc.CheckResponse value) + { + return new CheckResponse + { + CreatedAt = value.CreatedAt?.ToDateTime(), + UpdatedAt = value.UpdatedAt?.ToDateTime(), + }; + } + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + /// Maps the CheckResponse type into its Protobuf-equivalent representation. + /// + internal ProtoDataV1Grpc.CheckResponse ToProto() + { + var result = new ProtoDataV1Grpc.CheckResponse(); + if (CreatedAt != null) + { + result.CreatedAt = WellKnownProto.Timestamp.FromDateTime( + CreatedAt.Value.ToUniversalTime() + ); + } + if (UpdatedAt != null) + { + result.UpdatedAt = WellKnownProto.Timestamp.FromDateTime( + UpdatedAt.Value.ToUniversalTime() + ); + } + return result; + } + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/src/SeedApi/Types/UpdateResponse.cs b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/src/SeedApi/Types/UpdateResponse.cs index 9a583442a7e7..4af77fcc82b7 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/src/SeedApi/Types/UpdateResponse.cs +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/package-id/src/SeedApi/Types/UpdateResponse.cs @@ -38,7 +38,7 @@ internal static UpdateResponse FromProto(ProtoDataV1Grpc.UpdateResponse value) { return new UpdateResponse { - UpdatedAt = value.UpdatedAt.ToDateTime(), + UpdatedAt = value.UpdatedAt?.ToDateTime(), IndexType = value.IndexType switch { ProtoDataV1Grpc.IndexType.Invalid => SeedApi.IndexType.IndexTypeInvalid, diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/README.md b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/README.md index fb55bd59b407..58c55f2c0e0d 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/README.md +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/README.md @@ -41,7 +41,7 @@ Instantiate and use the client with the following: using SeedApi; var client = new SeedApiClient(); -await client.DataService.CreateAsync(new CreateRequest { Name = "name" }); +await client.DataService.CheckAsync(); ``` ## Exception Handling @@ -53,7 +53,7 @@ will be thrown. using SeedApi; try { - var response = await client.DataService.CreateAsync(...); + var response = await client.DataService.CheckAsync(...); } catch (SeedApiApiException e) { System.Console.WriteLine(e.Body); System.Console.WriteLine(e.StatusCode); @@ -77,7 +77,7 @@ A request is deemed retryable when any of the following HTTP status codes is ret Use the `MaxRetries` request option to configure this behavior. ```csharp -var response = await client.DataService.CreateAsync( +var response = await client.DataService.CheckAsync( ..., new RequestOptions { MaxRetries: 0 // Override MaxRetries at the request level @@ -90,7 +90,7 @@ var response = await client.DataService.CreateAsync( The SDK defaults to a 30 second timeout. Use the `Timeout` option to configure this behavior. ```csharp -var response = await client.DataService.CreateAsync( +var response = await client.DataService.CheckAsync( ..., new RequestOptions { Timeout: TimeSpan.FromSeconds(3) // Override timeout to 3s @@ -103,7 +103,7 @@ var response = await client.DataService.CreateAsync( If you would like to send additional headers as part of the request, use the `AdditionalHeaders` request option. ```csharp -var response = await client.DataService.CreateAsync( +var response = await client.DataService.CheckAsync( ..., new RequestOptions { AdditionalHeaders = new Dictionary diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/proto/data/v1/data.proto b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/proto/data/v1/data.proto index 4f302a5b1c95..a19a2141a882 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/proto/data/v1/data.proto +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/proto/data/v1/data.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package data.v1; import "google/protobuf/any.proto"; +import "google/protobuf/empty.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "google/api/annotations.proto"; @@ -262,7 +263,13 @@ message DescribeResponse { uint32 total_count = 4; } +message CheckResponse { + google.protobuf.Timestamp created_at = 1; + google.protobuf.Timestamp updated_at = 2; +} + service DataService { + rpc Check(google.protobuf.Empty) returns (CheckResponse); rpc Upload(UploadRequest) returns (UploadResponse); rpc Delete(DeleteRequest) returns (DeleteResponse); rpc Query(QueryRequest) returns (QueryResponse); diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/reference.md b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/reference.md index 4966498c8121..82f930a08322 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/reference.md +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/reference.md @@ -25,6 +25,31 @@ await client.PostFooAsync();
## DataService +
client.DataService.CheckAsync() -> WithRawResponseTask<CheckResponse> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.DataService.CheckAsync(); +``` +
+
+
+
+ + +
+
+
+
client.DataService.CreateAsync(CreateRequest { ... }) -> WithRawResponseTask<CreateResponse>
diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/snippet.json b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/snippet.json index 7c3c8dc56e5f..495ce6bb87db 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/snippet.json +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/snippet.json @@ -1,6 +1,18 @@ { "types": {}, "endpoints": [ + { + "example_identifier": null, + "id": { + "path": "/data.v1.DataService/Check", + "method": "POST", + "identifier_override": "endpoint_dataService.Check" + }, + "snippet": { + "type": "csharp", + "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.DataService.CheckAsync();\n" + } + }, { "example_identifier": null, "id": { diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/src/SeedApi/DataService/DataServiceClient.cs b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/src/SeedApi/DataService/DataServiceClient.cs index 6a2cdc53a8e7..361272e2feff 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/src/SeedApi/DataService/DataServiceClient.cs +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/src/SeedApi/DataService/DataServiceClient.cs @@ -19,6 +19,66 @@ internal DataServiceClient(RawClient client) _dataService = new DataService.DataServiceClient(_grpc.Channel); } + /// + /// await client.DataService.CheckAsync(); + /// + public async Task CheckAsync( + GrpcRequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + try + { + var metadata = new global::Grpc.Core.Metadata(); + foreach (var header in _client.Options.Headers) + { + var value = await header.Value.ResolveAsync().ConfigureAwait(false); + metadata.Add(header.Key, value); + } + if (_client.Options.AdditionalHeaders != null) + { + foreach (var header in _client.Options.AdditionalHeaders) + { + if (header.Value != null) + metadata.Add(header.Key, header.Value); + } + } + if (options?.AdditionalHeaders != null) + { + foreach (var header in options.AdditionalHeaders) + { + if (header.Value != null) + metadata.Add(header.Key, header.Value); + } + } + + var callOptions = _grpc.CreateCallOptions( + metadata, + options ?? new GrpcRequestOptions(), + cancellationToken + ); + var call = _dataService.CheckAsync( + new Google.Protobuf.WellKnownTypes.Empty(), + callOptions + ); + var response = await call.ConfigureAwait(false); + return CheckResponse.FromProto(response); + } + catch (RpcException rpc) + { + var statusCode = (int)rpc.StatusCode; + throw new SeedApiApiException( + $"Error with gRPC status code {statusCode}", + statusCode, + rpc.Message + ); + } + catch (Exception e) + { + throw new SeedApiException("Error", e); + } + } + /// /// await client.DataService.CreateAsync(new CreateRequest { Name = "name" }); /// diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/src/SeedApi/DataService/IDataServiceClient.cs b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/src/SeedApi/DataService/IDataServiceClient.cs index 572af69cadb5..9f6c264179ce 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/src/SeedApi/DataService/IDataServiceClient.cs +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/src/SeedApi/DataService/IDataServiceClient.cs @@ -2,6 +2,11 @@ namespace SeedApi; public partial interface IDataServiceClient { + Task CheckAsync( + GrpcRequestOptions? options = null, + CancellationToken cancellationToken = default + ); + Task CreateAsync( CreateRequest request, GrpcRequestOptions? options = null, diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/src/SeedApi/Types/CheckResponse.cs b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/src/SeedApi/Types/CheckResponse.cs new file mode 100644 index 000000000000..2d6e0357b4c4 --- /dev/null +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/src/SeedApi/Types/CheckResponse.cs @@ -0,0 +1,66 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using SeedApi.Core; +using ProtoDataV1Grpc = Data.V1.Grpc; +using WellKnownProto = Google.Protobuf.WellKnownTypes; + +namespace SeedApi; + +[Serializable] +public record CheckResponse : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("created_at")] + public DateTime? CreatedAt { get; set; } + + [JsonPropertyName("updated_at")] + public DateTime? UpdatedAt { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + /// + /// Returns a new CheckResponse type from its Protobuf-equivalent representation. + /// + internal static CheckResponse FromProto(ProtoDataV1Grpc.CheckResponse value) + { + return new CheckResponse + { + CreatedAt = value.CreatedAt?.ToDateTime(), + UpdatedAt = value.UpdatedAt?.ToDateTime(), + }; + } + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + /// Maps the CheckResponse type into its Protobuf-equivalent representation. + /// + internal ProtoDataV1Grpc.CheckResponse ToProto() + { + var result = new ProtoDataV1Grpc.CheckResponse(); + if (CreatedAt != null) + { + result.CreatedAt = WellKnownProto.Timestamp.FromDateTime( + CreatedAt.Value.ToUniversalTime() + ); + } + if (UpdatedAt != null) + { + result.UpdatedAt = WellKnownProto.Timestamp.FromDateTime( + UpdatedAt.Value.ToUniversalTime() + ); + } + return result; + } + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/src/SeedApi/Types/UpdateResponse.cs b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/src/SeedApi/Types/UpdateResponse.cs index 9a583442a7e7..4af77fcc82b7 100644 --- a/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/src/SeedApi/Types/UpdateResponse.cs +++ b/seed/csharp-sdk/csharp-grpc-proto-exhaustive/read-only-memory/src/SeedApi/Types/UpdateResponse.cs @@ -38,7 +38,7 @@ internal static UpdateResponse FromProto(ProtoDataV1Grpc.UpdateResponse value) { return new UpdateResponse { - UpdatedAt = value.UpdatedAt.ToDateTime(), + UpdatedAt = value.UpdatedAt?.ToDateTime(), IndexType = value.IndexType switch { ProtoDataV1Grpc.IndexType.Invalid => SeedApi.IndexType.IndexTypeInvalid, diff --git a/test-definitions/fern/apis/csharp-grpc-proto-exhaustive/proto/data/v1/data.proto b/test-definitions/fern/apis/csharp-grpc-proto-exhaustive/proto/data/v1/data.proto index 4f302a5b1c95..a19a2141a882 100644 --- a/test-definitions/fern/apis/csharp-grpc-proto-exhaustive/proto/data/v1/data.proto +++ b/test-definitions/fern/apis/csharp-grpc-proto-exhaustive/proto/data/v1/data.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package data.v1; import "google/protobuf/any.proto"; +import "google/protobuf/empty.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "google/api/annotations.proto"; @@ -262,7 +263,13 @@ message DescribeResponse { uint32 total_count = 4; } +message CheckResponse { + google.protobuf.Timestamp created_at = 1; + google.protobuf.Timestamp updated_at = 2; +} + service DataService { + rpc Check(google.protobuf.Empty) returns (CheckResponse); rpc Upload(UploadRequest) returns (UploadResponse); rpc Delete(DeleteRequest) returns (DeleteResponse); rpc Query(QueryRequest) returns (QueryResponse); From 264d733721f39e9fa0722d9b0fce17947996ed16 Mon Sep 17 00:00:00 2001 From: Naman Anand Date: Wed, 18 Mar 2026 03:08:53 +0530 Subject: [PATCH 09/11] fix(ruby): strip zero-millisecond datetime values from WireMock stubs (#13626) Add stripDatetimeMilliseconds to Ruby generator's WireTestSetupGenerator, following the same pattern used by Python and PHP generators. Ruby's DateTime/Time ISO 8601 serialization omits zero fractional seconds, so WireMock stubs need .000 stripped from datetime query parameter values for exact-match to work. --- .../src/wire-tests/WireTestSetupGenerator.ts | 40 ++++++++++++++++++- generators/ruby-v2/sdk/versions.yml | 13 ++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/generators/ruby-v2/sdk/src/wire-tests/WireTestSetupGenerator.ts b/generators/ruby-v2/sdk/src/wire-tests/WireTestSetupGenerator.ts index f91bc2d0d6e8..3240ff97a159 100644 --- a/generators/ruby-v2/sdk/src/wire-tests/WireTestSetupGenerator.ts +++ b/generators/ruby-v2/sdk/src/wire-tests/WireTestSetupGenerator.ts @@ -1,6 +1,6 @@ import { File } from "@fern-api/base-generator"; import { RelativeFilePath } from "@fern-api/fs-utils"; -import { WireMock } from "@fern-api/mock-utils"; +import { WireMock, WireMockStubMapping } from "@fern-api/mock-utils"; import { FernIr } from "@fern-fern/ir-sdk"; import { SdkGeneratorContext } from "../SdkGeneratorContext.js"; @@ -32,9 +32,47 @@ export class WireTestSetupGenerator { return new WireMock().convertToWireMock(ir); } + /** + * ISO 8601 datetime pattern that matches values with `.000` milliseconds. + * Example: "2008-01-02T00:00:00.000Z" or "2008-01-02T00:00:00.000+05:00" + */ + private static readonly DATETIME_WITH_ZERO_MILLIS_REGEX = + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.000(Z|[+-]\d{2}:\d{2})$/; + + /** + * Strips ".000" milliseconds from all datetime query parameter values in WireMock stub mappings. + * Ruby's DateTime/Time ISO 8601 serialization omits zero fractional seconds, so the SDK + * sends e.g. "2024-09-08T12:00:00Z" while mock-utils generates "2024-09-08T12:00:00.000Z" + * (via Date.toISOString()). Since WireMock's equalTo matcher is exact-match, the stubs + * never fire unless we strip the zero milliseconds. + * + * Mutates the input in-place for efficiency. + */ + private static stripDatetimeMilliseconds(stubMapping: WireMockStubMapping): void { + for (const mapping of stubMapping.mappings) { + if (mapping.request.queryParameters) { + for (const [, value] of Object.entries(mapping.request.queryParameters)) { + const paramValue = value as { equalTo: string }; + if ( + paramValue.equalTo != null && + WireTestSetupGenerator.DATETIME_WITH_ZERO_MILLIS_REGEX.test(paramValue.equalTo) + ) { + paramValue.equalTo = paramValue.equalTo.replace(".000", ""); + } + } + } + } + } + private generateWireMockConfigFile(): void { const wireMockConfigContent = WireTestSetupGenerator.getWiremockConfigContent(this.ir); + // mock-utils generates datetime values using Date.toISOString() which always includes + // ".000Z" milliseconds. Ruby's DateTime/Time ISO 8601 serialization omits fractional + // seconds, so the SDK sends e.g. "2024-09-08T12:00:00Z". Strip the zero milliseconds + // from WireMock stubs so that equalTo exact-matching works correctly. + WireTestSetupGenerator.stripDatetimeMilliseconds(wireMockConfigContent); + // Add OAuth token endpoint mapping if inferred auth is present const inferredAuth = this.context.getInferredAuth(); if (inferredAuth != null) { diff --git a/generators/ruby-v2/sdk/versions.yml b/generators/ruby-v2/sdk/versions.yml index 9b97d8875d74..d237fbf801c6 100644 --- a/generators/ruby-v2/sdk/versions.yml +++ b/generators/ruby-v2/sdk/versions.yml @@ -1,5 +1,18 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 1.1.1 + changelogEntry: + - summary: | + Strip zero-millisecond datetime values from WireMock stub query parameters. + Ruby's DateTime/Time ISO 8601 serialization omits zero fractional seconds + (e.g., `2024-09-08T12:00:00Z`), but mock-utils generates values with `.000Z` + (e.g., `2024-09-08T12:00:00.000Z`). WireMock's `equalTo` matcher is exact-match, + so the stubs never fired for datetime query parameters. This follows the same + pattern used by the Python and PHP generators. + type: fix + createdAt: "2026-03-17" + irVersion: 61 + - version: 1.1.0 changelogEntry: - summary: | From 989e42513e62025afa42a41dfbc9140d511622c0 Mon Sep 17 00:00:00 2001 From: jsklan <100491078+jsklan@users.noreply.github.com> Date: Tue, 17 Mar 2026 17:48:59 -0400 Subject: [PATCH 10/11] fix(cli): resolve LICENSE path relative to generators.yml in local generation runner (#13645) * fix: resolve custom LICENSE path relative to generators.yml instead of fern.config.json Co-Authored-By: judah * fix(cli): skip GitHub clone and post-generation pipeline in preview mode Co-Authored-By: judah * fix(cli): allow clone in preview mode, only skip post-generation push Co-Authored-By: judah * fix(cli): run replay in preview mode, only skip push and PR creation Co-Authored-By: judah * rip out preview changes --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- packages/cli/cli/versions.yml | 12 ++++++++++++ .../src/runLocalGenerationForWorkspace.ts | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index fd633771ce74..1cf448524478 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,4 +1,16 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 4.32.2 + changelogEntry: + - summary: | + Fix custom LICENSE files not being included in SDK output when using + `fern generate --local`. The license path (e.g. `../../LICENSE`) was + resolved relative to `fern.config.json` instead of `generators.yml`, + causing the file to not be found in multi-API workspace layouts where + these files are in different directories. + type: fix + createdAt: "2026-03-17" + irVersion: 65 + - version: 4.32.1 changelogEntry: - summary: | diff --git a/packages/cli/generation/local-generation/local-workspace-runner/src/runLocalGenerationForWorkspace.ts b/packages/cli/generation/local-generation/local-workspace-runner/src/runLocalGenerationForWorkspace.ts index 609ee9ad1e59..c8e5b14efed0 100644 --- a/packages/cli/generation/local-generation/local-workspace-runner/src/runLocalGenerationForWorkspace.ts +++ b/packages/cli/generation/local-generation/local-workspace-runner/src/runLocalGenerationForWorkspace.ts @@ -305,7 +305,8 @@ export async function runLocalGenerationForWorkspace({ autoVersioningVersionBumpReason } = await writeFilesToDiskAndRunGenerator({ organization: projectConfig.organization, - absolutePathToFernConfig: projectConfig._absolutePath, + absolutePathToFernConfig: + workspace.generatorsConfiguration?.absolutePathToConfiguration ?? projectConfig._absolutePath, workspace: fernWorkspace, generatorInvocation, absolutePathToLocalOutput, From aabbce710eb3e8ecedd262699639b26faa88add1 Mon Sep 17 00:00:00 2001 From: Naman Anand Date: Wed, 18 Mar 2026 03:46:39 +0530 Subject: [PATCH 11/11] fix(python): skip Docker container management when `WIREMOCK_URL` is set (#13625) fix(python): skip Docker container management when WIREMOCK_URL is set When WIREMOCK_URL environment variable is already set (e.g., by a CI/CD pipeline providing an external WireMock sidecar container), skip starting and stopping the Docker container. This matches the behavior of the PHP, Ruby, and Rust SDK generators. --- .../sdk/src/wire-tests/WireTestSetupGenerator.ts | 16 +++++++++++++++- generators/python/sdk/versions.yml | 11 +++++++++++ .../no-custom-config/tests/conftest.py | 16 +++++++++++++++- .../tests/conftest.py | 16 +++++++++++++++- .../with-wire-tests/tests/conftest.py | 16 +++++++++++++++- .../with-wire-tests/tests/conftest.py | 16 +++++++++++++++- 6 files changed, 86 insertions(+), 5 deletions(-) diff --git a/generators/python-v2/sdk/src/wire-tests/WireTestSetupGenerator.ts b/generators/python-v2/sdk/src/wire-tests/WireTestSetupGenerator.ts index 2353d26fa07e..ca29f7afc103 100644 --- a/generators/python-v2/sdk/src/wire-tests/WireTestSetupGenerator.ts +++ b/generators/python-v2/sdk/src/wire-tests/WireTestSetupGenerator.ts @@ -384,6 +384,7 @@ import subprocess import pytest _STARTED: bool = False +_EXTERNAL: bool = False # True when using an external WireMock instance (skip container lifecycle) _WIREMOCK_URL: str = "http://localhost:8080" # Default, will be updated after container starts _PROJECT_NAME: str = "${projectName}" @@ -410,10 +411,19 @@ def _get_wiremock_port() -> str: def _start_wiremock() -> None: """Starts the WireMock container using docker-compose.""" - global _STARTED, _WIREMOCK_URL + global _STARTED, _EXTERNAL, _WIREMOCK_URL if _STARTED: return + # If WIREMOCK_URL is already set (e.g., by CI/CD pipeline), skip container management + existing_url = os.environ.get("WIREMOCK_URL") + if existing_url: + _WIREMOCK_URL = existing_url + _EXTERNAL = True + _STARTED = True + print(f"\\nUsing external WireMock at {_WIREMOCK_URL} (container management skipped)") + return + print(f"\\nStarting WireMock container (project: {_PROJECT_NAME})...") try: subprocess.run( @@ -434,6 +444,10 @@ def _start_wiremock() -> None: def _stop_wiremock() -> None: """Stops and removes the WireMock container.""" + if _EXTERNAL: + # Container is managed externally; nothing to tear down. + return + print("\\nStopping WireMock container...") subprocess.run( ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "down", "-v"], diff --git a/generators/python/sdk/versions.yml b/generators/python/sdk/versions.yml index e8afe6f2de90..d97dfcefecd0 100644 --- a/generators/python/sdk/versions.yml +++ b/generators/python/sdk/versions.yml @@ -1,5 +1,16 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json # For unreleased changes, use unreleased.yml +- version: 5.0.3 + changelogEntry: + - summary: | + Skip Docker container management in generated `tests/conftest.py` when the + `WIREMOCK_URL` environment variable is already set. This allows wire tests to + run in CI/CD pipelines that provide an external WireMock sidecar container + without requiring Docker-in-Docker support. + type: fix + createdAt: "2026-03-17" + irVersion: 65 + - version: 5.0.2 changelogEntry: - summary: | diff --git a/seed/python-sdk/exhaustive/no-custom-config/tests/conftest.py b/seed/python-sdk/exhaustive/no-custom-config/tests/conftest.py index 80f143949200..ec7abafe7356 100644 --- a/seed/python-sdk/exhaustive/no-custom-config/tests/conftest.py +++ b/seed/python-sdk/exhaustive/no-custom-config/tests/conftest.py @@ -15,6 +15,7 @@ import pytest _STARTED: bool = False +_EXTERNAL: bool = False # True when using an external WireMock instance (skip container lifecycle) _WIREMOCK_URL: str = "http://localhost:8080" # Default, will be updated after container starts _PROJECT_NAME: str = "seed-exhaustive" @@ -41,10 +42,19 @@ def _get_wiremock_port() -> str: def _start_wiremock() -> None: """Starts the WireMock container using docker-compose.""" - global _STARTED, _WIREMOCK_URL + global _STARTED, _EXTERNAL, _WIREMOCK_URL if _STARTED: return + # If WIREMOCK_URL is already set (e.g., by CI/CD pipeline), skip container management + existing_url = os.environ.get("WIREMOCK_URL") + if existing_url: + _WIREMOCK_URL = existing_url + _EXTERNAL = True + _STARTED = True + print(f"\nUsing external WireMock at {_WIREMOCK_URL} (container management skipped)") + return + print(f"\nStarting WireMock container (project: {_PROJECT_NAME})...") try: subprocess.run( @@ -65,6 +75,10 @@ def _start_wiremock() -> None: def _stop_wiremock() -> None: """Stops and removes the WireMock container.""" + if _EXTERNAL: + # Container is managed externally; nothing to tear down. + return + print("\nStopping WireMock container...") subprocess.run( ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "down", "-v"], diff --git a/seed/python-sdk/exhaustive/wire-tests-custom-client-name/tests/conftest.py b/seed/python-sdk/exhaustive/wire-tests-custom-client-name/tests/conftest.py index 80f143949200..ec7abafe7356 100644 --- a/seed/python-sdk/exhaustive/wire-tests-custom-client-name/tests/conftest.py +++ b/seed/python-sdk/exhaustive/wire-tests-custom-client-name/tests/conftest.py @@ -15,6 +15,7 @@ import pytest _STARTED: bool = False +_EXTERNAL: bool = False # True when using an external WireMock instance (skip container lifecycle) _WIREMOCK_URL: str = "http://localhost:8080" # Default, will be updated after container starts _PROJECT_NAME: str = "seed-exhaustive" @@ -41,10 +42,19 @@ def _get_wiremock_port() -> str: def _start_wiremock() -> None: """Starts the WireMock container using docker-compose.""" - global _STARTED, _WIREMOCK_URL + global _STARTED, _EXTERNAL, _WIREMOCK_URL if _STARTED: return + # If WIREMOCK_URL is already set (e.g., by CI/CD pipeline), skip container management + existing_url = os.environ.get("WIREMOCK_URL") + if existing_url: + _WIREMOCK_URL = existing_url + _EXTERNAL = True + _STARTED = True + print(f"\nUsing external WireMock at {_WIREMOCK_URL} (container management skipped)") + return + print(f"\nStarting WireMock container (project: {_PROJECT_NAME})...") try: subprocess.run( @@ -65,6 +75,10 @@ def _start_wiremock() -> None: def _stop_wiremock() -> None: """Stops and removes the WireMock container.""" + if _EXTERNAL: + # Container is managed externally; nothing to tear down. + return + print("\nStopping WireMock container...") subprocess.run( ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "down", "-v"], diff --git a/seed/python-sdk/python-streaming-parameter-openapi/with-wire-tests/tests/conftest.py b/seed/python-sdk/python-streaming-parameter-openapi/with-wire-tests/tests/conftest.py index 2dc13b49306e..87c6dcbc13e7 100644 --- a/seed/python-sdk/python-streaming-parameter-openapi/with-wire-tests/tests/conftest.py +++ b/seed/python-sdk/python-streaming-parameter-openapi/with-wire-tests/tests/conftest.py @@ -15,6 +15,7 @@ import pytest _STARTED: bool = False +_EXTERNAL: bool = False # True when using an external WireMock instance (skip container lifecycle) _WIREMOCK_URL: str = "http://localhost:8080" # Default, will be updated after container starts _PROJECT_NAME: str = "seed-api" @@ -41,10 +42,19 @@ def _get_wiremock_port() -> str: def _start_wiremock() -> None: """Starts the WireMock container using docker-compose.""" - global _STARTED, _WIREMOCK_URL + global _STARTED, _EXTERNAL, _WIREMOCK_URL if _STARTED: return + # If WIREMOCK_URL is already set (e.g., by CI/CD pipeline), skip container management + existing_url = os.environ.get("WIREMOCK_URL") + if existing_url: + _WIREMOCK_URL = existing_url + _EXTERNAL = True + _STARTED = True + print(f"\nUsing external WireMock at {_WIREMOCK_URL} (container management skipped)") + return + print(f"\nStarting WireMock container (project: {_PROJECT_NAME})...") try: subprocess.run( @@ -65,6 +75,10 @@ def _start_wiremock() -> None: def _stop_wiremock() -> None: """Stops and removes the WireMock container.""" + if _EXTERNAL: + # Container is managed externally; nothing to tear down. + return + print("\nStopping WireMock container...") subprocess.run( ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "down", "-v"], diff --git a/seed/python-sdk/server-sent-events/with-wire-tests/tests/conftest.py b/seed/python-sdk/server-sent-events/with-wire-tests/tests/conftest.py index 742f032edba6..2a3936cc7513 100644 --- a/seed/python-sdk/server-sent-events/with-wire-tests/tests/conftest.py +++ b/seed/python-sdk/server-sent-events/with-wire-tests/tests/conftest.py @@ -15,6 +15,7 @@ import pytest _STARTED: bool = False +_EXTERNAL: bool = False # True when using an external WireMock instance (skip container lifecycle) _WIREMOCK_URL: str = "http://localhost:8080" # Default, will be updated after container starts _PROJECT_NAME: str = "seed-server-sent-events" @@ -41,10 +42,19 @@ def _get_wiremock_port() -> str: def _start_wiremock() -> None: """Starts the WireMock container using docker-compose.""" - global _STARTED, _WIREMOCK_URL + global _STARTED, _EXTERNAL, _WIREMOCK_URL if _STARTED: return + # If WIREMOCK_URL is already set (e.g., by CI/CD pipeline), skip container management + existing_url = os.environ.get("WIREMOCK_URL") + if existing_url: + _WIREMOCK_URL = existing_url + _EXTERNAL = True + _STARTED = True + print(f"\nUsing external WireMock at {_WIREMOCK_URL} (container management skipped)") + return + print(f"\nStarting WireMock container (project: {_PROJECT_NAME})...") try: subprocess.run( @@ -65,6 +75,10 @@ def _start_wiremock() -> None: def _stop_wiremock() -> None: """Stops and removes the WireMock container.""" + if _EXTERNAL: + # Container is managed externally; nothing to tear down. + return + print("\nStopping WireMock container...") subprocess.run( ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "down", "-v"],