Packages
oaspec
0.68.0
0.68.0
0.67.0
0.66.0
0.65.0
0.64.0
0.63.0
0.62.0
0.61.0
0.60.0
0.59.0
0.58.1
0.58.0
0.57.0
0.56.0
0.55.0
0.54.0
0.53.0
0.52.0
0.51.0
0.50.0
0.49.0
0.48.0
0.47.0
0.46.0
0.45.0
0.44.0
0.43.0
0.42.0
0.41.0
0.40.0
0.39.0
0.38.0
0.37.0
0.36.0
0.35.0
0.34.0
0.33.0
0.32.0
0.31.0
0.30.0
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.3
0.6.1
0.6.0
0.5.0
0.4.0
0.3.0
0.1.3
Generate Gleam code from OpenAPI 3.x specifications
Current section
Files
Jump to
Current section
Files
src/oaspec_yaml_safe_ffi.erl
-module(oaspec_yaml_safe_ffi).
%% Adapter around `yay:parse_string/1` (yay v2.0.x) that normalises
%% the FFI's raw `{yaml_error, ...}` tuple shape into the Gleam
%% encoding the upstream `yay.YamlError` Gleam type expects.
%%
%% Issue #576: yay's `yaml_ffi.erl` returns
%% `{error, {yaml_error, Msg, {Line, Col}}}` for yamerl errors that
%% surface a structured message + line/col (the alias-without-anchor
%% case being the obvious example). Gleam's runtime pattern match on
%% `yay.ParsingError(msg:, loc:)` expects
%% `{parsing_error, Msg, {yaml_error_loc, Line, Col}}` instead, so
%% `case e { ParsingError(...) -> _ }` falls through to a BEAM
%% case_clause crash for any input that exercises this code path.
%%
%% A representative crash payload from a self-referential alias:
%% YamlError("No anchor corresponds to alias \"a\"", #(7, 10))
%%
%% This is server-side DoS in any context where a user controls the
%% YAML input (CI plugins, public spec linters, multi-tenant
%% gateways) — a 9-line malformed YAML payload is enough to crash
%% the parsing process. Rather than vendor yay or wait for an
%% upstream fix, we adapt the tuple shape at the FFI boundary so
%% the rest of the parser keeps using the documented `yay.YamlError`
%% surface unchanged.
-export([parse_string/1]).
-spec parse_string(binary()) ->
{ok, list(term())}
| {error, unexpected_parsing_error
| {parsing_error, binary(), {yaml_error_loc, integer(), integer()}}}.
parse_string(Content) when is_binary(Content) ->
try yay:parse_string(Content) of
{ok, _} = Ok ->
Ok;
{error, Reason} ->
{error, normalise_error(Reason)}
catch
error:Reason:_ ->
%% Defence in depth: even if a future yay version
%% raises rather than returns, we surface a sensible
%% Gleam-shaped error instead of propagating the BEAM
%% exception out of the parsing process.
{error, normalise_error(Reason)}
end.
%% Map every observed yay-FFI error tuple shape onto the canonical
%% Gleam encoding for `yay.YamlError`:
%% - `UnexpectedParsingError` → atom `unexpected_parsing_error`
%% - `ParsingError(msg, loc)` → `{parsing_error, Msg, {yaml_error_loc, Line, Col}}`
%%
%% Anything we cannot classify falls through to
%% `unexpected_parsing_error` so the upstream error path keeps
%% working without a new variant.
normalise_error(unexpected_parsing_error) ->
unexpected_parsing_error;
normalise_error({yaml_error, unexpected_parsing_error}) ->
%% Inner-tagged variant from yay's `error:_` catch-all.
unexpected_parsing_error;
normalise_error({yaml_error, Msg, {Line, Col}})
when is_binary(Msg), is_integer(Line), is_integer(Col) ->
%% The shape that triggers #576.
{parsing_error, Msg, {yaml_error_loc, Line, Col}};
normalise_error({parsing_error, _, _} = Already) ->
%% Already in the documented Gleam shape — pass through.
Already;
normalise_error(_Other) ->
unexpected_parsing_error.