Packages
oaspec
0.50.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@generate.erl
-module(oaspec@generate).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/oaspec/generate.gleam").
-export([validate_only_with_progress_and_locations/4, validate_only/2, generate_all_files/1, generate_with_progress_and_locations/4, generate/2]).
-export_type([generation_summary/0, validation_summary/0, generate_error/0, prepared_context/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type generation_summary() :: {generation_summary,
list(oaspec@internal@codegen@context:generated_file()),
binary(),
list(oaspec@openapi@diagnostic:diagnostic())}.
-type validation_summary() :: {validation_summary,
binary(),
list(oaspec@openapi@diagnostic:diagnostic())}.
-type generate_error() :: {validation_errors,
list(oaspec@openapi@diagnostic:diagnostic())}.
-type prepared_context() :: {prepared_context,
oaspec@internal@codegen@context:context(),
binary(),
list(oaspec@openapi@diagnostic:diagnostic())}.
-file("src/oaspec/generate.gleam", 55).
?DOC(
" Shared pipeline: normalize → resolve → capability_check → hoist → dedup → validate.\n"
" Returns a validated context with accumulated warnings, or errors.\n"
"\n"
" Each stage is wrapped with `progress.timed` so `reporter` sees a\n"
" \"stage: X took Yms\" line per phase. The GitHub REST OpenAPI is\n"
" large enough that without these lines callers can't tell whether\n"
" the process is hung or working — see issue #352.\n"
).
-spec prepare_context(
oaspec@internal@openapi@spec:open_api_spec(oaspec@internal@openapi@spec:unresolved()),
oaspec@config:config(),
oaspec@internal@progress:reporter(),
oaspec@internal@openapi@location_index:location_index()
) -> {ok, prepared_context()} | {error, generate_error()}.
prepare_context(Spec, Cfg, Reporter, Index) ->
Spec_title = <<<<(erlang:element(2, erlang:element(3, Spec)))/binary,
" v"/utf8>>/binary,
(erlang:element(4, erlang:element(3, Spec)))/binary>>,
{Elapsed, Spec@1} = oaspec@internal@progress:timed(
fun() -> oaspec@internal@openapi@normalize:normalize(Spec) end
),
oaspec@internal@progress:report(
Reporter,
<<<<"normalize OAS 3.1 → 3.0 patterns (took "/utf8,
(oaspec@internal@progress:format_ms(Elapsed))/binary>>/binary,
")"/utf8>>
),
{Elapsed@1, Resolved} = oaspec@internal@progress:timed(
fun() -> oaspec@internal@openapi@resolve:resolve(Spec@1) end
),
oaspec@internal@progress:report(
Reporter,
<<<<"resolve component $ref aliases (took "/utf8,
(oaspec@internal@progress:format_ms(Elapsed@1))/binary>>/binary,
")"/utf8>>
),
gleam@result:'try'(
begin
_pipe = Resolved,
gleam@result:map_error(
_pipe,
fun(Errors) -> {validation_errors, Errors} end
)
end,
fun(Spec@2) ->
{Elapsed@2, Spec@3} = oaspec@internal@progress:timed(
fun() ->
oaspec@internal@openapi@filter:apply(
Spec@2,
oaspec@config:include(Cfg)
)
end
),
oaspec@internal@progress:report(
Reporter,
<<<<"apply include filter (took "/utf8,
(oaspec@internal@progress:format_ms(Elapsed@2))/binary>>/binary,
")"/utf8>>
),
{Elapsed@3, Capability_issues} = oaspec@internal@progress:timed(
fun() ->
_pipe@1 = oaspec@internal@openapi@capability_check:check(
Spec@3,
Index
),
oaspec@openapi@diagnostic:filter_by_mode(
_pipe@1,
oaspec@config:mode(Cfg)
)
end
),
oaspec@internal@progress:report(
Reporter,
<<<<"capability check (took "/utf8,
(oaspec@internal@progress:format_ms(Elapsed@3))/binary>>/binary,
")"/utf8>>
),
Capability_errors = oaspec@openapi@diagnostic:errors_only(
Capability_issues
),
Capability_warnings = oaspec@openapi@diagnostic:warnings_only(
Capability_issues
),
gleam@result:'try'(case gleam@list:is_empty(Capability_errors) of
false ->
{error, {validation_errors, Capability_errors}};
true ->
{ok, nil}
end, fun(_) ->
{Elapsed@4, Spec@4} = oaspec@internal@progress:timed(
fun() -> oaspec@internal@openapi@hoist:hoist(Spec@3) end
),
oaspec@internal@progress:report(
Reporter,
<<<<"hoist inline complex schemas (took "/utf8,
(oaspec@internal@progress:format_ms(Elapsed@4))/binary>>/binary,
")"/utf8>>
),
{Elapsed@5, Spec@5} = oaspec@internal@progress:timed(
fun() -> oaspec@internal@openapi@dedup:dedup(Spec@4) end
),
oaspec@internal@progress:report(
Reporter,
<<<<"deduplicate generated names (took "/utf8,
(oaspec@internal@progress:format_ms(Elapsed@5))/binary>>/binary,
")"/utf8>>
),
Ctx = oaspec@internal@codegen@context:new(Spec@5, Cfg),
{Elapsed@6, Preserved_warnings} = oaspec@internal@progress:timed(
fun() ->
_pipe@2 = oaspec@internal@openapi@capability_check:check_preserved(
Ctx,
Index
),
oaspec@openapi@diagnostic:filter_by_mode(
_pipe@2,
oaspec@config:mode(Cfg)
)
end
),
oaspec@internal@progress:report(
Reporter,
<<<<"preserved-feature warnings (took "/utf8,
(oaspec@internal@progress:format_ms(Elapsed@6))/binary>>/binary,
")"/utf8>>
),
{Elapsed@7, Validation_issues} = oaspec@internal@progress:timed(
fun() ->
_pipe@3 = oaspec@internal@codegen@validate:validate(
Ctx
),
oaspec@openapi@diagnostic:filter_by_mode(
_pipe@3,
oaspec@config:mode(Cfg)
)
end
),
oaspec@internal@progress:report(
Reporter,
<<<<"validate spec for unsupported features (took "/utf8,
(oaspec@internal@progress:format_ms(Elapsed@7))/binary>>/binary,
")"/utf8>>
),
Blocking_errors = oaspec@openapi@diagnostic:errors_only(
Validation_issues
),
Validation_warnings = oaspec@openapi@diagnostic:warnings_only(
Validation_issues
),
gleam@result:'try'(
case gleam@list:is_empty(Blocking_errors) of
false ->
{error, {validation_errors, Blocking_errors}};
true ->
{ok, nil}
end,
fun(_) ->
Warnings = lists:append(
[Capability_warnings,
Preserved_warnings,
Validation_warnings]
),
{ok, {prepared_context, Ctx, Spec_title, Warnings}}
end
)
end)
end
).
-file("src/oaspec/generate.gleam", 226).
?DOC(
" Combined `validate_only` entry point that accepts both a\n"
" `LocationIndex` and a `Reporter`. Issue #411 + #352.\n"
).
-spec validate_only_with_progress_and_locations(
oaspec@internal@openapi@spec:open_api_spec(oaspec@internal@openapi@spec:unresolved()),
oaspec@internal@openapi@location_index:location_index(),
oaspec@config:config(),
oaspec@internal@progress:reporter()
) -> {ok, validation_summary()} | {error, generate_error()}.
validate_only_with_progress_and_locations(Spec, Index, Cfg, Reporter) ->
gleam@result:'try'(
prepare_context(Spec, Cfg, Reporter, Index),
fun(Prepared) ->
{ok,
{validation_summary,
erlang:element(3, Prepared),
erlang:element(4, Prepared)}}
end
).
-file("src/oaspec/generate.gleam", 212).
?DOC(
" Validation-only pipeline: parse → normalize → resolve → capability_check → hoist → dedup → validate.\n"
" Runs the same checks as generate() but skips code generation and file writing.\n"
).
-spec validate_only(
oaspec@internal@openapi@spec:open_api_spec(oaspec@internal@openapi@spec:unresolved()),
oaspec@config:config()
) -> {ok, validation_summary()} | {error, generate_error()}.
validate_only(Spec, Cfg) ->
validate_only_with_progress_and_locations(
Spec,
oaspec@internal@openapi@location_index:empty(),
Cfg,
oaspec@internal@progress:noop()
).
-file("src/oaspec/generate.gleam", 260).
?DOC(
" Generate shared files (types, decoders, encoders, guards).\n"
"\n"
" `middleware.gleam` used to be emitted here too, but its `Handler` shape\n"
" did not actually compose with the generated client or server APIs (see\n"
" issue #116). It is no longer part of the default generated surface;\n"
" the `oaspec/internal/codegen/middleware` module is kept only as a library-level\n"
" helper for consumers who want to assemble their own middleware chain.\n"
).
-spec generate_shared(oaspec@internal@codegen@context:context()) -> list(oaspec@internal@codegen@context:generated_file()).
generate_shared(Ctx) ->
Type_files = oaspec@internal@codegen@types:generate(Ctx),
Decoder_files = oaspec@internal@codegen@decoders:generate(Ctx),
Encoder_files = oaspec@internal@codegen@encoders:generate(Ctx),
Guard_files = oaspec@internal@codegen@guards:generate(Ctx),
lists:append([Type_files, Decoder_files, Encoder_files, Guard_files]).
-file("src/oaspec/generate.gleam", 240).
?DOC(" Pure file generation: produce all GeneratedFile values without any IO.\n").
-spec generate_all_files(oaspec@internal@codegen@context:context()) -> list(oaspec@internal@codegen@context:generated_file()).
generate_all_files(Ctx) ->
Shared = generate_shared(Ctx),
Server_files = case oaspec@config:mode(
oaspec@internal@codegen@context:config(Ctx)
) of
server ->
oaspec@internal@codegen@server:generate(Ctx);
both ->
oaspec@internal@codegen@server:generate(Ctx);
client ->
[]
end,
Client_files = case oaspec@config:mode(
oaspec@internal@codegen@context:config(Ctx)
) of
client ->
oaspec@internal@codegen@client:generate(Ctx);
both ->
oaspec@internal@codegen@client:generate(Ctx);
server ->
[]
end,
lists:append([Shared, Server_files, Client_files]).
-file("src/oaspec/generate.gleam", 190).
?DOC(
" Combined entry point that accepts both a `LocationIndex` and a\n"
" `Reporter`. Issue #411 + #352. The CLI uses this so it can show\n"
" per-stage progress AND surface `path:line:column:` in capability\n"
" errors at the same time. Library callers that need only one of the\n"
" two can pass `location_index.empty()` or `progress.noop()`.\n"
).
-spec generate_with_progress_and_locations(
oaspec@internal@openapi@spec:open_api_spec(oaspec@internal@openapi@spec:unresolved()),
oaspec@internal@openapi@location_index:location_index(),
oaspec@config:config(),
oaspec@internal@progress:reporter()
) -> {ok, generation_summary()} | {error, generate_error()}.
generate_with_progress_and_locations(Spec, Index, Cfg, Reporter) ->
gleam@result:'try'(
prepare_context(Spec, Cfg, Reporter, Index),
fun(Prepared) ->
{Elapsed, Files} = oaspec@internal@progress:timed(
fun() -> generate_all_files(erlang:element(2, Prepared)) end
),
oaspec@internal@progress:report(
Reporter,
<<<<"render generated source files (took "/utf8,
(oaspec@internal@progress:format_ms(Elapsed))/binary>>/binary,
")"/utf8>>
),
{ok,
{generation_summary,
Files,
erlang:element(3, Prepared),
erlang:element(4, Prepared)}}
end
).
-file("src/oaspec/generate.gleam", 173).
?DOC(
" Pure generation pipeline: parse → normalize → resolve → capability_check → hoist → dedup → validate → codegen.\n"
" Takes an already-parsed spec and config; returns generated files or errors.\n"
" Does not perform IO — callers handle writing files and printing output.\n"
"\n"
" Capability-check diagnostics from this entry point carry no source\n"
" location information. Callers that already have a YAML\n"
" `LocationIndex` (e.g. via `parser.parse_file_with_locations`)\n"
" should prefer `generate_with_locations` so capability-check errors\n"
" surface line/column for the offending spec node (Issue #411).\n"
).
-spec generate(
oaspec@internal@openapi@spec:open_api_spec(oaspec@internal@openapi@spec:unresolved()),
oaspec@config:config()
) -> {ok, generation_summary()} | {error, generate_error()}.
generate(Spec, Cfg) ->
generate_with_progress_and_locations(
Spec,
oaspec@internal@openapi@location_index:empty(),
Cfg,
oaspec@internal@progress:noop()
).