-module(oaspec@openapi@diagnostic). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/oaspec/openapi/diagnostic.gleam"). -export([file_error/1, yaml_error/2, parse_limit_exceeded/3, missing_field/3, invalid_value/3, duplicate_key/3, resolve_error/4, capability/6, validation/5, validation_error_both/3, validation_error_server/3, validation_warning_both/3, errors_only/1, warnings_only/1, filter_by_mode/2, to_string/1, render/2, to_short_string/1]). -export_type([phase/0, severity/0, target/0, source_loc/0, diagnostic/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 phase() :: phase_parse | phase_normalize | phase_resolve | phase_capability_check | phase_validate | phase_codegen. -type severity() :: severity_error | severity_warning. -type target() :: target_both | target_client | target_server. -type source_loc() :: {source_loc, integer(), integer()} | no_source_loc. -type diagnostic() :: {diagnostic, binary(), phase(), severity(), target(), binary(), source_loc(), binary(), gleam@option:option(binary())}. -file("src/oaspec/openapi/diagnostic.gleam", 55). -spec file_error(binary()) -> diagnostic(). file_error(Detail) -> {diagnostic, <<"file_error"/utf8>>, phase_parse, severity_error, target_both, <<""/utf8>>, no_source_loc, Detail, none}. -file("src/oaspec/openapi/diagnostic.gleam", 68). -spec yaml_error(binary(), source_loc()) -> diagnostic(). yaml_error(Detail, Loc) -> {diagnostic, <<"yaml_error"/utf8>>, phase_parse, severity_error, target_both, <<""/utf8>>, Loc, Detail, none}. -file("src/oaspec/openapi/diagnostic.gleam", 85). ?DOC( " Parser refused the input because it exceeded a configured DoS\n" " limit (see `parser.ParseLimits`). The message names the limit, the\n" " configured cap, and the actual value so callers can either bump\n" " the limit (when they trust the source) or reject the input.\n" ). -spec parse_limit_exceeded(binary(), integer(), integer()) -> diagnostic(). parse_limit_exceeded(Limit, Configured, Actual) -> {diagnostic, <<"parse_limit_exceeded"/utf8>>, phase_parse, severity_error, target_both, <<""/utf8>>, no_source_loc, <<<<<<<<<<<<"Parser limit '"/utf8, Limit/binary>>/binary, "' exceeded: configured cap "/utf8>>/binary, (erlang:integer_to_binary(Configured))/binary>>/binary, ", actual "/utf8>>/binary, (erlang:integer_to_binary(Actual))/binary>>/binary, ". The input was refused before parsing to bound denial-of-service exposure (issue #553)."/utf8>>, {some, <<<<"If the source is trusted, raise '"/utf8, Limit/binary>>/binary, "' via parser.ParseLimits and call parse_string_with_limits with the larger cap. Untrusted sources should keep the default cap."/utf8>>}}. -file("src/oaspec/openapi/diagnostic.gleam", 116). -spec missing_field(binary(), binary(), source_loc()) -> diagnostic(). missing_field(Path, Field, Loc) -> {diagnostic, <<"missing_field"/utf8>>, phase_parse, severity_error, target_both, Path, Loc, <<"Missing required field: "/utf8, Field/binary>>, {some, <<"Check your OpenAPI spec structure."/utf8>>}}. -file("src/oaspec/openapi/diagnostic.gleam", 133). -spec invalid_value(binary(), binary(), source_loc()) -> diagnostic(). invalid_value(Path, Detail, Loc) -> {diagnostic, <<"invalid_value"/utf8>>, phase_parse, severity_error, target_both, Path, Loc, Detail, none}. -file("src/oaspec/openapi/diagnostic.gleam", 159). ?DOC( " Issue #573: a YAML mapping at `path` contains the same key twice.\n" " yamerl tolerates duplicate mapping keys (only the last value\n" " survives), so this is the only signal users get that their spec\n" " silently dropped one of two definitions. Surfaced as a parse-phase\n" " error so `oaspec validate` rejects the file before generation.\n" "\n" " `key` is the duplicated key as it appears in the source (e.g.\n" " `\"200\"` for a duplicated response status code, `\"foo\"` for a\n" " duplicated component name).\n" ). -spec duplicate_key(binary(), binary(), source_loc()) -> diagnostic(). duplicate_key(Path, Key, Loc) -> {diagnostic, <<"duplicate_key"/utf8>>, phase_parse, severity_error, target_both, Path, Loc, <<<<<<"Duplicate key '"/utf8, Key/binary>>/binary, "' in "/utf8>>/binary, Path/binary>>, {some, <<"OpenAPI inherits JSON-object key uniqueness; YAML 1.2 ยง3.2.1.3 leaves duplicate mapping keys undefined. Remove or rename the duplicate so the spec parses unambiguously."/utf8>>}}. -file("src/oaspec/openapi/diagnostic.gleam", 182). -spec resolve_error( binary(), binary(), gleam@option:option(binary()), source_loc() ) -> diagnostic(). resolve_error(Path, Detail, Hint, Loc) -> {diagnostic, <<"resolve_error"/utf8>>, phase_resolve, severity_error, target_both, Path, Loc, Detail, Hint}. -file("src/oaspec/openapi/diagnostic.gleam", 209). ?DOC( " Issue #411: capability checks now thread a YAML `SourceLoc` through\n" " every diagnostic. Pass `NoSourceLoc` only when the caller genuinely\n" " has no LocationIndex available (e.g. tests that bypass the parser\n" " path); production code always has one because it goes through\n" " `parser.parse_file_with_locations` / `generate.generate_with_locations`.\n" ). -spec capability( binary(), binary(), severity(), target(), gleam@option:option(binary()), source_loc() ) -> diagnostic(). capability(Path, Detail, Severity, Target, Hint, Loc) -> {diagnostic, <<"capability"/utf8>>, phase_capability_check, Severity, Target, Path, Loc, Detail, Hint}. -file("src/oaspec/openapi/diagnostic.gleam", 233). -spec validation( binary(), binary(), severity(), target(), gleam@option:option(binary()) ) -> diagnostic(). validation(Path, Detail, Severity, Target, Hint) -> {diagnostic, <<"validation"/utf8>>, phase_validate, Severity, Target, Path, no_source_loc, Detail, Hint}. -file("src/oaspec/openapi/diagnostic.gleam", 260). ?DOC( " Issue #416: severity / target shortcut builders for the common\n" " `validation` shapes used in `internal/codegen/validate`. The\n" " project review noted that the 5-field validation/5 constructor\n" " gets called 28+ times with the same severity / target pair on\n" " almost every site; these helpers collapse the common shape and\n" " let call sites focus on the variable bits (path / detail / hint).\n" " SeverityError + TargetBoth โ€” the most common shape across\n" " validate.gleam.\n" ). -spec validation_error_both(binary(), binary(), gleam@option:option(binary())) -> diagnostic(). validation_error_both(Path, Detail, Hint) -> validation(Path, Detail, severity_error, target_both, Hint). -file("src/oaspec/openapi/diagnostic.gleam", 276). ?DOC( " SeverityError + TargetServer โ€” the second most common shape\n" " (server-only feature restrictions).\n" ). -spec validation_error_server(binary(), binary(), gleam@option:option(binary())) -> diagnostic(). validation_error_server(Path, Detail, Hint) -> validation(Path, Detail, severity_error, target_server, Hint). -file("src/oaspec/openapi/diagnostic.gleam", 292). ?DOC( " SeverityWarning + TargetBoth โ€” used when a spec shape is\n" " supported but the user should know about it.\n" ). -spec validation_warning_both(binary(), binary(), gleam@option:option(binary())) -> diagnostic(). validation_warning_both(Path, Detail, Hint) -> validation(Path, Detail, severity_warning, target_both, Hint). -file("src/oaspec/openapi/diagnostic.gleam", 311). ?DOC(" Filter to only errors (not warnings).\n"). -spec errors_only(list(diagnostic())) -> list(diagnostic()). errors_only(Issues) -> gleam@list:filter( Issues, fun(E) -> erlang:element(4, E) =:= severity_error end ). -file("src/oaspec/openapi/diagnostic.gleam", 316). ?DOC(" Filter to only warnings (not errors).\n"). -spec warnings_only(list(diagnostic())) -> list(diagnostic()). warnings_only(Issues) -> gleam@list:filter( Issues, fun(E) -> erlang:element(4, E) =:= severity_warning end ). -file("src/oaspec/openapi/diagnostic.gleam", 321). ?DOC(" Filter diagnostics to those relevant for the selected generation mode.\n"). -spec filter_by_mode(list(diagnostic()), oaspec@config:generate_mode()) -> list(diagnostic()). filter_by_mode(Issues, Mode) -> case Mode of client -> gleam@list:filter( Issues, fun(E) -> erlang:element(5, E) /= target_server end ); server -> gleam@list:filter( Issues, fun(E@1) -> erlang:element(5, E@1) /= target_client end ); both -> Issues end. -file("src/oaspec/openapi/diagnostic.gleam", 337). ?DOC(" Convert a diagnostic to a human-readable string.\n"). -spec to_string(diagnostic()) -> binary(). to_string(D) -> Phase_str = case erlang:element(3, D) of phase_parse -> <<"Parse"/utf8>>; phase_normalize -> <<"Normalize"/utf8>>; phase_resolve -> <<"Resolve"/utf8>>; phase_capability_check -> <<"CapabilityCheck"/utf8>>; phase_validate -> <<"Validate"/utf8>>; phase_codegen -> <<"Codegen"/utf8>> end, Severity_str = case erlang:element(4, D) of severity_error -> <<"Error"/utf8>>; severity_warning -> <<"Warning"/utf8>> end, Loc_str = case erlang:element(7, D) of {source_loc, Line, Column} -> <<<<<<<<" (line "/utf8, (erlang:integer_to_binary(Line))/binary>>/binary, ", column "/utf8>>/binary, (erlang:integer_to_binary(Column))/binary>>/binary, ")"/utf8>>; no_source_loc -> <<""/utf8>> end, Pointer_str = case erlang:element(6, D) of <<""/utf8>> -> <<""/utf8>>; P -> <<" at "/utf8, P/binary>> end, Hint_str = case erlang:element(9, D) of {some, H} -> <<" "/utf8, H/binary>>; none -> <<""/utf8>> end, <<<<<<<<<<<<<<<<"["/utf8, Phase_str/binary>>/binary, "] "/utf8>>/binary, Severity_str/binary>>/binary, Pointer_str/binary>>/binary, ": "/utf8>>/binary, (erlang:element(8, D))/binary>>/binary, Loc_str/binary>>/binary, Hint_str/binary>>. -file("src/oaspec/openapi/diagnostic.gleam", 385). ?DOC( " Render a diagnostic with an editor-clickable `path:line:column:`\n" " prefix when the spec file path and a `SourceLoc` are both known.\n" " Falls back to plain `to_string` otherwise.\n" "\n" " Issue #411: CI runs that process several specs need a breadcrumb to\n" " which file produced an error; editors recognise the `path:line:col:`\n" " prefix and let users jump straight to the offending YAML line.\n" ). -spec render(diagnostic(), gleam@option:option(binary())) -> binary(). render(D, File_path) -> case {File_path, erlang:element(7, D)} of {{some, Path}, {source_loc, Line, Column}} -> <<<<<<<<<<<>/binary, (erlang:integer_to_binary(Line))/binary>>/binary, ":"/utf8>>/binary, (erlang:integer_to_binary(Column))/binary>>/binary, ": "/utf8>>/binary, (to_string(D))/binary>>; {{some, Path@1}, no_source_loc} -> <<<>/binary, (to_string(D))/binary>>; {none, _} -> to_string(D) end. -file("src/oaspec/openapi/diagnostic.gleam", 401). ?DOC(" Convert a diagnostic to a short string (for backward-compatible display).\n"). -spec to_short_string(diagnostic()) -> binary(). to_short_string(D) -> Prefix = case erlang:element(4, D) of severity_error -> <<"Error"/utf8>>; severity_warning -> <<"Warning"/utf8>> end, Loc_str = case erlang:element(7, D) of {source_loc, Line, Column} -> <<<<<<<<" (line "/utf8, (erlang:integer_to_binary(Line))/binary>>/binary, ", column "/utf8>>/binary, (erlang:integer_to_binary(Column))/binary>>/binary, ")"/utf8>>; no_source_loc -> <<""/utf8>> end, case erlang:element(2, D) of <<"file_error"/utf8>> -> erlang:element(8, D); <<"yaml_error"/utf8>> -> <<(erlang:element(8, D))/binary, Loc_str/binary>>; <<"missing_field"/utf8>> -> Location = oaspec@internal@openapi@diagnostic_format:pointer_to_human( erlang:element(6, D) ), Field = gleam@string:replace( erlang:element(8, D), <<"Missing required field: "/utf8>>, <<""/utf8>> ), <<<<<<<<"Missing required field '"/utf8, Field/binary>>/binary, "' at "/utf8>>/binary, Location/binary>>/binary, (case erlang:element(9, D) of {some, H} -> <<". "/utf8, H/binary>>; none -> <<""/utf8>> end)/binary>>; <<"invalid_value"/utf8>> -> Location@1 = oaspec@internal@openapi@diagnostic_format:pointer_to_human( erlang:element(6, D) ), <<<<<<"Invalid value at "/utf8, Location@1/binary>>/binary, ": "/utf8>>/binary, (erlang:element(8, D))/binary>>; _ -> <<<<<> -> <<""/utf8>>; P -> <<" at "/utf8, (oaspec@internal@openapi@diagnostic_format:pointer_to_human( P ))/binary>> end)/binary>>/binary, ": "/utf8>>/binary, (erlang:element( 8, D ))/binary>> end.