-module(oaspec@openapi@parser). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/oaspec/openapi/parser.gleam"). -export([default_limits/0, parse_error_to_string/1, parse_string_with_locations/1, parse_string/1, parse_string_with_limits/2, parse_json_string_with_locations/1, parse_file/1, parse_file_with_progress_and_locations/2, parse_json_string/1, parse_string_or_json_with_locations/1]). -export_type([parse_limits/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 parse_limits() :: {parse_limits, integer(), integer(), integer(), integer(), integer(), integer()}. -file("src/oaspec/openapi/parser.gleam", 146). -spec format_size(integer()) -> binary(). format_size(Bytes) -> case Bytes < 1024 of true -> <<(erlang:integer_to_binary(Bytes))/binary, " B"/utf8>>; false -> case Bytes < (1024 * 1024) of true -> <<(erlang:integer_to_binary(Bytes div 1024))/binary, " KiB"/utf8>>; false -> <<(erlang:integer_to_binary(case (1024 * 1024) of 0 -> 0; Gleam@denominator -> Bytes div Gleam@denominator end))/binary, " MiB"/utf8>> end end. -file("src/oaspec/openapi/parser.gleam", 162). ?DOC( " Normalize a path string for cycle detection. This is not a full\n" " canonicalization (no symlink resolution, no realpath call); it just\n" " collapses the `./` and duplicate-slash noise that `filepath.join`\n" " can leave behind so the same file reached via two equivalent\n" " relative paths compares equal.\n" ). -spec canonicalize_ref_path(binary()) -> binary(). canonicalize_ref_path(Path) -> Segments = begin _pipe = Path, _pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>), gleam@list:filter( _pipe@1, fun(Seg) -> (Seg /= <<""/utf8>>) andalso (Seg /= <<"."/utf8>>) end ) end, case gleam_stdlib:string_starts_with(Path, <<"/"/utf8>>) of true -> <<"/"/utf8, (gleam@string:join(Segments, <<"/"/utf8>>))/binary>>; false -> gleam@string:join(Segments, <<"/"/utf8>>) end. -file("src/oaspec/openapi/parser.gleam", 173). -spec cyclic_external_ref_diagnostic(binary(), list(binary())) -> oaspec@openapi@diagnostic:diagnostic(). cyclic_external_ref_diagnostic(Current, Visited) -> Chain = lists:reverse([Current | Visited]), oaspec@openapi@diagnostic:invalid_value( <<"external_ref"/utf8>>, <<<<"Cyclic external $ref graph detected: "/utf8, (gleam@string:join(Chain, <<" -> "/utf8>>))/binary>>/binary, ". oaspec requires external ref graphs to be acyclic."/utf8>>, no_source_loc ). -file("src/oaspec/openapi/parser.gleam", 231). ?DOC( " Project-default limits sized for real-world specs.\n" "\n" " - `max_input_bytes`: 16 MiB — Stripe's full OpenAPI is ~6 MB,\n" " GitHub's REST API is ~12 MB; 16 MiB clears both with headroom.\n" " - `max_schema_depth`: 100. Real specs rarely nest beyond ~12.\n" " - `max_allof_chain`: 32.\n" " - `max_external_ref_hops`: 16.\n" " - `max_paths`: 4096. Stripe (~1k operations), GitHub (~1k), and\n" " AsyncAPI (~50) all fit comfortably.\n" " - `max_parameters_per_op`: 64. The largest real-world operation\n" " the audit found has ~20 parameters.\n" ). -spec default_limits() -> parse_limits(). default_limits() -> {parse_limits, (16 * 1024) * 1024, 100, 32, 16, 4096, 64}. -file("src/oaspec/openapi/parser.gleam", 316). -spec enforce_input_byte_limit(binary(), parse_limits()) -> {ok, nil} | {error, oaspec@openapi@diagnostic:diagnostic()}. enforce_input_byte_limit(Content, Limits) -> Actual = erlang:byte_size(Content), case Actual > erlang:element(2, Limits) of false -> {ok, nil}; true -> {error, oaspec@openapi@diagnostic:parse_limit_exceeded( <<"max_input_bytes"/utf8>>, erlang:element(2, Limits), Actual )} end. -file("src/oaspec/openapi/parser.gleam", 396). -spec looks_like_json(binary()) -> boolean(). looks_like_json(Content) -> case gleam@string:trim_start(Content) of <<"{"/utf8, _/binary>> -> true; <<"["/utf8, _/binary>> -> true; _ -> false end. -file("src/oaspec/openapi/parser.gleam", 407). ?DOC( " Run yamerl on `content` and return the root node plus a\n" " location index built from the same content. Both pieces are\n" " consumed by `parse_root` to produce the OpenApiSpec.\n" ). -spec parse_to_node(binary()) -> {ok, {yay:node_(), oaspec@internal@openapi@location_index:location_index()}} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_to_node(Content) -> gleam@result:'try'( begin _pipe = yaml_ffi:parse_string(Content), gleam@result:map_error(_pipe, fun(E) -> case E of {parsing_error, Msg, Loc} -> oaspec@openapi@diagnostic:yaml_error( Msg, {source_loc, erlang:element(2, Loc), erlang:element(3, Loc)} ); unexpected_parsing_error -> oaspec@openapi@diagnostic:yaml_error( <<"Unexpected parsing error"/utf8>>, no_source_loc ) end end) end, fun(Docs) -> gleam@result:'try'(case Docs of [First | _] -> {ok, First}; [] -> {error, oaspec@openapi@diagnostic:yaml_error( <<"Empty document"/utf8>>, no_source_loc )} end, fun(Doc) -> Root = yay:document_root(Doc), Index = oaspec@internal@openapi@location_index:build( Content ), {ok, {Root, Index}} end) end ). -file("src/oaspec/openapi/parser.gleam", 445). ?DOC( " Decode a JSON string into a yay.Node via the OTP `json` module.\n" " Wraps the FFI's `Result(List(Document), YamlError)` shape into a\n" " `Diagnostic` so it lines up with `parse_to_node`'s contract.\n" ). -spec decode_json_to_node(binary()) -> {ok, yay:node_()} | {error, oaspec@openapi@diagnostic:diagnostic()}. decode_json_to_node(Content) -> gleam@result:'try'( begin _pipe = oaspec_json_ffi:parse_string(Content), gleam@result:map_error(_pipe, fun(E) -> case E of {parsing_error, Msg, Loc} -> oaspec@openapi@diagnostic:yaml_error( Msg, case {erlang:element(2, Loc), erlang:element(3, Loc)} of {0, 0} -> no_source_loc; {_, _} -> {source_loc, erlang:element(2, Loc), erlang:element(3, Loc)} end ); unexpected_parsing_error -> oaspec@openapi@diagnostic:yaml_error( <<"Unexpected JSON parsing error"/utf8>>, no_source_loc ) end end) end, fun(Docs) -> case Docs of [First | _] -> {ok, yay:document_root(First)}; [] -> {error, oaspec@openapi@diagnostic:yaml_error( <<"Empty document"/utf8>>, no_source_loc )} end end ). -file("src/oaspec/openapi/parser.gleam", 471). -spec looks_like_json_path(binary()) -> boolean(). looks_like_json_path(Path) -> Lower = string:lowercase(Path), gleam_stdlib:string_ends_with(Lower, <<".json"/utf8>>). -file("src/oaspec/openapi/parser.gleam", 553). -spec is_supported_openapi_version(binary()) -> boolean(). is_supported_openapi_version(Version) -> case gleam@string:split(Version, <<"."/utf8>>) of [<<"3"/utf8>>, <<"0"/utf8>>, Patch] -> case gleam_stdlib:parse_int(Patch) of {ok, N} when N >= 0 -> true; _ -> false end; [<<"3"/utf8>>, <<"1"/utf8>>, Patch] -> case gleam_stdlib:parse_int(Patch) of {ok, N} when N >= 0 -> true; _ -> false end; [<<"3"/utf8>>, <<"0"/utf8>>] -> true; [<<"3"/utf8>>, <<"1"/utf8>>] -> true; _ -> false end. -file("src/oaspec/openapi/parser.gleam", 539). ?DOC( " Reject spec files whose `openapi` field is not in the supported 3.0.x /\n" " 3.1.x range. OpenAPI 3.x is what oaspec claims to generate from, so\n" " accepting 2.0 or 4.0.0 would let users feed specs that are either\n" " subtly or grossly incompatible with the generator.\n" ). -spec validate_openapi_version(binary(), oaspec@openapi@diagnostic:source_loc()) -> {ok, nil} | {error, oaspec@openapi@diagnostic:diagnostic()}. validate_openapi_version(Version, Loc) -> gleam@bool:guard( is_supported_openapi_version(Version), {ok, nil}, fun() -> {error, oaspec@openapi@diagnostic:invalid_value( <<"openapi"/utf8>>, <<<<"Unsupported OpenAPI version: '"/utf8, Version/binary>>/binary, "'. oaspec only supports OpenAPI 3.0.x and 3.1.x."/utf8>>, Loc )} end ). -file("src/oaspec/openapi/parser.gleam", 1056). ?DOC(" Parse parameter location string.\n"). -spec parse_parameter_in( binary(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, oaspec@internal@openapi@spec:parameter_in()} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_parameter_in(Value, Index) -> case Value of <<"path"/utf8>> -> {ok, in_path}; <<"query"/utf8>> -> {ok, in_query}; <<"header"/utf8>> -> {ok, in_header}; <<"cookie"/utf8>> -> {ok, in_cookie}; _ -> {error, oaspec@openapi@diagnostic:invalid_value( <<"parameter.in"/utf8>>, <<<<"Unknown parameter location: "/utf8, Value/binary>>/binary, ". Must be one of: path, query, header, cookie"/utf8>>, oaspec@internal@openapi@location_index:lookup( Index, <<"parameter.in"/utf8>> ) )} end. -file("src/oaspec/openapi/parser.gleam", 1077). ?DOC(" Map a style string to a ParameterStyle ADT value.\n"). -spec parse_parameter_style( binary(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, oaspec@internal@openapi@spec:parameter_style()} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_parameter_style(Value, Index) -> case Value of <<"form"/utf8>> -> {ok, form_style}; <<"simple"/utf8>> -> {ok, simple_style}; <<"deepObject"/utf8>> -> {ok, deep_object_style}; <<"matrix"/utf8>> -> {ok, matrix_style}; <<"label"/utf8>> -> {ok, label_style}; <<"spaceDelimited"/utf8>> -> {ok, space_delimited_style}; <<"pipeDelimited"/utf8>> -> {ok, pipe_delimited_style}; _ -> {error, oaspec@openapi@diagnostic:invalid_value( <<"parameter.style"/utf8>>, <<<<"Unknown parameter style: '"/utf8, Value/binary>>/binary, "'. Must be one of: form, simple, deepObject, matrix, label, spaceDelimited, pipeDelimited"/utf8>>, oaspec@internal@openapi@location_index:lookup( Index, <<"parameter.style"/utf8>> ) )} end. -file("src/oaspec/openapi/parser.gleam", 1101). ?DOC(" Map an apiKey \"in\" string to a SecuritySchemeIn ADT value.\n"). -spec parse_security_scheme_in( binary(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, oaspec@internal@openapi@spec:security_scheme_in()} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_security_scheme_in(Value, Index) -> case Value of <<"header"/utf8>> -> {ok, scheme_in_header}; <<"query"/utf8>> -> {ok, scheme_in_query}; <<"cookie"/utf8>> -> {ok, scheme_in_cookie}; _ -> {error, oaspec@openapi@diagnostic:invalid_value( <<"securityScheme.in"/utf8>>, <<<<"Unknown apiKey location: "/utf8, Value/binary>>/binary, ". Must be one of: header, query, cookie"/utf8>>, oaspec@internal@openapi@location_index:lookup( Index, <<"securityScheme.in"/utf8>> ) )} end. -file("src/oaspec/openapi/parser.gleam", 1359). ?DOC( " Render a `yay` key node as a comparable string for duplicate\n" " detection. Returns `None` for node types that cannot occur as\n" " OpenAPI map keys (lists, maps, etc.); those positions are handled\n" " elsewhere as `invalid_value` errors and do not need duplicate\n" " checking.\n" ). -spec key_node_to_string(yay:node_()) -> gleam@option:option(binary()). key_node_to_string(Node) -> case Node of {node_str, S} -> {some, S}; {node_int, N} -> {some, erlang:integer_to_binary(N)}; {node_bool, true} -> {some, <<"true"/utf8>>}; {node_bool, false} -> {some, <<"false"/utf8>>}; _ -> none end. -file("src/oaspec/openapi/parser.gleam", 1333). ?DOC( " Issue #573: detect duplicate keys in a YAML mapping before consuming\n" " it into a `Dict`. yamerl preserves the original `entries` list with\n" " both occurrences of a duplicate key, but `dict.insert` silently\n" " overwrites — so without this check, duplicates disappear into\n" " undefined behavior (whichever entry happens to be inserted last\n" " wins). Returns the *first* duplicate as a `Diagnostic`; we don't\n" " accumulate every duplicate to keep the parse-phase contract\n" " (one error stops parsing).\n" "\n" " The `index` argument is reserved for a future source-location\n" " lookup (so the diagnostic can name the line of the second\n" " occurrence). For now the location index does not retain per-key\n" " positions inside arbitrary maps, so the diagnostic carries\n" " `NoSourceLoc` and the user-facing message names the path\n" " (`responses`, `components.responses`) instead.\n" ). -spec check_unique_yaml_keys( list({yay:node_(), yay:node_()}), binary(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, nil} | {error, oaspec@openapi@diagnostic:diagnostic()}. check_unique_yaml_keys(Entries, Path, Index) -> Loc = oaspec@internal@openapi@location_index:lookup(Index, Path), _pipe = gleam@list:try_fold( Entries, [], fun(Seen, Entry) -> {Key_node, _} = Entry, case key_node_to_string(Key_node) of {some, Key} -> case gleam@list:contains(Seen, Key) of true -> {error, oaspec@openapi@diagnostic:duplicate_key( Path, Key, Loc )}; false -> {ok, [Key | Seen]} end; none -> {ok, Seen} end end ), gleam@result:map(_pipe, fun(_) -> nil end). -file("src/oaspec/openapi/parser.gleam", 1495). ?DOC(" Parse the schemas map from components.\n"). -spec parse_schemas_map( yay:node_(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@dict:dict(binary(), oaspec@internal@openapi@schema:schema_ref())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_schemas_map(Components_node, Index) -> case yay:select_sugar(Components_node, <<"schemas"/utf8>>) of {ok, {node_map, Entries}} -> gleam@list:try_fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Name} -> gleam@result:'try'( oaspec@internal@openapi@parser_schema:parse_schema_ref( Value_node, <<"components.schemas."/utf8, Name/binary>>, Index ), fun(Schema_ref) -> {ok, gleam@dict:insert(Acc, Name, Schema_ref)} end ); _ -> {ok, Acc} end end ); _ -> {ok, maps:new()} end. -file("src/oaspec/openapi/parser.gleam", 1615). ?DOC( " Parse a schema reference (either $ref or inline schema).\n" " Convert a parse error to a human-readable string.\n" ). -spec parse_error_to_string(oaspec@openapi@diagnostic:diagnostic()) -> binary(). parse_error_to_string(Error) -> oaspec@openapi@diagnostic:to_short_string(Error). -file("src/oaspec/openapi/parser.gleam", 1781). ?DOC(" Parse OAuth2 flows from a security scheme node.\n"). -spec parse_oauth2_flows(yay:node_()) -> gleam@dict:dict(binary(), oaspec@internal@openapi@spec:o_auth2_flow()). parse_oauth2_flows(Node) -> case yay:select_sugar(Node, <<"flows"/utf8>>) of {ok, {node_map, Flow_entries}} -> gleam@list:fold( Flow_entries, maps:new(), fun(Acc, Entry) -> {Key_node, Flow_node} = Entry, case Key_node of {node_str, Flow_name} -> Authorization_url = oaspec@internal@openapi@parser_value:optional_string( Flow_node, <<"authorizationUrl"/utf8>> ), Token_url = oaspec@internal@openapi@parser_value:optional_string( Flow_node, <<"tokenUrl"/utf8>> ), Refresh_url = oaspec@internal@openapi@parser_value:optional_string( Flow_node, <<"refreshUrl"/utf8>> ), Scopes = case yay:select_sugar( Flow_node, <<"scopes"/utf8>> ) of {ok, {node_map, Scope_entries}} -> gleam@list:fold( Scope_entries, maps:new(), fun(Sacc, Sentry) -> {Sk, Sv} = Sentry, case {Sk, Sv} of {{node_str, Scope_name}, {node_str, Scope_desc}} -> gleam@dict:insert( Sacc, Scope_name, Scope_desc ); {_, _} -> Sacc end end ); _ -> maps:new() end, gleam@dict:insert( Acc, Flow_name, {o_auth2_flow, Authorization_url, Token_url, Refresh_url, Scopes} ); _ -> Acc end end ); _ -> maps:new() end. -file("src/oaspec/openapi/parser.gleam", 1684). ?DOC(" Parse a single security scheme.\n"). -spec parse_security_scheme( yay:node_(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, oaspec@internal@openapi@spec:security_scheme()} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_security_scheme(Node, Index) -> gleam@result:'try'( begin _pipe = yay:extract_string(Node, <<"type"/utf8>>), gleam@result:map_error( _pipe, fun(_capture) -> oaspec@internal@openapi@parser_yay_error:missing_field_from_extraction( _capture, <<"securityScheme"/utf8>>, <<"type"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <<"securityScheme"/utf8>>, <<"type"/utf8>> ) ) end ) end, fun(Type_str) -> case Type_str of <<"apiKey"/utf8>> -> gleam@result:'try'( begin _pipe@1 = yay:extract_string(Node, <<"name"/utf8>>), gleam@result:map_error( _pipe@1, fun(_capture@1) -> oaspec@internal@openapi@parser_yay_error:missing_field_from_extraction( _capture@1, <<"securityScheme.apiKey"/utf8>>, <<"name"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <<"securityScheme.apiKey"/utf8>>, <<"name"/utf8>> ) ) end ) end, fun(Name) -> gleam@result:'try'( begin _pipe@2 = yay:extract_string( Node, <<"in"/utf8>> ), gleam@result:map_error( _pipe@2, fun(_capture@2) -> oaspec@internal@openapi@parser_yay_error:missing_field_from_extraction( _capture@2, <<"securityScheme.apiKey"/utf8>>, <<"in"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <<"securityScheme.apiKey"/utf8>>, <<"in"/utf8>> ) ) end ) end, fun(In_str) -> case parse_security_scheme_in(In_str, Index) of {ok, In_} -> {ok, {api_key_scheme, Name, In_}}; {error, _} -> {error, oaspec@openapi@diagnostic:invalid_value( <<"securityScheme.apiKey.in"/utf8>>, <<<<"Only 'header', 'query' and 'cookie' are supported for apiKey. Got: '"/utf8, In_str/binary>>/binary, "'"/utf8>>, oaspec@internal@openapi@location_index:lookup( Index, <<"securityScheme.apiKey.in"/utf8>> ) )} end end ) end ); <<"http"/utf8>> -> gleam@result:'try'( begin _pipe@3 = yay:extract_string( Node, <<"scheme"/utf8>> ), gleam@result:map_error( _pipe@3, fun(_capture@3) -> oaspec@internal@openapi@parser_yay_error:missing_field_from_extraction( _capture@3, <<"securityScheme.http"/utf8>>, <<"scheme"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <<"securityScheme.http"/utf8>>, <<"scheme"/utf8>> ) ) end ) end, fun(Scheme) -> Bearer_format = oaspec@internal@openapi@parser_value:optional_string( Node, <<"bearerFormat"/utf8>> ), {ok, {http_scheme, Scheme, Bearer_format}} end ); <<"oauth2"/utf8>> -> Description = oaspec@internal@openapi@parser_value:optional_string( Node, <<"description"/utf8>> ), Flows = parse_oauth2_flows(Node), {ok, {o_auth2_scheme, Description, Flows}}; <<"openIdConnect"/utf8>> -> Description@1 = oaspec@internal@openapi@parser_value:optional_string( Node, <<"description"/utf8>> ), gleam@result:'try'( begin _pipe@4 = yay:extract_string( Node, <<"openIdConnectUrl"/utf8>> ), gleam@result:map_error( _pipe@4, fun(_capture@4) -> oaspec@internal@openapi@parser_yay_error:missing_field_from_extraction( _capture@4, <<"securityScheme.openIdConnect"/utf8>>, <<"openIdConnectUrl"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <<"securityScheme.openIdConnect"/utf8>>, <<"openIdConnectUrl"/utf8>> ) ) end ) end, fun(Open_id_connect_url) -> {ok, {open_id_connect_scheme, Open_id_connect_url, Description@1}} end ); _ -> {ok, {unsupported_scheme, Type_str}} end end ). -file("src/oaspec/openapi/parser.gleam", 1622). ?DOC( " Parse security schemes from components.\n" " Returns Ok(empty dict) if the section is absent, Error if present but\n" " malformed.\n" ). -spec parse_security_schemes_map( yay:node_(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@dict:dict(binary(), oaspec@internal@openapi@spec:ref_or(oaspec@internal@openapi@spec:security_scheme()))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_security_schemes_map(Components_node, Index) -> case yay:select_sugar(Components_node, <<"securitySchemes"/utf8>>) of {ok, {node_map, Entries}} -> gleam@list:try_fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Name} -> case yay:extract_optional_string( Value_node, <<"$ref"/utf8>> ) of {ok, {some, Ref_str}} -> {ok, gleam@dict:insert( Acc, Name, {ref, Ref_str} )}; _ -> gleam@result:'try'( parse_security_scheme(Value_node, Index), fun(Scheme) -> {ok, gleam@dict:insert( Acc, Name, {value, Scheme} )} end ) end; _ -> {ok, Acc} end end ); _ -> {ok, maps:new()} end. -file("src/oaspec/openapi/parser.gleam", 1865). ?DOC( " Parse a single security requirement object.\n" " Returns one SecurityRequirement whose schemes list contains all AND-ed\n" " scheme refs. The outer list (caller) represents OR alternatives.\n" ). -spec parse_security_requirement_object( yay:node_(), binary(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, oaspec@internal@openapi@spec:security_requirement()} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_security_requirement_object(Node, Context, Index) -> case Node of {node_map, Entries} -> gleam@result:'try'( gleam@list:try_map( Entries, fun(Entry) -> {Key_node, Scopes_node} = Entry, case Key_node of {node_str, Scheme_name} -> gleam@result:'try'(case Scopes_node of {node_seq, Scope_items} -> gleam@list:try_map( Scope_items, fun(S) -> case S of {node_str, V} -> {ok, V}; _ -> {error, oaspec@openapi@diagnostic:invalid_value( <<<>/binary, Scheme_name/binary>>, <<"Scope must be a string"/utf8>>, oaspec@internal@openapi@location_index:lookup( Index, <<<>/binary, Scheme_name/binary>> ) )} end end ); node_nil -> {ok, []}; _ -> {error, oaspec@openapi@diagnostic:invalid_value( <<<>/binary, Scheme_name/binary>>, <<"Scopes must be an array of strings"/utf8>>, oaspec@internal@openapi@location_index:lookup( Index, <<<>/binary, Scheme_name/binary>> ) )} end, fun(Scopes) -> {ok, {security_scheme_ref, Scheme_name, Scopes}} end); _ -> {error, oaspec@openapi@diagnostic:invalid_value( <>, <<"Security requirement key must be a string"/utf8>>, oaspec@internal@openapi@location_index:lookup( Index, <> ) )} end end ), fun(Scheme_refs) -> {ok, {security_requirement, Scheme_refs}} end ); _ -> {error, oaspec@openapi@diagnostic:invalid_value( <>, <<"Security requirement must be an object"/utf8>>, oaspec@internal@openapi@location_index:lookup( Index, <> ) )} end. -file("src/oaspec/openapi/parser.gleam", 1827). ?DOC( " Parse top-level security requirements.\n" " Returns Ok([]) if absent, Error if present but malformed.\n" ). -spec parse_security_requirements( yay:node_(), binary(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, list(oaspec@internal@openapi@spec:security_requirement())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_security_requirements(Node, Context, Index) -> case yay:select_sugar(Node, <<"security"/utf8>>) of {ok, {node_seq, Items}} -> gleam@list:try_map( Items, fun(Item) -> parse_security_requirement_object(Item, Context, Index) end ); _ -> {ok, []} end. -file("src/oaspec/openapi/parser.gleam", 1844). ?DOC( " Parse operation-level security requirements.\n" " Returns Ok(None) if absent (inherits top-level), Ok(Some([])) if explicitly\n" " empty (opts out), Ok(Some([...])) if specified.\n" ). -spec parse_optional_security_requirements( yay:node_(), binary(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@option:option(list(oaspec@internal@openapi@spec:security_requirement()))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_optional_security_requirements(Node, Context, Index) -> case yay:select_sugar(Node, <<"security"/utf8>>) of {ok, {node_seq, Items}} -> gleam@result:'try'( gleam@list:try_map( Items, fun(Item) -> parse_security_requirement_object(Item, Context, Index) end ), fun(Reqs) -> {ok, {some, Reqs}} end ); _ -> {ok, none} end. -file("src/oaspec/openapi/parser.gleam", 2039). ?DOC(" Parse optional contact from an info node.\n"). -spec parse_optional_contact(yay:node_()) -> gleam@option:option(oaspec@internal@openapi@spec:contact()). parse_optional_contact(Info_node) -> case yay:select_sugar(Info_node, <<"contact"/utf8>>) of {ok, Contact_node} -> Name = oaspec@internal@openapi@parser_value:optional_string( Contact_node, <<"name"/utf8>> ), Url = oaspec@internal@openapi@parser_value:optional_string( Contact_node, <<"url"/utf8>> ), Email = oaspec@internal@openapi@parser_value:optional_string( Contact_node, <<"email"/utf8>> ), {some, {contact, Name, Url, Email}}; _ -> none end. -file("src/oaspec/openapi/parser.gleam", 2052). ?DOC(" Parse optional license from an info node.\n"). -spec parse_optional_license(yay:node_()) -> gleam@option:option(oaspec@internal@openapi@spec:license()). parse_optional_license(Info_node) -> case yay:select_sugar(Info_node, <<"license"/utf8>>) of {ok, License_node} -> case yay:extract_string(License_node, <<"name"/utf8>>) of {ok, Name} -> Url = oaspec@internal@openapi@parser_value:optional_string( License_node, <<"url"/utf8>> ), {some, {license, Name, Url}}; _ -> none end; _ -> none end. -file("src/oaspec/openapi/parser.gleam", 584). ?DOC(" Parse the info object.\n"). -spec parse_info( yay:node_(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, oaspec@internal@openapi@spec:info()} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_info(Root, Index) -> gleam@result:'try'( begin _pipe = yay:select_sugar(Root, <<"info"/utf8>>), gleam@result:map_error( _pipe, fun(_capture) -> oaspec@internal@openapi@parser_yay_error:missing_field_from_selector( _capture, <<""/utf8>>, <<"info"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <<""/utf8>>, <<"info"/utf8>> ) ) end ) end, fun(Info_node) -> gleam@result:'try'( begin _pipe@1 = yay:extract_string(Info_node, <<"title"/utf8>>), gleam@result:map_error( _pipe@1, fun(_capture@1) -> oaspec@internal@openapi@parser_yay_error:missing_field_from_extraction( _capture@1, <<"info"/utf8>>, <<"title"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <<"info"/utf8>>, <<"title"/utf8>> ) ) end ) end, fun(Title) -> gleam@result:'try'( begin _pipe@2 = yay:extract_string( Info_node, <<"version"/utf8>> ), gleam@result:map_error( _pipe@2, fun(_capture@2) -> oaspec@internal@openapi@parser_yay_error:missing_field_from_extraction( _capture@2, <<"info"/utf8>>, <<"version"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <<"info"/utf8>>, <<"version"/utf8>> ) ) end ) end, fun(Version) -> Description = oaspec@internal@openapi@parser_value:optional_string( Info_node, <<"description"/utf8>> ), Summary = oaspec@internal@openapi@parser_value:optional_string( Info_node, <<"summary"/utf8>> ), Terms_of_service = oaspec@internal@openapi@parser_value:optional_string( Info_node, <<"termsOfService"/utf8>> ), Contact = parse_optional_contact(Info_node), License = parse_optional_license(Info_node), {ok, {info, Title, Description, Version, Summary, Terms_of_service, Contact, License}} end ) end ) end ). -file("src/oaspec/openapi/parser.gleam", 2068). ?DOC(" Parse server variables from a server node.\n"). -spec parse_server_variables(yay:node_()) -> gleam@dict:dict(binary(), oaspec@internal@openapi@spec:server_variable()). parse_server_variables(Node) -> case yay:select_sugar(Node, <<"variables"/utf8>>) of {ok, {node_map, Entries}} -> gleam@list:fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Var_name} -> Default = oaspec@internal@openapi@parser_value:string_default( Value_node, <<"default"/utf8>>, <<""/utf8>> ), Enum_values = case yay:extract_string_list( Value_node, <<"enum"/utf8>> ) of {ok, Values} -> Values; _ -> [] end, Description = oaspec@internal@openapi@parser_value:optional_string( Value_node, <<"description"/utf8>> ), gleam@dict:insert( Acc, Var_name, {server_variable, Default, Enum_values, Description} ); _ -> Acc end end ); _ -> maps:new() end. -file("src/oaspec/openapi/parser.gleam", 649). ?DOC(" Parse a single server object.\n"). -spec parse_server( yay:node_(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, oaspec@internal@openapi@spec:server()} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_server(Node, Index) -> gleam@result:'try'( begin _pipe = yay:extract_string(Node, <<"url"/utf8>>), gleam@result:map_error( _pipe, fun(_capture) -> oaspec@internal@openapi@parser_yay_error:missing_field_from_extraction( _capture, <<"servers"/utf8>>, <<"url"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <<"servers"/utf8>>, <<"url"/utf8>> ) ) end ) end, fun(Url) -> Description = oaspec@internal@openapi@parser_value:optional_string( Node, <<"description"/utf8>> ), Variables = parse_server_variables(Node), {ok, {server, Url, Description, Variables}} end ). -file("src/oaspec/openapi/parser.gleam", 637). ?DOC(" Parse servers array.\n"). -spec parse_servers( yay:node_(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, list(oaspec@internal@openapi@spec:server())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_servers(Root, Index) -> case yay:select_sugar(Root, <<"servers"/utf8>>) of {ok, {node_seq, Items}} -> gleam@list:try_map( Items, fun(Item) -> parse_server(Item, Index) end ); _ -> {ok, []} end. -file("src/oaspec/openapi/parser.gleam", 2096). ?DOC(" Parse optional external docs from a node.\n"). -spec parse_optional_external_docs(yay:node_()) -> gleam@option:option(oaspec@internal@openapi@spec:external_doc()). parse_optional_external_docs(Node) -> case yay:select_sugar(Node, <<"externalDocs"/utf8>>) of {ok, Doc_node} -> case yay:extract_string(Doc_node, <<"url"/utf8>>) of {ok, Url} -> Description = oaspec@internal@openapi@parser_value:optional_string( Doc_node, <<"description"/utf8>> ), {some, {external_doc, Url, Description}}; _ -> none end; _ -> none end. -file("src/oaspec/openapi/parser.gleam", 2113). ?DOC(" Parse tags array from root.\n"). -spec parse_tags(yay:node_()) -> list(oaspec@internal@openapi@spec:tag()). parse_tags(Node) -> case yay:select_sugar(Node, <<"tags"/utf8>>) of {ok, {node_seq, Items}} -> gleam@list:filter_map( Items, fun(Tag_node) -> case yay:extract_string(Tag_node, <<"name"/utf8>>) of {ok, Name} -> Description = oaspec@internal@openapi@parser_value:optional_string( Tag_node, <<"description"/utf8>> ), External_docs = parse_optional_external_docs( Tag_node ), {ok, {tag, Name, Description, External_docs}}; _ -> {error, nil} end end ); _ -> [] end. -file("src/oaspec/openapi/parser.gleam", 2212). ?DOC(" Parse encoding map from a media type node.\n"). -spec parse_encoding_map( yay:node_(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@dict:dict(binary(), oaspec@internal@openapi@spec:encoding())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_encoding_map(Node, Index) -> case yay:select_sugar(Node, <<"encoding"/utf8>>) of {ok, {node_map, Entries}} -> gleam@list:try_fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Prop_name} -> Content_type = oaspec@internal@openapi@parser_value:optional_string( Value_node, <<"contentType"/utf8>> ), gleam@result:'try'( case oaspec@internal@openapi@parser_value:optional_string( Value_node, <<"style"/utf8>> ) of {some, S} -> gleam@result:'try'( parse_parameter_style(S, Index), fun(Parsed) -> {ok, {some, Parsed}} end ); none -> {ok, none} end, fun(Style) -> Explode = oaspec@internal@openapi@parser_value:optional_bool( Value_node, <<"explode"/utf8>> ), {ok, gleam@dict:insert( Acc, Prop_name, {encoding, Content_type, Style, Explode} )} end ); _ -> {ok, Acc} end end ); _ -> {ok, maps:new()} end. -file("src/oaspec/openapi/parser.gleam", 1164). ?DOC(" Parse content map, requiring at least one entry for request bodies.\n"). -spec parse_required_content( yay:node_(), binary(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@dict:dict(binary(), oaspec@internal@openapi@spec:media_type())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_required_content(Node, Context, Index) -> case yay:select_sugar(Node, <<"content"/utf8>>) of {ok, {node_map, Entries}} -> gleam@list:try_fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Media_type_name} -> gleam@result:'try'( case yay:select_sugar( Value_node, <<"schema"/utf8>> ) of {ok, Schema_node} -> gleam@result:'try'( oaspec@internal@openapi@parser_schema:parse_schema_ref( Schema_node, <>, Index ), fun(Sr) -> {ok, {some, Sr}} end ); _ -> {ok, none} end, fun(Mt_schema) -> Mt_example = oaspec@internal@openapi@parser_value:extract_optional( Value_node, <<"example"/utf8>> ), Mt_examples = oaspec@internal@openapi@parser_value:extract_map( Value_node, <<"examples"/utf8>> ), gleam@result:'try'( parse_encoding_map(Value_node, Index), fun(Mt_encoding) -> {ok, gleam@dict:insert( Acc, Media_type_name, {media_type, Mt_schema, Mt_example, Mt_examples, Mt_encoding} )} end ) end ); _ -> {ok, Acc} end end ); _ -> {error, oaspec@openapi@diagnostic:missing_field( <>, <<"content"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <>, <<"content"/utf8>> ) )} end. -file("src/oaspec/openapi/parser.gleam", 1148). ?DOC(" Parse a request body object.\n"). -spec parse_request_body( yay:node_(), binary(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, oaspec@internal@openapi@spec:request_body(oaspec@internal@openapi@spec:unresolved())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_request_body(Node, Context, Index) -> Description = oaspec@internal@openapi@parser_value:optional_string( Node, <<"description"/utf8>> ), Required = oaspec@internal@openapi@parser_value:bool_default( Node, <<"required"/utf8>>, false ), gleam@result:'try'( parse_required_content(Node, Context, Index), fun(Content) -> {ok, {request_body, Description, Content, Required}} end ). -file("src/oaspec/openapi/parser.gleam", 1121). ?DOC(" Parse requestBody from a node.\n"). -spec parse_request_body_at( yay:node_(), binary(), gleam@option:option(oaspec@internal@openapi@spec:components(oaspec@internal@openapi@spec:unresolved())), oaspec@internal@openapi@location_index:location_index() ) -> {ok, oaspec@internal@openapi@spec:ref_or(oaspec@internal@openapi@spec:request_body(oaspec@internal@openapi@spec:unresolved()))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_request_body_at(Node, Context, _, Index) -> gleam@result:'try'( begin _pipe = yay:select_sugar(Node, <<"requestBody"/utf8>>), gleam@result:map_error( _pipe, fun(_capture) -> oaspec@internal@openapi@parser_yay_error:missing_field_from_selector( _capture, Context, <<"requestBody"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, Context, <<"requestBody"/utf8>> ) ) end ) end, fun(Rb_node) -> case yay:extract_optional_string(Rb_node, <<"$ref"/utf8>>) of {ok, {some, Ref_str}} -> {ok, {ref, Ref_str}}; _ -> gleam@result:'try'( parse_request_body(Rb_node, Context, Index), fun(Rb) -> {ok, {value, Rb}} end ) end end ). -file("src/oaspec/openapi/parser.gleam", 907). ?DOC( " Parse optional requestBody: Ok(None) if absent, Ok(Some(..)) if valid,\n" " Error if present but malformed.\n" ). -spec parse_optional_request_body( yay:node_(), binary(), gleam@option:option(oaspec@internal@openapi@spec:components(oaspec@internal@openapi@spec:unresolved())), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@option:option(oaspec@internal@openapi@spec:ref_or(oaspec@internal@openapi@spec:request_body(oaspec@internal@openapi@spec:unresolved())))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_optional_request_body(Node, Context, Components, Index) -> Present = gleam@result:is_ok(yay:select_sugar(Node, <<"requestBody"/utf8>>)), gleam@bool:guard( not Present, {ok, none}, fun() -> gleam@result:'try'( parse_request_body_at(Node, Context, Components, Index), fun(Rb) -> {ok, {some, Rb}} end ) end ). -file("src/oaspec/openapi/parser.gleam", 1221). ?DOC(" Parse content map (media type -> schema).\n"). -spec parse_content( yay:node_(), binary(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@dict:dict(binary(), oaspec@internal@openapi@spec:media_type())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_content(Node, Context, Index) -> case yay:select_sugar(Node, <<"content"/utf8>>) of {ok, {node_map, Entries}} -> gleam@list:try_fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Media_type_name} -> gleam@result:'try'( case yay:select_sugar( Value_node, <<"schema"/utf8>> ) of {ok, Schema_node} -> gleam@result:'try'( oaspec@internal@openapi@parser_schema:parse_schema_ref( Schema_node, <>, Index ), fun(Sr) -> {ok, {some, Sr}} end ); _ -> {ok, none} end, fun(Mt_schema) -> Mt_example = oaspec@internal@openapi@parser_value:extract_optional( Value_node, <<"example"/utf8>> ), Mt_examples = oaspec@internal@openapi@parser_value:extract_map( Value_node, <<"examples"/utf8>> ), gleam@result:'try'( parse_encoding_map(Value_node, Index), fun(Mt_encoding) -> {ok, gleam@dict:insert( Acc, Media_type_name, {media_type, Mt_schema, Mt_example, Mt_examples, Mt_encoding} )} end ) end ); _ -> {ok, Acc} end end ); _ -> {ok, maps:new()} end. -file("src/oaspec/openapi/parser.gleam", 1548). ?DOC(" Parse the requestBodies map from components.\n"). -spec parse_request_bodies_map( yay:node_(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@dict:dict(binary(), oaspec@internal@openapi@spec:ref_or(oaspec@internal@openapi@spec:request_body(oaspec@internal@openapi@spec:unresolved())))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_request_bodies_map(Components_node, Index) -> case yay:select_sugar(Components_node, <<"requestBodies"/utf8>>) of {ok, {node_map, Entries}} -> gleam@list:try_fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Name} -> case yay:extract_optional_string( Value_node, <<"$ref"/utf8>> ) of {ok, {some, Ref_str}} -> {ok, gleam@dict:insert( Acc, Name, {ref, Ref_str} )}; _ -> gleam@result:'try'( parse_request_body( Value_node, <<"components.requestBodies."/utf8, Name/binary>>, Index ), fun(Rb) -> {ok, gleam@dict:insert( Acc, Name, {value, Rb} )} end ) end; _ -> {ok, Acc} end end ); _ -> {ok, maps:new()} end. -file("src/oaspec/openapi/parser.gleam", 2166). ?DOC(" Parse a content map (non-result version for use in Parameter).\n"). -spec parse_content_map( yay:node_(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@dict:dict(binary(), oaspec@internal@openapi@spec:media_type())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_content_map(Node, Index) -> case yay:select_sugar(Node, <<"content"/utf8>>) of {ok, {node_map, Entries}} -> gleam@list:try_fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Media_type_name} -> gleam@result:'try'( case yay:select_sugar( Value_node, <<"schema"/utf8>> ) of {ok, Schema_node} -> gleam@result:'try'( oaspec@internal@openapi@parser_schema:parse_schema_ref( Schema_node, <<"content.schema"/utf8>>, Index ), fun(Sr) -> {ok, {some, Sr}} end ); _ -> {ok, none} end, fun(Mt_schema) -> Mt_example = oaspec@internal@openapi@parser_value:extract_optional( Value_node, <<"example"/utf8>> ), Mt_examples = oaspec@internal@openapi@parser_value:extract_map( Value_node, <<"examples"/utf8>> ), gleam@result:'try'( parse_encoding_map(Value_node, Index), fun(Mt_encoding) -> {ok, gleam@dict:insert( Acc, Media_type_name, {media_type, Mt_schema, Mt_example, Mt_examples, Mt_encoding} )} end ) end ); _ -> {ok, Acc} end end ); _ -> {ok, maps:new()} end. -file("src/oaspec/openapi/parser.gleam", 949). ?DOC(" Parse a single parameter.\n"). -spec parse_parameter( yay:node_(), gleam@option:option(oaspec@internal@openapi@spec:components(oaspec@internal@openapi@spec:unresolved())), oaspec@internal@openapi@location_index:location_index() ) -> {ok, oaspec@internal@openapi@spec:ref_or(oaspec@internal@openapi@spec:parameter(oaspec@internal@openapi@spec:unresolved()))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_parameter(Node, _, Index) -> case yay:extract_optional_string(Node, <<"$ref"/utf8>>) of {ok, {some, Ref_str}} -> {ok, {ref, Ref_str}}; _ -> gleam@result:'try'( begin _pipe = yay:extract_string(Node, <<"name"/utf8>>), gleam@result:map_error( _pipe, fun(_capture) -> oaspec@internal@openapi@parser_yay_error:missing_field_from_extraction( _capture, <<"parameter"/utf8>>, <<"name"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <<"parameter"/utf8>>, <<"name"/utf8>> ) ) end ) end, fun(Name) -> gleam@result:'try'( begin _pipe@1 = yay:extract_string(Node, <<"in"/utf8>>), gleam@result:map_error( _pipe@1, fun(_capture@1) -> oaspec@internal@openapi@parser_yay_error:missing_field_from_extraction( _capture@1, <<"parameter."/utf8, Name/binary>>, <<"in"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <<"parameter."/utf8, Name/binary>>, <<"in"/utf8>> ) ) end ) end, fun(In_str) -> gleam@result:'try'( parse_parameter_in(In_str, Index), fun(In_) -> Description = oaspec@internal@openapi@parser_value:optional_string( Node, <<"description"/utf8>> ), Explicit_required = oaspec@internal@openapi@parser_value:optional_bool( Node, <<"required"/utf8>> ), gleam@result:'try'( case {In_, Explicit_required} of {in_path, {some, false}} -> {error, oaspec@openapi@diagnostic:invalid_value( <<"parameter."/utf8, Name/binary>>, <<"Path parameters must have required: true"/utf8>>, oaspec@internal@openapi@location_index:lookup( Index, <<"parameter."/utf8, Name/binary>> ) )}; {in_path, _} -> {ok, true}; {_, {some, V}} -> {ok, V}; {_, none} -> {ok, false} end, fun(Required) -> Deprecated = oaspec@internal@openapi@parser_value:bool_default( Node, <<"deprecated"/utf8>>, false ), gleam@result:'try'( case yay:select_sugar( Node, <<"schema"/utf8>> ) of {ok, Schema_node} -> gleam@result:'try'( oaspec@internal@openapi@parser_schema:parse_schema_ref( Schema_node, <<"parameter.schema"/utf8>>, Index ), fun(Sr) -> {ok, {ok, Sr}} end ); _ -> {ok, {error, nil}} end, fun(Param_schema) -> gleam@result:'try'( case oaspec@internal@openapi@parser_value:optional_string( Node, <<"style"/utf8>> ) of {some, S} -> gleam@result:'try'( parse_parameter_style( S, Index ), fun(Parsed) -> {ok, {some, Parsed}} end ); none -> {ok, none} end, fun(Style) -> Explode = oaspec@internal@openapi@parser_value:optional_bool( Node, <<"explode"/utf8>> ), Allow_reserved = oaspec@internal@openapi@parser_value:bool_default( Node, <<"allowReserved"/utf8>>, false ), gleam@result:'try'( parse_content_map( Node, Index ), fun(Content) -> Examples = oaspec@internal@openapi@parser_value:extract_map( Node, <<"examples"/utf8>> ), Payload = case Param_schema of {ok, Sr@1} -> {parameter_schema, Sr@1}; {error, _} -> {parameter_content, Content} end, {ok, {value, {parameter, Name, In_, Description, Required, Payload, Style, Explode, Deprecated, Allow_reserved, Examples}}} end ) end ) end ) end ) end ) end ) end ) end. -file("src/oaspec/openapi/parser.gleam", 936). ?DOC(" Parse parameters list from a node.\n"). -spec parse_parameters_list( yay:node_(), gleam@option:option(oaspec@internal@openapi@spec:components(oaspec@internal@openapi@spec:unresolved())), oaspec@internal@openapi@location_index:location_index() ) -> {ok, list(oaspec@internal@openapi@spec:ref_or(oaspec@internal@openapi@spec:parameter(oaspec@internal@openapi@spec:unresolved())))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_parameters_list(Node, Components, Index) -> case yay:select_sugar(Node, <<"parameters"/utf8>>) of {ok, {node_seq, Items}} -> gleam@list:try_map( Items, fun(Item) -> parse_parameter(Item, Components, Index) end ); _ -> {ok, []} end. -file("src/oaspec/openapi/parser.gleam", 1521). ?DOC(" Parse the parameters map from components.\n"). -spec parse_parameters_map( yay:node_(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@dict:dict(binary(), oaspec@internal@openapi@spec:ref_or(oaspec@internal@openapi@spec:parameter(oaspec@internal@openapi@spec:unresolved())))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_parameters_map(Components_node, Index) -> case yay:select_sugar(Components_node, <<"parameters"/utf8>>) of {ok, {node_map, Entries}} -> gleam@list:try_fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Name} -> case yay:extract_optional_string( Value_node, <<"$ref"/utf8>> ) of {ok, {some, Ref_str}} -> {ok, gleam@dict:insert( Acc, Name, {ref, Ref_str} )}; _ -> gleam@result:'try'( parse_parameter(Value_node, none, Index), fun(Param) -> {ok, gleam@dict:insert( Acc, Name, Param )} end ) end; _ -> {ok, Acc} end end ); _ -> {ok, maps:new()} end. -file("src/oaspec/openapi/parser.gleam", 2248). ?DOC(" Parse headers map from a node.\n"). -spec parse_headers_map( yay:node_(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@dict:dict(binary(), oaspec@internal@openapi@spec:header())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_headers_map(Node, Index) -> case yay:select_sugar(Node, <<"headers"/utf8>>) of {ok, {node_map, Entries}} -> gleam@list:try_fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Header_name} -> Description = oaspec@internal@openapi@parser_value:optional_string( Value_node, <<"description"/utf8>> ), Required = oaspec@internal@openapi@parser_value:bool_default( Value_node, <<"required"/utf8>>, false ), gleam@result:'try'( case yay:select_sugar( Value_node, <<"schema"/utf8>> ) of {ok, Schema_node} -> gleam@result:'try'( oaspec@internal@openapi@parser_schema:parse_schema_ref( Schema_node, <<<<"header."/utf8, Header_name/binary>>/binary, ".schema"/utf8>>, Index ), fun(Sr) -> {ok, {some, Sr}} end ); _ -> {ok, none} end, fun(Hdr_schema) -> {ok, gleam@dict:insert( Acc, Header_name, {header, Description, Required, Hdr_schema} )} end ); _ -> {ok, Acc} end end ); _ -> {ok, maps:new()} end. -file("src/oaspec/openapi/parser.gleam", 2289). ?DOC(" Parse links map from a node.\n"). -spec parse_links_map(yay:node_()) -> {ok, gleam@dict:dict(binary(), oaspec@internal@openapi@spec:link())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_links_map(Node) -> case yay:select_sugar(Node, <<"links"/utf8>>) of {ok, {node_map, Entries}} -> gleam@list:try_fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Link_name} -> Operation_id = oaspec@internal@openapi@parser_value:optional_string( Value_node, <<"operationId"/utf8>> ), Description = oaspec@internal@openapi@parser_value:optional_string( Value_node, <<"description"/utf8>> ), {ok, gleam@dict:insert( Acc, Link_name, {link, Operation_id, Description} )}; _ -> {ok, Acc} end end ); _ -> {ok, maps:new()} end. -file("src/oaspec/openapi/parser.gleam", 1370). ?DOC(" Parse a single response object.\n"). -spec parse_response( yay:node_(), gleam@option:option(oaspec@internal@openapi@spec:components(oaspec@internal@openapi@spec:unresolved())), oaspec@internal@openapi@location_index:location_index() ) -> {ok, oaspec@internal@openapi@spec:ref_or(oaspec@internal@openapi@spec:response(oaspec@internal@openapi@spec:unresolved()))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_response(Node, _, Index) -> case yay:extract_optional_string(Node, <<"$ref"/utf8>>) of {ok, {some, Ref_str}} -> {ok, {ref, Ref_str}}; _ -> gleam@result:'try'( begin _pipe = yay:extract_string(Node, <<"description"/utf8>>), _pipe@1 = gleam@result:map( _pipe, fun(Field@0) -> {some, Field@0} end ), gleam@result:map_error( _pipe@1, fun(_capture) -> oaspec@internal@openapi@parser_yay_error:missing_field_from_extraction( _capture, <<"response"/utf8>>, <<"description"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <<"response"/utf8>>, <<"description"/utf8>> ) ) end ) end, fun(Description) -> gleam@result:'try'( parse_content(Node, <<"response"/utf8>>, Index), fun(Content) -> gleam@result:'try'( parse_headers_map(Node, Index), fun(Headers) -> gleam@result:'try'( parse_links_map(Node), fun(Links) -> {ok, {value, {response, Description, Content, Headers, Links}}} end ) end ) end ) end ) end. -file("src/oaspec/openapi/parser.gleam", 1269). ?DOC(" Parse responses object.\n"). -spec parse_responses( yay:node_(), binary(), gleam@option:option(oaspec@internal@openapi@spec:components(oaspec@internal@openapi@spec:unresolved())), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@dict:dict(oaspec@internal@util@http:http_status_code(), oaspec@internal@openapi@spec:ref_or(oaspec@internal@openapi@spec:response(oaspec@internal@openapi@spec:unresolved())))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_responses(Node, _, Components, Index) -> case yay:select_sugar(Node, <<"responses"/utf8>>) of {ok, {node_map, Entries}} -> gleam@result:'try'( check_unique_yaml_keys(Entries, <<"responses"/utf8>>, Index), fun(_) -> gleam@list:try_fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Status_code} -> case gleam_stdlib:string_starts_with( Status_code, <<"x-"/utf8>> ) of true -> {ok, Acc}; false -> case oaspec@internal@util@http:parse_status_code( Status_code ) of {ok, Code} -> gleam@result:'try'( parse_response( Value_node, Components, Index ), fun(Resp) -> {ok, gleam@dict:insert( Acc, Code, Resp )} end ); {error, _} -> {ok, Acc} end end; {node_int, Code@1} -> gleam@result:'try'( parse_response( Value_node, Components, Index ), fun(Resp@1) -> {ok, gleam@dict:insert( Acc, {status, Code@1}, Resp@1 )} end ); _ -> {ok, Acc} end end ) end ); _ -> {ok, maps:new()} end. -file("src/oaspec/openapi/parser.gleam", 923). ?DOC( " Parse responses, treating the field as optional.\n" " OpenAPI 3.x requires responses on operations, but webhook operations\n" " in the wild often omit them. Validation can catch missing responses.\n" ). -spec parse_responses_required( yay:node_(), binary(), gleam@option:option(oaspec@internal@openapi@spec:components(oaspec@internal@openapi@spec:unresolved())), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@dict:dict(oaspec@internal@util@http:http_status_code(), oaspec@internal@openapi@spec:ref_or(oaspec@internal@openapi@spec:response(oaspec@internal@openapi@spec:unresolved())))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_responses_required(Node, Context, Components, Index) -> parse_responses(Node, Context, Components, Index). -file("src/oaspec/openapi/parser.gleam", 1579). ?DOC(" Parse the responses map from components.\n"). -spec parse_responses_map( yay:node_(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@dict:dict(binary(), oaspec@internal@openapi@spec:ref_or(oaspec@internal@openapi@spec:response(oaspec@internal@openapi@spec:unresolved())))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_responses_map(Components_node, Index) -> case yay:select_sugar(Components_node, <<"responses"/utf8>>) of {ok, {node_map, Entries}} -> gleam@result:'try'( check_unique_yaml_keys( Entries, <<"components.responses"/utf8>>, Index ), fun(_) -> gleam@list:try_fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Name} -> case yay:extract_optional_string( Value_node, <<"$ref"/utf8>> ) of {ok, {some, Ref_str}} -> {ok, gleam@dict:insert( Acc, Name, {ref, Ref_str} )}; _ -> gleam@result:'try'( parse_response( Value_node, none, Index ), fun(Resp) -> {ok, gleam@dict:insert( Acc, Name, Resp )} end ) end; _ -> {ok, Acc} end end ) end ); _ -> {ok, maps:new()} end. -file("src/oaspec/openapi/parser.gleam", 1980). ?DOC( " Parse a single callback object (maps URL expressions -> PathItems).\n" " Propagates parse errors from individual URL expression path items.\n" ). -spec parse_callback_object( yay:node_(), binary(), gleam@option:option(oaspec@internal@openapi@spec:components(oaspec@internal@openapi@spec:unresolved())), oaspec@internal@openapi@location_index:location_index() ) -> {ok, oaspec@internal@openapi@spec:callback(oaspec@internal@openapi@spec:unresolved())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_callback_object(Node, Context, Components, Index) -> case Node of {node_map, Entries} -> gleam@result:'try'( gleam@list:try_fold( Entries, [], fun(Acc, Entry) -> {Key_node, Path_item_node} = Entry, case Key_node of {node_str, Url_expression} -> case oaspec@internal@openapi@parser_value:optional_string( Path_item_node, <<"$ref"/utf8>> ) of {some, Ref_str} -> {ok, [{Url_expression, {ref, Ref_str}} | Acc]}; none -> gleam@result:'try'( parse_path_item( Path_item_node, <<<>/binary, Url_expression/binary>>, Components, Index ), fun(Path_item) -> {ok, [{Url_expression, {value, Path_item}} | Acc]} end ) end; _ -> {ok, Acc} end end ), fun(Parsed_entries) -> case Parsed_entries of [] -> {error, oaspec@openapi@diagnostic:missing_field( <>, <<"url expression"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <>, <<"url expression"/utf8>> ) )}; _ -> {ok, {callback, maps:from_list(Parsed_entries)}} end end ); _ -> {error, oaspec@openapi@diagnostic:missing_field( <>, <<"url expression"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <>, <<"url expression"/utf8>> ) )} end. -file("src/oaspec/openapi/parser.gleam", 712). ?DOC(" Parse a single path item.\n"). -spec parse_path_item( yay:node_(), binary(), gleam@option:option(oaspec@internal@openapi@spec:components(oaspec@internal@openapi@spec:unresolved())), oaspec@internal@openapi@location_index:location_index() ) -> {ok, oaspec@internal@openapi@spec:path_item(oaspec@internal@openapi@spec:unresolved())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_path_item(Node, Path, Components, Index) -> Summary = oaspec@internal@openapi@parser_value:optional_string( Node, <<"summary"/utf8>> ), Description = oaspec@internal@openapi@parser_value:optional_string( Node, <<"description"/utf8>> ), gleam@result:'try'( parse_optional_operation(Node, <<"get"/utf8>>, Path, Components, Index), fun(Get) -> gleam@result:'try'( parse_optional_operation( Node, <<"post"/utf8>>, Path, Components, Index ), fun(Post) -> gleam@result:'try'( parse_optional_operation( Node, <<"put"/utf8>>, Path, Components, Index ), fun(Put) -> gleam@result:'try'( parse_optional_operation( Node, <<"delete"/utf8>>, Path, Components, Index ), fun(Delete) -> gleam@result:'try'( parse_optional_operation( Node, <<"patch"/utf8>>, Path, Components, Index ), fun(Patch) -> gleam@result:'try'( parse_optional_operation( Node, <<"head"/utf8>>, Path, Components, Index ), fun(Head) -> gleam@result:'try'( parse_optional_operation( Node, <<"options"/utf8>>, Path, Components, Index ), fun(Options) -> gleam@result:'try'( parse_optional_operation( Node, <<"trace"/utf8>>, Path, Components, Index ), fun(Trace) -> gleam@result:'try'( parse_parameters_list( Node, Components, Index ), fun( Parameters ) -> gleam@result:'try'( parse_servers( Node, Index ), fun( Servers ) -> {ok, {path_item, Summary, Description, Get, Post, Put, Delete, Patch, Head, Options, Trace, Parameters, Servers}} end ) end ) end ) end ) end ) end ) end ) end ) end ) end ). -file("src/oaspec/openapi/parser.gleam", 801). ?DOC( " Parse an optional operation: Ok(None) if key absent, Ok(Some(..)) if valid,\n" " Error if present but malformed.\n" ). -spec parse_optional_operation( yay:node_(), binary(), binary(), gleam@option:option(oaspec@internal@openapi@spec:components(oaspec@internal@openapi@spec:unresolved())), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@option:option(oaspec@internal@openapi@spec:operation(oaspec@internal@openapi@spec:unresolved()))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_optional_operation(Node, Method_key, Path, Components, Index) -> Present = gleam@result:is_ok(yay:select_sugar(Node, Method_key)), gleam@bool:guard( not Present, {ok, none}, fun() -> gleam@result:'try'( parse_operation_at(Node, Method_key, Path, Components, Index), fun(Op) -> {ok, {some, Op}} end ) end ). -file("src/oaspec/openapi/parser.gleam", 821). ?DOC(" Parse an operation at a specific HTTP method key.\n"). -spec parse_operation_at( yay:node_(), binary(), binary(), gleam@option:option(oaspec@internal@openapi@spec:components(oaspec@internal@openapi@spec:unresolved())), oaspec@internal@openapi@location_index:location_index() ) -> {ok, oaspec@internal@openapi@spec:operation(oaspec@internal@openapi@spec:unresolved())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_operation_at(Node, Method_key, Path, Components, Index) -> gleam@result:'try'( begin _pipe = yay:select_sugar(Node, Method_key), gleam@result:map_error( _pipe, fun(_capture) -> oaspec@internal@openapi@parser_yay_error:missing_field_from_selector( _capture, Path, Method_key, oaspec@internal@openapi@location_index:lookup_field( Index, Path, Method_key ) ) end ) end, fun(Op_node) -> parse_operation( Op_node, <<<>/binary, Method_key/binary>>, Components, Index ) end ). -file("src/oaspec/openapi/parser.gleam", 842). ?DOC(" Parse a single operation.\n"). -spec parse_operation( yay:node_(), binary(), gleam@option:option(oaspec@internal@openapi@spec:components(oaspec@internal@openapi@spec:unresolved())), oaspec@internal@openapi@location_index:location_index() ) -> {ok, oaspec@internal@openapi@spec:operation(oaspec@internal@openapi@spec:unresolved())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_operation(Node, Context, Components, Index) -> Operation_id = oaspec@internal@openapi@parser_value:optional_string( Node, <<"operationId"/utf8>> ), Summary = oaspec@internal@openapi@parser_value:optional_string( Node, <<"summary"/utf8>> ), Description = oaspec@internal@openapi@parser_value:optional_string( Node, <<"description"/utf8>> ), Tags = case yay:extract_string_list(Node, <<"tags"/utf8>>) of {ok, T} -> T; _ -> [] end, Deprecated = oaspec@internal@openapi@parser_value:bool_default( Node, <<"deprecated"/utf8>>, false ), gleam@result:'try'( parse_parameters_list(Node, Components, Index), fun(Parameters) -> gleam@result:'try'( parse_optional_request_body(Node, Context, Components, Index), fun(Request_body) -> gleam@result:'try'( parse_responses_required( Node, Context, Components, Index ), fun(Responses) -> gleam@result:'try'( parse_optional_security_requirements( Node, Context, Index ), fun(Security) -> gleam@result:'try'( parse_callbacks( Node, Context, Components, Index ), fun(Callbacks) -> gleam@result:'try'( parse_servers(Node, Index), fun(Op_servers) -> External_docs = parse_optional_external_docs( Node ), {ok, {operation, Operation_id, Summary, Description, Tags, Parameters, Request_body, Responses, Deprecated, Security, Callbacks, Op_servers, External_docs}} end ) end ) end ) end ) end ) end ). -file("src/oaspec/openapi/parser.gleam", 1932). ?DOC( " Parse callbacks from an operation node.\n" " Returns an empty dict if no callbacks are present.\n" " Each entry is a `RefOr(Callback)`: a top-level `$ref` pointing at\n" " `#/components/callbacks/foo` is preserved as-is (so the reusable\n" " callback reference survives into the resolved AST), while inline\n" " definitions are parsed into their URL-expression→PathItem shape.\n" ). -spec parse_callbacks( yay:node_(), binary(), gleam@option:option(oaspec@internal@openapi@spec:components(oaspec@internal@openapi@spec:unresolved())), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@dict:dict(binary(), oaspec@internal@openapi@spec:ref_or(oaspec@internal@openapi@spec:callback(oaspec@internal@openapi@spec:unresolved())))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_callbacks(Node, Context, Components, Index) -> case yay:select_sugar(Node, <<"callbacks"/utf8>>) of {ok, {node_map, Entries}} -> gleam@list:try_fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Callback_name} -> case yay:extract_optional_string( Value_node, <<"$ref"/utf8>> ) of {ok, {some, Ref_str}} -> {ok, gleam@dict:insert( Acc, Callback_name, {ref, Ref_str} )}; {ok, none} -> gleam@result:'try'( parse_callback_object( Value_node, Context, Components, Index ), fun(Callback) -> {ok, gleam@dict:insert( Acc, Callback_name, {value, Callback} )} end ); {error, _} -> {error, oaspec@openapi@diagnostic:invalid_value( <<<<<>/binary, Callback_name/binary>>/binary, ".$ref"/utf8>>, <<"`$ref` under a callback entry must be a string pointing at '#/components/callbacks/...'."/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <<<>/binary, Callback_name/binary>>, <<"$ref"/utf8>> ) )} end; _ -> {ok, Acc} end end ); _ -> {ok, maps:new()} end. -file("src/oaspec/openapi/parser.gleam", 671). ?DOC(" Parse the paths object.\n"). -spec parse_paths( yay:node_(), gleam@option:option(oaspec@internal@openapi@spec:components(oaspec@internal@openapi@spec:unresolved())), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@dict:dict(binary(), oaspec@internal@openapi@spec:ref_or(oaspec@internal@openapi@spec:path_item(oaspec@internal@openapi@spec:unresolved())))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_paths(Root, Components, Index) -> case yay:select_sugar(Root, <<"paths"/utf8>>) of {ok, {node_map, Entries}} -> gleam@list:try_fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Path} -> case gleam_stdlib:string_starts_with( Path, <<"x-"/utf8>> ) of true -> {ok, Acc}; false -> gleam@result:'try'( case oaspec@internal@openapi@parser_value:optional_string( Value_node, <<"$ref"/utf8>> ) of {some, Ref_str} -> {ok, {ref, Ref_str}}; none -> gleam@result:'try'( parse_path_item( Value_node, Path, Components, Index ), fun(Pi) -> {ok, {value, Pi}} end ) end, fun(Ref_or_path_item) -> {ok, gleam@dict:insert( Acc, Path, Ref_or_path_item )} end ) end; _ -> {ok, Acc} end end ); _ -> {ok, maps:new()} end. -file("src/oaspec/openapi/parser.gleam", 1453). ?DOC( " Parse `components.callbacks`: each entry is a named reusable callback\n" " object. Returns an empty dict if the section is missing. `$ref` values\n" " are preserved so chains like `components.callbacks.foo.$ref: ...`\n" " stay as references.\n" ). -spec parse_components_callbacks_map( yay:node_(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@dict:dict(binary(), oaspec@internal@openapi@spec:ref_or(oaspec@internal@openapi@spec:callback(oaspec@internal@openapi@spec:unresolved())))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_components_callbacks_map(Components_node, Index) -> case yay:select_sugar(Components_node, <<"callbacks"/utf8>>) of {ok, {node_map, Entries}} -> gleam@list:try_fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Name} -> case yay:extract_optional_string( Value_node, <<"$ref"/utf8>> ) of {ok, {some, Ref_str}} -> {ok, gleam@dict:insert( Acc, Name, {ref, Ref_str} )}; {ok, none} -> gleam@result:'try'( parse_callback_object( Value_node, <<"components.callbacks."/utf8, Name/binary>>, none, Index ), fun(Callback) -> {ok, gleam@dict:insert( Acc, Name, {value, Callback} )} end ); {error, _} -> {error, oaspec@openapi@diagnostic:invalid_value( <<<<"components.callbacks."/utf8, Name/binary>>/binary, ".$ref"/utf8>>, <<"`$ref` under a reusable callback must be a string pointing at '#/components/callbacks/...'."/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <<"components.callbacks."/utf8, Name/binary>>, <<"$ref"/utf8>> ) )} end; _ -> {ok, Acc} end end ); _ -> {ok, maps:new()} end. -file("src/oaspec/openapi/parser.gleam", 1652). ?DOC(" Parse the pathItems map from components.\n"). -spec parse_path_items_map( yay:node_(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@dict:dict(binary(), oaspec@internal@openapi@spec:ref_or(oaspec@internal@openapi@spec:path_item(oaspec@internal@openapi@spec:unresolved())))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_path_items_map(Components_node, Index) -> case yay:select_sugar(Components_node, <<"pathItems"/utf8>>) of {ok, {node_map, Entries}} -> gleam@list:try_fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Name} -> case yay:extract_optional_string( Value_node, <<"$ref"/utf8>> ) of {ok, {some, Ref_str}} -> {ok, gleam@dict:insert( Acc, Name, {ref, Ref_str} )}; _ -> gleam@result:'try'( parse_path_item( Value_node, <<"components.pathItems."/utf8, Name/binary>>, none, Index ), fun(Path_item) -> {ok, gleam@dict:insert( Acc, Name, {value, Path_item} )} end ) end; _ -> {ok, Acc} end end ); _ -> {ok, maps:new()} end. -file("src/oaspec/openapi/parser.gleam", 1401). ?DOC(" Parse the components section.\n"). -spec parse_components( yay:node_(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, oaspec@internal@openapi@spec:components(oaspec@internal@openapi@spec:unresolved())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_components(Root, Index) -> gleam@result:'try'( begin _pipe = yay:select_sugar(Root, <<"components"/utf8>>), gleam@result:map_error( _pipe, fun(_capture) -> oaspec@internal@openapi@parser_yay_error:missing_field_from_selector( _capture, <<""/utf8>>, <<"components"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <<""/utf8>>, <<"components"/utf8>> ) ) end ) end, fun(Components_node) -> gleam@result:'try'( parse_schemas_map(Components_node, Index), fun(Schemas) -> gleam@result:'try'( parse_parameters_map(Components_node, Index), fun(Parameters) -> gleam@result:'try'( parse_request_bodies_map(Components_node, Index), fun(Request_bodies) -> gleam@result:'try'( parse_responses_map( Components_node, Index ), fun(Responses) -> gleam@result:'try'( parse_security_schemes_map( Components_node, Index ), fun(Security_schemes) -> gleam@result:'try'( parse_path_items_map( Components_node, Index ), fun(Path_items) -> gleam@result:'try'( parse_headers_map( Components_node, Index ), fun(Headers) -> Examples = oaspec@internal@openapi@parser_value:extract_map( Components_node, <<"examples"/utf8>> ), gleam@result:'try'( parse_links_map( Components_node ), fun( Links ) -> gleam@result:'try'( parse_components_callbacks_map( Components_node, Index ), fun( Callbacks ) -> {ok, {components, Schemas, Parameters, Request_bodies, Responses, Security_schemes, Path_items, Headers, Examples, Links, Callbacks}} end ) end ) end ) end ) end ) end ) end ) end ) end ) end ). -file("src/oaspec/openapi/parser.gleam", 572). ?DOC( " Parse optional components section.\n" " Returns Ok(None) if not present, Ok(Some(..)) if valid, Error if malformed.\n" ). -spec parse_optional_components( yay:node_(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@option:option(oaspec@internal@openapi@spec:components(oaspec@internal@openapi@spec:unresolved()))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_optional_components(Root, Index) -> Present = gleam@result:is_ok(yay:select_sugar(Root, <<"components"/utf8>>)), gleam@bool:guard( not Present, {ok, none}, fun() -> gleam@result:'try'( parse_components(Root, Index), fun(Comps) -> {ok, {some, Comps}} end ) end ). -file("src/oaspec/openapi/parser.gleam", 2132). ?DOC(" Parse webhooks from root.\n"). -spec parse_webhooks( yay:node_(), gleam@option:option(oaspec@internal@openapi@spec:components(oaspec@internal@openapi@spec:unresolved())), oaspec@internal@openapi@location_index:location_index() ) -> {ok, gleam@dict:dict(binary(), oaspec@internal@openapi@spec:ref_or(oaspec@internal@openapi@spec:path_item(oaspec@internal@openapi@spec:unresolved())))} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_webhooks(Node, Components, Index) -> case yay:select_sugar(Node, <<"webhooks"/utf8>>) of {ok, {node_map, Entries}} -> gleam@list:try_fold( Entries, maps:new(), fun(Acc, Entry) -> {Key_node, Value_node} = Entry, case Key_node of {node_str, Name} -> case oaspec@internal@openapi@parser_value:optional_string( Value_node, <<"$ref"/utf8>> ) of {some, Ref_str} -> {ok, gleam@dict:insert( Acc, Name, {ref, Ref_str} )}; none -> gleam@result:'try'( parse_path_item( Value_node, <<"webhooks."/utf8, Name/binary>>, Components, Index ), fun(Path_item) -> {ok, gleam@dict:insert( Acc, Name, {value, Path_item} )} end ) end; _ -> {ok, Acc} end end ); _ -> {ok, maps:new()} end. -file("src/oaspec/openapi/parser.gleam", 477). ?DOC(" Parse the root OpenAPI object.\n"). -spec parse_root( yay:node_(), oaspec@internal@openapi@location_index:location_index() ) -> {ok, oaspec@internal@openapi@spec:open_api_spec(oaspec@internal@openapi@spec:unresolved())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_root(Node, Index) -> gleam@result:'try'( begin _pipe = yay:extract_string(Node, <<"openapi"/utf8>>), _pipe@2 = gleam@result:lazy_or( _pipe, fun() -> _pipe@1 = yay:extract_float(Node, <<"openapi"/utf8>>), gleam@result:map( _pipe@1, fun(F) -> case F =:= erlang:float(erlang:trunc(F)) of true -> <<(erlang:integer_to_binary(erlang:trunc(F)))/binary, ".0"/utf8>>; false -> gleam_stdlib:float_to_string(F) end end ) end ), gleam@result:map_error( _pipe@2, fun(_capture) -> oaspec@internal@openapi@parser_yay_error:missing_field_from_extraction( _capture, <<""/utf8>>, <<"openapi"/utf8>>, oaspec@internal@openapi@location_index:lookup_field( Index, <<""/utf8>>, <<"openapi"/utf8>> ) ) end ) end, fun(Openapi) -> gleam@result:'try'( validate_openapi_version( Openapi, oaspec@internal@openapi@location_index:lookup_field( Index, <<""/utf8>>, <<"openapi"/utf8>> ) ), fun(_) -> gleam@result:'try'( parse_info(Node, Index), fun(Info) -> gleam@result:'try'( parse_optional_components(Node, Index), fun(Components) -> gleam@result:'try'( parse_paths(Node, Components, Index), fun(Paths) -> gleam@result:'try'( parse_servers(Node, Index), fun(Servers) -> gleam@result:'try'( parse_security_requirements( Node, <<""/utf8>>, Index ), fun(Security) -> gleam@result:'try'( parse_webhooks( Node, Components, Index ), fun(Webhooks) -> Tags = parse_tags( Node ), External_docs = parse_optional_external_docs( Node ), Json_schema_dialect = oaspec@internal@openapi@parser_value:optional_string( Node, <<"jsonSchemaDialect"/utf8>> ), {ok, {open_api_spec, Openapi, Info, Paths, Components, Servers, Security, Webhooks, Tags, External_docs, Json_schema_dialect}} end ) end ) end ) end ) end ) end ) end ) end ). -file("src/oaspec/openapi/parser.gleam", 285). ?DOC( " Same as `parse_string` but also returns the YAML `LocationIndex`\n" " built from the input. Caller-side companion to\n" " `parse_file_with_locations` (Issue #411).\n" ). -spec parse_string_with_locations(binary()) -> {ok, {oaspec@internal@openapi@spec:open_api_spec(oaspec@internal@openapi@spec:unresolved()), oaspec@internal@openapi@location_index:location_index()}} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_string_with_locations(Content) -> gleam@result:'try'( parse_to_node(Content), fun(_use0) -> {Root, Index} = _use0, gleam@result:map( parse_root(Root, Index), fun(Spec) -> {Spec, Index} end ) end ). -file("src/oaspec/openapi/parser.gleam", 275). ?DOC( " Parse an OpenAPI spec from a YAML/JSON string. The default path\n" " runs the input through yamerl, which preserves YAML semantics and\n" " source locations but is too slow on large JSON specs (the GitHub\n" " REST OpenAPI is ~12 MB and yamerl effectively hangs — see issue\n" " #352). Use `parse_json_string` directly when the content is known\n" " to be JSON.\n" "\n" " `parse_string` does **not** apply the DoS limits documented in\n" " `ParseLimits`. Reach for `parse_string_with_limits` when the input\n" " is attacker-controlled or sourced from an untrusted file system\n" " (admin-uploaded specs, contract-validation pipelines, CI runners\n" " over user-supplied specs) — see issue #553.\n" "\n" " **YAML 1.1 type coercion: parse_string vs parse_json_string.**\n" " yamerl applies YAML 1.1 implicit-type rules to scalars before they\n" " reach metamon's tree walker. The OTP `json:decode/3` frontend used\n" " by `parse_json_string` does not. The two parsers therefore diverge\n" " on the same JSON bytes whenever a value matches a YAML 1.1\n" " implicit-type pattern:\n" "\n" " | JSON literal | `parse_string` (yamerl, YAML 1.1) | `parse_json_string` (OTP) |\n" " | --- | --- | --- |\n" " | `\"version\": \"Yes\"` | bool `True` | string `\"Yes\"` |\n" " | `\"role\": \"No\"` | bool `False` | string `\"No\"` |\n" " | `\"flag\": \"On\"` / `\"Off\"` | bool `True` / `False` | string `\"On\"` / `\"Off\"` |\n" " | `\"version\": 1.10` | float `1.1` (trailing zero lost) | float `1.10` |\n" " | `\"hex\": 0x10` | int `16` (yamerl extension) | parse error (not valid JSON) |\n" "\n" " For JSON OpenAPI documents — Stripe, GitHub, AsyncAPI, etc. — prefer\n" " `parse_json_string` (or `parse_string_or_json_with_locations`,\n" " which auto-routes by inspecting the first non-whitespace byte).\n" " `parse_string` remains correct for YAML input and for JSON inputs\n" " whose values do not collide with YAML 1.1 implicit-type patterns.\n" ). -spec parse_string(binary()) -> {ok, oaspec@internal@openapi@spec:open_api_spec(oaspec@internal@openapi@spec:unresolved())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_string(Content) -> _pipe = parse_string_with_locations(Content), gleam@result:map(_pipe, fun(Pair) -> erlang:element(1, Pair) end). -file("src/oaspec/openapi/parser.gleam", 306). ?DOC( " Parse an OpenAPI spec with DoS-aware resource limits. Currently\n" " enforces `limits.max_input_bytes` before parsing begins; the other\n" " fields on `ParseLimits` are reserved for future enforcement (see\n" " issue #553).\n" "\n" " The byte cap is checked via `string.byte_size` so the function\n" " returns immediately on oversized input rather than handing it to\n" " yamerl / `json:decode/3` (both of which allocate proportional\n" " tree memory before the size could be discovered downstream).\n" "\n" " Returns the same `Diagnostic`-bearing `Result` as `parse_string`\n" " when the limit is satisfied; returns a structured\n" " `parse_limit_exceeded` diagnostic when the limit is exceeded.\n" ). -spec parse_string_with_limits(binary(), parse_limits()) -> {ok, oaspec@internal@openapi@spec:open_api_spec(oaspec@internal@openapi@spec:unresolved())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_string_with_limits(Content, Limits) -> case enforce_input_byte_limit(Content, Limits) of {error, D} -> {error, D}; {ok, _} -> parse_string(Content) end. -file("src/oaspec/openapi/parser.gleam", 363). ?DOC( " JSON variant of `parse_string_with_locations`.\n" "\n" " OTP's `json:decode/3` does not expose token positions, so the\n" " returned `LocationIndex` is **always empty** (`location_index.empty()`).\n" " Capability-check diagnostics from a JSON-only spec therefore carry\n" " `NoSourceLoc`, while diagnostics from the YAML path can carry\n" " line/column info via the index. Downstream tooling that wants to\n" " dispatch over both formats with one signature should reach for\n" " `parse_string_or_json_with_locations`, which inspects the first\n" " non-whitespace byte to pick between the two parsers.\n" "\n" " Returning the empty index instead of an `Option(LocationIndex)` keeps\n" " the type identical to `parse_string_with_locations`, so callers do\n" " not need a separate code path — they only lose location-aware\n" " diagnostics on the JSON branch.\n" ). -spec parse_json_string_with_locations(binary()) -> {ok, {oaspec@internal@openapi@spec:open_api_spec(oaspec@internal@openapi@spec:unresolved()), oaspec@internal@openapi@location_index:location_index()}} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_json_string_with_locations(Content) -> gleam@result:'try'( decode_json_to_node(Content), fun(Root) -> gleam@result:map( parse_root(Root, oaspec@internal@openapi@location_index:empty()), fun(Spec) -> {Spec, oaspec@internal@openapi@location_index:empty()} end ) end ). -file("src/oaspec/openapi/parser.gleam", 70). ?DOC( " Internal parse entry that threads the `visited` stack through every\n" " external-ref recursion so `A.yaml -> B.yaml -> A.yaml` cycles fail\n" " fast with a dedicated diagnostic instead of spinning forever.\n" "\n" " Returns both the parsed spec and the YAML `LocationIndex` built from\n" " the file's content. Recursive external-ref resolution discards\n" " nested files' indices — only the top-level file's index is surfaced\n" " (capability-check needs *one* canonical index, and external refs\n" " land back at their `$ref` site in the main file anyway).\n" ). -spec parse_file_internal( binary(), list(binary()), oaspec@internal@progress:reporter() ) -> {ok, {oaspec@internal@openapi@spec:open_api_spec(oaspec@internal@openapi@spec:unresolved()), oaspec@internal@openapi@location_index:location_index()}} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_file_internal(Path, Visited, Reporter) -> Canonical = canonicalize_ref_path(Path), gleam@bool:guard( gleam@list:contains(Visited, Canonical), {error, cyclic_external_ref_diagnostic(Canonical, Visited)}, fun() -> {Elapsed, Read_result} = oaspec@internal@progress:timed( fun() -> simplifile:read(Path) end ), gleam@result:'try'( begin _pipe = Read_result, gleam@result:map_error( _pipe, fun(Err) -> oaspec@openapi@diagnostic:file_error( <<<<<<<<"Cannot read file: "/utf8, Path/binary>>/binary, " ("/utf8>>/binary, (simplifile:describe_error(Err))/binary>>/binary, ")"/utf8>> ) end ) end, fun(Content) -> oaspec@internal@progress:report( Reporter, <<<<<<<<<<<<"read "/utf8, Path/binary>>/binary, " ("/utf8>>/binary, (format_size(erlang:byte_size(Content)))/binary>>/binary, ", took "/utf8>>/binary, (oaspec@internal@progress:format_ms(Elapsed))/binary>>/binary, ")"/utf8>> ), Is_json = looks_like_json_path(Path), {Elapsed@1, Parsed} = oaspec@internal@progress:timed( fun() -> case Is_json of true -> parse_json_string_with_locations(Content); false -> parse_string_with_locations(Content) end end ), oaspec@internal@progress:report(Reporter, case Is_json of true -> <<<<"parse JSON document via OTP json (took "/utf8, (oaspec@internal@progress:format_ms( Elapsed@1 ))/binary>>/binary, ")"/utf8>>; false -> <<<<"parse YAML document via yamerl (took "/utf8, (oaspec@internal@progress:format_ms( Elapsed@1 ))/binary>>/binary, ")"/utf8>> end), gleam@result:'try'( Parsed, fun(_use0) -> {Spec, Index} = _use0, New_visited = [Canonical | Visited], {Elapsed@2, Resolved} = oaspec@internal@progress:timed( fun() -> oaspec@internal@openapi@external_loader:resolve_external_component_refs( Spec, oaspec@internal@openapi@external_loader_planner:base_dir_of( Path ), fun(Sub_path) -> _pipe@1 = parse_file_internal( Sub_path, New_visited, Reporter ), gleam@result:map( _pipe@1, fun(Pair) -> erlang:element(1, Pair) end ) end ) end ), oaspec@internal@progress:report( Reporter, <<<<"resolve relative-file $ref components (took "/utf8, (oaspec@internal@progress:format_ms( Elapsed@2 ))/binary>>/binary, ")"/utf8>> ), _pipe@2 = Resolved, gleam@result:map(_pipe@2, fun(S) -> {S, Index} end) end ) end ) end ). -file("src/oaspec/openapi/parser.gleam", 42). ?DOC( " Parse an OpenAPI spec from a file path.\n" " Supports both YAML (.yaml, .yml) and JSON (.json) files.\n" " After parsing, resolves relative-file `$ref` values across schemas,\n" " parameters, request bodies, responses, and path items — including\n" " nested object/array properties and composition branches — by loading\n" " the referenced files from disk and merging their definitions into the\n" " main spec. Cyclic external ref graphs (`A.yaml → B.yaml → A.yaml`)\n" " fail fast with a dedicated diagnostic. HTTP/HTTPS URLs are not\n" " followed.\n" ). -spec parse_file(binary()) -> {ok, oaspec@internal@openapi@spec:open_api_spec(oaspec@internal@openapi@spec:unresolved())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_file(Path) -> _pipe = parse_file_internal(Path, [], oaspec@internal@progress:noop()), gleam@result:map(_pipe, fun(Pair) -> erlang:element(1, Pair) end). -file("src/oaspec/openapi/parser.gleam", 54). ?DOC( " Combined `parse_file` entry point that accepts a `Reporter` and\n" " also returns the top-level YAML `LocationIndex`. Issue #411 +\n" " #352. The CLI uses this so capability-check diagnostics surface\n" " `path:line:column:` and progress lines on big specs at the same\n" " time. Library callers that don't need progress should pass\n" " `progress.noop()`; callers that don't need locations can discard\n" " the second tuple element.\n" ). -spec parse_file_with_progress_and_locations( binary(), oaspec@internal@progress:reporter() ) -> {ok, {oaspec@internal@openapi@spec:open_api_spec(oaspec@internal@openapi@spec:unresolved()), oaspec@internal@openapi@location_index:location_index()}} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_file_with_progress_and_locations(Path, Reporter) -> parse_file_internal(Path, [], Reporter). -file("src/oaspec/openapi/parser.gleam", 341). ?DOC( " Parse an OpenAPI spec from a JSON string using OTP's native JSON\n" " decoder instead of yamerl. Roughly two orders of magnitude faster\n" " than `parse_string` on large specs because the YAML pre-processing\n" " and constructor passes are skipped (issue #352). Behaves like\n" " `parse_string` once the tree is built — same `OpenApiSpec` shape,\n" " same downstream pipeline. Diagnostics from this path do not carry\n" " source line/column info because OTP `json:decode/3` does not\n" " expose decoder positions; the caller still gets the path-prefixed\n" " error message that downstream tooling relies on.\n" ). -spec parse_json_string(binary()) -> {ok, oaspec@internal@openapi@spec:open_api_spec(oaspec@internal@openapi@spec:unresolved())} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_json_string(Content) -> _pipe = parse_json_string_with_locations(Content), gleam@result:map(_pipe, fun(Pair) -> erlang:element(1, Pair) end). -file("src/oaspec/openapi/parser.gleam", 387). ?DOC( " Auto-dispatch over `parse_string_with_locations` (YAML) and\n" " `parse_json_string_with_locations` (JSON) based on the first\n" " non-whitespace byte of `content`.\n" "\n" " `{` and `[` route to the JSON parser (orders of magnitude faster on\n" " large specs — see `parse_json_string`); anything else routes to\n" " the YAML parser. The dispatch covers the conventional OpenAPI\n" " document shapes (object root for full specs, array root for the\n" " rare top-level component lists). Whitespace prefixes (BOM, leading\n" " spaces, blank lines) are skipped before the discriminator byte\n" " is inspected.\n" "\n" " Use this when downstream tooling needs a single entry point for\n" " both formats — LSP-style features, error-hint generators,\n" " source-map producers — without writing the dispatch wrapper at\n" " every call site.\n" ). -spec parse_string_or_json_with_locations(binary()) -> {ok, {oaspec@internal@openapi@spec:open_api_spec(oaspec@internal@openapi@spec:unresolved()), oaspec@internal@openapi@location_index:location_index()}} | {error, oaspec@openapi@diagnostic:diagnostic()}. parse_string_or_json_with_locations(Content) -> case looks_like_json(Content) of true -> parse_json_string_with_locations(Content); false -> parse_string_with_locations(Content) end.