JavaScript API reference
The generator core supports all modern JavaScript features from ES2023 (string interpolation,
classes, let/const, modules...) thanks to QuickJS - including
its standard library: JSON, Map, Set, RegExp, Array/Object methods, and everything else
ES2023 itself defines are all available with no setup, on top of the custom globals/built-ins
documented below. JSON.stringify, in particular, doubles as a correct escaped-and-quoted string
literal for any JS/TS-family output (see also toStringLiteral below for other languages).
main.js (and anything it imports) runs with the global values and built-in functions described
below already in scope - no require/import needed for any of them.
Global values
schema- the parsed OpenAPI specification (contents of thespec-file), mirroring the original document 1:1 (every field, including vendor/x-*extensions, unchanged) with exactly one difference: every$refis replaced by the actual object it points to. A schema reached through two different$refs (or a self-referential one) is the same JS object (===), not a copy - so cyclic types work, but it also means you should never pass a piece ofschemadirectly asrenderTemplate'sdataif it might be cyclic (seerenderTemplatebelow); useschemafor navigation and build a fresh plain object per render instead.vars- an object with the resolved generator variables (see-v/--varand thevariablessection ofgenerator.yml-generator-format.md), keyed by variable name.openApiSpecJson- the effective OpenAPI document (after$refresolution,--tagsfiltering, and conversion to the generator's declaredopenApiVersion) serialized as canonical JSON text. Unlikeschema, this is plain, non-cyclic text - safe to embed verbatim in generated output (e.g. write it withwriteFileand serve it from a generated controller) without the circular-reference concerns that apply toschema.
renderTemplate("model.h.j2", { schemas: schema.components.schemas, namespace: vars.namespace }, "model.h");
Built-in functions
These are split into two groups: a handful of general string/identifier helpers usable from both
JS and templates (see templating.md for calling them from Inja), and a set of
OpenAPI-specific + file-output functions usable from JS only.
Common functions (JS and templates)
dump
Logs values for debugging - a replacement for console.log.
dump(...args: any): void
dump("found", Object.keys(schema.components.schemas).length, "schemas");
// -> Dump: found 3 schemas
toCamelCase, toPascalCase, toSnakeCase, toScreamingSnakeCase
Converts a string identifier from any case convention to the specified one.
toCamelCase(s: string): string // -> camelCase
toPascalCase(s: string): string // -> PascalCase
toSnakeCase(s: string): string // -> snake_case
toScreamingSnakeCase(s: string): string // SCREAMING_SNAKE_CASE
toPascalCase("pet-status"); // -> "PetStatus"
toSnakeCase("PetStatus"); // -> "pet_status"
isValidIdentifier, sanitizeIdentifier
isValidIdentifier checks whether a string is already a valid identifier in any C-like language
(starts with a letter/underscore, followed by letters/digits/underscores - no keyword check, since
that's language-specific). sanitizeIdentifier turns an arbitrary string into one by replacing
every other character with _ and prefixing _ if the result would start with a digit or be
empty. Neither case-converts or escapes target-language keywords - combine with
toCamelCase/toPascalCase/... and your own keyword list as needed.
isValidIdentifier(s: string): boolean
sanitizeIdentifier(s: string): string // e.g. "pet/status" -> "pet_status", "2fa" -> "_2fa"
isValidIdentifier("pet_status"); // -> true
isValidIdentifier("pet-status"); // -> false
sanitizeIdentifier("pet-status"); // -> "pet_status"
toStringLiteral
Produces a JSON-style double-quoted, backslash-escaped string literal - also valid syntax for most
C-family languages' double-quoted string literals (C/C++/Java/Kotlin/JS/TS/C#/Go all treat
\\/\"/\n/\r/\t the same way), so most generators need no hand-rolled escaping at all for
emitting a string constant. Doesn't add a target language's extra escaping needs beyond that
(e.g. Kotlin also escapes $ because of string templates) - wrap the result yourself for that one
additional rule if your target needs it.
toStringLiteral(s: string): string
toStringLiteral('He said "hi"\n'); // -> "\"He said \\\"hi\\\"\\n\""
splitPathTemplate
Splits an OpenAPI path template into its literal and {param} segments, in declaration order -
every generator that builds a path-interpolation expression for path parameters needs exactly this
split; this saves reimplementing the same regex/parsing logic (and risking it drifting slightly)
in every generator.
splitPathTemplate(path: string): Array<{ literal: string } | { param: string }>
splitPathTemplate("/pets/{petId}/ratings");
// -> [{ literal: "pets" }, { param: "petId" }, { literal: "ratings" }]
buildDocComment
Builds a doc comment block from a summary, a longer description, a list of { name, description } @param entries and optional extra doc tags, in one of four comment styles
selected by style - the literal marker string itself, so a call site reads as what it produces.
Returns null (not "") when
there's nothing to document - Inja's {% if %} treats an empty string as truthy (only
false/null/0/[] are falsy), so templates can guard with a plain {% if docComment %} and
reindent the (possibly multi-line) result to their own call site's depth via indent() (see
templating.md) instead of re-splitting/reindenting it by hand.
buildDocComment(
summary: string | null,
description?: string | null,
params?: Array<{ name: string, description?: string | null }>,
style?: "/** */" | "//" | "///" | "#", // default: "/** */"
tags?: Array<{ tag: string, text?: string | null }> | null
): string | null
"/** */"(default) - Javadoc/KDoc/TSDoc/Doxygen block comment: Kotlin, TypeScript/JS, Java, C#, C/C++."//"- a plain line-comment block, one//per line: Go, Rust (a plain, non-rustdoc comment), or any C-family/Dart target when a doc-comment marker isn't wanted or supported."///"- triple-slash doc comments: Dart (dartdoc) and Rust (rustdoc) both parse this as markdown prose, with no formal@param-style tag syntax of their own - the@paramlines this function still emits are valid markdown text, just not a tag either tool renders specially."#"- a plain line-comment block, one#per line: Ruby (RDoc/YARD's own convention - a comment block directly above thedef/class), Bash/shell, Perl, YAML, TOML, and Python only as a preceding comment, not a docstring - Python's actual doc convention is a"""..."""string literal as the function/class body's first statement, which has to be positioned inside the body, not attached above the declaration the way every style here is; that shape doesn't fit what this function returns (one string, meant to be printed immediately above a declaration), so a Python generator wanting real docstrings needs to placebuildDocComment(...)'s"#"-style result (or the rawdescription) into a"""..."""block itself, at the right spot in its own template.
In the "/** */" style, a literal */ or /* in the text is written as *\/ / /\* - otherwise
the former ends the comment early and the latter opens a nested comment in languages whose block
comments nest (Kotlin, Swift, Rust, Scala). These are markdown escapes, so KDoc/TSDoc/dartdoc still
render the original text. The line-comment styles need no escaping and leave text as-is.
tags are appended after the params entries, each as <tag> <text> (just <tag> when text is
empty). tag is written verbatim, sigil included, since tag syntax differs per language well
beyond the leading character: "@return" (KDoc/JSDoc/YARD), "@returns" (TSDoc), "\\return"
(Doxygen), ":rtype:" (Sphinx), and so on. Use it for whatever the built-in params can't
express - typed parameters ({ tag: "@param", text: "id [Integer] Pet id" } for YARD), return
values, @throws, @deprecated. Entries without a non-empty string tag throw.
Every text may span several lines: each line gets its own comment marker, and continuation lines
of a @param/tag entry are indented by two spaces so they stay attached to the tag (YARD requires
this; KDoc/TSDoc/Doxygen accept it). Trailing newlines - e.g. from a YAML description: | block -
are dropped rather than becoming an empty comment line.
An unrecognized style throws rather than silently falling back, so a typo in a generator's own
call site fails loudly at generation time instead of emitting silently-wrong comments.
buildDocComment("Lists pets.", "Returns pets owned by the caller.", [{ name: "limit", description: "Max results." }]);
// -> "/**\n * Lists pets.\n * Returns pets owned by the caller.\n *\n * @param limit Max results.\n */"
buildDocComment(null, null, []); // -> null
buildDocComment("Lists pets.", null, [], "//"); // -> "// Lists pets."
buildDocComment("Lists pets.", null, [], "///"); // -> "/// Lists pets."
buildDocComment("Lists pets.", null, [], "#"); // -> "# Lists pets."
buildDocComment("Lists pets.", "Paged.\nNewest first.\n", [], "#", [
{ tag: "@param", text: "limit [Integer] Max results.\nDefaults to 20." },
{ tag: "@return", text: "[Array<Pet>]" },
]);
// -> "# Lists pets.\n# Paged.\n# Newest first.\n#\n# @param limit [Integer] Max results.\n# Defaults to 20.\n# @return [Array<Pet>]"
disambiguateName
Disambiguates candidate against a list of already-taken names: returns candidate unchanged if
it isn't in reservedNames, else the first of candidateWrapper, candidateWrapper2, ... not
itself reserved. For a generator's hint-derived synthetic type name (an inline oneOf/allOf/
object with no $ref of its own) that turns out to collide with a real top-level schema's own
generated name - nameOf() only resolves names reached via $ref, so every generator that
synthesizes a name for an inline schema needs its own collision check against real schema names.
disambiguateName(candidate: string, reservedNames: string[]): string
disambiguateName("Foo", ["Bar"]); // -> "Foo"
disambiguateName("Foo", ["Foo", "Bar"]); // -> "FooWrapper"
disambiguateName("Foo", ["Foo", "FooWrapper"]); // -> "FooWrapper2"
OpenAPI-specific functions (JS only)
kindOf, constraintsOf, nameOf, collectOperations
Since $ref is already resolved on schema, these save you from re-deriving the bookkeeping every
generator otherwise needs: classifying a schema's shape, extracting its validation keywords,
recovering the name a resolved schema/parameter/requestBody/response was reached through (there's
no $ref string to read anymore), merging path-level + operation-level parameters, and resolving
each operation's effective security (its own, if declared - even declared empty, meaning "no
auth" - otherwise the document-level default, per the spec's own inheritance rule).
kindOf(schema: object): "Object" | "Array" | "Enum" | "AllOf" | "OneOf" | "AnyOf" | "Map" | "Primitive" | "Unknown"
constraintsOf(schema: object): { minimum?, maximum?, exclusiveMinimum?, exclusiveMaximum?, multipleOf?, minLength?, maxLength?, minItems?, maxItems?, minProperties?, maxProperties?, pattern?, uniqueItems? }
nameOf(x: object): string | null // null if x is an inline/anonymous definition, never reached via $ref
collectOperations(): Operation[] // { method, path, operationId, summary, description, tags, parameters, requestBody, responses, security }
security is Array<{ [schemeName: string]: string[] }> - each entry names a security scheme
(look it up in schema.components.securitySchemes for its type/scheme/name/in/... to know
how to apply it) plus the scopes required for that scheme (non-empty only for oauth2/
openIdConnect). An empty array means the operation needs no authentication; multiple entries in
one array element mean all of those schemes are required together (AND); multiple array elements
are alternatives (OR) - satisfying any one of them is enough.
for (const [name, s] of Object.entries(schema.components.schemas)) {
if (kindOf(s) === "Object") { /* ... */ }
}
for (const op of collectOperations()) {
const responseSchema = op.responses["200"]?.content?.["application/json"]?.schema;
const typeName = responseSchema && nameOf(responseSchema); // null -> anonymous/inline type
}
kindOf reads schema.type however your generator's declared openApiVersion shapes it (see
generator-format.md): a plain string for a
generator on OAS 3.0 ("string"), or - for a generator that declared openApiVersion: "3.1"/
"3.2" - either a string or a JSON Schema type array (["string", "null"]); either way kindOf
classifies correctly. Nullability follows the same split: OAS 3.0-targeting generators see a
schema.nullable boolean, OAS 3.1/3.2-targeting ones see "null" inside the type array instead
- there's no
nullablekey on that dialect.
unwrapSchema
Peels through a chain of single-branch oneOf/anyOf/allOf wrappers (the common "attach a
sibling description next to a $ref" idiom, or a allOf: [$ref X] pattern some OAS tooling
emits) down to the schema that actually determines the wire shape. A multi-variant oneOf/anyOf/
allOf (or a schema that isn't a composition wrapper at all) is returned unchanged. The returned
schema keeps its original JS object identity, so nameOf/kindOf still work on it.
unwrapSchema(schema: object): object
kindOf(schema.components.schemas.WrappedEnum); // -> "AllOf" (allOf: [$ref Enum])
kindOf(unwrapSchema(schema.components.schemas.WrappedEnum)); // -> "Enum"
firstSuccessResponse
Picks the response every generator otherwise re-derives by hand: the first declared 2xx status
code (sorted), falling back to "default", or null if responses has neither.
firstSuccessResponse(responses: object): { statusCode: string, response: object } | null
for (const op of collectOperations()) {
const picked = firstSuccessResponse(op.responses);
const schema = picked?.response.content?.["application/json"]?.schema;
}
flattenAllOf
Recursively merges a schema's own properties/required with every (possibly itself allOf-
bearing) branch of its allOf, into a single flat { properties, required } - every generator
handling allOf otherwise hand-rolls a one-level-only version of this same merge.
flattenAllOf(schema: object): { properties: { [name: string]: object }, required: string[] }
const merged = flattenAllOf(schema.components.schemas.Cat);
for (const [name, propSchema] of Object.entries(merged.properties)) {
const required = merged.required.includes(name);
}
resolveDiscriminator
Detects a discriminated oneOf/anyOf (discriminator.propertyName set, every variant a $ref
to a named schema - the one shape a target language with algebraic/discriminated-union support can
dispatch on a single literal property) and resolves each variant's component name plus its
discriminator literal value (from discriminator.mapping, falling back to the component name
itself when a variant has no explicit mapping entry, per the OpenAPI spec's own default). Returns
null for anything else (no discriminator, or a variant that isn't a named $ref) - treat that as
an ordinary, non-dispatchable union instead.
resolveDiscriminator(schema: object): { property: string, variants: Array<{ name: string, literal: string }> } | null
const disc = resolveDiscriminator(schema.components.schemas.Shape);
// -> { property: "shapeType", variants: [{ name: "Circle", literal: "circle" }, { name: "Square", literal: "Square" }] }
resolveUnionDispatch
Sibling to resolveDiscriminator, for the case a oneOf/anyOf has no discriminator at all.
What a generator targeting a language with algebraic/discriminated-union support but no native
support for OpenAPI's undiscriminated unions (e.g. Kotlin's sealed interfaces) needs to build a
runtime deserializer: classifies each variant's wire shape as "object"/"array"/"string"/
"number"/"boolean"/"any" (an unconstrained schema matching every possible JSON value, e.g. a
bare {}), and - for 2+ object-shaped variants - tries to tell them apart two ways, in order:
- field-name dispatch: a property no sibling object-variant also declares (
dispatchField,dispatchValue: null) - "is this key present in the JSON object". - field-value dispatch, only among whichever variants step 1 couldn't resolve: a property
every one of them declares, but each pins to a different literal (a
const, or a single-entryenum) -dispatchFieldnames the shared property,dispatchValueis this variant's own literal (as a string, regardless of the underlying JSON type) - "does this key's value equaldispatchValue". This is the commonkind/type-style tag real-world specs use without a formaldiscriminatorkeyword.
Either way, at most one object-shaped variant may be left with neither (dispatchField: null,
dispatchValue: null) - it becomes a trailing shape-only fallback ("is this a JSON object at
all", tried last). Returns variants in the same order as schema.oneOf/schema.anyOf - reorder
them yourself (e.g. move the field-less fallback last) if your template needs a specific dispatch
order. Returns null if schema isn't a oneOf/anyOf at all. Throws if a variant's shape can't
be classified (e.g. a nested oneOf/anyOf variant), if 2+ variants share a non-object dispatch
kind, or if 2+ object variants still can't be told apart after both passes - not every oneOf/
anyOf can be dispatched this way.
resolveUnionDispatch(schema: object): { variants: Array<{ dispatchKind: string, dispatchField: string | null, dispatchValue: string | null }> } | null
const dispatch = resolveUnionDispatch(schema.components.schemas.PaymentMethod);
// -> { variants: [{ dispatchKind: "object", dispatchField: "card_number", dispatchValue: null }, { dispatchKind: "object", dispatchField: null, dispatchValue: null }] }
// A "kind" field pinned to a different literal per variant, no unique property name otherwise:
const shapeDispatch = resolveUnionDispatch(schema.components.schemas.Shape);
// -> { variants: [{ dispatchKind: "object", dispatchField: "kind", dispatchValue: "circle" }, { dispatchKind: "object", dispatchField: "kind", dispatchValue: "square" }] }
kindOf/unwrapSchema/constraintsOf/nameOf/firstSuccessResponse/flattenAllOf/
resolveDiscriminator/resolveUnionDispatch only
work while a schema still has its original JS object identity - that is, before it's passed into
renderTemplate/renderTemplateToString (which converts data via an internal value tree with no
object identity) or into a function called from inside a template. Call them in main.js first,
build a plain object with the results, and pass that into the template - not the raw schema
object. firstSuccessResponse/flattenAllOf/resolveDiscriminator each build a fresh plain
result object per call, but everything nested inside that result (a response's schema, a merged-in
property that's a $ref, ...) keeps its original identity - so nameOf/kindOf still work on
those nested values afterwards.
File output (JS only)
copyFile
Copies a file from the generator folder straight into the output directory, unmodified - for
static runtime files that need no substitution (previously the only way to emit one was
renderTemplate against a template with no {{ }} in it at all). The engine still prepends its
own auto-generated-file header first (see the root README.md's "Auto-generated file headers") -
"unmodified" here means no templating happens on the file's own content, not that the header is
skipped.
copyFile(srcFileName: string, outFileName: string): void
copyFile("Validation.kt", "Validation.kt"); // no templating needed for this file
writeFile
Writes JS-computed string content straight into the output directory - the counterpart to
copyFile for content that doesn't exist as a file in the generator folder (e.g. openApiSpecJson
above). Like copyFile/renderTemplate, the engine prepends its own auto-generated-file header
first, unless the output extension has no recognized comment syntax (e.g. .json), in which case
the content is written unmodified.
writeFile(outFileName: string, content: string): void
writeFile("openapi.json", openApiSpecJson);
renderTemplate
Renders the template at templateFilePath (in the generator folder) into outFilePath with the
given data object. Additionally, you can pass a set of JS-defined functions that will be
available for use in that template.
data must not contain a circular reference - build a fresh, per-render plain object from
whatever part of schema you need (e.g. represent a self-referential property as { name, type: "TreeNode" }, a reference by name, rather than passing the nested schema object itself).
renderTemplate(
templateFilePath: string,
data: { [key: string]: any },
outFilePath: string,
functions?: { [name: string]: Function }
): void
renderTemplate("model.h.j2", { schemas: schemasForTemplate, namespace: vars.namespace }, "model.h");
Like copyFile, the file actually written to disk gets the engine's own auto-generated-file
header prepended first - see the root README.md's "Auto-generated file headers".
renderTemplateToString
The same as renderTemplate, but returns the rendered result as a string instead of writing a
file - useful for building up a larger output (e.g. rendering a fragment per schema, then
assembling and writing them yourself), or post-processing the text before writing it. Since
nothing is written to disk here, this result never gets the auto-generated-file header
renderTemplate/copyFile do - only the file you eventually write (with one of those two) does.
renderTemplateToString(
templateFilePath: string,
data: { [key: string]: any },
functions?: { [name: string]: Function }
): string
const fragment = renderTemplateToString("field.j2", { name: "id", type: "string" });