Current section

Files

Jump to
oaspec src oaspec@codegen@validate.erl
Raw

src/oaspec@codegen@validate.erl

-module(oaspec@codegen@validate).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/oaspec/codegen/validate.gleam").
-export([error_to_string/1, validate/1]).
-export_type([validation_error/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type validation_error() :: {unsupported_feature, binary(), binary()}.
-file("src/oaspec/codegen/validate.gleam", 29).
?DOC(" Convert a validation error to a human-readable string.\n").
-spec error_to_string(validation_error()) -> binary().
error_to_string(Error) ->
case Error of
{unsupported_feature, Path, Detail} ->
<<<<<<"Unsupported feature at "/utf8, Path/binary>>/binary,
": "/utf8>>/binary,
Detail/binary>>
end.
-file("src/oaspec/codegen/validate.gleam", 49).
?DOC(" Validate parameters for unsupported patterns.\n").
-spec validate_parameters(binary(), list(oaspec@openapi@spec:parameter())) -> list(validation_error()).
validate_parameters(Op_id, Params) ->
gleam@list:flat_map(
Params,
fun(Param) ->
Path = <<<<Op_id/binary, ".parameters."/utf8>>/binary,
(erlang:element(2, Param))/binary>>,
Style_errors = case erlang:element(7, Param) of
{some, <<"deepObject"/utf8>>} ->
[{unsupported_feature,
Path,
<<"style: deepObject is not supported. Gleam cannot express deep object query serialization."/utf8>>}];
_ ->
[]
end,
Schema_errors = case erlang:element(6, Param) of
{some, {inline, {array_schema, _, _, _, _, _}}} ->
[{unsupported_feature,
Path,
<<"Array parameters are not yet supported. Use a single-value parameter instead."/utf8>>}];
{some, {inline, {object_schema, _, _, _, _, _, _}}} ->
[{unsupported_feature,
Path,
<<"Complex schema parameters (object/allOf/oneOf/anyOf) are not supported."/utf8>>}];
{some, {inline, {all_of_schema, _, _}}} ->
[{unsupported_feature,
Path,
<<"Complex schema parameters (object/allOf/oneOf/anyOf) are not supported."/utf8>>}];
{some, {inline, {one_of_schema, _, _, _}}} ->
[{unsupported_feature,
Path,
<<"Complex schema parameters (object/allOf/oneOf/anyOf) are not supported."/utf8>>}];
{some, {inline, {any_of_schema, _, _}}} ->
[{unsupported_feature,
Path,
<<"Complex schema parameters (object/allOf/oneOf/anyOf) are not supported."/utf8>>}];
_ ->
[]
end,
lists:append(Style_errors, Schema_errors)
end
).
-file("src/oaspec/codegen/validate.gleam", 332).
?DOC(" Check if any schema in a list is inline (not a $ref).\n").
-spec has_any_inline_schemas(list(oaspec@openapi@schema:schema_ref())) -> boolean().
has_any_inline_schemas(Schemas) ->
gleam@list:any(Schemas, fun(S) -> case S of
{inline, _} ->
true;
{reference, _} ->
false
end end).
-file("src/oaspec/codegen/validate.gleam", 410).
?DOC(
" Find duplicates in a list using a key function, producing errors via an\n"
" error function. Only reports the first occurrence of each duplicate.\n"
).
-spec find_duplicates(
list(IAC),
fun((IAC) -> binary()),
fun((binary()) -> validation_error())
) -> list(validation_error()).
find_duplicates(Items, Key_fn, Error_fn) ->
{_, Errors} = gleam@list:fold(
Items,
{gleam@set:new(), []},
fun(Acc, Item) ->
{Seen, Errs} = Acc,
Key = Key_fn(Item),
case gleam@set:contains(Seen, Key) of
true ->
{Seen, [Error_fn(Key) | Errs]};
false ->
{gleam@set:insert(Seen, Key), Errs}
end
end
),
lists:reverse(Errors).
-file("src/oaspec/codegen/validate.gleam", 342).
?DOC(" Validate for operationId duplicates and naming collisions.\n").
-spec validate_name_collisions(oaspec@codegen@context:context()) -> list(validation_error()).
validate_name_collisions(Ctx) ->
Operations = oaspec@codegen@types:collect_operations(Ctx),
Op_id_errors = find_duplicates(
Operations,
fun(Op) ->
{Op_id, _, _, _} = Op,
Op_id
end,
fun(Dup) ->
{unsupported_feature,
<<"paths"/utf8>>,
<<<<"Duplicate operationId: '"/utf8, Dup/binary>>/binary,
"'"/utf8>>}
end
),
Fn_name_errors = find_duplicates(
Operations,
fun(Op@1) ->
{Op_id@1, _, _, _} = Op@1,
oaspec@util@naming:operation_to_function_name(Op_id@1)
end,
fun(Dup@1) ->
{unsupported_feature,
<<"paths"/utf8>>,
<<<<"Function name collision after snake_case conversion: '"/utf8,
Dup@1/binary>>/binary,
"'"/utf8>>}
end
),
Type_name_errors = find_duplicates(
Operations,
fun(Op@2) ->
{Op_id@2, _, _, _} = Op@2,
oaspec@util@naming:schema_to_type_name(Op_id@2)
end,
fun(Dup@2) ->
{unsupported_feature,
<<"paths"/utf8>>,
<<<<"Type name collision after PascalCase conversion: '"/utf8,
Dup@2/binary>>/binary,
"'"/utf8>>}
end
),
lists:append([Op_id_errors, Fn_name_errors, Type_name_errors]).
-file("src/oaspec/codegen/validate.gleam", 401).
?DOC(" Find duplicates in a list of strings.\n").
-spec find_string_duplicates(
list(binary()),
fun((binary()) -> validation_error())
) -> list(validation_error()).
find_string_duplicates(Items, Error_fn) ->
find_duplicates(Items, fun(S) -> S end, Error_fn).
-file("src/oaspec/codegen/validate.gleam", 181).
?DOC(" Recursively validate a SchemaObject, descending into all sub-schemas.\n").
-spec validate_schema_recursive(binary(), oaspec@openapi@schema:schema_object()) -> list(validation_error()).
validate_schema_recursive(Path, Schema_obj) ->
case Schema_obj of
{object_schema,
_,
Properties,
_,
Additional_properties,
Additional_properties_untyped,
_} ->
Ap_errors = case Additional_properties_untyped of
true ->
[{unsupported_feature,
Path,
<<"additionalProperties: true is not supported. Gleam has no untyped map type."/utf8>>}];
false ->
[]
end,
Typed_ap_errors = case Additional_properties of
{some, _} ->
[{unsupported_feature,
Path,
<<"Typed additionalProperties is not yet supported."/utf8>>}];
none ->
[]
end,
Prop_errors = begin
_pipe = maps:to_list(Properties),
gleam@list:flat_map(
_pipe,
fun(Entry) ->
{Prop_name, Prop_ref} = Entry,
Prop_path = <<<<Path/binary, "."/utf8>>/binary,
Prop_name/binary>>,
Inline_obj_errors = case Prop_ref of
{inline, {object_schema, _, _, _, _, _, _}} ->
[{unsupported_feature,
Prop_path,
<<"Nested inline object properties are not supported. Extract to a named schema in components.schemas and use $ref."/utf8>>}];
{inline, {all_of_schema, _, _}} ->
[{unsupported_feature,
Prop_path,
<<"Nested inline allOf properties are not supported. Extract to a named schema in components.schemas and use $ref."/utf8>>}];
_ ->
[]
end,
lists:append(
Inline_obj_errors,
validate_schema_ref_recursive(Prop_path, Prop_ref)
)
end
)
end,
Prop_names = begin
_pipe@1 = maps:to_list(Properties),
gleam@list:map(
_pipe@1,
fun(Entry@1) ->
{Prop_name@1, _} = Entry@1,
oaspec@util@naming:to_snake_case(Prop_name@1)
end
)
end,
Prop_collision_errors = find_string_duplicates(
Prop_names,
fun(Dup) ->
{unsupported_feature,
Path,
<<<<"Property name collision after snake_case: '"/utf8,
Dup/binary>>/binary,
"'"/utf8>>}
end
),
lists:append(
[Ap_errors, Typed_ap_errors, Prop_errors, Prop_collision_errors]
);
{array_schema, _, Items, _, _, _} ->
Item_errors = case Items of
{inline, {object_schema, _, _, _, _, _, _}} ->
[{unsupported_feature,
<<Path/binary, ".items"/utf8>>,
<<"Inline complex array items (object/allOf/oneOf/anyOf) are not supported. Extract to components.schemas and use $ref."/utf8>>}];
{inline, {all_of_schema, _, _}} ->
[{unsupported_feature,
<<Path/binary, ".items"/utf8>>,
<<"Inline complex array items (object/allOf/oneOf/anyOf) are not supported. Extract to components.schemas and use $ref."/utf8>>}];
{inline, {one_of_schema, _, _, _}} ->
[{unsupported_feature,
<<Path/binary, ".items"/utf8>>,
<<"Inline complex array items (object/allOf/oneOf/anyOf) are not supported. Extract to components.schemas and use $ref."/utf8>>}];
{inline, {any_of_schema, _, _}} ->
[{unsupported_feature,
<<Path/binary, ".items"/utf8>>,
<<"Inline complex array items (object/allOf/oneOf/anyOf) are not supported. Extract to components.schemas and use $ref."/utf8>>}];
_ ->
[]
end,
lists:append(
Item_errors,
validate_schema_ref_recursive(
<<Path/binary, ".items"/utf8>>,
Items
)
);
{one_of_schema, _, Schemas, _} ->
validate_compound_schemas(Path, Schemas);
{any_of_schema, _, Schemas@1} ->
validate_compound_schemas(Path, Schemas@1);
{all_of_schema, _, Schemas@2} ->
gleam@list:flat_map(
Schemas@2,
fun(S_ref) ->
validate_schema_ref_recursive(
<<Path/binary, ".allOf"/utf8>>,
S_ref
)
end
);
_ ->
case Schema_obj of
{string_schema, _, _, Enum_values, _, _, _, _} when Enum_values =/= [] ->
Variant_names = gleam@list:map(
Enum_values,
fun(V) -> oaspec@util@naming:schema_to_type_name(V) end
),
find_string_duplicates(
Variant_names,
fun(Dup@1) ->
{unsupported_feature,
Path,
<<<<"Enum variant collision after PascalCase: '"/utf8,
Dup@1/binary>>/binary,
"'"/utf8>>}
end
);
_ ->
[]
end
end.
-file("src/oaspec/codegen/validate.gleam", 310).
?DOC(
" Validate oneOf/anyOf schemas: only $ref variants are supported.\n"
" Inline schemas (primitives, objects, arrays) cannot be generated as\n"
" union variants.\n"
).
-spec validate_compound_schemas(
binary(),
list(oaspec@openapi@schema:schema_ref())
) -> list(validation_error()).
validate_compound_schemas(Path, Schemas) ->
Inline_errors = case has_any_inline_schemas(Schemas) of
true ->
[{unsupported_feature,
Path,
<<"oneOf/anyOf with inline schemas is not supported. All variants must be $ref to named schemas."/utf8>>}];
false ->
[]
end,
Child_errors = gleam@list:flat_map(
Schemas,
fun(S_ref) -> validate_schema_ref_recursive(Path, S_ref) end
),
lists:append(Inline_errors, Child_errors).
-file("src/oaspec/codegen/validate.gleam", 170).
?DOC(" Recursively validate a SchemaRef at any depth.\n").
-spec validate_schema_ref_recursive(
binary(),
oaspec@openapi@schema:schema_ref()
) -> list(validation_error()).
validate_schema_ref_recursive(Path, Schema_ref) ->
case Schema_ref of
{reference, _} ->
[];
{inline, Schema_obj} ->
validate_schema_recursive(Path, Schema_obj)
end.
-file("src/oaspec/codegen/validate.gleam", 88).
?DOC(" Validate request body for unsupported patterns.\n").
-spec validate_request_body(
binary(),
gleam@option:option(oaspec@openapi@spec:request_body())
) -> list(validation_error()).
validate_request_body(Op_id, Request_body) ->
case Request_body of
none ->
[];
{some, Rb} ->
Content_keys = maps:keys(erlang:element(3, Rb)),
Non_json = gleam@list:filter(
Content_keys,
fun(Key) -> Key /= <<"application/json"/utf8>> end
),
Content_type_errors = case Non_json of
[] ->
[];
[Media_type | _] ->
[{unsupported_feature,
<<Op_id/binary, ".requestBody"/utf8>>,
<<<<"Content type '"/utf8, Media_type/binary>>/binary,
"' is not supported. Only 'application/json' is supported."/utf8>>}]
end,
Schema_errors = begin
_pipe = maps:to_list(erlang:element(3, Rb)),
gleam@list:flat_map(
_pipe,
fun(Entry) ->
{_, Media_type@1} = Entry,
case erlang:element(2, Media_type@1) of
{some, Schema_ref} ->
validate_schema_ref_recursive(
<<Op_id/binary, ".requestBody"/utf8>>,
Schema_ref
);
none ->
[]
end
end
)
end,
lists:append(Content_type_errors, Schema_errors)
end.
-file("src/oaspec/codegen/validate.gleam", 126).
?DOC(" Validate response schemas and content types.\n").
-spec validate_responses(
binary(),
gleam@dict:dict(binary(), oaspec@openapi@spec:response())
) -> list(validation_error()).
validate_responses(Op_id, Responses) ->
Entries = maps:to_list(Responses),
gleam@list:flat_map(
Entries,
fun(Entry) ->
{Status_code, Response} = Entry,
Content_entries = maps:to_list(erlang:element(3, Response)),
gleam@list:flat_map(
Content_entries,
fun(Ce) ->
{Media_type_name, Media_type} = Ce,
Path = <<<<Op_id/binary, ".responses."/utf8>>/binary,
Status_code/binary>>,
Content_type_errors = case Media_type_name of
<<"application/json"/utf8>> ->
[];
_ ->
[{unsupported_feature,
Path,
<<<<"Response content type '"/utf8,
Media_type_name/binary>>/binary,
"' is not supported. Only 'application/json' is supported."/utf8>>}]
end,
Schema_errors = case erlang:element(2, Media_type) of
{some, Schema_ref} ->
validate_schema_ref_recursive(Path, Schema_ref);
none ->
[]
end,
lists:append(Content_type_errors, Schema_errors)
end
)
end
).
-file("src/oaspec/codegen/validate.gleam", 37).
?DOC(" Validate all operations for unsupported patterns.\n").
-spec validate_operations(oaspec@codegen@context:context()) -> list(validation_error()).
validate_operations(Ctx) ->
Operations = oaspec@codegen@types:collect_operations(Ctx),
gleam@list:flat_map(
Operations,
fun(Op) ->
{Op_id, Operation, _, _} = Op,
Param_errors = validate_parameters(
Op_id,
erlang:element(6, Operation)
),
Body_errors = validate_request_body(
Op_id,
erlang:element(7, Operation)
),
Response_errors = validate_responses(
Op_id,
erlang:element(8, Operation)
),
lists:append([Param_errors, Body_errors, Response_errors])
end
).
-file("src/oaspec/codegen/validate.gleam", 158).
?DOC(" Validate component schemas recursively.\n").
-spec validate_component_schemas(oaspec@codegen@context:context()) -> list(validation_error()).
validate_component_schemas(Ctx) ->
Schemas = case erlang:element(5, erlang:element(2, Ctx)) of
{some, Components} ->
maps:to_list(erlang:element(2, Components));
none ->
[]
end,
gleam@list:flat_map(
Schemas,
fun(Entry) ->
{Name, Schema_ref} = Entry,
validate_schema_ref_recursive(
<<"components.schemas."/utf8, Name/binary>>,
Schema_ref
)
end
).
-file("src/oaspec/codegen/validate.gleam", 21).
?DOC(
" Validate the parsed spec for unsupported patterns.\n"
" Returns a list of errors; empty list means validation passed.\n"
).
-spec validate(oaspec@codegen@context:context()) -> list(validation_error()).
validate(Ctx) ->
Op_errors = validate_operations(Ctx),
Schema_errors = validate_component_schemas(Ctx),
Collision_errors = validate_name_collisions(Ctx),
lists:append([Op_errors, Schema_errors, Collision_errors]).