-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()).