Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 0 additions & 83 deletions .github/workflows/cli-release.yml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -601,12 +601,24 @@ private TypeSpec getDeserializer() {
.endControlFlow();
}
}
for (int i = 0; i < membersWithoutLiterals.size(); ++i) {
UndiscriminatedUnionMember member = membersWithoutLiterals.get(i);
// Sort: members with required key guards first, then unguarded (all-optional) members last.
// This prevents all-optional object types from greedily matching payloads intended for
// more specific members — e.g. PayMethodCloud (0 required keys) consuming {"achHolder":"x"}
// before Check (1 required key) gets a chance.
List<UndiscriminatedUnionMember> nonPrimitiveMembers = membersWithoutLiterals.stream()
.filter(m -> {
TypeName tn = memberTypeNames.get(m);
return !tn.isPrimitive() && !tn.isBoxedPrimitive();
})
.collect(Collectors.toList());
nonPrimitiveMembers.sort((a, b) -> {
boolean aHasGuard = !getRequiredWireKeys(a).isEmpty();
boolean bHasGuard = !getRequiredWireKeys(b).isEmpty();
if (aHasGuard == bHasGuard) return 0;
return aHasGuard ? -1 : 1;
});
for (UndiscriminatedUnionMember member : nonPrimitiveMembers) {
TypeName typeName = memberTypeNames.get(member);
if (typeName.isPrimitive() || typeName.isBoxedPrimitive()) {
continue;
}
List<String> requiredKeys = getRequiredWireKeys(member);
boolean hasRequiredKeyGuard = !requiredKeys.isEmpty();
if (hasRequiredKeyGuard) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# yaml-language-server: $schema=../../../../../fern-changes-yml.schema.json

- summary: |
Fix undiscriminated union deserialization when one member has all-optional fields.
Previously, an all-optional object variant (e.g. `PayMethodCloud`) could greedily
consume a payload intended for a more specific variant with required fields (e.g.
`Check` requiring `achHolder`), because Jackson's `@JsonIgnoreProperties(ignoreUnknown=true)`
silently accepts any JSON object when all fields are optional. The deserializer now
emits guarded members (those with at least one required field) before unguarded
(all-optional) members, ensuring the more specific match wins.
type: fix
13 changes: 13 additions & 0 deletions generators/java/sdk/versions.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
# yaml-language-server: $schema=../../../fern-versions-yml.schema.json
- version: 4.8.7
changelogEntry:
- summary: |
Fix undiscriminated union deserialization when one member has all-optional fields.
Previously, an all-optional object variant (e.g. `PayMethodCloud`) could greedily
consume a payload intended for a more specific variant with required fields (e.g.
`Check` requiring `achHolder`), because Jackson's `@JsonIgnoreProperties(ignoreUnknown=true)`
silently accepts any JSON object when all fields are optional. The deserializer now
emits guarded members (those with at least one required field) before unguarded
(all-optional) members, ensuring the more specific match wins.
type: fix
createdAt: "2026-05-12"
irVersion: 66
- version: 4.8.6
changelogEntry:
- summary: |
Expand Down
2 changes: 1 addition & 1 deletion generators/ruby-v2/ast/src/ast/Comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class Comment extends AstNode {
}

public write(writer: Writer): void {
if (this.docs != null) {
if (this.docs?.trim()) {
this.docs.split("\n").forEach((line) => {
const wrappedLines = this.wrapLine(line, writer);
wrappedLines.forEach((wrappedLine) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# yaml-language-server: $schema=../../../../../fern-changes-yml.schema.json

- summary: |
Fix `Layout/EmptyComment` RuboCop offense emitted for types with no
description. The AST `Comment` node now skips writing when `docs` is
empty or whitespace-only, so undocumented model classes no longer
produce a bare `#` line above their class definition.
type: fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# yaml-language-server: $schema=../../../../../fern-changes-yml.schema.json

- summary: |
Fix `Layout/EmptyComment` RuboCop offense emitted for types with no
description. The AST `Comment` node now skips writing when `docs` is
empty or whitespace-only, so undocumented model classes no longer
produce a bare `#` line above their class definition.
type: fix
20 changes: 20 additions & 0 deletions generators/ruby-v2/sdk/versions.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
# yaml-language-server: $schema=../../../fern-versions-yml.schema.json
- version: 1.12.8
changelogEntry:
- summary: |
Fix `Layout/EmptyComment` RuboCop offense emitted for types with no
description. The AST `Comment` node now skips writing when `docs` is
empty or whitespace-only, so undocumented model classes no longer
produce a bare `#` line above their class definition.
type: fix
createdAt: "2026-05-12"
irVersion: 66
- version: 1.12.7
changelogEntry:
- summary: |
Fix `Layout/EmptyComment` RuboCop offense emitted for types with no
description. The AST `Comment` node now skips writing when `docs` is
empty or whitespace-only, so undocumented model classes no longer
produce a bare `#` line above their class definition.
type: fix
createdAt: "2026-05-12"
irVersion: 66
- version: 1.12.6
changelogEntry:
- summary: |
Expand Down
4 changes: 2 additions & 2 deletions generators/rust/base/src/asIs/pagination.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ mod tests {
fn test_sync_paginator_error_propagation() {
let client = make_http_client();
let mut paginator = SyncPaginator::<String>::new(client, |_client, _cursor| {
Err(ApiError::Serialization("test error".to_string()))
Err(ApiError::Configuration("test error".to_string()))
}, None).unwrap();

let result = paginator.next_page();
Expand All @@ -537,7 +537,7 @@ mod tests {
fn test_sync_paginator_iterator_error() {
let client = make_http_client();
let mut paginator = SyncPaginator::<String>::new(client, |_client, _cursor| {
Err(ApiError::Serialization("test error".to_string()))
Err(ApiError::Configuration("test error".to_string()))
}, None).unwrap();

let item = paginator.next();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# yaml-language-server: $schema=../../../../../fern-changes-yml.schema.json

- summary: |
Fix generated Rust wire tests for single-URL SDKs and pagination error
tests. The wire test generator no longer emits `config.environment = None;`
when the SDK was generated against a single base URL (since the
`environment` field does not exist on `ClientConfig` in that case).
The synchronous-paginator error-propagation tests now use
`ApiError::Configuration` instead of the non-existent
`ApiError::Serialization` variant, so the generated `pagination.rs` test
module compiles.
type: fix
4 changes: 3 additions & 1 deletion generators/rust/sdk/src/wire-tests/WireTestGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,9 @@ export class WireTestGenerator {
lines.push(` };`);
// Override base_url and clear environment so requests go to WireMock
lines.push(` config.base_url = wiremock_base_url.to_string();`);
lines.push(` config.environment = None;`);
if (this.context.hasMultipleBaseUrls()) {
lines.push(` config.environment = None;`);
}
inConfigStruct = false;
} else {
lines.push(` ${trimmedLine}`);
Expand Down
14 changes: 14 additions & 0 deletions generators/rust/sdk/versions.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
# yaml-language-server: $schema=../../../fern-versions-yml.schema.json
- version: 0.36.6
changelogEntry:
- summary: |
Fix generated Rust wire tests for single-URL SDKs and pagination error
tests. The wire test generator no longer emits `config.environment = None;`
when the SDK was generated against a single base URL (since the
`environment` field does not exist on `ClientConfig` in that case).
The synchronous-paginator error-propagation tests now use
`ApiError::Configuration` instead of the non-existent
`ApiError::Serialization` variant, so the generated `pagination.rs` test
module compiles.
type: fix
createdAt: "2026-05-12"
irVersion: 66
- version: 0.36.5
changelogEntry:
- summary: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,8 @@ export class OpenAPIConverter extends AbstractSpecConverter<OpenAPIConverterCont
webhook: convertedWebHook.webhook,
operationId,
audiences: convertedWebHook.audiences,
group: convertedWebHook.group
group: convertedWebHook.group,
inlinedPayloadPropertiesByAudience: convertedWebHook.inlinedPayloadPropertiesByAudience
});
this.addTypesToIr(convertedWebHook.inlinedTypes);
}
Expand Down Expand Up @@ -296,15 +297,19 @@ export class OpenAPIConverter extends AbstractSpecConverter<OpenAPIConverterCont
endpoint: endpoint.streamEndpoint,
audiences: endpoint.audiences,
endpointGroup: endpoint.group,
endpointGroupDisplayName: endpoint.groupDisplayName
endpointGroupDisplayName: endpoint.groupDisplayName,
inlinedRequestPropertiesByAudience: endpoint.inlinedRequestPropertiesByAudience,
queryParametersByAudience: endpoint.queryParametersByAudience
});
}

this.addEndpointToIr({
endpoint: endpoint.endpoint,
audiences: endpoint.audiences,
endpointGroup: endpoint.group,
endpointGroupDisplayName: endpoint.groupDisplayName
endpointGroupDisplayName: endpoint.groupDisplayName,
inlinedRequestPropertiesByAudience: endpoint.inlinedRequestPropertiesByAudience,
queryParametersByAudience: endpoint.queryParametersByAudience
});

if (endpoint.servers) {
Expand Down Expand Up @@ -337,7 +342,8 @@ export class OpenAPIConverter extends AbstractSpecConverter<OpenAPIConverterCont
webhook: webhook.webhook,
operationId: group.join("."),
group,
audiences: webhook.audiences
audiences: webhook.audiences,
inlinedPayloadPropertiesByAudience: webhook.inlinedPayloadPropertiesByAudience
});
}
this.addTypesToIr(convertedPath.inlinedTypes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ export declare namespace RequestBodyConverter {
export interface Output extends Converters.AbstractConverters.AbstractMediaTypeObjectConverter.Output {
requestBody: HttpRequestBody;
streamRequestBody: HttpRequestBody | undefined;
/**
* Audience name → wire names of inline request body properties under that
* audience. Only populated for `inlinedRequestBody` outputs; `undefined`
* (or absent) means no inline property-level audience info to mark on the
* IR filter graph.
*/
inlinedPropertiesByAudience?: Record<string, Set<string>>;
}
}

Expand Down Expand Up @@ -180,7 +187,8 @@ export class RequestBodyConverter extends Converters.AbstractConverters.Abstract
inlinedTypes: this.context.removeSchemaFromInlinedTypes({
id: this.schemaId,
inlinedTypes: convertedSchema.inlinedTypes
})
}),
inlinedPropertiesByAudience: convertedSchema.schema?.propertiesByAudience
};
} else {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ interface ConvertedRequestBody {
requestBody: HttpRequestBody;
streamRequestBody: HttpRequestBody | undefined;
examples?: Record<string, OpenAPIV3_1.ExampleObject>;
inlinedPropertiesByAudience?: Record<string, Set<string>>;
}

export abstract class AbstractOperationConverter extends AbstractConverter<
Expand Down Expand Up @@ -238,7 +239,8 @@ export abstract class AbstractOperationConverter extends AbstractConverter<
convertedRequestBodies.push({
requestBody: convertedRequestBody.requestBody,
streamRequestBody: convertedRequestBody.streamRequestBody,
examples: convertedRequestBody.examples
examples: convertedRequestBody.examples,
inlinedPropertiesByAudience: convertedRequestBody.inlinedPropertiesByAudience
});
}
}
Expand Down
Loading
Loading