-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 = <<<>/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, <<<>/binary, Field_name/binary>>, <<"multipart/form-data fields must be string, integer, number, boolean, binary, or string enums."/utf8>>}] end end ); {some, _} -> [{unsupported_feature, <>, <<"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( <>, 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 = <<<>/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, <>, <<"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, <>, <<"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, <>, <<"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, <>, <<"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( <>, 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( <>, 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, <>, <<<<"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( <>, 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 = <<<>/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]).