Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
1038d9a
fix(typescript): drop nullable enum query params when undefined inste…
jsklan Jan 15, 2026
37c0739
chore(deps): update filelock and virtualenv for CVE fixes (#11542)
davidkonigsberg Jan 15, 2026
663082e
fix(openapi): webhook audience filtering in OpenAPI v3 importer (#11559)
devin-ai-integration[bot] Jan 15, 2026
8b16b6e
fix(ruby): support clientModuleName config for custom client class na…
iamnamananand996 Jan 15, 2026
175b9fe
chore(ruby): update ruby-sdk seed (#11553)
github-actions[bot] Jan 15, 2026
b896a30
fix(cli): downgrade severity ref (#11569)
fern-support Jan 15, 2026
bfb6b8f
chore(ruby): update ruby-sdk-v2 seed (#11554)
github-actions[bot] Jan 15, 2026
460cbd2
chore(cli): add exclude-apis experimental flag for docs generation (#…
fern-support Jan 15, 2026
da9909a
feat(cli): `--indent` flag on commands that generate specs (#11575)
patrickthornton Jan 15, 2026
fd82d94
feat(java): add enable-gradle-profiling config option for debugging s…
tjb9dc Jan 15, 2026
0ea394c
feat(cli): Initialize CLI v2 (#11563)
amckinney Jan 15, 2026
216c631
feat(csharp): refactor WebSocket API to use composition over inherita…
devin-ai-integration[bot] Jan 15, 2026
9bf6c9d
fix(csharp): Pass ClientOptions to exception interceptor constructor …
devin-ai-integration[bot] Jan 15, 2026
3594a4d
feat(cli): map OpenAPI validation fields from IR to FDR format (#11565)
sbawabe Jan 15, 2026
99b79a3
feat(java): add additionalQueryParameters support to RequestOptions (…
tstanmay13 Jan 15, 2026
487e47b
fix(cli): skip AI example enhancement in self-hosted environments (#1…
thesandlord Jan 15, 2026
11f9e29
chore(cli): upgrade fdr sdk (#11584)
fern-support Jan 15, 2026
8d6db0b
feat(csharp): add explicit Optional<T> type for nullable/optional han…
fern-support Jan 15, 2026
1bd0a08
chore(cli): add multipart form data tests (#11585)
kennyderek Jan 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,4 @@
"groupTotalTimeSeconds": 1456
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
"file-upload",
"circular-references",
"circular-references-advanced",
"nullable-optional",
"nullable-optional:no-custom-config",
"nullable-optional:explicit-nullable-optional",
"inferred-auth-implicit-no-expiry",
"multi-url-environment-no-default",
"no-environment",
Expand Down Expand Up @@ -132,7 +133,8 @@
"websocket:with-websockets",
"package-yml",
"path-parameters:no-custom-config",
"required-nullable",
"required-nullable:no-custom-config",
"required-nullable:explicit-nullable-optional",
"multi-url-environment:no-pascal-case-environments",
"object",
"error-property",
Expand Down Expand Up @@ -164,7 +166,8 @@
"csharp-namespace-conflict",
"streaming",
"single-url-environment-no-default",
"nullable",
"nullable:no-custom-config",
"nullable:explicit-nullable-optional",
"content-type",
"query-parameters",
"errors",
Expand All @@ -174,4 +177,4 @@
"groupTotalTimeSeconds": 3359
}
]
}
}
10 changes: 10 additions & 0 deletions docs-yml.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3788,6 +3788,16 @@
"type": "null"
}
]
},
"exclude-apis": {
"oneOf": [
{
"type": "boolean"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
Expand Down
5 changes: 5 additions & 0 deletions fern/apis/docs-yml/definition/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1662,6 +1662,11 @@ types:
Custom styling instructions for AI-generated examples. When provided, these instructions will guide the AI in generating examples that match your preferred style, naming conventions, or domain-specific terminology. Limited to 500 characters.

DEPRECATED: Use the top-level `ai-example-style-instructions` property instead.
exclude-apis:
type: optional<boolean>
availability: pre-release
docs: |
Experimental flag to exclude API reference sections from documentation generation. When enabled, API reference content will be omitted from the generated documentation.

PlaygroundSettings:
properties:
Expand Down
45 changes: 20 additions & 25 deletions generators/csharp/base/src/AsIs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,31 +39,22 @@ export const AsIsFiles = {
QueryStringConverter: "QueryStringConverter.Template.cs",
RawClient: "RawClient.Template.cs",
StreamRequest: "StreamRequest.Template.cs",
WebSocketAsync: {
Events: {
Closed: "Async/Events/Closed.Template.cs",
Connected: "Async/Events/Connected.Template.cs",
Event: "Async/Events/Event.Template.cs"
},
Exceptions: {
WebsocketException: "Async/Exceptions/WebsocketException.Template.cs"
},
Models: {
Options: "Async/Models/Options.Template.cs",
DisconnectionInfo: "Async/Models/DisconnectionInfo.Template.cs",
DisconnectionType: "Async/Models/DisconnectionType.Template.cs",
ReconnectionInfo: "Async/Models/ReconnectionInfo.Template.cs",
ReconnectionType: "Async/Models/ReconnectionType.Template.cs"
},
Threading: {
AsyncLock: "Async/Threading/AsyncLock.Template.cs"
},
AsyncApi: "Async/AsyncApi.Template.cs",
ConnectionStatus: "Async/ConnectionStatus.Template.cs",
Query: "Async/Query.Template.cs",
RequestMessage: "Async/RequestMessage.Template.cs",
WebSocketConnection: "Async/WebSocketConnection.Template.cs",
WebSocketConnectionSending: "Async/WebSocketConnection.Sending.Template.cs"
WebSockets: {
AsyncLock: "WebSockets/AsyncLock.Template.cs",
Closed: "WebSockets/Closed.Template.cs",
Connected: "WebSockets/Connected.Template.cs",
ConnectionStatus: "WebSockets/ConnectionStatus.Template.cs",
DisconnectionInfo: "WebSockets/DisconnectionInfo.Template.cs",
DisconnectionType: "WebSockets/DisconnectionType.Template.cs",
Event: "WebSockets/Event.Template.cs",
Query: "WebSockets/Query.Template.cs",
ReconnectionInfo: "WebSockets/ReconnectionInfo.Template.cs",
ReconnectionType: "WebSockets/ReconnectionType.Template.cs",
RequestMessage: "WebSockets/RequestMessage.Template.cs",
WebSocketClient: "WebSockets/WebSocketClient.Template.cs",
WebSocketConnection: "WebSockets/WebSocketConnection.Template.cs",
WebSocketConnectionSending: "WebSockets/WebSocketConnection.Sending.Template.cs",
WebsocketException: "WebSockets/WebsocketException.Template.cs"
},
Json: {
AdditionalProperties: "AdditionalProperties.Template.cs",
Expand All @@ -74,7 +65,10 @@ export const AsIsFiles = {
EnumSerializer: "EnumSerializer.Template.cs",
JsonAccessAttribute: "JsonAccessAttribute.Template.cs",
JsonConfiguration: "JsonConfiguration.Template.cs",
Nullable: "NullableAttribute.Template.cs",
OneOfSerializer: "OneOfSerializer.Template.cs",
Optional: "Optional.Template.cs",
OptionalAttribute: "OptionalAttribute.Template.cs",
StringEnumSerializer: "StringEnumSerializer.Template.cs"
},
Test: {
Expand All @@ -94,6 +88,7 @@ export const AsIsFiles = {
JsonElementComparer: "test/Utils/JsonElementComparer.Template.cs",
NUnitExtensions: "test/Utils/NUnitExtensions.Template.cs",
OneOfComparer: "test/Utils/OneOfComparer.Template.cs",
OptionalComparer: "test/Utils/OptionalComparer.Template.cs",
ReadOnlyMemoryComparer: "test/Utils/ReadOnlyMemoryComparer.Template.cs"
},
Pagination: [
Expand Down
16 changes: 0 additions & 16 deletions generators/csharp/base/src/asIs/Async/Models/Options.Template.cs

This file was deleted.

214 changes: 143 additions & 71 deletions generators/csharp/base/src/asIs/JsonConfiguration.Template.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ static JsonOptions()
#if USE_PORTABLE_DATE_ONLY
new DateOnlyConverter(),
#endif
new OneOfSerializer() },
new OneOfSerializer(),
new OptionalJsonConverterFactory() },
#if DEBUG
WriteIndented = true,
#endif
Expand All @@ -27,83 +28,154 @@ static JsonOptions()
{
Modifiers =
{
static typeInfo =>
{
if (typeInfo.Kind != JsonTypeInfoKind.Object)
return;

foreach (var propertyInfo in typeInfo.Properties)
{
var jsonAccessAttribute = propertyInfo
.AttributeProvider?.GetCustomAttributes(
typeof(JsonAccessAttribute),
true
)
.OfType<JsonAccessAttribute>()
.FirstOrDefault();

if (jsonAccessAttribute != 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<JsonIgnoreAttribute>()
.FirstOrDefault();

if (jsonIgnoreAttribute is not null)
{
propertyInfo.IsRequired = false;
}
}
<% if (additionalProperties) { %>
if (
typeInfo.Kind == JsonTypeInfoKind.Object
&& typeInfo.Properties.All(prop => !prop.IsExtensionData)
)
{
var extensionProp = typeInfo
.Type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
.FirstOrDefault(prop =>
prop.GetCustomAttribute<JsonExtensionDataAttribute>() != 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);
}
}
<% } %>
},
NullableOptionalModifier,
JsonAccessAndIgnoreModifier,<% if (additionalProperties) { %>
HandleExtensionDataFields,<% } %>
},
},
};
ConfigureJsonSerializerOptions(options);
JsonSerializerOptions = options;
}

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 == null)
continue;

// Check for ReadOnly JsonAccessAttribute - it overrides Optional/Nullable behavior
var jsonAccessAttribute = propertyInfo.GetCustomAttribute<JsonAccessAttribute>();
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<OptionalAttribute>() != null;
var hasNullableAttribute = propertyInfo.GetCustomAttribute<NullableAttribute>() != null;

if (isOptionalType && hasOptionalAttribute)
{
var originalGetter = property.Get;
if (originalGetter != 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 == 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<JsonAccessAttribute>()
.FirstOrDefault();

if (jsonAccessAttribute != 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<JsonIgnoreAttribute>()
.FirstOrDefault();

if (jsonIgnoreAttribute is not null)
{
propertyInfo.IsRequired = false;
}
}
}
<% if (additionalProperties) { %>
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<JsonExtensionDataAttribute>() != 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);
}

Expand Down
Loading
Loading