노트

OpenAPI 확장 필드와 enum 라벨

OpenAPI Specification Extensions

백엔드#ts#tooling · 연결된 개념 4개

쉽게 말하면

OpenAPI 확장 필드는 정해진 양식에 칸이 없는 정보를 적는 '비고'란이에요. enum 값마다 화면에 보일 이름(할인 제외 금액 등)을 둘 표준 칸이 없어서, x-로 시작하는 비고란에 적어 두죠.

비유가 깨지는 곳 비고란은 아는 도구만 읽어요. openapi-typescript는 x-enum-varnames 같은 관례는 알아보지만 직접 지은 확장은 무시해요. 그래서 라벨은 타입이 아니라 따로 뽑은 런타임 라벨 맵으로 둬요.

OpenAPI 명세(OpenAPI Specification)는 x-로 시작하는 필드를 명세 확장(Specification Extensions)으로 허용한다. 값은 아무 JSON이든 되고, 표준 도구는 모르는 확장을 무시한다. x-oai-·x-oas-로 시작하는 이름만 OpenAPI 이니셔티브가 예약해 두었다. 흔히 "vendor extension"이라 부른다.

enum 라벨을 둘 표준 자리가 없다

스키마의 enum은 허용되는 값의 목록일 뿐, 각 값에 붙는 이름이나 화면용 라벨을 담는 필드가 없다. 그래서 도구마다 확장을 만들어 썼다.

AmountType:
  type: string
  enum: [exclude, include]
  x-enum-varnames: [Exclude, Include]          # 코드에서 쓸 이름
  x-enum-descriptions: [할인 제외 금액, 할인 포함 금액]  # 설명
  • x-enum-varnames·x-enum-descriptions: OpenAPI Generator 등 여러 생성기가 알아보는 관례다. 배열 순서가 enum과 짝을 이룬다
  • x-enumNames: NSwag 계열
  • 서버 프레임워크가 만든 설명에서 직접 뽑아 자체 확장(x-enum-labels 같은)을 만들기도 한다. 예를 들어 drf-spectacular는 기본 설정에서 choice의 라벨을 enum의 description에 마크다운 목록으로 넣어 준다
  • OpenAPI 3.1부터는 스키마가 JSON Schema와 맞춰져 oneOf 안에 const와 title을 짝지어 라벨을 표현할 수도 있지만, 코드 생성기 지원이 고르지 않다

타입 생성기는 어떻게 다루나 (2026 기준)

openapi-typescript(7.x)는 기본적으로 enum을 문자열 유니언('exclude' | 'include')으로 만든다. x-enum-varnames·x-enum-descriptions(그리고 NSwag식 x-enumNames·x-enumDescriptions)는 알아본다. --enum 옵션으로 TS enum을 만들 때 멤버 이름과 주석으로 쓴다. 반면 직접 지은 확장(x-enum-labels 등)은 무시하므로 생성된 .ts에 들어가지 않는다.

라벨은 타입이 아니라 런타임 데이터

생성기가 확장을 알아보든 아니든, 따져 볼 점은 정보의 성격이다.

  • 값의 목록은 컴파일 타임에 검사할 대상이다 → 생성된 타입(유니언)
  • "할인 제외 금액" 같은 라벨은 화면에 그릴 때 찾아 쓰는 문자열이다 → 런타임 데이터

그래서 명세를 받아 한 번은 타입 생성기에 넘기고, 한 번은 스크립트로 확장 필드만 뽑아 enums.json 같은 라벨 맵을 만드는 구성이 자연스럽다. 코드에서는 labels.AmountType[value]로 꺼낸다. 라벨 맵의 키를 생성된 유니언 타입으로 좁혀 두면 값이 추가됐는데 라벨이 빠지는 일을 타입 검사로 잡을 수 있다(satisfies). 라벨을 다국어로 바꿔야 할 때도 타입을 건드리지 않는다.

명세와 실제 응답이 어긋나는 문제는 스키마 드리프트, 명세를 만드는 쪽은 DRF 시리얼라이저를 본다.

출처: OpenAPI Specification 3.2.0 — Specification Extensions · openapi-typescript — Enum extensions · openapi-typescript — CLI flags · drf-spectacular — Settings

연결된 개념

이 노트를 가리키는 문서

아직 없습니다.

뜻이 가까운 노트

  • type과 interface

    type(타입 별칭, Type Alias)과 interface는 둘 다 타입에 이름을 붙이는 방법이다. 객체 모양을 정의할 때는 대부분 바꿔 써도 되지만, 확장 방식과 표현 범위가 다르다.

  • 매핑된 타입과 keyof

    매핑된 타입(Mapped Types)은 기존 타입의 키를 하나씩 돌며 새 타입을 만드는 문법([K in keyof T]: …)이다. keyof T는 객체 타입의 키 유니언을, T[K](인덱스 접근 타입, Indexed Access Type)는 그 키의 값 타입을 꺼낸다.

  • 언어적 안티패턴

    이름, 타입, 주석이 말하는 것과 코드가 실제로 하는 일이 어긋나는 것. 읽는 사람을 잘못된 추측으로 이끈다.

  • 판별 유니언

    판별 유니언(Discriminated Union)은 모든 멤버가 리터럴 타입의 공통 필드(구분자(discriminant), 보통 type·kind·status)를 가진 유니언이다. 그 필드를 검사하면 TypeScript가 나머지 속성까지 자동으로 좁혀 준다.

  • 유니언·인터섹션·리터럴 타입

    유니언 타입(Union Type, A | B)은 "A이거나 B인 값"이고, 인터섹션 타입(Intersection Type, A & B)은 "A이면서 B인 값"이다. 리터럴 타입(Literal Type)은 'left'나 3처럼 특정 값 하나만 허용하는 타입이다.

보기 옵션