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", 33).
?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", 110).
-spec resolve_schema_object(
gleam@option:option(oaspec@openapi@schema:schema_ref()),
oaspec@codegen@context:context()
) -> gleam@option:option(oaspec@openapi@schema:schema_object()).
resolve_schema_object(Schema_ref, Ctx) ->
case Schema_ref of
{some, {inline, Schema_obj}} ->
{some, Schema_obj};
{some, Schema_ref@1} ->
case oaspec@openapi@resolver:resolve_schema_ref(
Schema_ref@1,
erlang:element(2, Ctx)
) of
{ok, Schema_obj@1} ->
{some, Schema_obj@1};
{error, _} ->
none
end;
none ->
none
end.
-file("src/oaspec/codegen/validate.gleam", 53).
?DOC(" Validate parameters for unsupported patterns.\n").
-spec validate_parameters(
binary(),
list(oaspec@openapi@spec:parameter()),
oaspec@codegen@context:context()
) -> list(validation_error()).
validate_parameters(Op_id, Params, Ctx) ->
gleam@list:flat_map(
Params,
fun(Param) ->
Path = <<<<Op_id/binary, ".parameters."/utf8>>/binary,
(erlang:element(2, Param))/binary>>,
Resolved_schema = resolve_schema_object(
erlang:element(6, Param),
Ctx
),
Deep_object_errors = case erlang:element(7, Param) of
{some, <<"deepObject"/utf8>>} ->
[{unsupported_feature,
Path,
<<"Parameter style 'deepObject' is not supported."/utf8>>}];
_ ->
[]
end,
Complex_schema_errors = case Resolved_schema of
{some, {object_schema, _, _, _, _, _, _}} ->
[{unsupported_feature,
Path,
<<"Complex schema parameters (object/allOf/oneOf/anyOf) are not supported."/utf8>>}];
{some, {all_of_schema, _, _}} ->
[{unsupported_feature,
Path,
<<"Complex schema parameters (object/allOf/oneOf/anyOf) are not supported."/utf8>>}];
{some, {one_of_schema, _, _, _}} ->
[{unsupported_feature,
Path,
<<"Complex schema parameters (object/allOf/oneOf/anyOf) are not supported."/utf8>>}];
{some, {any_of_schema, _, _}} ->
[{unsupported_feature,
Path,
<<"Complex schema parameters (object/allOf/oneOf/anyOf) are not supported."/utf8>>}];
_ ->
[]
end,
Array_errors = case {erlang:element(3, Param), Resolved_schema} of
{in_path, _} ->
[];
{_, {some, {array_schema, _, _, _, _, _}}} ->
[{unsupported_feature,
Path,
<<"Array parameters in query/header/cookie are not supported."/utf8>>}];
{_, _} ->
[]
end,
Required_errors = case {erlang:element(3, Param),
erlang:element(5, Param)} of
{in_path, false} ->
[{unsupported_feature,
Path,
<<"Path parameters with required: false are not supported."/utf8>>}];
{_, _} ->
[]
end,
lists:append(
[Deep_object_errors,
Complex_schema_errors,
Array_errors,
Required_errors]
)
end
).
-file("src/oaspec/codegen/validate.gleam", 206).
-spec multipart_field_is_stringifiable(
oaspec@openapi@schema:schema_ref(),
oaspec@codegen@context:context()
) -> boolean().
multipart_field_is_stringifiable(Schema_ref, Ctx) ->
case resolve_schema_object({some, Schema_ref}, Ctx) of
{some, {string_schema, _, _, _, _, _, _, _}} ->
true;
{some, {integer_schema, _, _, _, _, _}} ->
true;
{some, {number_schema, _, _, _, _, _}} ->
true;
{some, {boolean_schema, _, _}} ->
true;
_ ->
false
end.
-file("src/oaspec/codegen/validate.gleam", 172).
-spec validate_multipart_request_body_fields(
binary(),
gleam@dict:dict(binary(), oaspec@openapi@spec:media_type()),
oaspec@codegen@context:context()
) -> list(validation_error()).
validate_multipart_request_body_fields(Op_id, Content, Ctx) ->
case gleam_stdlib:map_get(Content, <<"multipart/form-data"/utf8>>) of
{ok, Media_type} ->
case resolve_schema_object(erlang:element(2, Media_type), Ctx) of
{some, {object_schema, _, Properties, _, _, _, _}} ->
_pipe = maps:to_list(Properties),
gleam@list:flat_map(
_pipe,
fun(Entry) ->
{Field_name, Field_schema} = Entry,
case multipart_field_is_stringifiable(
Field_schema,
Ctx
) of
true ->
[];
false ->
[{unsupported_feature,
<<<<Op_id/binary,
".requestBody.multipart."/utf8>>/binary,
Field_name/binary>>,
<<"multipart/form-data fields must be string, integer, number, boolean, binary, or string enums."/utf8>>}]
end
end
);
{some, _} ->
[{unsupported_feature,
<<Op_id/binary, ".requestBody"/utf8>>,
<<"multipart/form-data request bodies must use an object schema."/utf8>>}];
none ->
[]
end;
{error, _} ->
[]
end.
-file("src/oaspec/codegen/validate.gleam", 403).
?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", 465).
?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(IBQ),
fun((IBQ) -> 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", 483).
?DOC(" Find duplicate names in a simple list of strings.\n").
-spec find_name_collisions(
list(binary()),
fun((binary()) -> validation_error())
) -> list(validation_error()).
find_name_collisions(Names, Error_fn) ->
find_duplicates(Names, fun(Name) -> Name end, Error_fn).
-file("src/oaspec/codegen/validate.gleam", 413).
?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_names = gleam@list:map(
Operations,
fun(Op@1) ->
{Op_id@1, _, _, _} = Op@1,
oaspec@util@naming:operation_to_function_name(Op_id@1)
end
),
Fn_collision_errors = find_name_collisions(
Fn_names,
fun(Dup@1) ->
{unsupported_feature,
<<"paths"/utf8>>,
<<<<"Function name collision after case conversion: '"/utf8,
Dup@1/binary>>/binary,
"'"/utf8>>}
end
),
Type_names = gleam@list:map(
Operations,
fun(Op@2) ->
{Op_id@2, _, _, _} = Op@2,
oaspec@util@naming:schema_to_type_name(Op_id@2)
end
),
Type_collision_errors = find_name_collisions(
Type_names,
fun(Dup@2) ->
{unsupported_feature,
<<"paths"/utf8>>,
<<<<"Type name collision after case conversion: '"/utf8,
Dup@2/binary>>/binary,
"'"/utf8>>}
end
),
lists:append([Op_id_errors, Fn_collision_errors, Type_collision_errors]).
-file("src/oaspec/codegen/validate.gleam", 491).
?DOC(" Validate security schemes for unsupported types.\n").
-spec validate_security_schemes(oaspec@codegen@context:context()) -> list(validation_error()).
validate_security_schemes(Ctx) ->
Schemes = case erlang:element(5, erlang:element(2, Ctx)) of
{some, Components} ->
maps:to_list(erlang:element(6, Components));
none ->
[]
end,
gleam@list:flat_map(
Schemes,
fun(Entry) ->
{Name, Scheme} = Entry,
case Scheme of
{api_key_scheme, _, _} ->
[];
{http_scheme, <<"bearer"/utf8>>, _} ->
[];
{http_scheme, <<"basic"/utf8>>, _} ->
[];
{http_scheme, <<"digest"/utf8>>, _} ->
[];
{http_scheme, Scheme_name, _} ->
[{unsupported_feature,
<<"components.securitySchemes."/utf8, Name/binary>>,
<<<<"HTTP security scheme '"/utf8,
Scheme_name/binary>>/binary,
"' is not supported. Supported schemes: bearer, basic, digest."/utf8>>}];
{o_auth2_scheme, _} ->
[{unsupported_feature,
<<"components.securitySchemes."/utf8, Name/binary>>,
<<"OAuth2 security scheme is not supported."/utf8>>}]
end
end
).
-file("src/oaspec/codegen/validate.gleam", 276).
?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 = [],
_ = Additional_properties_untyped,
Typed_ap_errors = case Additional_properties of
{some, Ap_ref} ->
validate_schema_ref_recursive(
<<Path/binary, ".additionalProperties"/utf8>>,
Ap_ref
);
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_name_collisions(
Prop_names,
fun(Dup) ->
{unsupported_feature,
Path,
<<<<"Property name collision after snake_case conversion: '"/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
);
_ ->
[]
end.
-file("src/oaspec/codegen/validate.gleam", 381).
?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", 265).
?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", 126).
?DOC(" Validate request body for unsupported patterns.\n").
-spec validate_request_body(
binary(),
gleam@option:option(oaspec@openapi@spec:request_body()),
oaspec@codegen@context:context()
) -> list(validation_error()).
validate_request_body(Op_id, Request_body, Ctx) ->
case Request_body of
none ->
[];
{some, Rb} ->
Content_keys = maps:keys(erlang:element(3, Rb)),
Unsupported = gleam@list:filter(
Content_keys,
fun(Key) ->
not oaspec@util@content_type:is_supported_request(
oaspec@util@content_type:from_string(Key)
)
end
),
Content_type_errors = case Unsupported of
[] ->
[];
[Media_type | _] ->
[{unsupported_feature,
<<Op_id/binary, ".requestBody"/utf8>>,
<<<<"Content type '"/utf8, Media_type/binary>>/binary,
"' is not supported. Only 'application/json' and 'multipart/form-data' are supported for request bodies."/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,
Multipart_field_errors = validate_multipart_request_body_fields(
Op_id,
erlang:element(3, Rb),
Ctx
),
lists:append(
[Content_type_errors, Schema_errors, Multipart_field_errors]
)
end.
-file("src/oaspec/codegen/validate.gleam", 217).
?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 oaspec@util@content_type:is_supported_response(
oaspec@util@content_type:from_string(Media_type_name)
) of
true ->
[];
false ->
[{unsupported_feature,
Path,
<<<<"Response content type '"/utf8,
Media_type_name/binary>>/binary,
"' is not supported. Only 'application/json' and 'text/plain' are 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", 41).
?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),
Ctx
),
Body_errors = validate_request_body(
Op_id,
erlang:element(7, Operation),
Ctx
),
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", 253).
?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", 24).
?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),
Security_errors = validate_security_schemes(Ctx),
lists:append([Op_errors, Schema_errors, Collision_errors, Security_errors]).