From 5b441e3084d41eaf9164a82bd94cb34589df8dac Mon Sep 17 00:00:00 2001 From: Swimburger <3382717+Swimburger@users.noreply.github.com> Date: Mon, 27 Apr 2026 03:59:00 +0000 Subject: [PATCH] chore(csharp): update csharp-sdk seed --- .../no-custom-config/Snippets/Example10.cs | 15 +- .../no-custom-config/Snippets/Example11.cs | 15 +- .../no-custom-config/Snippets/Example12.cs | 4 +- .../no-custom-config/Snippets/Example13.cs | 22 ++ .../no-custom-config/reference.md | 42 +++ .../no-custom-config/snippet.json | 12 + .../Union/AliasedObjectUnionTest.cs | 46 ++++ .../Union/IUnionClient.cs | 6 + .../Union/Types/LeafObjectA.cs | 31 +++ .../Union/Types/LeafObjectB.cs | 28 ++ .../Union/UnionClient.cs | 82 ++++++ .../Snippets/Example10.cs | 15 +- .../Snippets/Example11.cs | 15 +- .../Snippets/Example12.cs | 4 +- .../Snippets/Example13.cs | 22 ++ .../with-undiscriminated-unions/reference.md | 42 +++ .../with-undiscriminated-unions/snippet.json | 12 + .../Union/AliasedObjectUnionTest.cs | 46 ++++ .../Union/IUnionClient.cs | 6 + .../Union/Types/AliasedObjectUnion.cs | 256 ++++++++++++++++++ .../Union/Types/LeafObjectA.cs | 31 +++ .../Union/Types/LeafObjectB.cs | 28 ++ .../Union/UnionClient.cs | 82 ++++++ 23 files changed, 826 insertions(+), 36 deletions(-) create mode 100644 seed/csharp-sdk/undiscriminated-unions/no-custom-config/Snippets/Example13.cs create mode 100644 seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions.Test/Unit/MockServer/Union/AliasedObjectUnionTest.cs create mode 100644 seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions/Union/Types/LeafObjectA.cs create mode 100644 seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions/Union/Types/LeafObjectB.cs create mode 100644 seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/Snippets/Example13.cs create mode 100644 seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions.Test/Unit/MockServer/Union/AliasedObjectUnionTest.cs create mode 100644 seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/Types/AliasedObjectUnion.cs create mode 100644 seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/Types/LeafObjectA.cs create mode 100644 seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/Types/LeafObjectB.cs diff --git a/seed/csharp-sdk/undiscriminated-unions/no-custom-config/Snippets/Example10.cs b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/Snippets/Example10.cs index 299c6615db29..fc0eacc7b664 100644 --- a/seed/csharp-sdk/undiscriminated-unions/no-custom-config/Snippets/Example10.cs +++ b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/Snippets/Example10.cs @@ -9,17 +9,10 @@ public async Task Example10() { } ); - await client.Union.GetWithBasePropertiesAsync( - new NamedMetadata { - Name = "name", - Value = new Dictionary(){ - ["value"] = new Dictionary() - { - ["key"] = "value", - } - , - } - + await client.Union.AliasedObjectUnionAsync( + new LeafObjectA { + OnlyInA = "onlyInA", + SharedNumber = 1 } ); } diff --git a/seed/csharp-sdk/undiscriminated-unions/no-custom-config/Snippets/Example11.cs b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/Snippets/Example11.cs index f8d752bacb0e..a03f3add5644 100644 --- a/seed/csharp-sdk/undiscriminated-unions/no-custom-config/Snippets/Example11.cs +++ b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/Snippets/Example11.cs @@ -9,12 +9,17 @@ public async Task Example11() { } ); - await client.Union.TestCamelCasePropertiesAsync( - new PaymentRequest { - PaymentMethod = new TokenizeCard { - Method = "card", - CardNumber = "1234567890123456" + await client.Union.GetWithBasePropertiesAsync( + new NamedMetadata { + Name = "name", + Value = new Dictionary(){ + ["value"] = new Dictionary() + { + ["key"] = "value", + } + , } + } ); } diff --git a/seed/csharp-sdk/undiscriminated-unions/no-custom-config/Snippets/Example12.cs b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/Snippets/Example12.cs index fa85785e7bf2..3bd00fcc47b7 100644 --- a/seed/csharp-sdk/undiscriminated-unions/no-custom-config/Snippets/Example12.cs +++ b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/Snippets/Example12.cs @@ -12,8 +12,8 @@ public async Task Example12() { await client.Union.TestCamelCasePropertiesAsync( new PaymentRequest { PaymentMethod = new TokenizeCard { - Method = "method", - CardNumber = "cardNumber" + Method = "card", + CardNumber = "1234567890123456" } } ); diff --git a/seed/csharp-sdk/undiscriminated-unions/no-custom-config/Snippets/Example13.cs b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/Snippets/Example13.cs new file mode 100644 index 000000000000..c9c980d4a9cd --- /dev/null +++ b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/Snippets/Example13.cs @@ -0,0 +1,22 @@ +using SeedUndiscriminatedUnions; + +public partial class Examples +{ + public async Task Example13() { + var client = new SeedUndiscriminatedUnionsClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.Union.TestCamelCasePropertiesAsync( + new PaymentRequest { + PaymentMethod = new TokenizeCard { + Method = "method", + CardNumber = "cardNumber" + } + } + ); + } + +} diff --git a/seed/csharp-sdk/undiscriminated-unions/no-custom-config/reference.md b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/reference.md index 05da486fe2c9..5e77b1765a02 100644 --- a/seed/csharp-sdk/undiscriminated-unions/no-custom-config/reference.md +++ b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/reference.md @@ -280,6 +280,48 @@ await client.Union.NestedObjectUnionsAsync("string"); + + + + +
client.Union.AliasedObjectUnionAsync(OneOf<LeafObjectA, LeafObjectB> { ... }) -> WithRawResponseTask<string> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Union.AliasedObjectUnionAsync( + new LeafObjectA { OnlyInA = "onlyInA", SharedNumber = 1 } +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `OneOf` + +
+
+
+
+ +
diff --git a/seed/csharp-sdk/undiscriminated-unions/no-custom-config/snippet.json b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/snippet.json index 6e6e37469b0a..0f7b254c6468 100644 --- a/seed/csharp-sdk/undiscriminated-unions/no-custom-config/snippet.json +++ b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/snippet.json @@ -85,6 +85,18 @@ "client": "using SeedUndiscriminatedUnions;\n\nvar client = new SeedUndiscriminatedUnionsClient();\nawait client.Union.NestedObjectUnionsAsync(\"string\");\n" } }, + { + "example_identifier": null, + "id": { + "path": "/aliased-object", + "method": "POST", + "identifier_override": "endpoint_union.aliasedObjectUnion" + }, + "snippet": { + "type": "csharp", + "client": "using SeedUndiscriminatedUnions;\n\nvar client = new SeedUndiscriminatedUnionsClient();\nawait client.Union.AliasedObjectUnionAsync(\n new LeafObjectA { OnlyInA = \"onlyInA\", SharedNumber = 1 }\n);\n" + } + }, { "example_identifier": null, "id": { diff --git a/seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions.Test/Unit/MockServer/Union/AliasedObjectUnionTest.cs b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions.Test/Unit/MockServer/Union/AliasedObjectUnionTest.cs new file mode 100644 index 000000000000..20deadeaf920 --- /dev/null +++ b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions.Test/Unit/MockServer/Union/AliasedObjectUnionTest.cs @@ -0,0 +1,46 @@ +using NUnit.Framework; +using SeedUndiscriminatedUnions; +using SeedUndiscriminatedUnions.Test.Unit.MockServer; +using SeedUndiscriminatedUnions.Test.Utils; + +namespace SeedUndiscriminatedUnions.Test.Unit.MockServer.Union; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class AliasedObjectUnionTest : BaseMockServerTest +{ + [NUnit.Framework.Test] + public async Task MockServerTest() + { + const string requestJson = """ + { + "onlyInA": "onlyInA", + "sharedNumber": 1 + } + """; + + const string mockResponse = """ + "string" + """; + + Server + .Given( + WireMock + .RequestBuilders.Request.Create() + .WithPath("/aliased-object") + .UsingPost() + .WithBodyAsJson(requestJson) + ) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.Union.AliasedObjectUnionAsync( + new LeafObjectA { OnlyInA = "onlyInA", SharedNumber = 1 } + ); + JsonAssert.AreEqual(response, mockResponse); + } +} diff --git a/seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions/Union/IUnionClient.cs b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions/Union/IUnionClient.cs index 3589156a6071..a69a750aae26 100644 --- a/seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions/Union/IUnionClient.cs +++ b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions/Union/IUnionClient.cs @@ -72,6 +72,12 @@ WithRawResponseTask NestedObjectUnionsAsync( CancellationToken cancellationToken = default ); + WithRawResponseTask AliasedObjectUnionAsync( + OneOf request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ); + WithRawResponseTask< OneOf?> > GetWithBasePropertiesAsync( diff --git a/seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions/Union/Types/LeafObjectA.cs b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions/Union/Types/LeafObjectA.cs new file mode 100644 index 000000000000..d2dc9b100f4d --- /dev/null +++ b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions/Union/Types/LeafObjectA.cs @@ -0,0 +1,31 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedUndiscriminatedUnions.Core; + +namespace SeedUndiscriminatedUnions; + +[Serializable] +public record LeafObjectA : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("onlyInA")] + public required string OnlyInA { get; set; } + + [JsonPropertyName("sharedNumber")] + public required int SharedNumber { 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/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions/Union/Types/LeafObjectB.cs b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions/Union/Types/LeafObjectB.cs new file mode 100644 index 000000000000..9f3fe6992b70 --- /dev/null +++ b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions/Union/Types/LeafObjectB.cs @@ -0,0 +1,28 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedUndiscriminatedUnions.Core; + +namespace SeedUndiscriminatedUnions; + +[Serializable] +public record LeafObjectB : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("onlyInB")] + public required string OnlyInB { 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/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions/Union/UnionClient.cs b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions/Union/UnionClient.cs index 7e491da6f643..e0f467966efd 100644 --- a/seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions/Union/UnionClient.cs +++ b/seed/csharp-sdk/undiscriminated-unions/no-custom-config/src/SeedUndiscriminatedUnions/Union/UnionClient.cs @@ -528,6 +528,72 @@ private async Task> NestedObjectUnionsAsyncCore( } } + private async Task> AliasedObjectUnionAsyncCore( + OneOf request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + var _headers = await new SeedUndiscriminatedUnions.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.Post, + Path = "/aliased-object", + Body = request, + Headers = _headers, + Options = options, + }, + cancellationToken + ) + .ConfigureAwait(false); + if (response.StatusCode is >= 200 and < 400) + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + try + { + var responseData = JsonUtils.Deserialize(responseBody)!; + return new WithRawResponse() + { + Data = responseData, + RawResponse = new RawResponse() + { + StatusCode = response.Raw.StatusCode, + Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), + Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), + }, + }; + } + catch (JsonException e) + { + throw new SeedUndiscriminatedUnionsApiException( + "Failed to deserialize response", + response.StatusCode, + responseBody, + e + ); + } + } + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + throw new SeedUndiscriminatedUnionsApiException( + $"Error with status code {response.StatusCode}", + response.StatusCode, + responseBody + ); + } + } + private async Task< WithRawResponse?>> > GetWithBasePropertiesAsyncCore( @@ -812,6 +878,22 @@ public WithRawResponseTask NestedObjectUnionsAsync( ); } + /// + /// await client.Union.AliasedObjectUnionAsync( + /// new LeafObjectA { OnlyInA = "onlyInA", SharedNumber = 1 } + /// ); + /// + public WithRawResponseTask AliasedObjectUnionAsync( + OneOf request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + return new WithRawResponseTask( + AliasedObjectUnionAsyncCore(request, options, cancellationToken) + ); + } + /// /// await client.Union.GetWithBasePropertiesAsync( /// new NamedMetadata diff --git a/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/Snippets/Example10.cs b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/Snippets/Example10.cs index 299c6615db29..fc0eacc7b664 100644 --- a/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/Snippets/Example10.cs +++ b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/Snippets/Example10.cs @@ -9,17 +9,10 @@ public async Task Example10() { } ); - await client.Union.GetWithBasePropertiesAsync( - new NamedMetadata { - Name = "name", - Value = new Dictionary(){ - ["value"] = new Dictionary() - { - ["key"] = "value", - } - , - } - + await client.Union.AliasedObjectUnionAsync( + new LeafObjectA { + OnlyInA = "onlyInA", + SharedNumber = 1 } ); } diff --git a/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/Snippets/Example11.cs b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/Snippets/Example11.cs index f8d752bacb0e..a03f3add5644 100644 --- a/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/Snippets/Example11.cs +++ b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/Snippets/Example11.cs @@ -9,12 +9,17 @@ public async Task Example11() { } ); - await client.Union.TestCamelCasePropertiesAsync( - new PaymentRequest { - PaymentMethod = new TokenizeCard { - Method = "card", - CardNumber = "1234567890123456" + await client.Union.GetWithBasePropertiesAsync( + new NamedMetadata { + Name = "name", + Value = new Dictionary(){ + ["value"] = new Dictionary() + { + ["key"] = "value", + } + , } + } ); } diff --git a/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/Snippets/Example12.cs b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/Snippets/Example12.cs index fa85785e7bf2..3bd00fcc47b7 100644 --- a/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/Snippets/Example12.cs +++ b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/Snippets/Example12.cs @@ -12,8 +12,8 @@ public async Task Example12() { await client.Union.TestCamelCasePropertiesAsync( new PaymentRequest { PaymentMethod = new TokenizeCard { - Method = "method", - CardNumber = "cardNumber" + Method = "card", + CardNumber = "1234567890123456" } } ); diff --git a/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/Snippets/Example13.cs b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/Snippets/Example13.cs new file mode 100644 index 000000000000..c9c980d4a9cd --- /dev/null +++ b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/Snippets/Example13.cs @@ -0,0 +1,22 @@ +using SeedUndiscriminatedUnions; + +public partial class Examples +{ + public async Task Example13() { + var client = new SeedUndiscriminatedUnionsClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.Union.TestCamelCasePropertiesAsync( + new PaymentRequest { + PaymentMethod = new TokenizeCard { + Method = "method", + CardNumber = "cardNumber" + } + } + ); + } + +} diff --git a/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/reference.md b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/reference.md index 81681c3fa00c..b2055050a8cd 100644 --- a/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/reference.md +++ b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/reference.md @@ -280,6 +280,48 @@ await client.Union.NestedObjectUnionsAsync("string"); + + + + +
client.Union.AliasedObjectUnionAsync(AliasedObjectUnion { ... }) -> WithRawResponseTask<string> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Union.AliasedObjectUnionAsync( + new LeafObjectA { OnlyInA = "onlyInA", SharedNumber = 1 } +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `AliasedObjectUnion` + +
+
+
+
+ +
diff --git a/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/snippet.json b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/snippet.json index 6e6e37469b0a..0f7b254c6468 100644 --- a/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/snippet.json +++ b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/snippet.json @@ -85,6 +85,18 @@ "client": "using SeedUndiscriminatedUnions;\n\nvar client = new SeedUndiscriminatedUnionsClient();\nawait client.Union.NestedObjectUnionsAsync(\"string\");\n" } }, + { + "example_identifier": null, + "id": { + "path": "/aliased-object", + "method": "POST", + "identifier_override": "endpoint_union.aliasedObjectUnion" + }, + "snippet": { + "type": "csharp", + "client": "using SeedUndiscriminatedUnions;\n\nvar client = new SeedUndiscriminatedUnionsClient();\nawait client.Union.AliasedObjectUnionAsync(\n new LeafObjectA { OnlyInA = \"onlyInA\", SharedNumber = 1 }\n);\n" + } + }, { "example_identifier": null, "id": { diff --git a/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions.Test/Unit/MockServer/Union/AliasedObjectUnionTest.cs b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions.Test/Unit/MockServer/Union/AliasedObjectUnionTest.cs new file mode 100644 index 000000000000..20deadeaf920 --- /dev/null +++ b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions.Test/Unit/MockServer/Union/AliasedObjectUnionTest.cs @@ -0,0 +1,46 @@ +using NUnit.Framework; +using SeedUndiscriminatedUnions; +using SeedUndiscriminatedUnions.Test.Unit.MockServer; +using SeedUndiscriminatedUnions.Test.Utils; + +namespace SeedUndiscriminatedUnions.Test.Unit.MockServer.Union; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class AliasedObjectUnionTest : BaseMockServerTest +{ + [NUnit.Framework.Test] + public async Task MockServerTest() + { + const string requestJson = """ + { + "onlyInA": "onlyInA", + "sharedNumber": 1 + } + """; + + const string mockResponse = """ + "string" + """; + + Server + .Given( + WireMock + .RequestBuilders.Request.Create() + .WithPath("/aliased-object") + .UsingPost() + .WithBodyAsJson(requestJson) + ) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.Union.AliasedObjectUnionAsync( + new LeafObjectA { OnlyInA = "onlyInA", SharedNumber = 1 } + ); + JsonAssert.AreEqual(response, mockResponse); + } +} diff --git a/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/IUnionClient.cs b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/IUnionClient.cs index d7ea040f7a55..e694ec53a1bc 100644 --- a/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/IUnionClient.cs +++ b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/IUnionClient.cs @@ -43,6 +43,12 @@ WithRawResponseTask NestedObjectUnionsAsync( CancellationToken cancellationToken = default ); + WithRawResponseTask AliasedObjectUnionAsync( + AliasedObjectUnion request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ); + WithRawResponseTask GetWithBasePropertiesAsync( UnionWithBaseProperties request, RequestOptions? options = null, diff --git a/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/Types/AliasedObjectUnion.cs b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/Types/AliasedObjectUnion.cs new file mode 100644 index 000000000000..dc9ed4ee5716 --- /dev/null +++ b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/Types/AliasedObjectUnion.cs @@ -0,0 +1,256 @@ +// ReSharper disable NullableWarningSuppressionIsUsed +// ReSharper disable InconsistentNaming + +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedUndiscriminatedUnions.Core; + +namespace SeedUndiscriminatedUnions; + +/// +/// Undiscriminated union whose members are named aliases of object types +/// (including an alias-of-alias). Required keys are disjoint, so a correct +/// deserializer must emit containsKey() guards for each alias variant. +/// +[JsonConverter(typeof(AliasedObjectUnion.JsonConverter))] +[Serializable] +public class AliasedObjectUnion +{ + private AliasedObjectUnion(string type, object? value) + { + Type = type; + Value = value; + } + + /// + /// Type discriminator + /// + [JsonIgnore] + public string Type { get; internal set; } + + /// + /// Union value + /// + [JsonIgnore] + public object? Value { get; internal set; } + + /// + /// Factory method to create a union from a LeafObjectA value. + /// + public static AliasedObjectUnion FromAliasedLeafA(LeafObjectA value) => + new("aliasedLeafA", value); + + /// + /// Factory method to create a union from a LeafObjectB value. + /// + public static AliasedObjectUnion FromAliasedLeafB(LeafObjectB value) => + new("aliasedLeafB", value); + + /// + /// Returns true if is "aliasedLeafA" + /// + public bool IsAliasedLeafA() => Type == "aliasedLeafA"; + + /// + /// Returns true if is "aliasedLeafB" + /// + public bool IsAliasedLeafB() => Type == "aliasedLeafB"; + + /// + /// Returns the value as a if is 'aliasedLeafA', otherwise throws an exception. + /// + /// Thrown when is not 'aliasedLeafA'. + public LeafObjectA AsAliasedLeafA() => + IsAliasedLeafA() + ? (LeafObjectA)Value! + : throw new SeedUndiscriminatedUnionsException("Union type is not 'aliasedLeafA'"); + + /// + /// Returns the value as a if is 'aliasedLeafB', otherwise throws an exception. + /// + /// Thrown when is not 'aliasedLeafB'. + public LeafObjectB AsAliasedLeafB() => + IsAliasedLeafB() + ? (LeafObjectB)Value! + : throw new SeedUndiscriminatedUnionsException("Union type is not 'aliasedLeafB'"); + + /// + /// Attempts to cast the value to a and returns true if successful. + /// + public bool TryGetAliasedLeafA(out LeafObjectA? value) + { + if (Type == "aliasedLeafA") + { + value = (LeafObjectA)Value!; + return true; + } + value = null; + return false; + } + + /// + /// Attempts to cast the value to a and returns true if successful. + /// + public bool TryGetAliasedLeafB(out LeafObjectB? value) + { + if (Type == "aliasedLeafB") + { + value = (LeafObjectB)Value!; + return true; + } + value = null; + return false; + } + + public T Match(Func onAliasedLeafA, Func onAliasedLeafB) + { + return Type switch + { + "aliasedLeafA" => onAliasedLeafA(AsAliasedLeafA()), + "aliasedLeafB" => onAliasedLeafB(AsAliasedLeafB()), + _ => throw new SeedUndiscriminatedUnionsException($"Unknown union type: {Type}"), + }; + } + + public void Visit(Action onAliasedLeafA, Action onAliasedLeafB) + { + switch (Type) + { + case "aliasedLeafA": + onAliasedLeafA(AsAliasedLeafA()); + break; + case "aliasedLeafB": + onAliasedLeafB(AsAliasedLeafB()); + break; + default: + throw new SeedUndiscriminatedUnionsException($"Unknown union type: {Type}"); + } + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Type.GetHashCode(); + if (Value != null) + { + hashCode = (hashCode * 397) ^ Value.GetHashCode(); + } + return hashCode; + } + } + + public override bool Equals(object? obj) + { + if (obj is null) + return false; + if (ReferenceEquals(this, obj)) + return true; + if (obj is not AliasedObjectUnion other) + return false; + + // Compare type discriminators + if (Type != other.Type) + return false; + + // Compare values using EqualityComparer for deep comparison + return System.Collections.Generic.EqualityComparer.Default.Equals( + Value, + other.Value + ); + } + + public override string ToString() => JsonUtils.Serialize(this); + + public static implicit operator AliasedObjectUnion(LeafObjectA value) => + new("aliasedLeafA", value); + + public static implicit operator AliasedObjectUnion(LeafObjectB value) => + new("aliasedLeafB", value); + + [Serializable] + internal sealed class JsonConverter : JsonConverter + { + public override AliasedObjectUnion? Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + if (reader.TokenType == JsonTokenType.StartObject) + { + var document = JsonDocument.ParseValue(ref reader); + + var types = new (string Key, System.Type Type)[] + { + ("aliasedLeafA", typeof(LeafObjectA)), + ("aliasedLeafB", typeof(LeafObjectB)), + }; + + foreach (var (key, type) in types) + { + try + { + var value = document.Deserialize(type, options); + if (value != null) + { + AliasedObjectUnion result = new(key, value); + return result; + } + } + catch (JsonException) + { + // Try next type; + } + } + } + + throw new JsonException( + $"Cannot deserialize JSON token {reader.TokenType} into AliasedObjectUnion" + ); + } + + public override void Write( + Utf8JsonWriter writer, + AliasedObjectUnion value, + JsonSerializerOptions options + ) + { + if (value == null) + { + writer.WriteNullValue(); + return; + } + + value.Visit( + obj => JsonSerializer.Serialize(writer, obj, options), + obj => JsonSerializer.Serialize(writer, obj, options) + ); + } + + public override AliasedObjectUnion ReadAsPropertyName( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = reader.GetString()!; + AliasedObjectUnion result = new("string", stringValue); + return result; + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + AliasedObjectUnion value, + JsonSerializerOptions options + ) + { + writer.WritePropertyName(value.Value?.ToString() ?? "null"); + } + } +} diff --git a/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/Types/LeafObjectA.cs b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/Types/LeafObjectA.cs new file mode 100644 index 000000000000..d2dc9b100f4d --- /dev/null +++ b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/Types/LeafObjectA.cs @@ -0,0 +1,31 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedUndiscriminatedUnions.Core; + +namespace SeedUndiscriminatedUnions; + +[Serializable] +public record LeafObjectA : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("onlyInA")] + public required string OnlyInA { get; set; } + + [JsonPropertyName("sharedNumber")] + public required int SharedNumber { 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/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/Types/LeafObjectB.cs b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/Types/LeafObjectB.cs new file mode 100644 index 000000000000..9f3fe6992b70 --- /dev/null +++ b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/Types/LeafObjectB.cs @@ -0,0 +1,28 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedUndiscriminatedUnions.Core; + +namespace SeedUndiscriminatedUnions; + +[Serializable] +public record LeafObjectB : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("onlyInB")] + public required string OnlyInB { 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/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/UnionClient.cs b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/UnionClient.cs index cf42f9309f99..db428f9602bd 100644 --- a/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/UnionClient.cs +++ b/seed/csharp-sdk/undiscriminated-unions/with-undiscriminated-unions/src/SeedUndiscriminatedUnions/Union/UnionClient.cs @@ -472,6 +472,72 @@ private async Task> NestedObjectUnionsAsyncCore( } } + private async Task> AliasedObjectUnionAsyncCore( + AliasedObjectUnion request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + var _headers = await new SeedUndiscriminatedUnions.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.Post, + Path = "/aliased-object", + Body = request, + Headers = _headers, + Options = options, + }, + cancellationToken + ) + .ConfigureAwait(false); + if (response.StatusCode is >= 200 and < 400) + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + try + { + var responseData = JsonUtils.Deserialize(responseBody)!; + return new WithRawResponse() + { + Data = responseData, + RawResponse = new RawResponse() + { + StatusCode = response.Raw.StatusCode, + Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), + Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), + }, + }; + } + catch (JsonException e) + { + throw new SeedUndiscriminatedUnionsApiException( + "Failed to deserialize response", + response.StatusCode, + responseBody, + e + ); + } + } + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + throw new SeedUndiscriminatedUnionsApiException( + $"Error with status code {response.StatusCode}", + response.StatusCode, + responseBody + ); + } + } + private async Task> GetWithBasePropertiesAsyncCore( UnionWithBaseProperties request, RequestOptions? options = null, @@ -716,6 +782,22 @@ public WithRawResponseTask NestedObjectUnionsAsync( ); } + /// + /// await client.Union.AliasedObjectUnionAsync( + /// new LeafObjectA { OnlyInA = "onlyInA", SharedNumber = 1 } + /// ); + /// + public WithRawResponseTask AliasedObjectUnionAsync( + AliasedObjectUnion request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + return new WithRawResponseTask( + AliasedObjectUnionAsyncCore(request, options, cancellationToken) + ); + } + /// /// await client.Union.GetWithBasePropertiesAsync( /// new NamedMetadata