Packages
oaspec
0.34.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/3, validate_only/2, generate_all_files/1, generate_with_progress/3, 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", 53).
?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()
) -> {ok, prepared_context()} | {error, generate_error()}.
prepare_context(Spec, Cfg, Reporter) ->
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, Capability_issues} = oaspec@internal@progress:timed(
fun() ->
_pipe@1 = oaspec@internal@openapi@capability_check:check(
Spec@2
),
oaspec@internal@codegen@validate: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@2))/binary>>/binary,
")"/utf8>>
),
Capability_errors = oaspec@internal@codegen@validate:errors_only(
Capability_issues
),
Capability_warnings = oaspec@internal@codegen@validate: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@3, Spec@3} = oaspec@internal@progress:timed(
fun() -> oaspec@internal@openapi@hoist:hoist(Spec@2) end
),
oaspec@internal@progress:report(
Reporter,
<<<<"hoist inline complex schemas (took "/utf8,
(oaspec@internal@progress:format_ms(Elapsed@3))/binary>>/binary,
")"/utf8>>
),
{Elapsed@4, Spec@4} = oaspec@internal@progress:timed(
fun() -> oaspec@internal@openapi@dedup:dedup(Spec@3) end
),
oaspec@internal@progress:report(
Reporter,
<<<<"deduplicate generated names (took "/utf8,
(oaspec@internal@progress:format_ms(Elapsed@4))/binary>>/binary,
")"/utf8>>
),
Ctx = oaspec@internal@codegen@context:new(Spec@4, Cfg),
{Elapsed@5, Preserved_warnings} = oaspec@internal@progress:timed(
fun() ->
_pipe@2 = oaspec@internal@openapi@capability_check:check_preserved(
Ctx
),
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@5))/binary>>/binary,
")"/utf8>>
),
{Elapsed@6, Validation_issues} = oaspec@internal@progress:timed(
fun() ->
_pipe@3 = oaspec@internal@codegen@validate:validate(
Ctx
),
oaspec@internal@codegen@validate: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@6))/binary>>/binary,
")"/utf8>>
),
Blocking_errors = oaspec@internal@codegen@validate:errors_only(
Validation_issues
),
Validation_warnings = oaspec@internal@codegen@validate: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", 195).
?DOC(
" Same as `validate_only`, with a `Reporter` that receives one line\n"
" per pipeline stage with elapsed time. See `generate_with_progress`.\n"
).
-spec validate_only_with_progress(
oaspec@internal@openapi@spec:open_api_spec(oaspec@internal@openapi@spec:unresolved()),
oaspec@config:config(),
oaspec@internal@progress:reporter()
) -> {ok, validation_summary()} | {error, generate_error()}.
validate_only_with_progress(Spec, Cfg, Reporter) ->
gleam@result:'try'(
prepare_context(Spec, Cfg, Reporter),
fun(Prepared) ->
{ok,
{validation_summary,
erlang:element(3, Prepared),
erlang:element(4, Prepared)}}
end
).
-file("src/oaspec/generate.gleam", 186).
?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(Spec, Cfg, oaspec@internal@progress:noop()).
-file("src/oaspec/generate.gleam", 228).
?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", 208).
?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", 165).
?DOC(
" Same as `generate`, with a `Reporter` that receives one line per\n"
" pipeline stage with elapsed time. Used by the CLI to surface\n"
" progress for large specs (issue #352); library callers should\n"
" prefer `generate` and pass `progress.noop()` if needed.\n"
).
-spec generate_with_progress(
oaspec@internal@openapi@spec:open_api_spec(oaspec@internal@openapi@spec:unresolved()),
oaspec@config:config(),
oaspec@internal@progress:reporter()
) -> {ok, generation_summary()} | {error, generate_error()}.
generate_with_progress(Spec, Cfg, Reporter) ->
gleam@result:'try'(
prepare_context(Spec, Cfg, Reporter),
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", 154).
?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"
).
-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(Spec, Cfg, oaspec@internal@progress:noop()).