-module(oaisp@cli).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/oaisp/cli.gleam").
-export([build_document/2, main/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.
?MODULEDOC(
" `gleam run -m oaisp/cli` — the build-time CLI.\n"
"\n"
" `generate` orchestrates the pipeline: it runs `gleam export\n"
" package-interface` for the resolved type information, runs `gleam run --\n"
" --emit-endpoints` to collect the endpoint declarations the app wires into\n"
" `add_openapi`, merges the two, and writes the document atomically.\n"
"\n"
" Status messages always go to stderr; with `generate -o -` the document is\n"
" the only thing on stdout, so it can be piped to other tools.\n"
).
-file("src/oaisp/cli.gleam", 299).
-spec help_text() -> binary().
help_text() ->
<<"oaisp — generate an OpenAPI 3.1 document from your Gleam code
USAGE:
gleam run -m oaisp/cli <COMMAND> [OPTIONS]
COMMANDS:
generate Emit the OpenAPI 3.1 document
OPTIONS:
-o, --out <PATH> Output path (default ./openapi.json; - for stdout)
--package-interface <PATH> Use this package-interface.json instead of
running `gleam export package-interface`
--quiet Suppress status output on stderr
-h, --help Print this help"/utf8>>.
-file("src/oaisp/cli.gleam", 295).
-spec log_error(binary()) -> nil.
log_error(Message) ->
gleam_stdlib:println_error(<<"oaisp: error: "/utf8, Message/binary>>).
-file("src/oaisp/cli.gleam", 121).
?DOC(
" Two routes with the same method and path would describe one operation in the\n"
" document while the server runs only the first — the document and the server\n"
" would disagree. List the offenders so the stray route is easy to find.\n"
).
-spec duplicate_routes_message(list({binary(), binary()})) -> binary().
duplicate_routes_message(Routes) ->
Bullets = begin
_pipe = Routes,
_pipe@1 = gleam@list:map(
_pipe,
fun(Route) ->
<<<<<<" - "/utf8,
(string:uppercase(erlang:element(1, Route)))/binary>>/binary,
" "/utf8>>/binary,
(erlang:element(2, Route))/binary>>
end
),
gleam@string:join(_pipe@1, <<"\n"/utf8>>)
end,
<<<<"these routes share a method and path — each (method, path) must be unique, or "/utf8,
"the generated document and your running server will drift:\n"/utf8>>/binary,
Bullets/binary>>.
-file("src/oaisp/cli.gleam", 149).
?DOC(
" A path parameter that doesn't match the path template leaves an invalid\n"
" OpenAPI 3.1 document. List the offenders — the endpoint and whether a\n"
" parameter has no placeholder, or a placeholder has no parameter — so the\n"
" missing declaration or the typo is obvious.\n"
).
-spec path_param_mismatches_message(
list(oaisp@internal@merge:path_param_mismatch())
) -> binary().
path_param_mismatches_message(Mismatches) ->
Bullets = begin
_pipe = Mismatches,
_pipe@1 = gleam@list:map(_pipe, fun(Mismatch) -> case Mismatch of
{param_without_placeholder, Method, Path, Name} ->
<<<<<<<<<<<<<<<<" - "/utf8, Method/binary>>/binary,
" "/utf8>>/binary,
Path/binary>>/binary,
": path parameter `"/utf8>>/binary,
Name/binary>>/binary,
"` has no matching `{"/utf8>>/binary,
Name/binary>>/binary,
"}` in the path"/utf8>>;
{placeholder_without_param, Method@1, Path@1, Name@1} ->
<<<<<<<<<<<<" - "/utf8, Method@1/binary>>/binary,
" "/utf8>>/binary,
Path@1/binary>>/binary,
": `{"/utf8>>/binary,
Name@1/binary>>/binary,
"}` in the path has no declared path parameter"/utf8>>
end end),
gleam@string:join(_pipe@1, <<"\n"/utf8>>)
end,
<<<<<<"these path parameters don't match the path template — every `in: path` "/utf8,
"parameter must name a `{placeholder}`, and every placeholder must be "/utf8>>/binary,
"declared:\n"/utf8>>/binary,
Bullets/binary>>.
-file("src/oaisp/cli.gleam", 135).
?DOC(
" A `type_ref` that doesn't resolve would leave a dangling `$ref` in the\n"
" document. List the offenders so the typo — or the missing `pub` — is obvious.\n"
).
-spec unresolved_refs_message(list({binary(), binary()})) -> binary().
unresolved_refs_message(Refs) ->
Bullets = begin
_pipe = Refs,
_pipe@1 = gleam@list:map(
_pipe,
fun(Ref) ->
<<<<<<" - "/utf8, (erlang:element(1, Ref))/binary>>/binary,
"."/utf8>>/binary,
(erlang:element(2, Ref))/binary>>
end
),
gleam@string:join(_pipe@1, <<"\n"/utf8>>)
end,
<<<<"these type references don't resolve against the package interface — check the "/utf8,
"module path and name, and that the type is public:\n"/utf8>>/binary,
Bullets/binary>>.
-file("src/oaisp/cli.gleam", 186).
?DOC(
" A malformed `@format` directive is dropped at emit time, so the format it\n"
" asked for never reaches the schema. List the offenders — the type and the\n"
" line — so the typo (a missing colon, an empty field or format) is obvious.\n"
).
-spec malformed_formats_message(list({binary(), binary(), binary()})) -> binary().
malformed_formats_message(Formats) ->
Bullets = begin
_pipe = Formats,
_pipe@1 = gleam@list:map(
_pipe,
fun(Format) ->
{Module, Name, Line} = Format,
<<<<<<<<<<" - "/utf8, Module/binary>>/binary, "."/utf8>>/binary,
Name/binary>>/binary,
": "/utf8>>/binary,
Line/binary>>
end
),
gleam@string:join(_pipe@1, <<"\n"/utf8>>)
end,
<<<<"these `@format` directives are malformed — each must read `@format <field>: "/utf8,
"<format>`:\n"/utf8>>/binary,
Bullets/binary>>.
-file("src/oaisp/cli.gleam", 232).
-spec decode_inputs(binary(), binary()) -> {ok,
{gleam@package_interface:package(), oaisp@internal@emit:document()}} |
{error, binary()}.
decode_inputs(Package_interface_json, Endpoints_json) ->
gleam@result:'try'(
begin
_pipe = oaisp@internal@package_interface:decode_string(
Package_interface_json
),
gleam@result:map_error(
_pipe,
fun(_) -> <<"could not decode the package interface"/utf8>> end
)
end,
fun(Package) ->
gleam@result:'try'(
begin
_pipe@1 = oaisp@internal@emit:parse(Endpoints_json),
gleam@result:map_error(
_pipe@1,
fun(_) ->
<<"could not parse the emitted endpoints"/utf8>>
end
)
end,
fun(Document) -> {ok, {Package, Document}} end
)
end
).
-file("src/oaisp/cli.gleam", 91).
?DOC(
" Build the OpenAPI document from the two JSON inputs the pipeline gathers.\n"
" Pure, so the heart of `generate` is testable without shelling out.\n"
).
-spec build_document(binary(), binary()) -> {ok, binary()} | {error, binary()}.
build_document(Package_interface_json, Endpoints_json) ->
gleam@result:'try'(
decode_inputs(Package_interface_json, Endpoints_json),
fun(_use0) ->
{Package, Document} = _use0,
case oaisp@internal@merge:duplicate_routes(
erlang:element(3, Document)
) of
[] ->
case oaisp@internal@merge:path_param_mismatches(
erlang:element(3, Document)
) of
[] ->
case oaisp@internal@merge:unresolved_refs(
erlang:element(3, Document),
Package
) of
[] ->
case oaisp@internal@merge:malformed_formats(
erlang:element(3, Document),
Package
) of
[] ->
{ok,
oaisp@internal@merge:to_string(
erlang:element(3, Document),
erlang:element(2, Document),
Package
)};
Formats ->
{error,
malformed_formats_message(
Formats
)}
end;
Refs ->
{error, unresolved_refs_message(Refs)}
end;
Mismatches ->
{error, path_param_mismatches_message(Mismatches)}
end;
Routes ->
{error, duplicate_routes_message(Routes)}
end
end
).
-file("src/oaisp/cli.gleam", 259).
-spec emit_endpoints() -> {ok, binary()} | {error, binary()}.
emit_endpoints() ->
Output = oaisp@internal@exec:run(<<"gleam run -- --emit-endpoints"/utf8>>),
case erlang:element(2, Output) of
0 ->
{ok, erlang:element(3, Output)};
Code ->
{error,
<<"`gleam run -- --emit-endpoints` exited with "/utf8,
(erlang:integer_to_binary(Code))/binary>>}
end.
-file("src/oaisp/cli.gleam", 270).
-spec read_file(binary()) -> {ok, binary()} | {error, binary()}.
read_file(Path) ->
_pipe = oaisp@internal@fs:read(Path),
gleam@result:map_error(
_pipe,
fun(Reason) ->
<<<<<<"could not read "/utf8, Path/binary>>/binary, ": "/utf8>>/binary,
Reason/binary>>
end
).
-file("src/oaisp/cli.gleam", 247).
-spec export_package_interface() -> {ok, binary()} | {error, binary()}.
export_package_interface() ->
Output = oaisp@internal@exec:run(
<<"gleam export package-interface --out "/utf8,
"build/oaisp_package_interface.json"/utf8>>
),
case erlang:element(2, Output) of
0 ->
read_file(<<"build/oaisp_package_interface.json"/utf8>>);
Code ->
{error,
<<"`gleam export package-interface` exited with "/utf8,
(erlang:integer_to_binary(Code))/binary>>}
end.
-file("src/oaisp/cli.gleam", 216).
-spec package_interface_source(
oaisp@internal@argv:options(),
fun((binary()) -> nil)
) -> {ok, binary()} | {error, binary()}.
package_interface_source(Options, Log) ->
case erlang:element(3, Options) of
{some, Path} ->
Log(<<"reading package interface from "/utf8, Path/binary>>),
read_file(Path);
none ->
Log(<<"running `gleam export package-interface`"/utf8>>),
export_package_interface()
end.
-file("src/oaisp/cli.gleam", 203).
-spec gather(oaisp@internal@argv:options(), fun((binary()) -> nil)) -> {ok,
{binary(), binary()}} |
{error, binary()}.
gather(Options, Log) ->
gleam@result:'try'(
package_interface_source(Options, Log),
fun(Package_interface_json) ->
Log(<<"collecting endpoint declarations"/utf8>>),
gleam@result:'try'(
emit_endpoints(),
fun(Endpoints_json) ->
{ok, {Package_interface_json, Endpoints_json}}
end
)
end
).
-file("src/oaisp/cli.gleam", 291).
-spec log_status(binary()) -> nil.
log_status(Message) ->
gleam_stdlib:println_error(<<"oaisp: "/utf8, Message/binary>>).
-file("src/oaisp/cli.gleam", 275).
-spec logger(oaisp@internal@argv:options()) -> fun((binary()) -> nil).
logger(Options) ->
fun(Message) -> case erlang:element(4, Options) of
true ->
nil;
false ->
log_status(Message)
end end.
-file("src/oaisp/cli.gleam", 60).
-spec run_generate(oaisp@internal@argv:options()) -> {ok, nil} |
{error, binary()}.
run_generate(Options) ->
Log = logger(Options),
gleam@result:'try'(
gather(Options, Log),
fun(_use0) ->
{Package_interface_json, Endpoints_json} = _use0,
gleam@result:'try'(
build_document(Package_interface_json, Endpoints_json),
fun(Document) -> case erlang:element(2, Options) of
to_stdout ->
gleam_stdlib:println(Document),
{ok, nil};
{to_file, Path} ->
gleam@result:'try'(
begin
_pipe = oaisp@internal@atomic_write:write(
Path,
Document
),
gleam@result:map_error(
_pipe,
fun(Reason) ->
<<<<<<"could not write "/utf8,
Path/binary>>/binary,
": "/utf8>>/binary,
Reason/binary>>
end
)
end,
fun(_) ->
Log(<<"wrote "/utf8, Path/binary>>),
{ok, nil}
end
)
end end
)
end
).
-file("src/oaisp/cli.gleam", 284).
-spec arg_error_message(oaisp@internal@argv:error()) -> binary().
arg_error_message(Error) ->
case Error of
{unknown_flag, Flag} ->
<<<<"unknown flag `"/utf8, Flag/binary>>/binary, "`"/utf8>>;
{missing_value, Flag@1} ->
<<<<"`"/utf8, Flag@1/binary>>/binary, "` needs a value"/utf8>>
end.
-file("src/oaisp/cli.gleam", 43).
-spec generate(list(binary())) -> nil.
generate(Arguments) ->
case oaisp@internal@argv:parse(Arguments) of
{error, Error} ->
log_error(arg_error_message(Error)),
erlang:halt(2);
{ok, Options} ->
case run_generate(Options) of
{ok, nil} ->
nil;
{error, Message} ->
log_error(Message),
erlang:halt(1)
end
end.
-file("src/oaisp/cli.gleam", 29).
?DOC(" CLI entrypoint.\n").
-spec main() -> nil.
main() ->
case erlang:element(4, argv:load()) of
[<<"generate"/utf8>> | Rest] ->
generate(Rest);
[] ->
gleam_stdlib:println(help_text());
[<<"help"/utf8>>] ->
gleam_stdlib:println(help_text());
[<<"--help"/utf8>>] ->
gleam_stdlib:println(help_text());
[<<"-h"/utf8>>] ->
gleam_stdlib:println(help_text());
[Command | _] ->
log_error(
<<<<"unknown command `"/utf8, Command/binary>>/binary,
"`"/utf8>>
),
gleam_stdlib:println_error(help_text()),
erlang:halt(2)
end.