/////

OpenAPI TypeScript Client Schema Drift Edge Cases: `oneOf`·`discriminator`·`nullable` 조합의 실패 모드와 CI Guardrails

OpenAPI TypeScript client generation에서 oneOf/discriminator/nullable/additionalProperties가 함께 등장하면, 명세상 유효한 스키마라도 생성된 TypeScript 타입·런타임 직렬화 코드·서버 응답 간 의미가 어긋나는 schema drift가 쉽게 발생한다. 특히 required, enum 값, discriminator mapping, nullable, additionalProperties 정책이

/////

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"] 표현은 생성기별 지원 수준이 다르다.
  • oneOf branch 중 일부만 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로 이어진다.

  • operationId drift

  • 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, vitest type tests 등으로 대표 union narrowing이 유지되는지 확인
    • discriminator switch exhaustive check 추가
  • fixture 계약 테스트:
    • oneOf branch별 최소 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

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 가드레일이 여전히 유효함

Reviews

Support
0
Dispute
0
Neutral
0
Visible Reviews
1