Packages
oaspec
0.44.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([empty_include/0, include_is_empty/1, new/6, input/1, output_server/1, output_client/1, package/1, mode/1, validate/1, include/1, parse_mode/1, with_mode/2, with_validate/2, with_include/2, with_output/2, validate_output_package_match/1, validate_output_dir_layout/1, error_to_string/1, load_all/1, load/1]).
-export_type([config/0, include/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(),
include()}.
-type include() :: {include, list(binary()), list(binary())}.
-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", 47).
?DOC(
" The default include filter — matches everything. Equivalent to\n"
" `oaspec.yaml` omitting the `include:` key entirely.\n"
).
-spec empty_include() -> include().
empty_include() ->
{include, [], []}.
-file("src/oaspec/config.gleam", 52).
?DOC(" True when no filter is active (both tag and path lists empty).\n").
-spec include_is_empty(include()) -> boolean().
include_is_empty(Include) ->
gleam@list:is_empty(erlang:element(2, Include)) andalso gleam@list:is_empty(
erlang:element(3, Include)
).
-file("src/oaspec/config.gleam", 61).
?DOC(
" Construct a new `Config` from its six required fields. Prefer\n"
" `load/1` in production code; `new/6` is primarily for tests and\n"
" ad-hoc tooling that assembles a config in memory. The include\n"
" filter defaults to `empty_include()` (match everything); use\n"
" `with_include/2` to override.\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,
empty_include()}.
-file("src/oaspec/config.gleam", 81).
?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", 86).
?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", 91).
?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", 96).
?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", 101).
?DOC(" Generation mode: server, client, or both.\n").
-spec mode(config()) -> generate_mode().
mode(Cfg) ->
erlang:element(6, Cfg).
-file("src/oaspec/config.gleam", 106).
?DOC(" Whether guard-based runtime validation is enabled.\n").
-spec validate(config()) -> boolean().
validate(Cfg) ->
erlang:element(7, Cfg).
-file("src/oaspec/config.gleam", 111).
?DOC(" Operation include filter (tags / paths). Issue #387.\n").
-spec include(config()) -> include().
include(Cfg) ->
erlang:element(8, Cfg).
-file("src/oaspec/config.gleam", 132).
?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", 389).
?DOC(
" Read a list-of-strings YAML field that may be absent, in which\n"
" case an empty list is returned. `field_path` is the dotted name\n"
" shown in any `InvalidValue` diagnostic.\n"
).
-spec extract_optional_string_list(yay:node_(), binary(), binary()) -> {ok,
list(binary())} |
{error, config_error()}.
extract_optional_string_list(Node, Field_path, Key) ->
case yay:extract_string_list(Node, Key) of
{ok, Values} ->
{ok, Values};
{error, _} ->
case yay:select_sugar(Node, Key) of
{error, _} ->
{ok, []};
{ok, _} ->
{error,
{invalid_value,
Field_path,
<<"must be a list of strings"/utf8>>}}
end
end.
-file("src/oaspec/config.gleam", 366).
?DOC(
" Parse the optional `include:` block. Returns `empty_include()`\n"
" when the key is absent so the absence path stays cheap.\n"
).
-spec parse_include(yay:node_()) -> {ok, include()} | {error, config_error()}.
parse_include(Root) ->
case yay:select_sugar(Root, <<"include"/utf8>>) of
{error, _} ->
{ok, empty_include()};
{ok, Include_node} ->
gleam@result:'try'(
extract_optional_string_list(
Include_node,
<<"include.tags"/utf8>>,
<<"tags"/utf8>>
),
fun(Tags) ->
gleam@result:'try'(
extract_optional_string_list(
Include_node,
<<"include.paths"/utf8>>,
<<"paths"/utf8>>
),
fun(Paths) -> {ok, {include, Tags, Paths}} end
)
end
)
end.
-file("src/oaspec/config.gleam", 411).
?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),
erlang:element(8, Config)}.
-file("src/oaspec/config.gleam", 416).
?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,
erlang:element(8, Config)}.
-file("src/oaspec/config.gleam", 422).
?DOC(
" Apply include-filter override (Issue #387). Useful for tests\n"
" that build a `Config` in memory.\n"
).
-spec with_include(config(), include()) -> config().
with_include(Config, Include) ->
{config,
erlang:element(2, Config),
erlang:element(3, Config),
erlang:element(4, Config),
erlang:element(5, Config),
erlang:element(6, Config),
erlang:element(7, Config),
Include}.
-file("src/oaspec/config.gleam", 434).
?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),
erlang:element(8, Config)};
none ->
Config
end.
-file("src/oaspec/config.gleam", 535).
?DOC(
" Split a package name into its slash-separated segments. Empty\n"
" entries are dropped so trailing slashes and accidental double\n"
" slashes are tolerated. `\"api\"` → `[\"api\"]`,\n"
" `\"dco_check/github\"` → `[\"dco_check\", \"github\"]`.\n"
).
-spec package_segments(binary()) -> list(binary()).
package_segments(Package) ->
_pipe = Package,
_pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>),
gleam@list:filter(_pipe@1, fun(S) -> S /= <<""/utf8>> end).
-file("src/oaspec/config.gleam", 544).
?DOC(
" Split a filesystem path into its segments, dropping `.` and empty\n"
" entries so `./src/foo/`, `src/foo`, and `./src//foo` all yield\n"
" `[\"src\", \"foo\"]`.\n"
).
-spec path_segments(binary()) -> list(binary()).
path_segments(Path) ->
_pipe = Path,
_pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>),
gleam@list:filter(
_pipe@1,
fun(S) -> (S /= <<""/utf8>>) andalso (S /= <<"."/utf8>>) end
).
-file("src/oaspec/config.gleam", 552).
?DOC(
" Return the last `n` entries of `items`, preserving order. If\n"
" `items` has fewer than `n` entries, returns `items` unchanged.\n"
).
-spec last_n(list(HEW), integer()) -> list(HEW).
last_n(Items, N) ->
_pipe = Items,
_pipe@1 = lists:reverse(_pipe),
_pipe@2 = gleam@list:take(_pipe@1, N),
lists:reverse(_pipe@2).
-file("src/oaspec/config.gleam", 558).
?DOC(
" Drop the last `n` entries of `items`, preserving order. If\n"
" `items` has fewer than `n` entries, returns `[]`.\n"
).
-spec drop_last_n(list(HEZ), integer()) -> list(HEZ).
drop_last_n(Items, N) ->
_pipe = Items,
_pipe@1 = lists:reverse(_pipe),
_pipe@2 = gleam@list:drop(_pipe@1, N),
lists:reverse(_pipe@2).
-file("src/oaspec/config.gleam", 566).
?DOC(
" Append `suffix` to the LAST segment of `segments`. Empty input\n"
" stays empty. `[\"a\",\"b\"]` + `\"_client\"` → `[\"a\",\"b_client\"]`. Used\n"
" to derive the client-output spelling for nested packages while\n"
" keeping all but the leaf intact.\n"
).
-spec suffix_last_segment(list(binary()), binary()) -> list(binary()).
suffix_last_segment(Segments, Suffix) ->
case lists:reverse(Segments) of
[] ->
[];
[Last | Rest] ->
lists:reverse([<<Last/binary, Suffix/binary>> | Rest])
end.
-file("src/oaspec/config.gleam", 499).
-spec validate_path_tail_matches_package(
binary(),
binary(),
list(binary()),
boolean()
) -> {ok, nil} | {error, config_error()}.
validate_path_tail_matches_package(Field, Path, Pkg, Allow_client_suffix) ->
Tail = last_n(path_segments(Path), erlang:length(Pkg)),
Pkg_with_suffix = suffix_last_segment(Pkg, <<"_client"/utf8>>),
case {Tail =:= Pkg, Allow_client_suffix andalso (Tail =:= Pkg_with_suffix)} of
{true, _} ->
{ok, nil};
{_, true} ->
{ok, nil};
{false, false} ->
{error, {invalid_value, Field, case Allow_client_suffix of
true ->
<<<<<<<<<<<<"Output path tail '"/utf8,
(gleam@string:join(
Tail,
<<"/"/utf8>>
))/binary>>/binary,
"' must match package '"/utf8>>/binary,
(gleam@string:join(
Pkg,
<<"/"/utf8>>
))/binary>>/binary,
"' or '"/utf8>>/binary,
(gleam@string:join(
Pkg_with_suffix,
<<"/"/utf8>>
))/binary>>/binary,
"'"/utf8>>;
false ->
<<<<<<<<"Output path tail '"/utf8,
(gleam@string:join(
Tail,
<<"/"/utf8>>
))/binary>>/binary,
"' must match package '"/utf8>>/binary,
(gleam@string:join(Pkg, <<"/"/utf8>>))/binary>>/binary,
"'"/utf8>>
end}}
end.
-file("src/oaspec/config.gleam", 466).
?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"
"\n"
" Nested packages (Issue #387): a `package` containing `/` such as\n"
" `dco_check/github` declares an N-segment Gleam module path. The path\n"
" tail compared against the package is N segments deep — the LAST N\n"
" segments of the output path must equal the package's segments. The\n"
" `_client` suffix attaches to the LAST package segment only, matching\n"
" the single-segment behaviour (`dco_check/github` →\n"
" `dco_check/github_client`, never `dco_check_client/github`).\n"
).
-spec validate_output_package_match(config()) -> {ok, nil} |
{error, config_error()}.
validate_output_package_match(Config) ->
Pkg = package_segments(erlang:element(5, Config)),
case Pkg of
[] ->
{ok, nil};
_ ->
_pipe = case erlang:element(6, Config) of
server ->
validate_path_tail_matches_package(
<<"output.server"/utf8>>,
erlang:element(3, Config),
Pkg,
false
);
both ->
validate_path_tail_matches_package(
<<"output.server"/utf8>>,
erlang:element(3, Config),
Pkg,
false
);
client ->
{ok, nil}
end,
gleam@result:'try'(
_pipe,
fun(_) -> case erlang:element(6, Config) of
client ->
validate_path_tail_matches_package(
<<"output.client"/utf8>>,
erlang:element(4, Config),
Pkg,
true
);
both ->
validate_path_tail_matches_package(
<<"output.client"/utf8>>,
erlang:element(4, Config),
Pkg,
true
);
server ->
{ok, nil}
end end
)
end.
-file("src/oaspec/config.gleam", 623).
-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", 632).
-spec path_has_misplaced_src(binary(), integer()) -> boolean().
path_has_misplaced_src(Path, Package_segment_count) ->
Parent_segments = drop_last_n(path_segments(Path), Package_segment_count),
Earlier_parents = drop_last_n(Parent_segments, 1),
case gleam@list:last(Parent_segments) of
{ok, <<"src"/utf8>>} ->
gleam@list:any(Earlier_parents, fun(S) -> S =:= <<"src"/utf8>> end);
_ ->
gleam@list:any(
Parent_segments,
fun(S@1) -> S@1 =:= <<"src"/utf8>> end
)
end.
-file("src/oaspec/config.gleam", 594).
?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's top-level\n"
" directory, `src` must either be the immediate parent (the \"<dir>\n"
" is the project's src/\" pattern) or be absent (the standalone-\n"
" Gleam-project pattern). `src` in any other position is the\n"
" foot-gun.\n"
"\n"
" Nested packages (Issue #387): for a package like `dco_check/github`\n"
" the \"package directory\" is the last 2 path segments, and the rule\n"
" applies to whatever sits BEFORE that pair. So `./src/dco_check/github`\n"
" has parent chain `[src]` (immediate parent `src` → ok), and\n"
" `./pkg/src/foo/dco_check/github` has parent chain\n"
" `[pkg, src, foo]` (last is `foo` and `src` appears earlier → bad).\n"
).
-spec validate_output_dir_layout(config()) -> {ok, nil} |
{error, config_error()}.
validate_output_dir_layout(Config) ->
Segment_count = erlang:length(package_segments(erlang:element(5, Config))),
case Segment_count of
0 ->
{ok, nil};
_ ->
_pipe = case erlang:element(6, Config) of
server ->
case path_has_misplaced_src(
erlang:element(3, Config),
Segment_count
) 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),
Segment_count
) 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),
Segment_count
) 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),
Segment_count
) of
false ->
{ok, nil};
true ->
{error,
misplaced_src_error(
<<"output.client"/utf8>>,
erlang:element(4, Config)
)}
end;
server ->
{ok, nil}
end end
)
end.
-file("src/oaspec/config.gleam", 649).
?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", 665).
?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", 289).
?DOC(
" Parse a single target node — either the YAML root (legacy\n"
" single-target config) or one element of the `targets:` sequence\n"
" — into a fully-formed `Config`. The shared `input` / `mode` /\n"
" `validate` come from the top-level YAML and are baked into the\n"
" returned value.\n"
).
-spec parse_target_node(yay:node_(), binary(), generate_mode(), boolean()) -> {ok,
config()} |
{error, config_error()}.
parse_target_node(Node, Input, Mode, Validate) ->
Package = oaspec@internal@openapi@parser_value:string_default(
Node,
<<"package"/utf8>>,
<<"api"/utf8>>
),
Output_dir = begin
_pipe = extract_nested_string(Node, <<"output"/utf8>>, <<"dir"/utf8>>),
gleam@option:unwrap(_pipe, <<"./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@1 = extract_nested_string(
Node,
<<"output"/utf8>>,
<<"server"/utf8>>
),
gleam@option:unwrap(_pipe@1, Server_default)
end,
Output_client = begin
_pipe@2 = extract_nested_string(
Node,
<<"output"/utf8>>,
<<"client"/utf8>>
),
gleam@option:unwrap(_pipe@2, Client_default)
end,
gleam@result:'try'(
parse_include(Node),
fun(Include) ->
{ok,
{config,
Input,
Output_server,
Output_client,
Package,
Mode,
Validate,
Include}}
end
).
-file("src/oaspec/config.gleam", 342).
-spec parse_target_item(
yay:node_(),
integer(),
binary(),
generate_mode(),
boolean()
) -> {ok, config()} | {error, config_error()}.
parse_target_item(Node, Idx, Input, Mode, Validate) ->
case oaspec@internal@openapi@parser_value:optional_string(
Node,
<<"package"/utf8>>
) of
none ->
{error,
{invalid_value,
<<<<"targets["/utf8,
(erlang:integer_to_binary(Idx))/binary>>/binary,
"].package"/utf8>>,
<<"each target must declare a package name"/utf8>>}};
{some, _} ->
parse_target_node(Node, Input, Mode, Validate)
end.
-file("src/oaspec/config.gleam", 677).
?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", 177).
?DOC(
" Load every target declared by an oaspec.yaml. A config without a\n"
" `targets:` key is treated as a single implicit target whose\n"
" fields come from the top-level `package:`, `output:`, and\n"
" `include:` keys (the legacy single-target shape). A config with\n"
" a `targets:` sequence yields one `Config` per element, each\n"
" carrying its own `package` / `output_*` / `include` while\n"
" sharing the top-level `input:`, `mode:`, and `validate:` values.\n"
).
-spec load_all(binary()) -> {ok, list(config())} | {error, config_error()}.
load_all(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) ->
gleam@result:'try'(
case oaspec@internal@openapi@parser_value:optional_string(
Root,
<<"mode"/utf8>>
) 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) ->
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) ->
case yay:select_sugar(
Root,
<<"targets"/utf8>>
) of
{error, _} ->
gleam@result:'try'(
parse_target_node(
Root,
Input,
Mode,
Validate
),
fun(Cfg) ->
{ok, [Cfg]}
end
);
{ok, {node_seq, Items}} ->
case Items of
[] ->
{error,
{invalid_value,
<<"targets"/utf8>>,
<<"must declare at least one target"/utf8>>}};
_ ->
_pipe@3 = Items,
_pipe@4 = gleam@list:index_map(
_pipe@3,
fun(
Item,
Idx
) ->
parse_target_item(
Item,
Idx,
Input,
Mode,
Validate
)
end
),
gleam@result:all(
_pipe@4
)
end;
{ok, _} ->
{error,
{invalid_value,
<<"targets"/utf8>>,
<<"must be a sequence of target objects"/utf8>>}}
end
end
)
end
)
end
)
end) end
)
end
).
-file("src/oaspec/config.gleam", 153).
?DOC(
" Load config from a YAML file.\n"
"\n"
" Returns the single target the YAML declares. When the YAML has a\n"
" `targets:` array with more than one entry, this errors out with\n"
" a multi-target hint — callers that need to handle the multi-\n"
" target case must use `load_all/1` instead. For ordinary\n"
" single-target configs (no `targets:` key, or a `targets:` array\n"
" with exactly one entry) the result is the same as before.\n"
).
-spec load(binary()) -> {ok, config()} | {error, config_error()}.
load(Path) ->
gleam@result:'try'(load_all(Path), fun(Targets) -> case Targets of
[Single] ->
{ok, Single};
[_, _ | _] ->
{error,
{invalid_value,
<<"targets"/utf8>>,
<<"config declares multiple targets; use config.load_all/1 to retrieve the full list"/utf8>>}};
[] ->
{error,
{invalid_value,
<<"targets"/utf8>>,
<<"must declare at least one target"/utf8>>}}
end end).