Packages
oaspec
0.13.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/openapi/resolver.gleam
import gleam/dict
import gleam/list
import gleam/option.{None, Some}
import gleam/result
import gleam/set.{type Set}
import gleam/string
import oaspec/openapi/schema.{
type SchemaObject, type SchemaRef, AllOfSchema, AnyOfSchema, ArraySchema,
Forbidden, Inline, ObjectSchema, OneOfSchema, Reference, Typed, Untyped,
}
import oaspec/openapi/spec.{type OpenApiSpec, type Resolved}
/// Errors during reference resolution.
pub type ResolveError {
UnresolvedRef(ref: String)
CircularRef(ref: String)
}
/// Resolve a $ref string to its schema name.
/// Example: "#/components/schemas/User" -> "User"
pub fn ref_to_name(ref: String) -> String {
ref
|> string.split("/")
|> list.last
|> result.unwrap("Unknown")
}
/// Resolve a SchemaRef to a SchemaObject, looking up $ref in components.
/// Tracks seen refs to detect circular references.
pub fn resolve_schema_ref(
schema_ref: SchemaRef,
spec: OpenApiSpec(Resolved),
) -> Result(SchemaObject, ResolveError) {
resolve_schema_ref_with_seen(schema_ref, spec, set.new())
}
/// Internal resolver that tracks visited refs for cycle detection.
fn resolve_schema_ref_with_seen(
schema_ref: SchemaRef,
spec: OpenApiSpec(Resolved),
seen: Set(String),
) -> Result(SchemaObject, ResolveError) {
case schema_ref {
Inline(schema) -> Ok(schema)
Reference(ref:, name:) -> {
case set.contains(seen, ref) {
True -> Error(CircularRef(ref:))
False -> {
let new_seen = set.insert(seen, ref)
case spec.components {
Some(components) -> {
case dict.get(components.schemas, name) {
Ok(Inline(schema)) -> Ok(schema)
Ok(Reference(ref: inner_ref, name: inner_name)) ->
resolve_schema_ref_with_seen(
Reference(ref: inner_ref, name: inner_name),
spec,
new_seen,
)
Error(_) -> Error(UnresolvedRef(ref:))
}
}
None -> Error(UnresolvedRef(ref:))
}
}
}
}
}
}
/// Resolve all $ref in a schema object's nested schemas (one level).
pub fn resolve_schema_refs_in_schema(
schema: SchemaObject,
spec: OpenApiSpec(Resolved),
) -> SchemaObject {
case schema {
ObjectSchema(properties:, additional_properties:, ..) as obj -> {
let resolved_props =
dict.map_values(properties, fn(_k, v) { resolve_one_ref(v, spec) })
let resolved_ap = case additional_properties {
Typed(ap) -> Typed(resolve_one_ref(ap, spec))
Untyped -> Untyped
Forbidden -> Forbidden
}
ObjectSchema(
..obj,
properties: resolved_props,
additional_properties: resolved_ap,
)
}
ArraySchema(items:, ..) as arr ->
ArraySchema(..arr, items: resolve_one_ref(items, spec))
AllOfSchema(metadata:, schemas:) -> {
let resolved = list_map_ref(schemas, spec)
AllOfSchema(metadata:, schemas: resolved)
}
OneOfSchema(metadata:, schemas:, discriminator:) -> {
let resolved = list_map_ref(schemas, spec)
OneOfSchema(metadata:, schemas: resolved, discriminator:)
}
AnyOfSchema(metadata:, schemas:, discriminator:) -> {
let resolved = list_map_ref(schemas, spec)
AnyOfSchema(metadata:, schemas: resolved, discriminator:)
}
other -> other
}
}
/// Try to resolve a single ref, keeping it as-is if resolution fails.
fn resolve_one_ref(
schema_ref: SchemaRef,
spec: OpenApiSpec(Resolved),
) -> SchemaRef {
case schema_ref {
Reference(..) ->
case resolve_schema_ref(schema_ref, spec) {
Ok(schema) -> Inline(schema)
Error(_) -> schema_ref
}
inline -> inline
}
}
/// Map a list of schema refs, resolving each.
fn list_map_ref(
refs: List(SchemaRef),
spec: OpenApiSpec(Resolved),
) -> List(SchemaRef) {
list.map(refs, fn(r) { resolve_one_ref(r, spec) })
}