OpenAPI Schema Conversion#
Kubb's adapter-oas package converts OpenAPI/JSON Schema objects into a language-neutral AST before any code generator runs. The conversion is a multi-step pipeline: a rule table selects the right converter, converter functions build AST nodes, format-mapping constants translate OAS format strings to Kubb schema types, and a rich set of AST node types preserves semantic meaning for downstream generators.
Architecture Overview#
OAS SchemaObject
│
▼
schemaRules (ordered rule table) ← parseSchema.ts
│ first matching rule wins
▼
converter function (scalar / structural / composition)
│ produces
▼
ast.SchemaNode (one of ~15 typed variants)
│
▼
downstream plugin (plugin-ts, plugin-zod, …)
The rule table, converter modules, and AST node types are each in a different package; the constants file ties them together.
1. Rule Table — parseSchema.ts#
schemaRules is an ordered Array<SchemaRule> where each entry has a match predicate and a convert function. The first rule whose match returns true wins; no fall-through occurs.
Priority order :
$ref→convertRefallOf→convertAllOfoneOf/anyOf→convertUnionconst→convertConstformat(handled or date-ish withdateType !== false) →convertFormat- Binary (
contentMediaType: application/octet-stream) →convertBinary - OAS 3.1 multi-type array →
convertMultiType - Implicit string (has
minLength/maxLength/patternbut notype) →convertString - Implicit number (has
minimum/maximumbut notype) →convertNumeric enum→convertEnum- Object / properties →
convertObject prefixItems→convertTuple- Array →
convertArray - Explicit
type: 'string' | 'number' | 'integer' | 'boolean' | 'null'
Each converter receives a ConvertContext , which combines the pre-computed SchemaContext (normalized type, options, defaults) with ConverterDeps (a ParseFn for recursion, the document, and the $ref service).
2. Converter Functions#
Converters live in three files under packages/adapter-oas/src/emit/converters/:
| File | Handles |
|---|---|
scalar.ts | string, number, integer, boolean, null, const, enum, format, blob |
structural.ts | object, array, tuple |
composition.ts | $ref, allOf, oneOf/anyOf, multi-type |
The key format converter is convertFormat, which handles three classes:
int64→bigintorintegerdepending onoptions.integerType- Date/time (
date-time,date,time) → dispatches togetDateType, which honorsoptions.dateTypeto producedatetime,date, ortimenodes withoffset/local/representationflags - Everything else → looks up
formatMapviagetSchemaType, then emits a typed node;url,uuid, andemailalso carrymin/maxlength fromminLength/maxLength
3. Format Mapping — constants.ts#
formatMap is the static lookup table from OAS format strings to Kubb SchemaType values. It only lists formats whose AST type differs from the raw OAS type:
| OAS format | Kubb SchemaType |
|---|---|
uuid | uuid |
email, idn-email | email |
uri, uri-reference, url | url |
hostname, idn-hostname | url |
ipv4 | ipv4 |
ipv6 | ipv6 |
binary, byte | blob |
int32 | integer |
float, double | number |
Formats that require runtime option awareness (int64, date-time, date, time) are not in formatMap; they are listed separately in specialCasedFormats and handled directly in convertFormat.
isHandledFormat gates the format rule in schemaRules: it returns true for any formatMap entry or any specialCasedFormat. Formats not in either set fall back to the base type, and the parser emits a KUBB_UNSUPPORTED_FORMAT diagnostic.
4. AST Node Types — packages/ast/src/nodes/schema.ts#
The full SchemaType union has three layers:
PrimitiveSchemaType—string,number,integer,bigint,boolean,null,any,unknown,void,never,object,array,dateComplexSchemaType—tuple,union,intersection,enumSpecialSchemaType—ref,datetime,time,uuid,email,url,ipv4,ipv6,blob
Dedicated semantic node types for string formats :
| Type | Node | Extra fields |
|---|---|---|
uuid | FormatStringSchemaNode | min?, max? |
email | FormatStringSchemaNode | min?, max? |
url | UrlSchemaNode | path?, min?, max? |
ipv4 | Ipv4SchemaNode | — |
ipv6 | Ipv6SchemaNode | — |
The SchemaNodeBase shared by all nodes stores primitive — the underlying JavaScript primitive type (e.g., uuid nodes carry primitive: 'string') — enabling generators to fall back gracefully when they don't support a given special type .
The factory function createSchema is the single entry point for constructing any SchemaNode. It automatically fills primitive from the TYPE_TO_PRIMITIVE map .
5. Parser Options & Defaults#
DEFAULT_PARSER_OPTIONS provides the baseline ast.ParserOptions:
| Option | Default | Controls |
|---|---|---|
dateType | 'string' | How date-time/date/time formats are emitted |
integerType | 'bigint' | Whether int64 becomes bigint or integer |
unknownType | 'any' | AST type for unrecognized schemas |
emptySchemaType | 'any' | AST type for empty {} schemas |
enumSuffix | 'enum' | Suffix on derived enum names |
These defaults are applied in parser.ts and adapter.ts when callers don't supply explicit options.
Key Files#
| Path | Role |
|---|---|
packages/adapter-oas/src/emit/parseSchema.ts | schemaRules table, SchemaRule/ConvertContext types |
packages/adapter-oas/src/emit/converters/scalar.ts | Scalar/format/enum/const converters |
packages/adapter-oas/src/emit/converters/composition.ts | Ref/allOf/union converters |
packages/adapter-oas/src/emit/converters/structural.ts | Object/array/tuple converters |
packages/adapter-oas/src/emit/schemaShape.ts | getSchemaType, isHandledFormat, getDateType, flattenSchema |
packages/adapter-oas/src/constants.ts | formatMap, specialCasedFormats, DEFAULT_PARSER_OPTIONS |
packages/ast/src/nodes/schema.ts | All SchemaNode types, createSchema factory |