Skip to main content

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 the spec-file), mirroring the original document 1:1 (every field, including vendor/x-* extensions, unchanged) with exactly one difference: every $ref is 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 of schema directly as renderTemplate's data if it might be cyclic (see renderTemplate below); use schema for navigation and build a fresh plain object per render instead.
  • vars - an object with the resolved generator variables (see -v/--var and the variables section of generator.yml - generator-format.md), keyed by variable name.
  • openApiSpecJson - the effective OpenAPI document (after $ref resolution, --tags filtering, and conversion to the generator's declared openApiVersion) serialized as canonical JSON text. Unlike schema, this is plain, non-cyclic text - safe to embed verbatim in generated output (e.g. write it with writeFile and serve it from a generated controller) without the circular-reference concerns that apply to schema.
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 @param lines 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 the def/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 place buildDocComment(...)'s "#"-style result (or the raw description) 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 nullable key 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:

  1. field-name dispatch: a property no sibling object-variant also declares (dispatchField, dispatchValue: null) - "is this key present in the JSON object".
  2. 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-entry enum) - dispatchField names the shared property, dispatchValue is this variant's own literal (as a string, regardless of the underlying JSON type) - "does this key's value equal dispatchValue". This is the common kind/type-style tag real-world specs use without a formal discriminator keyword.

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" });