Packages
oaspec
0.40.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@config.erl
-module(oaspec@config).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/oaspec/config.gleam").
-export([new/6, input/1, output_server/1, output_client/1, package/1, mode/1, validate/1, parse_mode/1, with_mode/2, with_validate/2, with_output/2, validate_output_package_match/1, validate_output_dir_layout/1, error_to_string/1, load/1]).
-export_type([config/0, generate_mode/0, config_error/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.
-opaque config() :: {config,
binary(),
binary(),
binary(),
binary(),
generate_mode(),
boolean()}.
-type generate_mode() :: server | client | both.
-type config_error() :: {file_not_found, binary()} |
{file_read_error, binary(), binary()} |
{parse_error, binary()} |
{missing_field, binary()} |
{invalid_value, binary(), binary()}.
-file("src/oaspec/config.gleam", 28).
?DOC(
" Construct a new `Config` from its six fields. Prefer `load/1` in\n"
" production code; `new/6` is primarily for tests and ad-hoc tooling\n"
" that assembles a config in memory.\n"
).
-spec new(binary(), binary(), binary(), binary(), generate_mode(), boolean()) -> config().
new(Input, Output_server, Output_client, Package, Mode, Validate) ->
{config, Input, Output_server, Output_client, Package, Mode, Validate}.
-file("src/oaspec/config.gleam", 40).
?DOC(" Path to the OpenAPI spec this config was built for.\n").
-spec input(config()) -> binary().
input(Cfg) ->
erlang:element(2, Cfg).
-file("src/oaspec/config.gleam", 45).
?DOC(" Output directory for server-side generated files.\n").
-spec output_server(config()) -> binary().
output_server(Cfg) ->
erlang:element(3, Cfg).
-file("src/oaspec/config.gleam", 50).
?DOC(" Output directory for client-side generated files.\n").
-spec output_client(config()) -> binary().
output_client(Cfg) ->
erlang:element(4, Cfg).
-file("src/oaspec/config.gleam", 55).
?DOC(" Gleam package name (module prefix) for generated files.\n").
-spec package(config()) -> binary().
package(Cfg) ->
erlang:element(5, Cfg).
-file("src/oaspec/config.gleam", 60).
?DOC(" Generation mode: server, client, or both.\n").
-spec mode(config()) -> generate_mode().
mode(Cfg) ->
erlang:element(6, Cfg).
-file("src/oaspec/config.gleam", 65).
?DOC(" Whether guard-based runtime validation is enabled.\n").
-spec validate(config()) -> boolean().
validate(Cfg) ->
erlang:element(7, Cfg).
-file("src/oaspec/config.gleam", 86).
?DOC(" Parse a mode string into GenerateMode.\n").
-spec parse_mode(binary()) -> {ok, generate_mode()} | {error, config_error()}.
parse_mode(Mode) ->
case Mode of
<<"server"/utf8>> ->
{ok, server};
<<"client"/utf8>> ->
{ok, client};
<<"both"/utf8>> ->
{ok, both};
_ ->
{error,
{invalid_value,
<<"mode"/utf8>>,
<<"must be one of: server, client, both"/utf8>>}}
end.
-file("src/oaspec/config.gleam", 216).
?DOC(" Apply CLI overrides to a config.\n").
-spec with_mode(config(), generate_mode()) -> config().
with_mode(Config, Mode) ->
{config,
erlang:element(2, Config),
erlang:element(3, Config),
erlang:element(4, Config),
erlang:element(5, Config),
Mode,
erlang:element(7, Config)}.
-file("src/oaspec/config.gleam", 221).
?DOC(" Apply validation mode override.\n").
-spec with_validate(config(), boolean()) -> config().
with_validate(Config, Validate) ->
{config,
erlang:element(2, Config),
erlang:element(3, Config),
erlang:element(4, Config),
erlang:element(5, Config),
erlang:element(6, Config),
Validate}.
-file("src/oaspec/config.gleam", 233).
?DOC(
" Apply output base directory override.\n"
" Derives server/client paths as <dir>/<package> and <dir>/<package>_client\n"
" in `Both` mode. In client-only mode the client path drops the suffix\n"
" (Issue #262) so generated `import <package>/...` lines resolve.\n"
"\n"
" The suffix decision reads `config.mode` at call time, so apply\n"
" `with_mode/2` before `with_output/2` if both overrides are needed —\n"
" otherwise the client path will reflect the previous mode's default.\n"
).
-spec with_output(config(), gleam@option:option(binary())) -> config().
with_output(Config, Output) ->
case Output of
{some, Dir} ->
{config,
erlang:element(2, Config),
<<<<Dir/binary, "/"/utf8>>/binary,
(erlang:element(5, Config))/binary>>,
case erlang:element(6, Config) of
client ->
<<<<Dir/binary, "/"/utf8>>/binary,
(erlang:element(5, Config))/binary>>;
server ->
<<<<<<Dir/binary, "/"/utf8>>/binary,
(erlang:element(5, Config))/binary>>/binary,
"_client"/utf8>>;
both ->
<<<<<<Dir/binary, "/"/utf8>>/binary,
(erlang:element(5, Config))/binary>>/binary,
"_client"/utf8>>
end,
erlang:element(5, Config),
erlang:element(6, Config),
erlang:element(7, Config)};
none ->
Config
end.
-file("src/oaspec/config.gleam", 302).
?DOC(" Get the basename of a path (last segment after /).\n").
-spec basename(binary()) -> binary().
basename(Path) ->
_pipe = Path,
_pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>),
_pipe@2 = gleam@list:last(_pipe@1),
gleam@result:unwrap(_pipe@2, <<""/utf8>>).
-file("src/oaspec/config.gleam", 257).
?DOC(
" Validate that output directory basenames are valid Gleam module names\n"
" usable as import roots.\n"
"\n"
" Server output must end in `<package>` so generated imports such as\n"
" `import <package>/types` resolve. Client output may end in either\n"
" `<package>` (when client lives in its own project) or `<package>_client`\n"
" (the new default since Issue #248 — both server and client share the same\n"
" `<dir>` and need distinct basenames). Anything else is a misconfigured\n"
" package/output mismatch the user should be told about.\n"
).
-spec validate_output_package_match(config()) -> {ok, nil} |
{error, config_error()}.
validate_output_package_match(Config) ->
_pipe = case erlang:element(6, Config) of
server ->
case basename(erlang:element(3, Config)) =:= erlang:element(
5,
Config
) of
true ->
{ok, nil};
false ->
{error,
{invalid_value,
<<"output.server"/utf8>>,
<<<<<<<<"Directory basename '"/utf8,
(basename(erlang:element(3, Config)))/binary>>/binary,
"' must match package '"/utf8>>/binary,
(erlang:element(5, Config))/binary>>/binary,
"'"/utf8>>}}
end;
both ->
case basename(erlang:element(3, Config)) =:= erlang:element(
5,
Config
) of
true ->
{ok, nil};
false ->
{error,
{invalid_value,
<<"output.server"/utf8>>,
<<<<<<<<"Directory basename '"/utf8,
(basename(erlang:element(3, Config)))/binary>>/binary,
"' must match package '"/utf8>>/binary,
(erlang:element(5, Config))/binary>>/binary,
"'"/utf8>>}}
end;
client ->
{ok, nil}
end,
gleam@result:'try'(_pipe, fun(_) -> case erlang:element(6, Config) of
client ->
Client_basename = basename(erlang:element(4, Config)),
Client_suffix = <<(erlang:element(5, Config))/binary,
"_client"/utf8>>,
case (Client_basename =:= erlang:element(5, Config)) orelse (Client_basename
=:= Client_suffix) of
true ->
{ok, nil};
false ->
{error,
{invalid_value,
<<"output.client"/utf8>>,
<<<<<<<<<<<<"Directory basename '"/utf8,
Client_basename/binary>>/binary,
"' must match package '"/utf8>>/binary,
(erlang:element(5, Config))/binary>>/binary,
"' or '"/utf8>>/binary,
Client_suffix/binary>>/binary,
"'"/utf8>>}}
end;
both ->
Client_basename = basename(erlang:element(4, Config)),
Client_suffix = <<(erlang:element(5, Config))/binary,
"_client"/utf8>>,
case (Client_basename =:= erlang:element(5, Config)) orelse (Client_basename
=:= Client_suffix) of
true ->
{ok, nil};
false ->
{error,
{invalid_value,
<<"output.client"/utf8>>,
<<<<<<<<<<<<"Directory basename '"/utf8,
Client_basename/binary>>/binary,
"' must match package '"/utf8>>/binary,
(erlang:element(5, Config))/binary>>/binary,
"' or '"/utf8>>/binary,
Client_suffix/binary>>/binary,
"'"/utf8>>}}
end;
server ->
{ok, nil}
end end).
-file("src/oaspec/config.gleam", 345).
-spec misplaced_src_error(binary(), binary()) -> config_error().
misplaced_src_error(Field, Path) ->
{invalid_value,
Field,
<<<<"'"/utf8, Path/binary>>/binary,
"' contains a 'src' segment that is not the immediate parent of the package directory. Generated code lands inside src/.../<package>/, but oaspec emits imports as `<package>/...`, which the Gleam compiler resolves relative to src/, not relative to <dir>. Either set the path so that 'src' is the direct parent of the package directory (e.g. './src'), or move the output outside any 'src/' tree (e.g. './gen') and treat that directory as a standalone Gleam project root with its own gleam.toml."/utf8>>}.
-file("src/oaspec/config.gleam", 354).
-spec path_has_misplaced_src(binary()) -> boolean().
path_has_misplaced_src(Path) ->
Segments = begin
_pipe = Path,
_pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>),
gleam@list:filter(
_pipe@1,
fun(S) -> (S /= <<""/utf8>>) andalso (S /= <<"."/utf8>>) end
)
end,
case lists:reverse(Segments) of
[_ | Rest_reversed] ->
Parent_segments = lists:reverse(Rest_reversed),
case gleam@list:last(Parent_segments) of
{ok, <<"src"/utf8>>} ->
false;
_ ->
gleam@list:any(
Parent_segments,
fun(S@1) -> S@1 =:= <<"src"/utf8>> end
)
end;
[] ->
false
end.
-file("src/oaspec/config.gleam", 322).
?DOC(
" Validate the on-disk layout implied by `output.dir`.\n"
"\n"
" Issue #319: when `output.dir` is something like `./src/gen`, generated\n"
" code lands at `src/gen/<pkg>/types.gleam` whose Gleam module path is\n"
" `gen/<pkg>/types` — but oaspec emits `import <pkg>/types`, which the\n"
" compiler can't resolve. Catch this at config time so the user sees a\n"
" clear error instead of a wall of `Unknown module ...` from\n"
" `gleam build`.\n"
"\n"
" Heuristic: in the path leading up to the package directory, `src`\n"
" must either be the immediate parent of the package (the \"<dir> is\n"
" the project's src/\" pattern) or be absent (the standalone-Gleam-\n"
" project pattern). `src` in any other position is the foot-gun.\n"
).
-spec validate_output_dir_layout(config()) -> {ok, nil} |
{error, config_error()}.
validate_output_dir_layout(Config) ->
_pipe = case erlang:element(6, Config) of
server ->
case path_has_misplaced_src(erlang:element(3, Config)) of
false ->
{ok, nil};
true ->
{error,
misplaced_src_error(
<<"output.server"/utf8>>,
erlang:element(3, Config)
)}
end;
both ->
case path_has_misplaced_src(erlang:element(3, Config)) of
false ->
{ok, nil};
true ->
{error,
misplaced_src_error(
<<"output.server"/utf8>>,
erlang:element(3, Config)
)}
end;
client ->
{ok, nil}
end,
gleam@result:'try'(_pipe, fun(_) -> case erlang:element(6, Config) of
client ->
case path_has_misplaced_src(erlang:element(4, Config)) of
false ->
{ok, nil};
true ->
{error,
misplaced_src_error(
<<"output.client"/utf8>>,
erlang:element(4, Config)
)}
end;
both ->
case path_has_misplaced_src(erlang:element(4, Config)) of
false ->
{ok, nil};
true ->
{error,
misplaced_src_error(
<<"output.client"/utf8>>,
erlang:element(4, Config)
)}
end;
server ->
{ok, nil}
end end).
-file("src/oaspec/config.gleam", 377).
?DOC(" Convert config error to a human-readable string.\n").
-spec error_to_string(config_error()) -> binary().
error_to_string(Error) ->
case Error of
{file_not_found, Path} ->
<<<<"Config file not found: "/utf8, Path/binary>>/binary,
" (paths resolve relative to the current working directory)"/utf8>>;
{file_read_error, Path@1, Detail} ->
<<<<<<"Error reading config file "/utf8, Path@1/binary>>/binary,
": "/utf8>>/binary,
Detail/binary>>;
{parse_error, Detail@1} ->
<<"Config parse error: "/utf8, Detail@1/binary>>;
{missing_field, Field} ->
<<"Missing required config field: "/utf8, Field/binary>>;
{invalid_value, Field@1, Detail@2} ->
<<<<<<"Invalid value for "/utf8, Field@1/binary>>/binary,
": "/utf8>>/binary,
Detail@2/binary>>
end.
-file("src/oaspec/config.gleam", 393).
?DOC(" Extract a nested string value from YAML like output.server.\n").
-spec extract_nested_string(yay:node_(), binary(), binary()) -> gleam@option:option(binary()).
extract_nested_string(Root, Key1, Key2) ->
case yay:select_sugar(
Root,
<<<<Key1/binary, "."/utf8>>/binary, Key2/binary>>
) of
{ok, {node_str, Value}} ->
{some, Value};
_ ->
none
end.
-file("src/oaspec/config.gleam", 405).
?DOC(" Convert a yay YAML error to string.\n").
-spec yaml_error_to_string(yay:yaml_error()) -> binary().
yaml_error_to_string(Error) ->
case Error of
unexpected_parsing_error ->
<<"Unexpected parsing error"/utf8>>;
{parsing_error, Msg, _} ->
Msg
end.
-file("src/oaspec/config.gleam", 100).
?DOC(" Load config from a YAML file.\n").
-spec load(binary()) -> {ok, config()} | {error, config_error()}.
load(Path) ->
gleam@result:'try'(
begin
_pipe = simplifile:read(Path),
gleam@result:map_error(_pipe, fun(E) -> case E of
enoent ->
{file_not_found, Path};
_ ->
{file_read_error,
Path,
<<"Failed to read file: "/utf8,
(simplifile:describe_error(E))/binary>>}
end end)
end,
fun(Content) ->
gleam@result:'try'(
begin
_pipe@1 = yaml_ffi:parse_string(Content),
gleam@result:map_error(
_pipe@1,
fun(E@1) ->
{parse_error,
<<"YAML parse error: "/utf8,
(yaml_error_to_string(E@1))/binary>>}
end
)
end,
fun(Docs) -> gleam@result:'try'(case Docs of
[First | _] ->
{ok, First};
[] ->
{error,
{parse_error,
<<"Empty YAML document"/utf8>>}}
end, fun(Doc) ->
Root = yay:document_root(Doc),
gleam@result:'try'(
begin
_pipe@2 = yay:extract_string(
Root,
<<"input"/utf8>>
),
gleam@result:map_error(
_pipe@2,
fun(_) ->
{missing_field, <<"input"/utf8>>}
end
)
end,
fun(Input) ->
Package = begin
_pipe@3 = yay:extract_optional_string(
Root,
<<"package"/utf8>>
),
_pipe@4 = gleam@result:unwrap(
_pipe@3,
none
),
gleam@option:unwrap(
_pipe@4,
<<"api"/utf8>>
)
end,
gleam@result:'try'(
case begin
_pipe@5 = yay:extract_optional_string(
Root,
<<"mode"/utf8>>
),
gleam@result:unwrap(_pipe@5, none)
end of
{some, <<"server"/utf8>>} ->
{ok, server};
{some, <<"client"/utf8>>} ->
{ok, client};
{some, <<"both"/utf8>>} ->
{ok, both};
none ->
{ok, both};
{some, Other} ->
{error,
{invalid_value,
<<"mode"/utf8>>,
<<<<"must be one of: server, client, both (got: "/utf8,
Other/binary>>/binary,
")"/utf8>>}}
end,
fun(Mode) ->
Output_dir = begin
_pipe@6 = extract_nested_string(
Root,
<<"output"/utf8>>,
<<"dir"/utf8>>
),
gleam@option:unwrap(
_pipe@6,
<<"./gen"/utf8>>
)
end,
Server_default = <<<<Output_dir/binary,
"/"/utf8>>/binary,
Package/binary>>,
Client_default = case Mode of
client ->
<<<<Output_dir/binary,
"/"/utf8>>/binary,
Package/binary>>;
server ->
<<<<<<Output_dir/binary,
"/"/utf8>>/binary,
Package/binary>>/binary,
"_client"/utf8>>;
both ->
<<<<<<Output_dir/binary,
"/"/utf8>>/binary,
Package/binary>>/binary,
"_client"/utf8>>
end,
Output_server = begin
_pipe@7 = extract_nested_string(
Root,
<<"output"/utf8>>,
<<"server"/utf8>>
),
gleam@option:unwrap(
_pipe@7,
Server_default
)
end,
Output_client = begin
_pipe@8 = extract_nested_string(
Root,
<<"output"/utf8>>,
<<"client"/utf8>>
),
gleam@option:unwrap(
_pipe@8,
Client_default
)
end,
gleam@result:'try'(
case yay:select_sugar(
Root,
<<"validate"/utf8>>
) of
{ok, {node_bool, true}} ->
{ok, true};
{ok,
{node_str,
<<"true"/utf8>>}} ->
{ok, true};
{ok, {node_bool, false}} ->
{ok, false};
{ok,
{node_str,
<<"false"/utf8>>}} ->
{ok, false};
{error, _} ->
case Mode of
server ->
{ok, true};
both ->
{ok, true};
client ->
{ok, false}
end;
{ok, _} ->
{error,
{invalid_value,
<<"validate"/utf8>>,
<<"must be a boolean (true or false)"/utf8>>}}
end,
fun(Validate) ->
{ok,
{config,
Input,
Output_server,
Output_client,
Package,
Mode,
Validate}}
end
)
end
)
end
)
end) end
)
end
).