Packages
oaspec
0.39.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/openapi/diagnostic_format.gleam
//// Human-readable rendering of diagnostic pointer strings. The machine
//// layer — `Diagnostic.pointer` itself — is left untouched so future
//// `--format json` output or any consumer that wants the raw
//// JSON Pointer / dotted path can still read it verbatim. This module
//// only owns the CLI presentation transform.
////
//// The function is deliberately spec-context free: it never looks a
//// path up in a parsed spec. That is a larger follow-up; here the goal
//// is simply to turn `paths.~1pets.get.parameters.0` into
//// `GET /pets, parameter #0` so a reader does not need to know about
//// JSON Pointer escape rules.
import gleam/list
import gleam/string
/// Translate a diagnostic pointer into a human-readable location string.
///
/// Recognised shapes:
/// - `paths.<escaped>.<method>.parameters.<idx>[...]`
/// → `METHOD /path, parameter #idx[...]`
/// - `paths.<escaped>.<method>.requestBody[...]`
/// → `METHOD /path, requestBody[...]`
/// - `paths.<escaped>.<method>.responses.<status>[...]`
/// → `METHOD /path, response <status>[...]`
/// - `paths.<escaped>.<method>[...]`
/// → `METHOD /path[...]`
/// - `components.<kind>.<name>[...]`
/// → `<kind>.<name>[...]`
///
/// Falls back to the escape-decoded pointer if no pattern matches, so
/// callers always get *something* intelligible.
pub fn pointer_to_human(pointer: String) -> String {
case pointer {
"" -> "root"
_ -> format_segments(split_pointer(pointer), pointer)
}
}
fn split_pointer(pointer: String) -> List(String) {
// Known limitation: this splits on `.` unconditionally, so a pointer whose
// path contains a literal dot (e.g. `/v1.0/pets`) will fragment. Every
// pointer constructed inside this repo is dot-free today. If that changes,
// switch to splitting on `/` first and only normalising `.` where the
// segment is known not to be a user-supplied path.
pointer
|> string.replace("#/", "")
|> string.replace("/", ".")
|> string.split(".")
|> list.filter(fn(s) { s != "" })
|> list.map(unescape_segment)
}
/// JSON Pointer unescaping per RFC 6901: `~1` → `/`, `~0` → `~`.
/// Order matters: `~1` must be decoded first so `~01` ends up as `~1`,
/// not `/`.
fn unescape_segment(s: String) -> String {
s
|> string.replace("~1", "/")
|> string.replace("~0", "~")
}
fn format_segments(segments: List(String), original: String) -> String {
case segments {
["paths", path, method, "parameters", idx, ..rest] ->
with_tail(
string.uppercase(method) <> " " <> path <> ", parameter #" <> idx,
rest,
)
["paths", path, method, "requestBody", ..rest] ->
with_tail(
string.uppercase(method) <> " " <> path <> ", requestBody",
rest,
)
["paths", path, method, "responses", status, ..rest] ->
with_tail(
string.uppercase(method) <> " " <> path <> ", response " <> status,
rest,
)
["paths", path, method, ..rest] ->
with_tail(string.uppercase(method) <> " " <> path, rest)
["components", "schemas", name, ..rest] ->
with_tail("schemas." <> name, rest)
["components", "parameters", name, ..rest] ->
with_tail("parameters." <> name, rest)
["components", "responses", name, ..rest] ->
with_tail("responses." <> name, rest)
["components", "requestBodies", name, ..rest] ->
with_tail("requestBodies." <> name, rest)
["components", kind, name, ..rest] -> with_tail(kind <> "." <> name, rest)
_ -> default_format(segments, original)
}
}
fn with_tail(base: String, rest: List(String)) -> String {
case rest {
[] -> base
_ -> base <> " (" <> string.join(rest, ".") <> ")"
}
}
fn default_format(segments: List(String), original: String) -> String {
case segments {
[] -> original
_ -> string.join(segments, ".")
}
}