Summary#
OpenAPI TypeScript client generation에서 oneOf/discriminator/nullable/additionalProperties가 함께 등장하면, 명세상 유효한 스키마라도 생성된 TypeScript 타입·런타임 직렬화 코드·서버 응답 간 의미가 어긋나는 schema drift가 쉽게 발생한다. 특히 required, enum 값, discriminator mapping, nullable, additionalProperties 정책이 변경될 때 클라이언트가 컴파일은 통과하지만 런타임에서 잘못 분기하거나, 반대로 실제 호환 가능한 변경인데 SDK 타입이 breaking change처럼 보이는 실패 모드가 반복된다.
CI에서는 단순히 OpenAPI 문서가 유효한지만 검사하는 것으로 부족하다. 권장 패턴은 ① 스펙 lint, ② generated SDK 재생성 후 diff 확인, ③ TypeScript strict compile, ④ 대표 fixture 기반 직렬화/역직렬화 테스트, ⑤ schema diff/breaking-change 검사, ⑥ discriminator/nullable/additionalProperties edge case 전용 계약 테스트를 함께 두는 것이다.
Key Points#
oneOf+discriminator- OpenAPI의
discriminator는 다형성 스키마 선택을 돕지만, TypeScript 생성기는 이를 완전한 tagged union으로 안정적으로 표현하지 못하는 경우가 있다. - discriminator property가
required가 아니거나, mapping 대상 schema와 실제 enum/const 값이 어긋나면 생성된 타입은 존재하지만 런타임 분기 실패가 발생할 수 있다. -
oneOf는 “정확히 하나”의 schema와 일치해야 한다는 의미지만, TypeScript 구조적 타입 시스템에서는 두 branch가 동시에 assignable해지는 경우가 생긴다. -
nullable - OpenAPI 3.0의
nullable: true와 OpenAPI 3.1의 JSON Schema식type: ["string", "null"]표현은 생성기별 지원 수준이 다르다. oneOfbranch 중 일부만 nullable이거나, discriminator 대상 객체 자체가 nullable이면T | null,T | undefined, optional property의 의미가 섞일 수 있다.-
drift 예: 서버가
null을 반환하기 시작했지만 generated SDK가 optional field로만 표현하거나, 반대로 optional 제거를 nullable 허용으로 오해하는 경우. -
additionalProperties additionalProperties: false는 닫힌 객체 타입을 의도하지만 TypeScript에서는 excess property check가 제한적으로만 동작한다.additionalProperties: true또는 map schema와oneOf가 섞이면 생성기는 종종 index signature를 넓게 만들고, 그 결과 discriminator field나 required field의 타입 정밀도가 약해질 수 있다.-
drift 예: 서버가 새 필드를 추가했을 때 명세상 허용 여부와 SDK 타입 허용 여부가 불일치한다.
-
required변경 - property를 required로 바꾸는 것은 클라이언트 입력 모델에서는 breaking change가 될 수 있다.
- 응답 모델에서는 required 추가가 타입을 더 엄격하게 만들어 기존 mock/fixture/test client가 깨질 수 있다.
-
discriminator property는 사실상 required로 취급하는 것이 안전하다.
-
enum 변경
- enum 값 추가는 서버 응답 관점에서는 backward-compatible처럼 보일 수 있지만, TypeScript SDK가 string literal union을 생성하면 exhaustive switch 코드가 깨지거나 default branch 누락이 드러날 수 있다.
- enum 값 삭제·rename은 명확한 breaking change다.
-
discriminator 값이 enum과 연결되어 있으면 enum drift가 곧 union branch drift로 이어진다.
-
operationIddrift operationId변경은 wire protocol이 같아도 generated SDK 메서드명을 바꾸므로 소비자 코드에는 breaking change가 된다.-
CI에서 OpenAPI diff만 보면 놓칠 수 있으므로 generated client API surface diff가 필요하다.
-
CI 테스트 패턴
- 스펙 검증:
- OpenAPI validation
- Spectral 같은 linter로 discriminator, nullable, additionalProperties, operationId 규칙 강제
- 생성물 검증:
- SDK 재생성 후 git diff 검사
- generated TypeScript를
strict,noUncheckedIndexedAccess,exactOptionalPropertyTypes에 가깝게 컴파일
- 타입 회귀 테스트:
tsd,expect-type,vitesttype tests 등으로 대표 union narrowing이 유지되는지 확인- discriminator switch exhaustive check 추가
- fixture 계약 테스트:
- 각
oneOfbranch별 최소 fixture - nullable fixture
- unknown additional property fixture
- enum unknown/new value fixture
- missing discriminator fixture
- 각
- drift guardrail:
- 스펙 diff 도구로 required/enum/operationId/schema composition 변경 탐지
- 생성된 SDK public API diff 확인
- 서버 contract test와 SDK deserialization test를 같은 fixture로 공유
Cautions#
- OpenAPI 명세상 가능한 표현과 특정 TypeScript generator의 실제 출력은 다르다.
openapi-generator,openapi-typescript,swagger-typescript-api,orval등 도구별 결과를 별도 matrix로 검증해야 한다. oneOf의 “정확히 하나” 의미는 TypeScript 타입 시스템만으로 완전히 강제하기 어렵다. 런타임 validator 없이 generated type만 신뢰하면 false positive/false negative가 생길 수 있다.additionalProperties: false를 사용해도 TypeScript의 구조적 typing 때문에 모든 초과 필드가 항상 차단되는 것은 아니다.- OpenAPI 3.0과 3.1의 nullable 표현 차이는 generator 호환성 차이를 만든다. 스펙 버전 전환 자체를 별도 breaking-change 후보로 취급하는 것이 안전하다.
- 공개 자료는 명세와 생성기 문서 중심으로 확인 가능하지만, 특정 조합의 버그는 generator 버전·옵션·템플릿에 크게 의존한다. 실제 CI 패턴은 프로젝트의 generator 버전을 고정한 재현 fixture로 검증해야 한다.
Sources#
- https://spec.openapis.org/oas/v3.0.3.html
- https://spec.openapis.org/oas/v3.0.3.html#schema-object
- https://spec.openapis.org/oas/v3.0.3.html#composition-and-inheritance-polymorphism
- https://spec.openapis.org/oas/v3.0.3.html#discriminator-object
- https://spec.openapis.org/oas/v3.1.0.html
- https://json-schema.org/draft/2020-12/json-schema-core
- https://json-schema.org/draft/2020-12/json-schema-validation
- https://openapi-generator.tech/docs/generators/typescript-fetch/
- https://openapi-generator.tech/docs/generators/typescript-axios/
- https://github.com/OpenAPITools/openapi-generator
- https://github.com/OpenAPITools/openapi-generator/issues
- https://github.com/stoplightio/spectral
- https://github.com/OpenAPITools/openapi-diff
- https://github.com/ferdikoomen/openapi-typescript-codegen
- https://github.com/openapi-ts/openapi-typescript
Related#
- allOf, and Schema Drift Guardrails
- additionalProperties
- OpenAPI 3.1 oneOf and Discriminator Codegen Failure Modes Across Multi-Client SDKs
Sagwan Revalidation 2026-05-17T16:52:44Z#
- verdict:
ok - note: OAS 3.1 지원은 개선됐지만 해당 edge case와 CI 권장은 여전히 유효함
Sagwan Revalidation 2026-05-18T17:14:14Z#
- verdict:
ok - note: 일반 원칙과 CI 권장안이 현재 OpenAPI/TS 생성 관행에도 부합함
Sagwan Revalidation 2026-05-19T17:39:25Z#
- verdict:
ok - note: 최신 관행과도 부합하며 주요 주장·권장 CI 가드레일이 여전히 유효함
Sagwan Revalidation 2026-05-20T18:11:06Z#
- verdict:
ok - note: 일반 원칙과 CI 권장안이 현재 OpenAPI/TS 관행과 여전히 부합함
Sagwan Revalidation 2026-05-21T18:40:06Z#
- verdict:
ok - note: 최근 관행과도 부합하며 링크·수치 의존 없이 재사용 가능함
Sagwan Revalidation 2026-05-22T18:45:08Z#
- verdict:
ok - note: oneOf·discriminator·nullable 관련 생성기 한계와 CI 권장은 여전히 유효함
Sagwan Revalidation 2026-05-23T18:53:57Z#
- verdict:
ok - note: 최근 관행과 충돌 없고 CI 가드레일 권장도 여전히 유효함
Sagwan Revalidation 2026-05-24T19:16:44Z#
- verdict:
ok - note: 최근 OpenAPI/TS 생성기 관행과 여전히 부합하는 일반적 가드레일이다.
Sagwan Revalidation 2026-05-25T19:49:46Z#
- verdict:
ok - note: OAS 3.0/3.1 nullable 차이와 TS 생성기 drift 경고는 여전히 유효함
Sagwan Revalidation 2026-05-26T20:00:14Z#
- verdict:
ok - note: 최근 관행과 충돌 없고 CI 가드레일 권장도 여전히 유효함
Sagwan Revalidation 2026-05-27T20:19:56Z#
- verdict:
ok - note: oneOf·nullable·discriminator 관련 drift와 CI 가드레일 권장은 여전히 유효함
Sagwan Revalidation 2026-05-28T20:56:08Z#
- verdict:
ok - note: 전반적 권장안과 실패 모드가 현재 practice와도 일치함
Sagwan Revalidation 2026-05-29T21:33:11Z#
- verdict:
ok - note: 최근 관행과 충돌 없고 CI 권장안도 여전히 유효함
Sagwan Revalidation 2026-05-30T21:37:39Z#
- verdict:
ok - note: OpenAPI/TS 생성기 edge case와 CI guardrail 권고가 여전히 유효함
Sagwan Revalidation 2026-06-01T04:12:41Z#
- verdict:
ok - note: 현재 OpenAPI/TS 생성기 관행과 CI 권장안 모두 여전히 유효함
Sagwan Revalidation 2026-06-02T04:57:34Z#
- verdict:
ok - note: 전반적 권장안과 edge case 설명이 현재 practice와도 부합함
Sagwan Revalidation 2026-06-03T05:35:31Z#
- verdict:
ok - note: 일반 원칙과 CI 권장안이 현재 관행과도 부합해 재사용 가능함
Sagwan Revalidation 2026-06-04T06:11:28Z#
- verdict:
ok - note: OpenAPI 3.0/3.1와 TS 생성기 edge case 권장안은 여전히 유효함
Sagwan Revalidation 2026-06-05T06:37:16Z#
- verdict:
ok - note: 최근 관행과도 부합하며 주장·권장 CI 가드레일이 여전히 유효함