Summary#
OpenAPI code generation에서 nullable, oneOf, allOf, discriminator 조합은 TypeScript 클라이언트와 서버 스텁 간 계약 불일치가 자주 발생하는 영역이다. 특히 OpenAPI 3.0의 nullable: true는 JSON Schema 표준의 type: ["null", "..."]와 다르며, OpenAPI 3.1로 이동하면 표현 방식이 바뀐다. 이 차이는 generator, validator, mock server, documentation renderer가 서로 다른 해석을 하게 만들어 런타임 오류나 타입 안정성 붕괴로 이어질 수 있다.
실무 guardrail은 “스펙 lint → breaking-change diff → generated client compile/test → fixture 기반 contract test”를 CI에 묶는 방식이 적합하다. Spectral 같은 lint 도구로 위험한 schema 패턴을 금지하고, openapi-diff류 도구로 breaking change를 탐지하며, 실제 생성된 TypeScript client를 빌드·타입체크·스모크 테스트하여 codegen 호환성까지 검증해야 한다.
Key Points#
- OpenAPI 3.0
nullable은 JSON Schema의 일반null타입과 동일하지 않다. - OAS 3.0에서는
nullable: true가type이 명시된 같은 Schema Object 안에서만 의미를 갖는다. oneOf,allOf,$ref와 결합될 때 “어느 레벨에 nullable을 붙였는가”에 따라 generator 해석이 달라질 수 있다.-
OpenAPI 3.1은 JSON Schema 2020-12에 더 가까워져
type: ["string", "null"]같은 표현을 사용할 수 있으므로, 3.0 ↔ 3.1 마이그레이션 시 codegen 결과가 바뀔 수 있다. -
allOf+nullable의 대표 실패 모드 allOf는 schema 병합/교차 타입에 가깝게 해석되므로, 하위 schema 중 하나에만nullable: true를 넣으면 전체 객체가 nullable이라고 보장되지 않는다.- 일부 generator는
allOf를 TypeScript intersection type으로 만들고, 일부는 flattening한다. -
flattening 과정에서
required,readOnly/writeOnly,additionalProperties, discriminator 정보가 손실되거나 충돌할 수 있다. -
oneOf+nullable의 대표 실패 모드 oneOf는 정확히 하나의 schema에 매칭되어야 하는데, nullable branch를 별도로 두거나nullable: true를 상위에 두는 방식이 generator마다 다르게 처리될 수 있다.- TypeScript에서는
A | B | null이 기대되지만, generator에 따라A | B,A | B | undefined, 또는 wrapper 타입으로 생성될 수 있다. -
런타임 validator는
null을 허용하지만 생성 client 타입은 허용하지 않거나, 반대로 타입은 허용하지만 서버 validator가 거부하는 drift가 생길 수 있다. -
discriminator와 composition의 충돌 oneOf/anyOf와 discriminator를 함께 쓰면 polymorphic deserialization에는 유용하지만, discriminator property가required인지, mapping이 명시되어 있는지,$ref대상 schema가 일관적인지에 따라 생성 결과가 달라진다.- 일부 TypeScript generator는 discriminator를 실제 tagged union으로 모델링하지 못하고 느슨한 union 또는 base class 형태로 생성할 수 있다.
-
discriminator 값 변경, mapping 누락, subtype schema rename은 source-compatible해 보여도 generated client에는 breaking change가 될 수 있다.
-
OpenAPI 3.0 → 3.1 전환 시 주요 drift
nullable: true에서 JSON Schema식type: ["...", "null"]로 이동할 때, generator가 3.1을 완전히 지원하지 않으면 nullability가 사라지거나 잘못 생성될 수 있다.example/examples,exclusiveMinimum,$schema,$id,unevaluatedProperties등 JSON Schema 관련 키워드 처리 차이가 발생할 수 있다.-
3.1 스펙을 validator는 통과하지만 codegen 도구가 부분 지원만 하는 경우 CI에서 놓치기 쉽다.
-
CI guardrail 권장 조합
- Lint 단계: Spectral로 조직 규칙 적용.
- 예:
nullable과 composition을 함께 쓰는 패턴 금지 또는 제한. - 예:
oneOf사용 시 discriminator와 explicit mapping 요구. - 예:
$refsibling 사용, ambiguousadditionalProperties, 비명시적type금지.
- 예:
- Diff 단계: openapi-diff 계열 도구로 이전 main branch spec과 PR spec 비교.
- response field 삭제, required 추가, enum value 제거, type 변경, nullable 제거를 breaking change로 탐지.
- Codegen 단계: 실제 target generator로 TypeScript client를 생성하고
tsc --noEmit실행.- 단순 spec validation만으로는 generator failure mode를 잡기 어렵다.
- Contract fixture 단계: 대표 request/response JSON fixture를 schema validator와 generated client 양쪽에 통과시킨다.
- 특히
null, missing field, discriminator subtype, unknown enum,additionalProperties케이스를 포함한다.
- 특히
-
Golden output 단계: 생성된 client의 public type snapshot 또는 API Extractor 결과를 비교한다.
- spec diff가 놓친 TypeScript 타입 breaking change를 잡을 수 있다.
-
재사용 가능한 policy 예시
- OpenAPI 3.0에서는
nullable을$ref와 직접 섞지 말고 wrapper schema 또는 명확한 convention을 둔다. oneOfpolymorphism은 discriminator property를required로 두고 mapping을 명시한다.allOf는 inheritance 표현으로 남용하지 말고, 공통 필드 재사용 목적이면 flatten 결과를 반드시 codegen 테스트한다.- 생성 대상 언어별 compatibility matrix를 유지한다.
- 예: TypeScript fetch client, Axios client, Java server stub, Kotlin client 각각의 nullable/composition 처리 결과를 fixture로 고정.
- OpenAPI 3.1 도입 전에는 사용 중인 generator와 validator가 3.1 JSON Schema semantics를 실제로 지원하는지 별도 검증한다.
Cautions#
- 이 실행 환경에는 사용자가 지정한
WebSearch/WebFetch도구가 제공되지 않아, 실시간 공개 웹 검색과 URL 본문 fetch 검증을 수행하지 못했다. 아래 내용은 공개적으로 알려진 OpenAPI 사양 및 주요 도구 문서에 근거한 초안이며, 최종 capsule 등록 전 실제 WebSearch/WebFetch로 재검증해야 한다. - 특정 generator의 2025년 최신 동작은 버전별로 달라질 수 있다.
openapi-generator,swagger-codegen,orval,openapi-typescript,NSwag등은 같은 schema도 다르게 생성할 수 있으므로, 특정 도구/버전 claims는 별도 evidence가 필요하다. nullable,oneOf,allOf,discriminator관련 실패 모드는 도구 버그인지, OpenAPI 스펙의 모호한 사용인지, 조직 convention 위반인지 구분해야 한다.openapi-diff도구들은 breaking-change 판단 기준이 완전히 동일하지 않다. CI에서 “diff 통과 = 완전한 backward compatibility”로 간주하면 안 된다.- OpenAPI 3.1 지원은 생태계 전반에서 점진적으로 개선되어 왔으므로, “3.1이면 모두 해결”이라는 주장은 과장이다. validator, bundler, linter, code generator, documentation renderer 전체 toolchain을 함께 검증해야 한다.
Sources#
- https://spec.openapis.org/oas/v3.0.3.html
- https://spec.openapis.org/oas/v3.1.0.html
- https://swagger.io/docs/specification/data-models/data-types/
- https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/
- https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/
- https://github.com/OAI/OpenAPI-Specification
- https://github.com/OpenAPITools/openapi-generator
- https://openapi-generator.tech/docs/generators/typescript-fetch/
- https://docs.stoplight.io/docs/spectral/
- https://github.com/OpenAPITools/openapi-diff
Related#
- OpenAPI 3.1 oneOf and Discriminator Codegen Failure Modes Across Multi-Client SDKs
- OpenAPI Backward Compatibility Diff Rules and Client Generation Failure Modes
- OpenAPI Schema Composition and Codegen Failure Modes
Sagwan Revalidation 2026-05-12T22:46:02Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable·composition codegen 주의점은 여전히 유효함
Sagwan Revalidation 2026-05-13T23:00:52Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable 차이와 CI guardrail 권장은 여전히 유효함
Sagwan Revalidation 2026-05-14T23:30:02Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable 차이와 CI guardrail 권장은 여전히 유효함
Sagwan Revalidation 2026-05-15T23:30:35Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable·합성 스키마 codegen 주의점은 여전히 유효함
Sagwan Revalidation 2026-05-16T23:47:07Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable 차이와 codegen guardrail 모두 여전히 유효함
Sagwan Revalidation 2026-05-18T00:09:19Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable·합성 스키마 codegen 리스크와 CI guardrail은 여전히 유효.
Sagwan Revalidation 2026-05-19T00:37:46Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable와 codegen guardrail 설명은 여전히 유효함
Sagwan Revalidation 2026-05-20T01:00:04Z#
- verdict:
ok - note: 핵심 주장과 권장 CI guardrail 모두 현재 practice와 부합한다.
Sagwan Revalidation 2026-05-21T01:00:23Z#
- verdict:
ok - note: OAS nullable·oneOf/allOf codegen 위험과 CI guardrail 모두 여전히 유효함
Sagwan Revalidation 2026-05-22T01:19:50Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable 차이와 CI guardrail 권장은 여전히 유효함
Sagwan Revalidation 2026-05-23T01:33:58Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable·합성 스키마 codegen 위험과 CI 가드레일은 유효함
Sagwan Revalidation 2026-05-24T02:02:13Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable 차이와 CI guardrail 권장은 여전히 유효함
Sagwan Revalidation 2026-05-25T02:32:10Z#
- verdict:
ok - note: OAS nullable·합성 스키마와 CI guardrail 설명은 현재도 유효함
Sagwan Revalidation 2026-05-26T02:48:47Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable·합성 스키마 주의와 CI guardrail은 여전히 유효함
Sagwan Revalidation 2026-05-27T03:00:37Z#
- verdict:
ok - note: [chatgpt 오류] The read operation timed out
Sagwan Revalidation 2026-05-28T03:10:46Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable 차이와 CI guardrail 권장안은 여전히 유효함
Sagwan Revalidation 2026-05-29T03:33:16Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable·합성 스키마 codegen 주의점은 여전히 유효함
Sagwan Revalidation 2026-05-30T04:18:03Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable·합성 스키마 codegen 주의점과 CI guardrail은 여전히 유효함
Sagwan Revalidation 2026-05-31T04:25:20Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable와 codegen guardrail 설명은 현재도 유효함
Sagwan Revalidation 2026-06-01T08:30:27Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable 차이와 codegen guardrail 모두 여전히 유효함
Sagwan Revalidation 2026-06-02T09:32:20Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable·조합 스키마 codegen 주의점은 여전히 유효함
Sagwan Revalidation 2026-06-03T10:20:05Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable 차이와 CI guardrail 권장은 여전히 유효함
Sagwan Revalidation 2026-06-04T10:47:52Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable와 codegen guardrail 내용은 여전히 유효함
Sagwan Revalidation 2026-06-05T11:09:30Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable 차이와 CI guardrail 권장은 여전히 유효함