Packages
oaspec
0.45.0
0.68.0
0.67.0
0.66.0
0.65.0
0.64.0
0.63.0
0.62.0
0.61.0
0.60.0
0.59.0
0.58.1
0.58.0
0.57.0
0.56.0
0.55.0
0.54.0
0.53.0
0.52.0
0.51.0
0.50.0
0.49.0
0.48.0
0.47.0
0.46.0
0.45.0
0.44.0
0.43.0
0.42.0
0.41.0
0.40.0
0.39.0
0.38.0
0.37.0
0.36.0
0.35.0
0.34.0
0.33.0
0.32.0
0.31.0
0.30.0
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.3
0.6.1
0.6.0
0.5.0
0.4.0
0.3.0
0.1.3
Generate Gleam code from OpenAPI 3.x specifications
Current section
Files
Jump to
Current section
Files
src/oaspec/internal/codegen/schema_dispatch.gleam
/// Centralized schema dispatch logic.
/// This module is the single source of truth for mapping schemas to:
/// - Gleam type names
/// - To-string conversion expressions
/// - Decoder function names
/// - Encoder function names/expressions
///
/// Previously this logic was duplicated across types.gleam, decoders.gleam,
/// client.gleam, and guards.gleam (14+ functions with the same dispatch).
import gleam/option.{type Option, None, Some}
import oaspec/internal/codegen/context.{type Context}
import oaspec/internal/openapi/schema.{
type SchemaObject, type SchemaRef, AllOfSchema, AnyOfSchema, ArraySchema,
BooleanSchema, Inline, IntegerSchema, NumberSchema, ObjectSchema, OneOfSchema,
Reference, StringSchema,
}
import oaspec/internal/util/naming
/// Map a schema object to its Gleam type string (without nullable wrapping).
pub fn schema_base_type(schema: SchemaObject) -> String {
case schema {
StringSchema(..) -> "String"
IntegerSchema(..) -> "Int"
NumberSchema(..) -> "Float"
BooleanSchema(..) -> "Bool"
ArraySchema(items:, ..) -> "List(" <> schema_ref_type(items) <> ")"
ObjectSchema(..) -> "String"
AllOfSchema(..) -> "String"
OneOfSchema(..) -> "String"
AnyOfSchema(..) -> "String"
}
}
/// Map a schema object to its Gleam type string (with nullable wrapping).
pub fn schema_type(schema: SchemaObject) -> String {
let base = schema_base_type(schema)
case schema.is_nullable(schema) {
True -> "Option(" <> base <> ")"
False -> base
}
}
/// Map a schema ref to a Gleam type string.
fn schema_ref_type(ref: SchemaRef) -> String {
case ref {
Inline(schema) -> schema_base_type(schema)
Reference(name:, ..) -> naming.schema_to_type_name(name)
}
}
/// Map a schema ref to a qualified Gleam type (with types. prefix for refs).
pub fn schema_ref_qualified_type(ref: SchemaRef) -> String {
case ref {
Inline(schema) -> schema_base_type(schema)
Reference(name:, ..) -> "types." <> naming.schema_to_type_name(name)
}
}
/// Map a primitive schema to its to_string expression.
/// Returns the expression that converts a value to String for URL encoding.
pub fn to_string_expr(schema: SchemaObject, value: String) -> String {
case schema {
IntegerSchema(..) -> "int.to_string(" <> value <> ")"
NumberSchema(..) -> "float.to_string(" <> value <> ")"
BooleanSchema(..) -> "bool.to_string(" <> value <> ")"
StringSchema(..) -> value
_ -> value
}
}
/// Map a schema ref to a to_string expression, resolving refs if needed.
pub fn schema_ref_to_string_expr(
ref: SchemaRef,
value: String,
ctx: Context,
) -> String {
case ref {
Inline(schema) -> to_string_expr(schema, value)
Reference(name:, ..) -> {
case context.resolve_schema_ref(ref, ctx) {
Ok(StringSchema(enum_values:, ..)) if enum_values != [] ->
"encode.encode_"
<> naming.to_snake_case(name)
<> "_to_string("
<> value
<> ")"
Ok(resolved) -> to_string_expr(resolved, value)
// nolint: thrown_away_error -- unresolved refs fall back to the raw accessor; the resolver reports the ref error separately
Error(_) -> value
}
}
}
}
/// Map a schema ref to its decoder expression (for inline primitives)
/// or decoder function name (for references).
///
/// Inline complex schemas (object, allOf, oneOf, anyOf) are not
/// supported here — they must be defined under `components/schemas`
/// and referenced via `$ref`. The catch-all panics so unsupported
/// patterns fail fast instead of silently generating broken code
/// (#290).
pub fn decoder_expr(ref: SchemaRef) -> String {
case ref {
Inline(StringSchema(..)) -> "decode.string"
Inline(IntegerSchema(..)) -> "decode.int"
Inline(NumberSchema(..)) -> "decode.float"
Inline(BooleanSchema(..)) -> "decode.bool"
Reference(name:, ..) -> naming.to_snake_case(name) <> "_decoder()"
// Issue #390: this panic guards a contract that the hoist pass
// (oaspec/internal/openapi/hoist) is responsible for: every inline
// composite (object / allOf / oneOf / anyOf / array of composites)
// must be hoisted to components/schemas before codegen runs, so
// decoder_expr only ever sees primitives or References. If this
// branch fires, the bug is either in hoist or in a new spec shape
// that hoist doesn't recognise — fix at the hoist level rather
// than here. Converting this dispatch to Result was considered in
// #390 but rippled through 9+ caller sites and would have masked
// the contract. The panic stays as a fail-fast tripwire.
Inline(schema) ->
panic as {
"oaspec: inline "
<> schema_kind(schema)
<> " schema reached decoder_expr after hoist. "
<> "This is a hoist contract bug — inline composites must be "
<> "moved to components/schemas before codegen. Please open an "
<> "issue at https://github.com/nao1215/oaspec/issues with the "
<> "spec that triggered this."
}
}
}
/// Map a schema ref to its JSON encoder expression.
pub fn json_encoder_expr(ref: SchemaRef, value: String) -> String {
case ref {
Inline(StringSchema(..)) -> "json.string(" <> value <> ")"
Inline(IntegerSchema(..)) -> "json.int(" <> value <> ")"
Inline(NumberSchema(..)) -> "json.float(" <> value <> ")"
Inline(BooleanSchema(..)) -> "json.bool(" <> value <> ")"
Reference(name:, ..) ->
"encode_" <> naming.to_snake_case(name) <> "_json(" <> value <> ")"
// Issue #390: same hoist contract as decoder_expr above —
// inline composites must be hoisted before reaching this dispatch.
Inline(schema) ->
panic as {
"oaspec: inline "
<> schema_kind(schema)
<> " schema reached json_encoder_expr after hoist. "
<> "This is a hoist contract bug; please file an issue with the spec."
}
}
}
/// Map a schema ref to its JSON encoder function reference (for higher-order use).
pub fn json_encoder_fn(ref: SchemaRef) -> String {
case ref {
Inline(StringSchema(..)) -> "json.string"
Inline(IntegerSchema(..)) -> "json.int"
Inline(NumberSchema(..)) -> "json.float"
Inline(BooleanSchema(..)) -> "json.bool"
Reference(name:, ..) -> "encode_" <> naming.to_snake_case(name) <> "_json"
// Issue #390: see decoder_expr's panic note. Inline composites
// are a post-hoist invariant violation, not a user-facing error.
Inline(schema) ->
panic as {
"oaspec: inline "
<> schema_kind(schema)
<> " schema reached json_encoder_fn after hoist. "
<> "This is a hoist contract bug; please file an issue with the spec."
}
}
}
/// Map a schema ref to its to_string function reference (for list.map etc).
pub fn to_string_fn(ref: SchemaRef, ctx: Context) -> String {
case ref {
Inline(StringSchema(..)) -> "fn(x) { x }"
Inline(IntegerSchema(..)) -> "int.to_string"
Inline(NumberSchema(..)) -> "float.to_string"
Inline(BooleanSchema(..)) -> "bool.to_string"
Reference(name:, ..) -> {
case context.resolve_schema_ref(ref, ctx) {
Ok(StringSchema(enum_values:, ..)) if enum_values != [] ->
"encode.encode_" <> naming.to_snake_case(name) <> "_to_string"
Ok(resolved) -> to_string_fn(Inline(resolved), ctx)
// nolint: thrown_away_error -- unresolved refs fall back to identity; the resolver reports the ref error separately
Error(_) -> "fn(x) { x }"
}
}
// Issue #390: see decoder_expr's panic note. Inline composites are
// a post-hoist invariant violation, not a user-facing error.
Inline(schema) ->
panic as {
"oaspec: inline "
<> schema_kind(schema)
<> " schema reached to_string_fn after hoist. "
<> "This is a hoist contract bug; please file an issue with the spec."
}
}
}
/// Human-readable name for a schema variant (for error messages).
fn schema_kind(schema: SchemaObject) -> String {
case schema {
StringSchema(..) -> "string"
IntegerSchema(..) -> "integer"
NumberSchema(..) -> "number"
BooleanSchema(..) -> "boolean"
ArraySchema(..) -> "array"
ObjectSchema(..) -> "object"
AllOfSchema(..) -> "allOf"
OneOfSchema(..) -> "oneOf"
AnyOfSchema(..) -> "anyOf"
}
}
/// Resolve a schema ref and return the base type (for parameter type resolution).
pub fn resolve_param_type(schema_ref: Option(SchemaRef), ctx: Context) -> String {
case schema_ref {
Some(Inline(schema)) -> schema_base_type(schema)
Some(Reference(name:, ..) as ref) -> {
case context.resolve_schema_ref(ref, ctx) {
Ok(ArraySchema(items:, ..)) -> "List(" <> schema_ref_type(items) <> ")"
_ -> "types." <> naming.schema_to_type_name(name)
}
}
None -> "String"
}
}