diff --git a/fern/pages/changelogs/csharp-sdk/2025-03-31.mdx b/fern/pages/changelogs/csharp-sdk/2025-03-31.mdx
new file mode 100644
index 00000000000..89f65b74bc1
--- /dev/null
+++ b/fern/pages/changelogs/csharp-sdk/2025-03-31.mdx
@@ -0,0 +1,5 @@
+## 1.15.12
+**`(fix):`** Fallback from `init` to `set` on .NET Framework & .NET Standard 2.0 for public and protected properties.
+This ensures the properties can be set on older TFMs without compilation errors.
+
+
diff --git a/generators/csharp/base/src/asIs/FileParameter.Template.cs b/generators/csharp/base/src/asIs/FileParameter.Template.cs
index 5756f8c2499..dc6eda5d42b 100644
--- a/generators/csharp/base/src/asIs/FileParameter.Template.cs
+++ b/generators/csharp/base/src/asIs/FileParameter.Template.cs
@@ -14,17 +14,17 @@ public record FileParameter :
///
/// The name of the file to be uploaded.
///
- public string? FileName { get; init; }
+ public string? FileName { get; set; }
///
/// The content type of the file to be uploaded.
///
- public string? ContentType { get; init; }
+ public string? ContentType { get; set; }
///
/// The content of the file to be uploaded.
///
- public required Stream Stream { get; init; }
+ public required Stream Stream { get; set; }
///
public void Dispose()
diff --git a/generators/csharp/base/src/asIs/GrpcRequestOptions.Template.cs b/generators/csharp/base/src/asIs/GrpcRequestOptions.Template.cs
index 2055b3aeb35..0103fc997dd 100644
--- a/generators/csharp/base/src/asIs/GrpcRequestOptions.Template.cs
+++ b/generators/csharp/base/src/asIs/GrpcRequestOptions.Template.cs
@@ -8,25 +8,60 @@ public partial class GrpcRequestOptions
///
/// The maximum number of retry attempts.
///
- public int? MaxRetries { get; init; }
+ public int? MaxRetries {
+ get;
+#if NET5_0_OR_GREATER
+ init;
+#else
+ set;
+#endif
+ }
///
/// The timeout for the request.
///
- public TimeSpan? Timeout { get; init; }
+ public TimeSpan? Timeout {
+ get;
+#if NET5_0_OR_GREATER
+ init;
+#else
+ set;
+#endif
+ }
///
/// Options for write operations.
///
- public WriteOptions? WriteOptions { get; init; }
+ public WriteOptions? WriteOptions {
+ get;
+#if NET5_0_OR_GREATER
+ init;
+#else
+ set;
+#endif
+ }
///
/// Client-side call credentials. Provide authorization with per-call granularity.
///
- public CallCredentials? CallCredentials { get; init; }
+ public CallCredentials? CallCredentials {
+ get;
+#if NET5_0_OR_GREATER
+ init;
+#else
+ set;
+#endif
+ }
///
/// Headers to be sent with this particular request.
///
- internal Headers Headers { get; init; } = new();
+ internal Headers Headers {
+ get;
+#if NET5_0_OR_GREATER
+ init;
+#else
+ set;
+#endif
+ } = new();
}
diff --git a/generators/csharp/codegen/src/ast/Field.ts b/generators/csharp/codegen/src/ast/Field.ts
index 237721d7fc2..0c370f7dc98 100644
--- a/generators/csharp/codegen/src/ast/Field.ts
+++ b/generators/csharp/codegen/src/ast/Field.ts
@@ -208,10 +208,27 @@ export class Field extends AstNode {
writer.write("get; ");
}
if (this.init) {
- if (!this.hasSameAccess(this.init)) {
- writer.write(`${this.init} `);
+ // if init is accessible to the end user (public, or protected through inheritance),
+ // we should not expose init to the user on .NET Framework
+ const needsFallback =
+ (this.access === Access.Public || this.access === Access.Protected) &&
+ (this.init === true || this.init === Access.Public || this.init === Access.Protected);
+ if (needsFallback) {
+ writer.writeLine();
+ writer.writeNoIndent("#if NET5_0_OR_GREATER\n");
+ if (!this.hasSameAccess(this.init)) {
+ writer.write(`${this.init} `);
+ }
+ writer.writeTextStatement("init");
+ writer.writeNoIndent("#else\n");
+ writer.writeTextStatement("set");
+ writer.writeNoIndent("#endif\n");
+ } else {
+ if (!this.hasSameAccess(this.init)) {
+ writer.write(`${this.init} `);
+ }
+ writer.write("init; ");
}
- writer.write("init; ");
}
if (this.set) {
if (!this.hasSameAccess(this.set)) {
diff --git a/generators/csharp/sdk/src/root-client/RootClientGenerator.ts b/generators/csharp/sdk/src/root-client/RootClientGenerator.ts
index cc1a155006e..64ebdf260b4 100644
--- a/generators/csharp/sdk/src/root-client/RootClientGenerator.ts
+++ b/generators/csharp/sdk/src/root-client/RootClientGenerator.ts
@@ -111,7 +111,6 @@ export class RootClientGenerator extends FileGenerator IR parser.
-
+ the new OpenAPI -> IR parser.
+
type: fix
irVersion: 57
version: 0.57.5
@@ -66,7 +66,7 @@
```
Each namespace creates a separate package in the generated IR, allowing for clear separation between different versions or components of your API.
-
+
type: feat
irVersion: 57
version: 0.57.4
@@ -80,9 +80,9 @@
- changelogEntry:
- summary: |
- Add support for parsing `type: enum` in OpenAPI schemas. This allows for proper conversion of enum types that use the
+ Add support for parsing `type: enum` in OpenAPI schemas. This allows for proper conversion of enum types that use the
non-standard `type: enum` format instead of the standard `type: string` with `enum` values.
-
+
type: feat
irVersion: 57
version: 0.57.2
@@ -97,7 +97,7 @@
You are an AI assistant.
Only respond to questions using information from the documents.
Include markdown footnotes to the sources of your information.
-
+
type: feat
irVersion: 57
version: 0.57.1
@@ -203,8 +203,8 @@
- changelogEntry:
- summary: |
- The `fern init --openapi` command now references the OpenAPI file in
- place using the new `specs` syntax in generators.yml instead of copying the file into the
+ The `fern init --openapi` command now references the OpenAPI file in
+ place using the new `specs` syntax in generators.yml instead of copying the file into the
Fern directory. This makes it easier to maintain the source OpenAPI file separately from the Fern configuration.
type: fix
irVersion: 57
@@ -327,8 +327,8 @@
- changelogEntry:
- summary: |
- Updated the CLI to gracefully handle unsupported security schemes (e.g. cookie-based auth) by skipping them
- instead of throwing an error. This allows the CLI to continue processing the rest of the API definition even when
+ Updated the CLI to gracefully handle unsupported security schemes (e.g. cookie-based auth) by skipping them
+ instead of throwing an error. This allows the CLI to continue processing the rest of the API definition even when
it encounters security schemes it cannot convert.
type: fix
irVersion: 56
@@ -373,14 +373,14 @@
type: fix
irVersion: 56
version: 0.56.0
-
+
- changelogEntry:
- summary: |
- Hidden sections are now removed from the docs sitemap.
+ Hidden sections are now removed from the docs sitemap.
type: fix
irVersion: 56
version: 0.56.0-rc6
-
+
- changelogEntry:
- summary: |
Fixed duplicate validation messages in docs validation by deduplicating violations
diff --git a/seed/csharp-model/accept-header/src/SeedAccept/Core/Public/FileParameter.cs b/seed/csharp-model/accept-header/src/SeedAccept/Core/Public/FileParameter.cs
index 5af2475c4dd..5615002ed06 100644
--- a/seed/csharp-model/accept-header/src/SeedAccept/Core/Public/FileParameter.cs
+++ b/seed/csharp-model/accept-header/src/SeedAccept/Core/Public/FileParameter.cs
@@ -13,17 +13,17 @@ public record FileParameter : IDisposable
///
/// The name of the file to be uploaded.
///
- public string? FileName { get; init; }
+ public string? FileName { get; set; }
///
/// The content type of the file to be uploaded.
///
- public string? ContentType { get; init; }
+ public string? ContentType { get; set; }
///
/// The content of the file to be uploaded.
///
- public required Stream Stream { get; init; }
+ public required Stream Stream { get; set; }
///
public void Dispose()
diff --git a/seed/csharp-model/alias-extends/src/SeedAliasExtends/Core/Public/FileParameter.cs b/seed/csharp-model/alias-extends/src/SeedAliasExtends/Core/Public/FileParameter.cs
index 1b9d68d8ba1..d25743e74d4 100644
--- a/seed/csharp-model/alias-extends/src/SeedAliasExtends/Core/Public/FileParameter.cs
+++ b/seed/csharp-model/alias-extends/src/SeedAliasExtends/Core/Public/FileParameter.cs
@@ -13,17 +13,17 @@ public record FileParameter : IDisposable
///
/// The name of the file to be uploaded.
///
- public string? FileName { get; init; }
+ public string? FileName { get; set; }
///
/// The content type of the file to be uploaded.
///
- public string? ContentType { get; init; }
+ public string? ContentType { get; set; }
///
/// The content of the file to be uploaded.
///
- public required Stream Stream { get; init; }
+ public required Stream Stream { get; set; }
///
public void Dispose()
diff --git a/seed/csharp-model/alias/src/SeedAlias/Core/Public/FileParameter.cs b/seed/csharp-model/alias/src/SeedAlias/Core/Public/FileParameter.cs
index 58078ca8750..f43f769d2ad 100644
--- a/seed/csharp-model/alias/src/SeedAlias/Core/Public/FileParameter.cs
+++ b/seed/csharp-model/alias/src/SeedAlias/Core/Public/FileParameter.cs
@@ -13,17 +13,17 @@ public record FileParameter : IDisposable
///
/// The name of the file to be uploaded.
///
- public string? FileName { get; init; }
+ public string? FileName { get; set; }
///
/// The content type of the file to be uploaded.
///
- public string? ContentType { get; init; }
+ public string? ContentType { get; set; }
///
/// The content of the file to be uploaded.
///
- public required Stream Stream { get; init; }
+ public required Stream Stream { get; set; }
///
public void Dispose()
diff --git a/seed/csharp-model/any-auth/src/SeedAnyAuth/Core/Public/FileParameter.cs b/seed/csharp-model/any-auth/src/SeedAnyAuth/Core/Public/FileParameter.cs
index bbe5574a4e8..3ec65fe0e70 100644
--- a/seed/csharp-model/any-auth/src/SeedAnyAuth/Core/Public/FileParameter.cs
+++ b/seed/csharp-model/any-auth/src/SeedAnyAuth/Core/Public/FileParameter.cs
@@ -13,17 +13,17 @@ public record FileParameter : IDisposable
///
/// The name of the file to be uploaded.
///
- public string? FileName { get; init; }
+ public string? FileName { get; set; }
///
/// The content type of the file to be uploaded.
///
- public string? ContentType { get; init; }
+ public string? ContentType { get; set; }
///
/// The content of the file to be uploaded.
///
- public required Stream Stream { get; init; }
+ public required Stream Stream { get; set; }
///
public void Dispose()
diff --git a/seed/csharp-model/api-wide-base-path/src/SeedApiWideBasePath/Core/Public/FileParameter.cs b/seed/csharp-model/api-wide-base-path/src/SeedApiWideBasePath/Core/Public/FileParameter.cs
index ab236cf8f95..2c6034398e8 100644
--- a/seed/csharp-model/api-wide-base-path/src/SeedApiWideBasePath/Core/Public/FileParameter.cs
+++ b/seed/csharp-model/api-wide-base-path/src/SeedApiWideBasePath/Core/Public/FileParameter.cs
@@ -13,17 +13,17 @@ public record FileParameter : IDisposable
///
/// The name of the file to be uploaded.
///
- public string? FileName { get; init; }
+ public string? FileName { get; set; }
///
/// The content type of the file to be uploaded.
///
- public string? ContentType { get; init; }
+ public string? ContentType { get; set; }
///
/// The content of the file to be uploaded.
///
- public required Stream Stream { get; init; }
+ public required Stream Stream { get; set; }
///
public void Dispose()
diff --git a/seed/csharp-model/audiences/src/SeedAudiences/Core/Public/FileParameter.cs b/seed/csharp-model/audiences/src/SeedAudiences/Core/Public/FileParameter.cs
index ac20013e45d..c9b7cd268e5 100644
--- a/seed/csharp-model/audiences/src/SeedAudiences/Core/Public/FileParameter.cs
+++ b/seed/csharp-model/audiences/src/SeedAudiences/Core/Public/FileParameter.cs
@@ -13,17 +13,17 @@ public record FileParameter : IDisposable
///
/// The name of the file to be uploaded.
///
- public string? FileName { get; init; }
+ public string? FileName { get; set; }
///
/// The content type of the file to be uploaded.
///
- public string? ContentType { get; init; }
+ public string? ContentType { get; set; }
///
/// The content of the file to be uploaded.
///
- public required Stream Stream { get; init; }
+ public required Stream Stream { get; set; }
///
public void Dispose()
diff --git a/seed/csharp-model/auth-environment-variables/src/SeedAuthEnvironmentVariables/Core/Public/FileParameter.cs b/seed/csharp-model/auth-environment-variables/src/SeedAuthEnvironmentVariables/Core/Public/FileParameter.cs
index 55824949e95..c17c8ce2a11 100644
--- a/seed/csharp-model/auth-environment-variables/src/SeedAuthEnvironmentVariables/Core/Public/FileParameter.cs
+++ b/seed/csharp-model/auth-environment-variables/src/SeedAuthEnvironmentVariables/Core/Public/FileParameter.cs
@@ -13,17 +13,17 @@ public record FileParameter : IDisposable
///
/// The name of the file to be uploaded.
///
- public string? FileName { get; init; }
+ public string? FileName { get; set; }
///
/// The content type of the file to be uploaded.
///
- public string? ContentType { get; init; }
+ public string? ContentType { get; set; }
///
/// The content of the file to be uploaded.
///
- public required Stream Stream { get; init; }
+ public required Stream Stream { get; set; }
///
public void Dispose()
diff --git a/seed/csharp-model/basic-auth-environment-variables/src/SeedBasicAuthEnvironmentVariables/Core/Public/FileParameter.cs b/seed/csharp-model/basic-auth-environment-variables/src/SeedBasicAuthEnvironmentVariables/Core/Public/FileParameter.cs
index 9660209f564..de0606a06a6 100644
--- a/seed/csharp-model/basic-auth-environment-variables/src/SeedBasicAuthEnvironmentVariables/Core/Public/FileParameter.cs
+++ b/seed/csharp-model/basic-auth-environment-variables/src/SeedBasicAuthEnvironmentVariables/Core/Public/FileParameter.cs
@@ -13,17 +13,17 @@ public record FileParameter : IDisposable
///
/// The name of the file to be uploaded.
///
- public string? FileName { get; init; }
+ public string? FileName { get; set; }
///
/// The content type of the file to be uploaded.
///
- public string? ContentType { get; init; }
+ public string? ContentType { get; set; }
///
/// The content of the file to be uploaded.
///
- public required Stream Stream { get; init; }
+ public required Stream Stream { get; set; }
///
public void Dispose()
diff --git a/seed/csharp-model/basic-auth/src/SeedBasicAuth/Core/Public/FileParameter.cs b/seed/csharp-model/basic-auth/src/SeedBasicAuth/Core/Public/FileParameter.cs
index 575394f2b64..a586c7a2d9b 100644
--- a/seed/csharp-model/basic-auth/src/SeedBasicAuth/Core/Public/FileParameter.cs
+++ b/seed/csharp-model/basic-auth/src/SeedBasicAuth/Core/Public/FileParameter.cs
@@ -13,17 +13,17 @@ public record FileParameter : IDisposable
///
/// The name of the file to be uploaded.
///
- public string? FileName { get; init; }
+ public string? FileName { get; set; }
///
/// The content type of the file to be uploaded.
///
- public string? ContentType { get; init; }
+ public string? ContentType { get; set; }
///
/// The content of the file to be uploaded.
///
- public required Stream Stream { get; init; }
+ public required Stream Stream { get; set; }
///
public void Dispose()
diff --git a/seed/csharp-model/bearer-token-environment-variable/src/SeedBearerTokenEnvironmentVariable/Core/Public/FileParameter.cs b/seed/csharp-model/bearer-token-environment-variable/src/SeedBearerTokenEnvironmentVariable/Core/Public/FileParameter.cs
index 44d6f26b118..51c078fc2b1 100644
--- a/seed/csharp-model/bearer-token-environment-variable/src/SeedBearerTokenEnvironmentVariable/Core/Public/FileParameter.cs
+++ b/seed/csharp-model/bearer-token-environment-variable/src/SeedBearerTokenEnvironmentVariable/Core/Public/FileParameter.cs
@@ -13,17 +13,17 @@ public record FileParameter : IDisposable
///
/// The name of the file to be uploaded.
///
- public string? FileName { get; init; }
+ public string? FileName { get; set; }
///
/// The content type of the file to be uploaded.
///
- public string? ContentType { get; init; }
+ public string? ContentType { get; set; }
///
/// The content of the file to be uploaded.
///
- public required Stream Stream { get; init; }
+ public required Stream Stream { get; set; }
///
public void Dispose()
diff --git a/seed/csharp-model/bytes/src/SeedBytes/Core/Public/FileParameter.cs b/seed/csharp-model/bytes/src/SeedBytes/Core/Public/FileParameter.cs
index f8f63e299af..4486f242873 100644
--- a/seed/csharp-model/bytes/src/SeedBytes/Core/Public/FileParameter.cs
+++ b/seed/csharp-model/bytes/src/SeedBytes/Core/Public/FileParameter.cs
@@ -13,17 +13,17 @@ public record FileParameter : IDisposable
///
/// The name of the file to be uploaded.
///
- public string? FileName { get; init; }
+ public string? FileName { get; set; }
///
/// The content type of the file to be uploaded.
///
- public string? ContentType { get; init; }
+ public string? ContentType { get; set; }
///
/// The content of the file to be uploaded.
///
- public required Stream Stream { get; init; }
+ public required Stream Stream { get; set; }
///
public void Dispose()
diff --git a/seed/csharp-model/circular-references-advanced/src/SeedApi/Core/Public/FileParameter.cs b/seed/csharp-model/circular-references-advanced/src/SeedApi/Core/Public/FileParameter.cs
index 97e498c91c9..f33d4902888 100644
--- a/seed/csharp-model/circular-references-advanced/src/SeedApi/Core/Public/FileParameter.cs
+++ b/seed/csharp-model/circular-references-advanced/src/SeedApi/Core/Public/FileParameter.cs
@@ -13,17 +13,17 @@ public record FileParameter : IDisposable
///
/// The name of the file to be uploaded.
///
- public string? FileName { get; init; }
+ public string? FileName { get; set; }
///
/// The content type of the file to be uploaded.
///
- public string? ContentType { get; init; }
+ public string? ContentType { get; set; }
///
/// The content of the file to be uploaded.
///
- public required Stream Stream { get; init; }
+ public required Stream Stream { get; set; }
///
public void Dispose()
diff --git a/seed/csharp-model/circular-references/src/SeedApi/Core/Public/FileParameter.cs b/seed/csharp-model/circular-references/src/SeedApi/Core/Public/FileParameter.cs
index 97e498c91c9..f33d4902888 100644
--- a/seed/csharp-model/circular-references/src/SeedApi/Core/Public/FileParameter.cs
+++ b/seed/csharp-model/circular-references/src/SeedApi/Core/Public/FileParameter.cs
@@ -13,17 +13,17 @@ public record FileParameter : IDisposable
///
/// The name of the file to be uploaded.
///
- public string? FileName { get; init; }
+ public string? FileName { get; set; }
///
/// The content type of the file to be uploaded.
///
- public string? ContentType { get; init; }
+ public string? ContentType { get; set; }
///
/// The content of the file to be uploaded.
///
- public required Stream Stream { get; init; }
+ public required Stream Stream { get; set; }
///
public void Dispose()
diff --git a/seed/csharp-model/content-type/.editorconfig b/seed/csharp-model/content-type/.editorconfig
new file mode 100644
index 00000000000..a450a4ea14b
--- /dev/null
+++ b/seed/csharp-model/content-type/.editorconfig
@@ -0,0 +1,34 @@
+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_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_not_accessed_field_local_highlighting = hint
+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_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
+resharper_property_can_be_made_init_only_local_highlighting = hint
+resharper_member_hides_static_from_outer_class_highlighting = hint
+resharper_class_never_instantiated_local_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-model/content-type/.github/workflows/ci.yml b/seed/csharp-model/content-type/.github/workflows/ci.yml
new file mode 100644
index 00000000000..03bb29b44a1
--- /dev/null
+++ b/seed/csharp-model/content-type/.github/workflows/ci.yml
@@ -0,0 +1,69 @@
+name: ci
+
+on: [push]
+
+jobs:
+ compile:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v3
+
+ - uses: actions/checkout@master
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 8.x
+
+ - name: Install tools
+ run: |
+ dotnet tool restore
+
+ - name: Build Release
+ run: dotnet build src -c Release /p:ContinuousIntegrationBuild=true
+
+ unit-tests:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v3
+
+ - uses: actions/checkout@master
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 8.x
+
+ - name: Install tools
+ run: |
+ dotnet tool restore
+
+ - name: Run Tests
+ run: |
+ dotnet test src
+
+
+ publish:
+ needs: [compile]
+ if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v3
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 8.x
+
+ - name: Publish
+ env:
+ NUGET_API_KEY: ${{ secrets.NUGET_API_TOKEN }}
+ run: |
+ dotnet pack src -c Release
+ dotnet nuget push src/SeedContentTypes/bin/Release/*.nupkg --api-key $NUGET_API_KEY --source "nuget.org"
diff --git a/seed/csharp-model/content-type/.gitignore b/seed/csharp-model/content-type/.gitignore
new file mode 100644
index 00000000000..11014f2b33d
--- /dev/null
+++ b/seed/csharp-model/content-type/.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-model/content-type/.mock/definition/api.yml b/seed/csharp-model/content-type/.mock/definition/api.yml
new file mode 100644
index 00000000000..ea70b01ab76
--- /dev/null
+++ b/seed/csharp-model/content-type/.mock/definition/api.yml
@@ -0,0 +1 @@
+name: content-types
\ No newline at end of file
diff --git a/seed/csharp-model/content-type/.mock/definition/service.yml b/seed/csharp-model/content-type/.mock/definition/service.yml
new file mode 100644
index 00000000000..8f029ae49e0
--- /dev/null
+++ b/seed/csharp-model/content-type/.mock/definition/service.yml
@@ -0,0 +1,16 @@
+service:
+ auth: false
+ base-path: ''
+ endpoints:
+ patch:
+ method: PATCH
+ path: /
+ request:
+ body:
+ properties:
+ application: nullable
+ require_auth: nullable
+ content-type: application/merge-patch+json
+ name: PatchProxyRequest
+ source:
+ openapi: ../specs/vault/swagger.json
diff --git a/seed/csharp-model/content-type/.mock/fern.config.json b/seed/csharp-model/content-type/.mock/fern.config.json
new file mode 100644
index 00000000000..4c8e54ac313
--- /dev/null
+++ b/seed/csharp-model/content-type/.mock/fern.config.json
@@ -0,0 +1 @@
+{"organization": "fern-test", "version": "*"}
\ No newline at end of file
diff --git a/seed/csharp-model/content-type/.mock/generators.yml b/seed/csharp-model/content-type/.mock/generators.yml
new file mode 100644
index 00000000000..0967ef424bc
--- /dev/null
+++ b/seed/csharp-model/content-type/.mock/generators.yml
@@ -0,0 +1 @@
+{}
diff --git a/seed/csharp-model/content-type/snippet-templates.json b/seed/csharp-model/content-type/snippet-templates.json
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/seed/csharp-model/content-type/snippet.json b/seed/csharp-model/content-type/snippet.json
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/seed/csharp-model/content-type/src/SeedContentTypes.Test/Core/Json/DateOnlyJsonTests.cs b/seed/csharp-model/content-type/src/SeedContentTypes.Test/Core/Json/DateOnlyJsonTests.cs
new file mode 100644
index 00000000000..882da6acf4c
--- /dev/null
+++ b/seed/csharp-model/content-type/src/SeedContentTypes.Test/Core/Json/DateOnlyJsonTests.cs
@@ -0,0 +1,76 @@
+using NUnit.Framework;
+using SeedContentTypes.Core;
+
+namespace SeedContentTypes.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-model/content-type/src/SeedContentTypes.Test/Core/Json/DateTimeJsonTests.cs b/seed/csharp-model/content-type/src/SeedContentTypes.Test/Core/Json/DateTimeJsonTests.cs
new file mode 100644
index 00000000000..124430db4ef
--- /dev/null
+++ b/seed/csharp-model/content-type/src/SeedContentTypes.Test/Core/Json/DateTimeJsonTests.cs
@@ -0,0 +1,110 @@
+using NUnit.Framework;
+using SeedContentTypes.Core;
+
+namespace SeedContentTypes.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-model/content-type/src/SeedContentTypes.Test/Core/Json/EnumSerializerTests.cs b/seed/csharp-model/content-type/src/SeedContentTypes.Test/Core/Json/EnumSerializerTests.cs
new file mode 100644
index 00000000000..21950a9b28d
--- /dev/null
+++ b/seed/csharp-model/content-type/src/SeedContentTypes.Test/Core/Json/EnumSerializerTests.cs
@@ -0,0 +1,60 @@
+using System.Runtime.Serialization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using NUnit.Framework;
+using SeedContentTypes.Core;
+
+namespace SeedContentTypes.Test.Core.Json;
+
+[TestFixture]
+[Parallelizable(ParallelScope.All)]
+public class StringEnumSerializerTests
+{
+ private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
+
+ private const DummyEnum KnownEnumValue2 = DummyEnum.KnownValue2;
+ private const string KnownEnumValue2String = "known_value2";
+
+ private const string JsonWithKnownEnum2 = $$"""
+ {
+ "enum_property": "{{KnownEnumValue2String}}"
+ }
+ """;
+
+ [Test]
+ public void ShouldParseKnownEnumValue2()
+ {
+ var obj = JsonSerializer.Deserialize(JsonWithKnownEnum2, JsonOptions);
+ Assert.That(obj, Is.Not.Null);
+ Assert.That(obj.EnumProperty, Is.EqualTo(KnownEnumValue2));
+ }
+
+ [Test]
+ public void ShouldSerializeKnownEnumValue2()
+ {
+ var json = JsonSerializer.SerializeToElement(
+ new DummyObject { EnumProperty = KnownEnumValue2 },
+ JsonOptions
+ );
+ TestContext.Out.WriteLine("Serialized JSON: \n" + json);
+ var enumString = json.GetProperty("enum_property").GetString();
+ Assert.That(enumString, Is.Not.Null);
+ Assert.That(enumString, Is.EqualTo(KnownEnumValue2String));
+ }
+}
+
+public class DummyObject
+{
+ [JsonPropertyName("enum_property")]
+ public DummyEnum EnumProperty { get; set; }
+}
+
+[JsonConverter(typeof(EnumSerializer))]
+public enum DummyEnum
+{
+ [EnumMember(Value = "known_value1")]
+ KnownValue1,
+
+ [EnumMember(Value = "known_value2")]
+ KnownValue2,
+}
diff --git a/seed/csharp-model/content-type/src/SeedContentTypes.Test/Core/Json/JsonAccessAttributeTests.cs b/seed/csharp-model/content-type/src/SeedContentTypes.Test/Core/Json/JsonAccessAttributeTests.cs
new file mode 100644
index 00000000000..783763a148b
--- /dev/null
+++ b/seed/csharp-model/content-type/src/SeedContentTypes.Test/Core/Json/JsonAccessAttributeTests.cs
@@ -0,0 +1,46 @@
+using global::System.Text.Json.Serialization;
+using NUnit.Framework;
+using SeedContentTypes.Core;
+
+namespace SeedContentTypes.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; }
+ }
+
+ [Test]
+ public void JsonAccessAttribute_ShouldWorkAsExpected()
+ {
+ const string json =
+ """ { "read_only_prop": "read", "write_only_prop": "write", "normal_prop": "normal_prop" } """;
+ var obj = JsonUtils.Deserialize(json);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(obj.ReadOnlyProp, Is.EqualTo("read"));
+ Assert.That(obj.WriteOnlyProp, Is.Null);
+ Assert.That(obj.NormalProp, Is.EqualTo("normal_prop"));
+ });
+
+ obj.WriteOnlyProp = "write";
+ obj.NormalProp = "new_value";
+
+ var serializedJson = JsonUtils.Serialize(obj);
+ const string expectedJson =
+ "{ \"write_only_prop\": \"write\",\n \"normal_prop\": \"new_value\" }";
+ Assert.That(serializedJson, Is.EqualTo(expectedJson).IgnoreWhiteSpace);
+ }
+}
diff --git a/seed/csharp-model/content-type/src/SeedContentTypes.Test/Core/Json/OneOfSerializerTests.cs b/seed/csharp-model/content-type/src/SeedContentTypes.Test/Core/Json/OneOfSerializerTests.cs
new file mode 100644
index 00000000000..d52b073f676
--- /dev/null
+++ b/seed/csharp-model/content-type/src/SeedContentTypes.Test/Core/Json/OneOfSerializerTests.cs
@@ -0,0 +1,314 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using NUnit.Framework;
+using OneOf;
+using SeedContentTypes.Core;
+
+namespace SeedContentTypes.Test.Core.Json;
+
+[TestFixture]
+[Parallelizable(ParallelScope.All)]
+public class OneOfSerializerTests
+{
+ private class Foo
+ {
+ [JsonPropertyName("string_prop")]
+ public required string StringProp { get; set; }
+ }
+
+ private class Bar
+ {
+ [JsonPropertyName("int_prop")]
+ public required int IntProp { get; set; }
+ }
+
+ private static readonly OneOf OneOf1 = OneOf<
+ string,
+ int,
+ object,
+ Foo,
+ Bar
+ >.FromT2(new { });
+ private const string OneOf1String = "{}";
+
+ private static readonly OneOf OneOf2 = OneOf<
+ string,
+ int,
+ object,
+ Foo,
+ Bar
+ >.FromT0("test");
+ private const string OneOf2String = "\"test\"";
+
+ private static readonly OneOf OneOf3 = OneOf<
+ string,
+ int,
+ object,
+ Foo,
+ Bar
+ >.FromT1(123);
+ private const string OneOf3String = "123";
+
+ private static readonly OneOf OneOf4 = OneOf<
+ string,
+ int,
+ object,
+ Foo,
+ Bar
+ >.FromT3(new Foo { StringProp = "test" });
+ private const string OneOf4String = "{\"string_prop\": \"test\"}";
+
+ private static readonly OneOf OneOf5 = OneOf<
+ string,
+ int,
+ object,
+ Foo,
+ Bar
+ >.FromT4(new Bar { IntProp = 5 });
+ private const string OneOf5String = "{\"int_prop\": 5}";
+
+ [Test]
+ public void Serialize_OneOfs_Should_Return_Expected_String()
+ {
+ (OneOf, string)[] testData =
+ [
+ (OneOf1, OneOf1String),
+ (OneOf2, OneOf2String),
+ (OneOf3, OneOf3String),
+ (OneOf4, OneOf4String),
+ (OneOf5, OneOf5String),
+ ];
+ Assert.Multiple(() =>
+ {
+ foreach (var (oneOf, expected) in testData)
+ {
+ var result = JsonUtils.Serialize(oneOf);
+ Assert.That(result, Is.EqualTo(expected).IgnoreWhiteSpace);
+ }
+ });
+ }
+
+ [Test]
+ public void OneOfs_Should_Deserialize_From_String()
+ {
+ (OneOf, string)[] testData =
+ [
+ (OneOf1, OneOf1String),
+ (OneOf2, OneOf2String),
+ (OneOf3, OneOf3String),
+ (OneOf4, OneOf4String),
+ (OneOf5, OneOf5String),
+ ];
+ Assert.Multiple(() =>
+ {
+ foreach (var (oneOf, json) in testData)
+ {
+ var result = JsonUtils.Deserialize>(json);
+ Assert.That(result.Index, Is.EqualTo(oneOf.Index));
+ Assert.That(json, Is.EqualTo(JsonUtils.Serialize(result.Value)).IgnoreWhiteSpace);
+ }
+ });
+ }
+
+ private static readonly OneOf? NullableOneOf1 = null;
+ private const string NullableOneOf1String = "null";
+
+ private static readonly OneOf? NullableOneOf2 = OneOf<
+ string,
+ int,
+ object,
+ Foo,
+ Bar
+ >.FromT4(new Bar { IntProp = 5 });
+ private const string NullableOneOf2String = "{\"int_prop\": 5}";
+
+ [Test]
+ public void Serialize_NullableOneOfs_Should_Return_Expected_String()
+ {
+ (OneOf?, string)[] testData =
+ [
+ (NullableOneOf1, NullableOneOf1String),
+ (NullableOneOf2, NullableOneOf2String),
+ ];
+ Assert.Multiple(() =>
+ {
+ foreach (var (oneOf, expected) in testData)
+ {
+ var result = JsonUtils.Serialize(oneOf);
+ Assert.That(result, Is.EqualTo(expected).IgnoreWhiteSpace);
+ }
+ });
+ }
+
+ [Test]
+ public void NullableOneOfs_Should_Deserialize_From_String()
+ {
+ (OneOf?, string)[] testData =
+ [
+ (NullableOneOf1, NullableOneOf1String),
+ (NullableOneOf2, NullableOneOf2String),
+ ];
+ Assert.Multiple(() =>
+ {
+ foreach (var (oneOf, json) in testData)
+ {
+ var result = JsonUtils.Deserialize?>(json);
+ Assert.That(result?.Index, Is.EqualTo(oneOf?.Index));
+ Assert.That(json, Is.EqualTo(JsonUtils.Serialize(result?.Value)).IgnoreWhiteSpace);
+ }
+ });
+ }
+
+ private static readonly OneOf OneOfWithNullable1 = OneOf<
+ string,
+ int,
+ Foo?
+ >.FromT2(null);
+ private const string OneOfWithNullable1String = "null";
+
+ private static readonly OneOf OneOfWithNullable2 = OneOf<
+ string,
+ int,
+ Foo?
+ >.FromT2(new Foo { StringProp = "test" });
+ private const string OneOfWithNullable2String = "{\"string_prop\": \"test\"}";
+
+ private static readonly OneOf OneOfWithNullable3 = OneOf<
+ string,
+ int,
+ Foo?
+ >.FromT0("test");
+ private const string OneOfWithNullable3String = "\"test\"";
+
+ [Test]
+ public void Serialize_OneOfWithNullables_Should_Return_Expected_String()
+ {
+ (OneOf, string)[] testData =
+ [
+ (OneOfWithNullable1, OneOfWithNullable1String),
+ (OneOfWithNullable2, OneOfWithNullable2String),
+ (OneOfWithNullable3, OneOfWithNullable3String),
+ ];
+ Assert.Multiple(() =>
+ {
+ foreach (var (oneOf, expected) in testData)
+ {
+ var result = JsonUtils.Serialize(oneOf);
+ Assert.That(result, Is.EqualTo(expected).IgnoreWhiteSpace);
+ }
+ });
+ }
+
+ [Test]
+ public void OneOfWithNullables_Should_Deserialize_From_String()
+ {
+ (OneOf, string)[] testData =
+ [
+ // (OneOfWithNullable1, OneOfWithNullable1String), // not possible with .NET's JSON serializer
+ (OneOfWithNullable2, OneOfWithNullable2String),
+ (OneOfWithNullable3, OneOfWithNullable3String),
+ ];
+ Assert.Multiple(() =>
+ {
+ foreach (var (oneOf, json) in testData)
+ {
+ var result = JsonUtils.Deserialize>(json);
+ Assert.That(result.Index, Is.EqualTo(oneOf.Index));
+ Assert.That(json, Is.EqualTo(JsonUtils.Serialize(result.Value)).IgnoreWhiteSpace);
+ }
+ });
+ }
+
+ [Test]
+ public void Serialize_OneOfWithObjectLast_Should_Return_Expected_String()
+ {
+ var oneOfWithObjectLast = OneOf.FromT4(
+ new { random = "data" }
+ );
+ const string oneOfWithObjectLastString = "{\"random\": \"data\"}";
+
+ var result = JsonUtils.Serialize(oneOfWithObjectLast);
+ Assert.That(result, Is.EqualTo(oneOfWithObjectLastString).IgnoreWhiteSpace);
+ }
+
+ [Test]
+ public void OneOfWithObjectLast_Should_Deserialize_From_String()
+ {
+ const string oneOfWithObjectLastString = "{\"random\": \"data\"}";
+ var result = JsonUtils.Deserialize>(
+ oneOfWithObjectLastString
+ );
+ Assert.Multiple(() =>
+ {
+ Assert.That(result.Index, Is.EqualTo(4));
+ Assert.That(result.Value, Is.InstanceOf