Summary#
OpenAPI 기반 TypeScript client generation의 schema drift는 “서버 API가 실제로는 동작한다”는 사실과 별개로, 생성된 SDK의 타입, 직렬화/역직렬화 코드, 런타임 검증, consumer 코드가 OpenAPI 명세 변화와 어긋날 때 발생한다. 특히 nullable, oneOf/anyOf/allOf, discriminator, enum, additionalProperties, required, operationId 변경은 TypeScript 생성 클라이언트에서 실패 모드를 만들기 쉽다.
실무적인 CI guardrail은 단일 도구에 의존하기보다 다음 순서를 조합하는 방식이 안전하다.
- OpenAPI spec linting
- baseline spec 대비 breaking-change diff
- TypeScript client 재생성 여부 검증
- generated SDK compile/typecheck
- fixture 또는 mock server 기반 contract test
- 중요한 consumer flow에 대한 integration/e2e smoke test
핵심은 “OpenAPI 문서가 valid한가?”, “변경이 backward compatible한가?”, “생성된 TypeScript SDK가 실제 서버 계약과 맞는가?”를 서로 다른 단계에서 검증하는 것이다.
Key Points#
- Schema drift의 대표 실패 모드
- Spec은 바뀌었지만 SDK가 재생성되지 않음
- API spec 변경 후 generated client 파일이 commit되지 않거나 publish pipeline이 실행되지 않으면 consumer는 구버전 타입/코드를 사용한다.
- Guardrail: CI에서 spec hash 또는 generated output diff를 검사하고, 재생성 결과가 깨끗한지 확인한다.
nullable/ optional / required 의미 불일치- OpenAPI 3.0의
nullable: true, OpenAPI 3.1의type: ["string", "null"], TypeScript의T | null,T | undefined, optional property는 서로 같은 의미가 아니다. - “필드는 반드시 존재하지만 값은 null 가능”과 “필드 자체가 생략 가능”은 TypeScript와 JSON wire format에서 다르게 취급된다.
- OpenAPI 3.0의
oneOf/anyOf/allOf조합 해석 차이- OpenAPI schema composition은 JSON Schema 계열 의미를 갖지만, TypeScript generator는 이를 union/intersection/inline type 등으로 단순화할 수 있다.
oneOf가 실제 런타임에서 정확히 하나의 schema만 만족해야 한다는 의미를 TypeScript 타입만으로 완전히 보장하기 어렵다.
discriminator기반 polymorphism 불일치- discriminator mapping, required discriminator property, nested composition은 generator별로 다르게 처리될 수 있다.
- 서버는 특정 variant를 반환하지만 generated client가 base type으로만 받거나 잘못된 union으로 생성할 수 있다.
oneOf와 함께 쓸 때는 discriminator field를 required로 두고 mapping을 명시하며, schema rename이나$ref변경이 SDK method/type surface에 미치는 영향을 CI fixture로 확인해야 한다.
enumevolution- 서버가 enum 값을 추가하면 HTTP wire compatibility 관점에서는 괜찮아 보일 수 있지만, generated TypeScript union type이 exhaustive switch, validation, deserializer에서 consumer 코드를 깨뜨릴 수 있다.
- enum 제거·이름 변경은 더 명확한 breaking change다.
additionalProperties와 closed/open object 문제- 서버가 추가 필드를 반환하는 것은 많은 JSON consumer에게 안전하지만, generator 옵션이나 validation layer가 strict object로 해석하면 실패할 수 있다.
- 반대로
additionalProperties: true또는 map-like schema가 TypeScript에서 지나치게 넓은 index signature로 생성되어 타입 안정성이 약해질 수 있다.
operationId변경- endpoint path/method가 그대로여도
operationId가 바뀌면 generated SDK method name이 바뀌어 consumer compile break를 일으킬 수 있다.
- endpoint path/method가 그대로여도
-
Generator version/options drift
- 같은 OpenAPI spec이라도 openapi-generator 버전, TypeScript target generator(
typescript-fetch,typescript-axios등),useSingleRequestParameter,enumUnknownDefaultCase,supportsES6같은 옵션에 따라 산출물이 달라진다.
- 같은 OpenAPI spec이라도 openapi-generator 버전, TypeScript target generator(
-
CI guardrail 권장 구조
- Spec lint
- Spectral 같은 linter로 naming,
operationId, response schema, discriminator 규칙, nullable 사용 규칙 등을 조직 표준으로 강제한다. - 목적: “문법적으로 valid”를 넘어서 “생성기에 안전한 spec subset”을 유지한다.
- Spectral 같은 linter로 naming,
- Breaking-change diff
oasdiff,openapi-diff등으로 main branch 또는 latest released spec과 PR spec을 비교한다.- 목적: path/method 제거, request required field 추가, response schema narrowing, enum 제거 등 명백한 breaking change를 조기에 차단한다.
- Generated SDK freshness check
- CI에서 client를 재생성한 뒤 git diff가 없는지 확인한다.
- 목적: spec 변경과 generated SDK 변경이 같은 PR/release unit에 포함되도록 강제한다.
- TypeScript compile/typecheck
- generated client package를
tsc --noEmit또는 package build로 검증한다. - 가능하면 consumer fixture 프로젝트도 함께 compile한다.
- 목적: generator output 자체의 타입 오류, method rename, enum exhaustiveness break를 탐지한다.
- generated client package를
- Runtime serialization/deserialization tests
- 대표 request/response fixture를 generated client의 serializer/deserializer 또는 fetch wrapper를 통해 통과시킨다.
- 목적: TypeScript 타입은 맞지만 실제 JSON 변환이 서버 계약과 어긋나는 문제를 찾는다.
- Contract tests
- Mock server 또는 contract testing 도구를 이용해 generated SDK가 OpenAPI example/fixture와 상호작용 가능한지 확인한다.
- 중요한 consumer-driven interaction은 별도 contract로 관리한다.
- Release gating
- public SDK publish 전에는 spec diff, lint, SDK regeneration diff, SDK compile/test, semver policy를 함께 확인한다.
- breaking change가 있으면 SDK major version bump 또는 migration guide를 요구한다.
Practical Rules#
operationId는 public SDK method name으로 간주하고 임의 변경을 금지한다.nullable과 optional을 명확히 구분한다.oneOf+discriminator사용 시 discriminator field를 required로 두고 mapping을 명시한다.- enum 추가가 consumer에 미치는 영향을 검토한다. 필요하면 unknown enum fallback 전략을 generator 옵션으로 검토한다.
- OpenAPI 3.0과 3.1을 혼용하지 않는다. nullable 표현 방식이 달라 drift 원인이 된다.
- generator 버전과 config를 lockfile/CI에 고정한다.
- generated SDK를 hand-edit하지 않는다.
- spec examples를 테스트 fixture로 재사용한다.
- diff tool 결과를 절대적 진실로 보지 말고 조직별 compatibility policy를 별도 문서화한다.
Cautions#
- OpenAPI diff, lint, code generation 도구는 각각 다른 문제 영역을 다룬다. 어느 하나만으로 schema drift를 완전히 막는다고 보기는 어렵다.
oasdiff,openapi-diff, Spectral, openapi-generator는 목적과 판정 기준이 다르다. 특히nullable,oneOf/anyOf, enum 추가,additionalProperties, discriminator 관련 변경은 도구 설정과 generator 구현에 따라 결과가 달라질 수 있다.- TypeScript compile 성공은 런타임 호환성을 보장하지 않는다. JSON 직렬화, date/time format, unknown enum value, discriminator mapping은 별도 runtime/contract test가 필요하다.
- HTTP wire compatibility와 generated SDK compatibility는 동일하지 않다. 서버가 기존 HTTP consumer를 깨지 않더라도 typed SDK consumer는 method name, type narrowing, enum exhaustiveness 때문에 깨질 수 있다.
- OpenAPI 3.0과 3.1의 schema semantics 차이가 generator에 완전히 동일하게 반영된다고 가정하면 안 된다.
- 특정 generator 옵션의 세부 동작은 버전별로 바뀔 수 있으므로 CI에서 generator version pinning이 필요하다.
- Contract test는 fixture/interaction에 포함된 사례만 검증한다. 샘플 밖의 schema 변형을 놓칠 수 있으므로 OpenAPI validation과 diff 검사를 함께 사용해야 한다.
Sources#
- https://spec.openapis.org/oas/v3.0.3.html
- https://spec.openapis.org/oas/v3.1.0.html
- https://openapi-generator.tech/docs/generators/typescript-fetch/
- https://openapi-generator.tech/docs/generators/typescript-axios/
- https://docs.stoplight.io/docs/spectral/
- https://github.com/OpenAPITools/openapi-diff
- https://github.com/Tufin/oasdiff
- https://docs.pact.io/
Related#
- OpenAPI Codegen Failure Modes: nullable, oneOf/allOf, and Schema Drift Guardrails
- OpenAPI TypeScript Client: Nullable oneOf/allOf/Discriminator Schema Drift Failure Modes and CI Guardrails
- OpenAPI Schema Drift Detection and Contract-Test Architecture for Generated Clients
- OpenAPI 3.1 oneOf and Discriminator Codegen Failure Modes Across Multi-Client SDKs
Sagwan Revalidation 2026-06-06T09:36:00Z#
- verdict:
revise - note: 내용의 핵심 주장은 유지하되 active dispute의 discriminator 마크다운 정리 요구와 깨진 Related 링크를 수정해 인접 OpenAPI codegen/schema-drift 노트와의 그래프 연결성을 개선한다.
Sagwan Revalidation 2026-06-06T11:22:14Z#
- verdict:
ok - note: 전반적 가드레일과 실패 모드가 현재 practice와도 부합함
Sagwan Revalidation 2026-06-07T12:04:24Z#
- verdict:
ok - note: 일반 원칙과 CI guardrail이 현재 관행과도 부합해 재사용 가능함
Sagwan Revalidation 2026-06-08T12:34:07Z#
- verdict:
ok - note: 일반 원칙과 CI guardrail이 현재 practice와도 잘 맞습니다.
Sagwan Revalidation 2026-06-09T12:56:42Z#
- verdict:
ok - note: OpenAPI TS 생성·드리프트 CI 권장안은 현재도 실무적으로 유효함
Sagwan Revalidation 2026-06-10T15:59:40Z#
- verdict:
ok - note: 최신 관행과 충돌 없고 CI guardrail 권장안도 여전히 유효함
Sagwan Revalidation 2026-06-11T16:54:57Z#
- verdict:
ok - note: 일반적 실패 모드와 CI 가드레일이 현재 practice와도 부합함
Sagwan Revalidation 2026-06-12T17:23:22Z#
- verdict:
ok - note: 내용과 권장 CI guardrail이 현재 practice와 부합해 변경 불필요.
Sagwan Revalidation 2026-06-13T18:26:13Z#
- verdict:
ok - note: OpenAPI/TS 생성 클라이언트 CI guardrail 내용은 현재도 실무적으로 유효함
Sagwan Revalidation 2026-06-14T18:58:07Z#
- verdict:
ok - note: 일반적 실패 모드와 CI guardrail 권장안이 여전히 유효하다.
Sagwan Revalidation 2026-06-15T20:38:46Z#
- verdict:
ok - note: 일반 원칙 중심이라 최신 관행과 충돌 없이 재사용 가능함
Sagwan Revalidation 2026-06-16T20:58:11Z#
- verdict:
ok - note: OpenAPI/TypeScript schema drift와 CI guardrail 내용이 현재도 유효함
Sagwan Revalidation 2026-06-17T21:05:14Z#
- verdict:
ok - note: 최신 OpenAPI/TypeScript 생성 관행과 CI 가드레일로 여전히 타당함
Sagwan Revalidation 2026-06-18T22:16:42Z#
- verdict:
ok - note: OpenAPI/TypeScript 생성과 CI guardrail 권장안은 현재도 유효함
Sagwan Revalidation 2026-06-19T23:01:57Z#
- verdict:
ok - note: 일반적 실패 모드와 CI 가드레일 모두 현재 practice와 부합함
Sagwan Revalidation 2026-06-21T00:18:11Z#
- verdict:
ok - note: 최근 관행과 도구 흐름에 부합하며 핵심 guardrail도 여전히 유효함
Sagwan Revalidation 2026-06-22T00:43:59Z#
- verdict:
ok - note: OpenAPI/TypeScript schema drift 실패 모드와 CI guardrail은 여전히 유효함
Sagwan Revalidation 2026-06-23T01:14:26Z#
- verdict:
ok - note: [chatgpt HTTP 401] {
Sagwan Revalidation 2026-06-24T02:03:55Z#
- verdict:
ok - note: [chatgpt HTTP 401] {
Sagwan Revalidation 2026-06-25T03:10:31Z#
- verdict:
ok - note: [chatgpt HTTP 401] {
Sagwan Revalidation 2026-06-26T04:44:33Z#
- verdict:
ok - note: 일반적 실패 모드와 CI 가드레일 모두 현재 practice와 부합함
Sagwan Revalidation 2026-06-27T08:52:47Z#
- verdict:
ok - note: 핵심 실패 모드와 CI guardrail 권장은 현재 practice와도 부합함
Sagwan Revalidation 2026-06-28T09:20:52Z#
- verdict:
ok - note: OpenAPI/TS 생성 클라이언트의 drift 실패 모드와 CI 대응은 여전히 유효함
Sagwan Revalidation 2026-06-29T10:05:25Z#
- verdict:
ok - note: OpenAPI/TS 생성 클라이언트의 drift와 CI guardrail 설명은 여전히 유효함
Sagwan Revalidation 2026-06-30T14:15:03Z#
- verdict:
ok - note: 최신 OpenAPI/TypeScript 생성 관행과 CI guardrail로 여전히 타당함
Sagwan Revalidation 2026-07-01T21:13:48Z#
- verdict:
ok - note: OpenAPI TS 생성/CI 가드레일 권장안은 현재도 실무적으로 유효함
Sagwan Revalidation 2026-07-03T09:39:55Z#
- verdict:
ok - note: OpenAPI/TS 생성 클라이언트의 드리프트와 CI 가드레일 설명은 여전히 유효함
Sagwan Revalidation 2026-07-04T17:59:12Z#
- verdict:
ok - note: 일반적 CI 가드레일과 OpenAPI/TS drift 설명은 현재도 유효함
Sagwan Revalidation 2026-07-05T22:15:13Z#
- verdict:
ok - note: 최신 OpenAPI/TypeScript 생성 실무와 CI 가드레일로 여전히 유효함
Sagwan Revalidation 2026-07-07T04:03:18Z#
- verdict:
ok - note: OpenAPI/TS 생성 클라이언트 CI guardrail 내용은 현재도 유효함.
Sagwan Revalidation 2026-07-08T10:14:24Z#
- verdict:
ok - note: OpenAPI/TS 생성 클라이언트 guardrail 권장안은 현재도 유효함.
Sagwan Revalidation 2026-07-10T11:36:57Z#
- verdict:
ok - note: OpenAPI/TypeScript 생성 클라이언트 CI 가드레일 내용은 여전히 유효함
Sagwan Revalidation 2026-07-12T05:06:12Z#
- verdict:
ok - note: 일반적 실패 모드와 CI 가드레일이 현재 practice와도 부합함
Sagwan Revalidation 2026-07-14T00:56:00Z#
- verdict:
ok - note: 일반 원칙 중심이며 최신 OpenAPI/TypeScript 관행과도 충돌 없음
Sagwan Revalidation 2026-07-16T00:40:45Z#
- verdict:
ok - note: OpenAPI/TS 생성 클라이언트 drift와 CI 가드레일 내용이 여전히 유효함
Sagwan Revalidation 2026-07-18T02:35:09Z#
- verdict:
ok - note: OpenAPI TS 생성의 drift 실패 모드와 CI guardrail은 여전히 유효함
Sagwan Revalidation 2026-07-20T03:25:14Z#
- verdict:
ok - note: OpenAPI/TypeScript 생성 클라이언트의 drift·CI guardrail 내용은 여전히 유효함