Current section

Files

Jump to
oaspec src oaspec@openapi@capability_check.erl
Raw

src/oaspec@openapi@capability_check.erl

-module(oaspec@openapi@capability_check).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/oaspec/openapi/capability_check.gleam").
-export([check_preserved/1, check/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-file("src/oaspec/openapi/capability_check.gleam", 192).
?DOC(" Check security schemes for unsupported types (e.g. mutualTLS).\n").
-spec check_security_schemes(
oaspec@openapi@spec:open_api_spec(oaspec@openapi@spec:resolved())
) -> list(oaspec@openapi@diagnostic:diagnostic()).
check_security_schemes(Spec) ->
case erlang:element(5, Spec) of
none ->
[];
{some, Components} ->
_pipe = maps:to_list(erlang:element(6, Components)),
gleam@list:filter_map(
_pipe,
fun(Entry) ->
{Name, Ref_or} = Entry,
case Ref_or of
{value, {unsupported_scheme, Scheme_type}} ->
{ok,
oaspec@openapi@diagnostic:capability(
<<"components.securitySchemes."/utf8,
Name/binary>>,
<<<<"Unsupported security scheme type: '"/utf8,
Scheme_type/binary>>/binary,
"'. Supported types: apiKey, http, oauth2, openIdConnect."/utf8>>,
severity_error,
target_both,
{some,
<<"Use apiKey, http (bearer), oauth2, or openIdConnect instead."/utf8>>}
)};
_ ->
{error, nil}
end
end
)
end.
-file("src/oaspec/openapi/capability_check.gleam", 221).
?DOC(
" Check for parsed-but-unused AST features, emitting warnings.\n"
" This is the single source of truth for \"parsed but not generated\" diagnostics.\n"
" Requires Context because some checks iterate over resolved operations.\n"
).
-spec check_preserved(oaspec@codegen@context:context()) -> list(oaspec@openapi@diagnostic:diagnostic()).
check_preserved(Ctx) ->
Webhook_warnings = case gleam@dict:is_empty(
erlang:element(8, erlang:element(2, Ctx))
) of
true ->
[];
false ->
[oaspec@openapi@diagnostic:capability(
<<"webhooks"/utf8>>,
<<"Webhooks are parsed but not used by code generation."/utf8>>,
severity_warning,
target_both,
{some,
<<"Webhooks will not appear in generated code. No action needed unless you expected them."/utf8>>}
)]
end,
Ops = oaspec@openapi@operations:collect_operations(Ctx),
Response_warnings = gleam@list:flat_map(
Ops,
fun(Op) ->
{Op_id, Operation, _, _} = Op,
Entries = maps:to_list(erlang:element(8, Operation)),
gleam@list:flat_map(
Entries,
fun(Entry) ->
{Status_code, Ref_or} = Entry,
case Ref_or of
{value, Response} ->
Base_path = <<<<Op_id/binary, ".responses."/utf8>>/binary,
(oaspec@util@http:status_code_to_string(
Status_code
))/binary>>,
Multi_content_warnings = case {erlang:element(
6,
erlang:element(3, Ctx)
),
erlang:length(
maps:to_list(erlang:element(3, Response))
)} of
{client, _} ->
[];
{_, N} when N > 1 ->
[oaspec@openapi@diagnostic:capability(
<<Base_path/binary,
".content"/utf8>>,
<<"Multiple response content types are not fully supported for server code generation. Generated server responses lose the content-type header."/utf8>>,
severity_warning,
target_server,
{some,
<<"Use a single content type per response for full server code generation support."/utf8>>}
)];
{_, _} ->
[]
end,
Header_warnings = case gleam@dict:is_empty(
erlang:element(4, Response)
) of
true ->
[];
false ->
[oaspec@openapi@diagnostic:capability(
<<Base_path/binary,
".headers"/utf8>>,
<<"Response headers are parsed but not used by code generation."/utf8>>,
severity_warning,
target_both,
{some,
<<"Response headers will not appear in generated code. No action needed."/utf8>>}
)]
end,
Link_warnings = case gleam@dict:is_empty(
erlang:element(5, Response)
) of
true ->
[];
false ->
[oaspec@openapi@diagnostic:capability(
<<Base_path/binary, ".links"/utf8>>,
<<"Response links are parsed but not used by code generation."/utf8>>,
severity_warning,
target_both,
{some,
<<"Response links will not appear in generated code. No action needed."/utf8>>}
)]
end,
Content_entries = maps:to_list(
erlang:element(3, Response)
),
Encoding_warnings = gleam@list:flat_map(
Content_entries,
fun(Ce) ->
{Media_type_name, Media_type} = Ce,
case gleam@dict:is_empty(
erlang:element(5, Media_type)
) of
true ->
[];
false ->
[oaspec@openapi@diagnostic:capability(
<<<<<<Base_path/binary,
"."/utf8>>/binary,
Media_type_name/binary>>/binary,
".encoding"/utf8>>,
<<"MediaType encoding is parsed but not used by code generation."/utf8>>,
severity_warning,
target_both,
{some,
<<"Encoding settings will not affect generated code. No action needed."/utf8>>}
)]
end
end
),
lists:append(
[Multi_content_warnings,
Header_warnings,
Link_warnings,
Encoding_warnings]
);
_ ->
[]
end
end
)
end
),
External_docs_warnings = case erlang:element(10, erlang:element(2, Ctx)) of
{some, _} ->
[oaspec@openapi@diagnostic:capability(
<<"externalDocs"/utf8>>,
<<"External docs are parsed but not used by code generation."/utf8>>,
severity_warning,
target_both,
{some,
<<"External docs will not appear in generated code. Include documentation in descriptions instead."/utf8>>}
)];
none ->
[]
end,
Tag_warnings = case gleam@list:is_empty(
erlang:element(9, erlang:element(2, Ctx))
) of
true ->
[];
false ->
[oaspec@openapi@diagnostic:capability(
<<"tags"/utf8>>,
<<"Top-level tags are parsed but not used by code generation."/utf8>>,
severity_warning,
target_both,
{some,
<<"Tags will not appear in generated code. No action needed."/utf8>>}
)]
end,
Operation_server_warnings = gleam@list:flat_map(
Ops,
fun(Op@1) ->
{Op_id@1, Operation@1, _, _} = Op@1,
case gleam@list:is_empty(erlang:element(12, Operation@1)) of
true ->
[];
false ->
[oaspec@openapi@diagnostic:capability(
<<Op_id@1/binary, ".servers"/utf8>>,
<<"Operation-level servers are parsed but client code generation uses only the top-level server URL."/utf8>>,
severity_warning,
target_client,
{some,
<<"Only the first top-level server URL is used for default_base_url(). Override at runtime via ClientConfig if needed."/utf8>>}
)]
end
end
),
Path_server_warnings = begin
_pipe = maps:to_list(erlang:element(4, erlang:element(2, Ctx))),
gleam@list:flat_map(
_pipe,
fun(Entry@1) ->
{Path, Ref_or@1} = Entry@1,
case Ref_or@1 of
{value, Path_item} ->
case gleam@list:is_empty(erlang:element(13, Path_item)) of
true ->
[];
false ->
[oaspec@openapi@diagnostic:capability(
<<<<"paths."/utf8, Path/binary>>/binary,
".servers"/utf8>>,
<<"Path-level servers are parsed but client code generation uses only the top-level server URL."/utf8>>,
severity_warning,
target_client,
{some,
<<"Only the first top-level server URL is used for default_base_url(). Override at runtime via ClientConfig if needed."/utf8>>}
)]
end;
_ ->
[]
end
end
)
end,
Component_warnings = case erlang:element(5, erlang:element(2, Ctx)) of
{some, Components} ->
Header_w = case gleam@dict:is_empty(erlang:element(8, Components)) of
true ->
[];
false ->
[oaspec@openapi@diagnostic:capability(
<<"components.headers"/utf8>>,
<<"Component headers are parsed but not used by code generation."/utf8>>,
severity_warning,
target_both,
{some,
<<"Component headers will not appear in generated code. No action needed."/utf8>>}
)]
end,
Example_w = case gleam@dict:is_empty(erlang:element(9, Components)) of
true ->
[];
false ->
[oaspec@openapi@diagnostic:capability(
<<"components.examples"/utf8>>,
<<"Component examples are parsed but not used by code generation."/utf8>>,
severity_warning,
target_both,
{some,
<<"Component examples will not appear in generated code. Include examples in descriptions instead."/utf8>>}
)]
end,
Link_w = case gleam@dict:is_empty(erlang:element(10, Components)) of
true ->
[];
false ->
[oaspec@openapi@diagnostic:capability(
<<"components.links"/utf8>>,
<<"Component links are parsed but not used by code generation."/utf8>>,
severity_warning,
target_both,
{some,
<<"Component links will not appear in generated code. No action needed."/utf8>>}
)]
end,
lists:append([Header_w, Example_w, Link_w]);
none ->
[]
end,
lists:append(
[Webhook_warnings,
Response_warnings,
External_docs_warnings,
Tag_warnings,
Operation_server_warnings,
Path_server_warnings,
Component_warnings]
).
-file("src/oaspec/openapi/capability_check.gleam", 135).
?DOC(" Check a single schema and recurse into children.\n").
-spec check_schema(binary(), oaspec@openapi@schema:schema_object()) -> list(oaspec@openapi@diagnostic:diagnostic()).
check_schema(Path, Schema_obj) ->
Metadata = oaspec@openapi@schema:get_metadata(Schema_obj),
Keyword_errors = case erlang:element(12, Metadata) of
[] ->
[];
Keywords ->
Keyword_list = gleam@string:join(Keywords, <<"', '"/utf8>>),
[oaspec@openapi@diagnostic:capability(
Path,
<<<<<<"Unsupported JSON Schema keyword '"/utf8,
Keyword_list/binary>>/binary,
"' found. Remove or model differently. See: "/utf8>>/binary,
(gleam@string:join(
gleam@list:filter_map(
Keywords,
fun(K) ->
_pipe = gleam@list:find(
oaspec@capability:registry(),
fun(C) -> erlang:element(2, C) =:= K end
),
gleam@result:map(
_pipe,
fun(C@1) -> erlang:element(5, C@1) end
)
end
),
<<"; "/utf8>>
))/binary>>,
severity_error,
target_both,
{some,
<<"Remove the keyword or restructure the schema using supported constructs."/utf8>>}
)]
end,
Child_errors = case Schema_obj of
{object_schema, _, Properties, _, Additional_properties, _, _} ->
Prop_errors = begin
_pipe@1 = maps:to_list(Properties),
gleam@list:flat_map(
_pipe@1,
fun(E) ->
check_schema_ref(
<<<<Path/binary, "."/utf8>>/binary,
(erlang:element(1, E))/binary>>,
erlang:element(2, E)
)
end
)
end,
Ap_errors = case Additional_properties of
{typed, Ap} ->
check_schema_ref(
<<Path/binary, ".additionalProperties"/utf8>>,
Ap
);
_ ->
[]
end,
lists:append(Prop_errors, Ap_errors);
{array_schema, _, Items, _, _, _} ->
check_schema_ref(<<Path/binary, ".items"/utf8>>, Items);
{all_of_schema, _, Schemas} ->
gleam@list:flat_map(
Schemas,
fun(S) ->
check_schema_ref(<<Path/binary, ".allOf"/utf8>>, S)
end
);
{one_of_schema, _, Schemas@1, _} ->
gleam@list:flat_map(
Schemas@1,
fun(S@1) ->
check_schema_ref(<<Path/binary, ".oneOf"/utf8>>, S@1)
end
);
{any_of_schema, _, Schemas@2, _} ->
gleam@list:flat_map(
Schemas@2,
fun(S@2) ->
check_schema_ref(<<Path/binary, ".anyOf"/utf8>>, S@2)
end
);
_ ->
[]
end,
lists:append(Keyword_errors, Child_errors).
-file("src/oaspec/openapi/capability_check.gleam", 127).
?DOC(" Check a SchemaRef recursively for unsupported keywords.\n").
-spec check_schema_ref(binary(), oaspec@openapi@schema:schema_ref()) -> list(oaspec@openapi@diagnostic:diagnostic()).
check_schema_ref(Path, Ref) ->
case Ref of
{reference, _, _} ->
[];
{inline, Schema_obj} ->
check_schema(Path, Schema_obj)
end.
-file("src/oaspec/openapi/capability_check.gleam", 80).
-spec check_operation_schemas(
binary(),
oaspec@openapi@spec:operation(oaspec@openapi@spec:resolved())
) -> list(oaspec@openapi@diagnostic:diagnostic()).
check_operation_schemas(Base_path, Op) ->
Rb_errors = case erlang:element(7, Op) of
{some, {value, Rb}} ->
_pipe = maps:to_list(erlang:element(3, Rb)),
gleam@list:flat_map(
_pipe,
fun(Entry) ->
{Ct, Mt} = Entry,
case erlang:element(2, Mt) of
{some, Sr} ->
check_schema_ref(
<<<<Base_path/binary, ".requestBody."/utf8>>/binary,
Ct/binary>>,
Sr
);
none ->
[]
end
end
);
_ ->
[]
end,
Resp_errors = begin
_pipe@1 = maps:to_list(erlang:element(8, Op)),
gleam@list:flat_map(
_pipe@1,
fun(Entry@1) ->
{Code, Ref_or} = Entry@1,
case Ref_or of
{value, Resp} ->
_pipe@2 = maps:to_list(erlang:element(3, Resp)),
gleam@list:flat_map(
_pipe@2,
fun(Ct_entry) ->
{Ct@1, Mt@1} = Ct_entry,
case erlang:element(2, Mt@1) of
{some, Sr@1} ->
check_schema_ref(
<<<<<<<<Base_path/binary,
".responses."/utf8>>/binary,
(oaspec@util@http:status_code_to_string(
Code
))/binary>>/binary,
"."/utf8>>/binary,
Ct@1/binary>>,
Sr@1
);
none ->
[]
end
end
);
_ ->
[]
end
end
)
end,
lists:append(Rb_errors, Resp_errors).
-file("src/oaspec/openapi/capability_check.gleam", 57).
-spec check_path_item_schemas(
binary(),
oaspec@openapi@spec:path_item(oaspec@openapi@spec:resolved())
) -> list(oaspec@openapi@diagnostic:diagnostic()).
check_path_item_schemas(Base_path, Pi) ->
Method_ops = [{<<"get"/utf8>>, erlang:element(4, Pi)},
{<<"post"/utf8>>, erlang:element(5, Pi)},
{<<"put"/utf8>>, erlang:element(6, Pi)},
{<<"delete"/utf8>>, erlang:element(7, Pi)},
{<<"patch"/utf8>>, erlang:element(8, Pi)},
{<<"head"/utf8>>, erlang:element(9, Pi)},
{<<"options"/utf8>>, erlang:element(10, Pi)},
{<<"trace"/utf8>>, erlang:element(11, Pi)}],
gleam@list:flat_map(
Method_ops,
fun(Entry) ->
{Method, Maybe_op} = Entry,
case Maybe_op of
{some, Op} ->
check_operation_schemas(
<<<<Base_path/binary, "."/utf8>>/binary, Method/binary>>,
Op
);
none ->
[]
end
end
).
-file("src/oaspec/openapi/capability_check.gleam", 46).
?DOC(" Check inline schemas within operations (request bodies, responses, parameters).\n").
-spec check_inline_schemas(
oaspec@openapi@spec:open_api_spec(oaspec@openapi@spec:resolved())
) -> list(oaspec@openapi@diagnostic:diagnostic()).
check_inline_schemas(Spec) ->
_pipe = maps:to_list(erlang:element(4, Spec)),
gleam@list:flat_map(
_pipe,
fun(Path_entry) ->
{Path, Ref_or} = Path_entry,
case Ref_or of
{value, Path_item} ->
check_path_item_schemas(
<<"paths."/utf8, Path/binary>>,
Path_item
);
_ ->
[]
end
end
).
-file("src/oaspec/openapi/capability_check.gleam", 31).
?DOC(
" Check all schemas for unsupported keywords stored during lossless parse.\n"
" Covers both component schemas and inline schemas in operations.\n"
).
-spec check_schemas(
oaspec@openapi@spec:open_api_spec(oaspec@openapi@spec:resolved())
) -> list(oaspec@openapi@diagnostic:diagnostic()).
check_schemas(Spec) ->
Component_errors = case erlang:element(5, Spec) of
none ->
[];
{some, Components} ->
_pipe = maps:to_list(erlang:element(2, Components)),
gleam@list:flat_map(
_pipe,
fun(Entry) ->
{Name, Schema_ref} = Entry,
check_schema_ref(
<<"components.schemas."/utf8, Name/binary>>,
Schema_ref
)
end
)
end,
Inline_errors = check_inline_schemas(Spec),
lists:append(Component_errors, Inline_errors).
-file("src/oaspec/openapi/capability_check.gleam", 23).
?DOC(
" Run capability checks on a resolved spec.\n"
" Returns errors for unsupported features and warnings for parsed-but-unused features.\n"
).
-spec check(oaspec@openapi@spec:open_api_spec(oaspec@openapi@spec:resolved())) -> list(oaspec@openapi@diagnostic:diagnostic()).
check(Spec) ->
Schema_errors = check_schemas(Spec),
Security_errors = check_security_schemes(Spec),
lists:append([Schema_errors, Security_errors]).