Current section

Files

Jump to
oaspec src oaspec@config.erl
Raw

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