-
Notifications
You must be signed in to change notification settings - Fork 271
Description
Which Fern component?
SDK Generator
How urgent is this?
P0 - Critical (Blocking work)
What's the issue?
Summary
I encountered a critical issue where fern check validates an OpenAPI schema as correct, but the generated code is either syntactically invalid (Python) or logically broken (Rust).
The Core Issue
The problem arises during the flattening of a schema that combines allOf inheritance with anyOf polymorphism, specifically when a property is defined in the base and re-defined in the polymorphic branches.
openapi: 3.0.0
info:
title: Fern Duplicate Fields Repro
version: 1.0.0
paths:
/repro:
post:
operationId: repro
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Root'
responses:
'200':
description: OK
components:
schemas:
Root:
allOf:
- $ref: '#/components/schemas/Base'
- $ref: '#/components/schemas/UnionWrapper'
type: object
Base:
type: object
properties:
id:
type: integer
description: "ID from Base schema"
UnionWrapper:
anyOf:
- $ref: '#/components/schemas/BranchWithSameId'
- $ref: '#/components/schemas/BranchWithDifferentId'
type: object
BranchWithSameId:
type: object
properties:
id:
type: integer
description: "ID from Branch (Same Type)"
other_field:
type: string
BranchWithDifferentId:
type: object
properties:
id:
type: string
description: "ID from Branch (Different Type)"
unique_field:
type: stringIn the attached reproduction (repro_issue.yaml), the schema Root is defined using allOf to merge two components:
Base: Defines a common propertyid(type:integer).UnionWrapper: Contains ananyOflist pointing to branches likeBranchWithSameIdandBranchWithDifferentId.
The Conflict:
Crucially, the branches inside UnionWrapper also define an id field. Fern fails to correctly merge, override, or deduplicate these fields during generation. This happens regardless of whether the types match or conflict:
- Same Type Scenario:
Basehasid: integerandBranchWithSameIdhasid: integer. Even though the types are compatible, Fern generates duplicate entries foridinstead of merging them. - Conflicting Type Scenario:
Basehasid: integerandBranchWithDifferentIdhasid: string(e.g., UUID). This is a structural conflict thatfern checkignores entirely, leading to broken code generation.
Impact on Generated Code
Instead of failing fast or resolving the hierarchy, the generators output models with colliding properties:
- Python: The generator produces code with duplicate dictionary keys or arguments (e.g.,
id=..., id=...), causing syntax errors or linter crashes (F601 Dictionary key literal "id" repeated). - Rust: The generator creates a struct with multiple fields mapping to the same JSON key. As seen in the output,
Rootcontains bothpub root_id(renamed to"id") andpub id(implicitly"id"). This compiles successfully but creates a runtime logic hazard. Serde behavior becomes ambiguous (duplicate keys in JSON payload), leading to silent data loss or deserialization errors depending on which field is processed last.
pub struct Root {
/// ID from Branch (Different Type)
#[serde(rename = "id")]
#[serde(skip_serializing_if = "Option::is_none")]
pub root_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub other_field: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub unique_field: Option<String>,
/// ID from Base schema
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
}Steps to reproduce:
- Initialize Fern project (or use an existing one).
- Create an OpenAPI specification file (e.g.,
repro_issue.yaml). - Configure
fern/generators.ymlto point to this OpenAPI file.
api:
specs:
- openapi: repro_issue.yaml
default-group: local
groups:
local:
generators:
- name: fernapi/fern-python-sdk
version: 4.42.0
output:
location: local-file-system
path: ./out/fern/python
- name: fernapi/fern-rust-sdk
version: 0.13.3
output:
location: local-file-system
path: ./out/fern/rust- Ensure
fern checkdoesn't fail.
fern check
# [api]: ✓ All checks passed- Run the generation command:
fern generate --local --log-level traceExpected behavior:
fern check should detect the property collision between the allOf base schema and anyOf branches instead of passing silently.
I see two valid approaches for handling this:
-
Type-Aware Resolution:
- If types match:
fern checkshould emit a warning regarding the duplicate field definition but proceed to generate valid code where the fields are merged into a single property. - If types differ:
fern checkmust fail with an error, as the conflict cannot be resolved safely without manual intervention.
- If types match:
-
Strict Validation:
fern checkshould always fail with an error (e.g.,Object has multiple properties named "id"), regardless of whether the types match or not. This avoids ambiguity and forces the user to explicitily resolve the schema structure.
Actual behavior:
fern checkpasses- Python generator crashes during linting stage (ruff) with
F601 Dictionary key literal "id" repeated - Rust generator produces unsafe code (contradicts the fail-fast principle)
Environment:
- macOS 26.1
- Node.js 25.2.1
- podman instead of docker (v5.7.0, 8Gi, 4 CPU machine)
python_config.json
python_generator_out.txt
python_ir.json
rust_config.json
rust_generator_out.txt
rust_ir.json
Fern CLI & Generator Versions
Fern CLI version: 3.4.2
SDK Generator versions:
- fernapi/fern-python-sdk v4.42.0
- fernapi/fern-rust-sdk v0.13.3
Workaround
No response
Are you interested in contributing a fix?
No