-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 = <<<>/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 = <<<>/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, <>, <<"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 ); _ -> 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, <>, <<<<"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( <>, 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 = <<<>/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]).