|
|
@@ -22,11 +22,14 @@
|
|
|
-define(DEFAULT_FIELDS, [example, allowReserved, style,
|
|
|
explode, maxLength, allowEmptyValue, deprecated, minimum, maximum]).
|
|
|
|
|
|
--define(INIT_SCHEMA, #{fields => #{}, translations => #{}, validations => [], namespace => undefined}).
|
|
|
+-define(INIT_SCHEMA, #{fields => #{}, translations => #{},
|
|
|
+ validations => [], namespace => undefined}).
|
|
|
|
|
|
-define(TO_REF(_N_, _F_), iolist_to_binary([to_bin(_N_), ".", to_bin(_F_)])).
|
|
|
--define(TO_COMPONENTS_SCHEMA(_M_, _F_), iolist_to_binary([<<"#/components/schemas/">>, ?TO_REF(namespace(_M_), _F_)])).
|
|
|
--define(TO_COMPONENTS_PARAM(_M_, _F_), iolist_to_binary([<<"#/components/parameters/">>, ?TO_REF(namespace(_M_), _F_)])).
|
|
|
+-define(TO_COMPONENTS_SCHEMA(_M_, _F_), iolist_to_binary([<<"#/components/schemas/">>,
|
|
|
+ ?TO_REF(namespace(_M_), _F_)])).
|
|
|
+-define(TO_COMPONENTS_PARAM(_M_, _F_), iolist_to_binary([<<"#/components/parameters/">>,
|
|
|
+ ?TO_REF(namespace(_M_), _F_)])).
|
|
|
|
|
|
-define(MAX_ROW_LIMIT, 100).
|
|
|
|
|
|
@@ -116,9 +119,9 @@ translate_req(Request, #{module := Module, path := Path, method := Method}, Chec
|
|
|
#{Method := Spec} = apply(Module, schema, [Path]),
|
|
|
try
|
|
|
Params = maps:get(parameters, Spec, []),
|
|
|
- Body = maps:get(requestBody, Spec, []),
|
|
|
+ Body = maps:get('requestBody', Spec, []),
|
|
|
{Bindings, QueryStr} = check_parameters(Request, Params, Module),
|
|
|
- NewBody = check_requestBody(Request, Body, Module, CheckFun, hoconsc:is_schema(Body)),
|
|
|
+ NewBody = check_request_body(Request, Body, Module, CheckFun, hoconsc:is_schema(Body)),
|
|
|
{ok, Request#{bindings => Bindings, query_string => QueryStr, body => NewBody}}
|
|
|
catch throw:Error ->
|
|
|
{_, [{validation_error, ValidErr}]} = Error,
|
|
|
@@ -155,34 +158,41 @@ parse_spec_ref(Module, Path) ->
|
|
|
{Spec, SubRefs} = meta_to_spec(Meta, Module),
|
|
|
{Acc#{Method => Spec}, SubRefs ++ RefsAcc}
|
|
|
end, {#{}, []},
|
|
|
- maps:without([operationId], Schema)),
|
|
|
- {maps:get(operationId, Schema), Specs, Refs}.
|
|
|
+ maps:without(['operationId'], Schema)),
|
|
|
+ {maps:get('operationId', Schema), Specs, Refs}.
|
|
|
|
|
|
check_parameters(Request, Spec, Module) ->
|
|
|
#{bindings := Bindings, query_string := QueryStr} = Request,
|
|
|
- BindingsBin = maps:fold(fun(Key, Value, Acc) -> Acc#{atom_to_binary(Key) => Value} end, #{}, Bindings),
|
|
|
+ BindingsBin = maps:fold(fun(Key, Value, Acc) ->
|
|
|
+ Acc#{atom_to_binary(Key) => Value}
|
|
|
+ end, #{}, Bindings),
|
|
|
check_parameter(Spec, BindingsBin, QueryStr, Module, #{}, #{}).
|
|
|
|
|
|
check_parameter([?REF(Fields) | Spec], Bindings, QueryStr, LocalMod, BindingsAcc, QueryStrAcc) ->
|
|
|
- check_parameter([?R_REF(LocalMod, Fields) | Spec], Bindings, QueryStr, LocalMod, BindingsAcc, QueryStrAcc);
|
|
|
-check_parameter([?R_REF(Module, Fields) | Spec], Bindings, QueryStr, LocalMod, BindingsAcc, QueryStrAcc) ->
|
|
|
+ check_parameter([?R_REF(LocalMod, Fields) | Spec],
|
|
|
+ Bindings, QueryStr, LocalMod, BindingsAcc, QueryStrAcc);
|
|
|
+check_parameter([?R_REF(Module, Fields) | Spec],
|
|
|
+ Bindings, QueryStr, LocalMod, BindingsAcc, QueryStrAcc) ->
|
|
|
Params = apply(Module, fields, [Fields]),
|
|
|
check_parameter(Params ++ Spec, Bindings, QueryStr, LocalMod, BindingsAcc, QueryStrAcc);
|
|
|
-check_parameter([], _Bindings, _QueryStr, _Module, NewBindings, NewQueryStr) -> {NewBindings, NewQueryStr};
|
|
|
+check_parameter([], _Bindings, _QueryStr, _Module, NewBindings, NewQueryStr) ->
|
|
|
+ {NewBindings, NewQueryStr};
|
|
|
check_parameter([{Name, Type} | Spec], Bindings, QueryStr, Module, BindingsAcc, QueryStrAcc) ->
|
|
|
Schema = ?INIT_SCHEMA#{roots => [{Name, Type}]},
|
|
|
case hocon_schema:field_schema(Type, in) of
|
|
|
path ->
|
|
|
- NewBindings = hocon_schema:check_plain(Schema, Bindings, #{atom_key => true, override_env => false}),
|
|
|
+ Option = #{atom_key => true, override_env => false},
|
|
|
+ NewBindings = hocon_schema:check_plain(Schema, Bindings, Option),
|
|
|
NewBindingsAcc = maps:merge(BindingsAcc, NewBindings),
|
|
|
check_parameter(Spec, Bindings, QueryStr, Module, NewBindingsAcc, QueryStrAcc);
|
|
|
query ->
|
|
|
- NewQueryStr = hocon_schema:check_plain(Schema, QueryStr, #{override_env => false}),
|
|
|
+ Option = #{override_env => false},
|
|
|
+ NewQueryStr = hocon_schema:check_plain(Schema, QueryStr, Option),
|
|
|
NewQueryStrAcc = maps:merge(QueryStrAcc, NewQueryStr),
|
|
|
check_parameter(Spec, Bindings, QueryStr, Module,BindingsAcc, NewQueryStrAcc)
|
|
|
end.
|
|
|
|
|
|
-check_requestBody(#{body := Body}, Schema, Module, CheckFun, true) ->
|
|
|
+check_request_body(#{body := Body}, Schema, Module, CheckFun, true) ->
|
|
|
Type0 = hocon_schema:field_schema(Schema, type),
|
|
|
Type =
|
|
|
case Type0 of
|
|
|
@@ -190,16 +200,17 @@ check_requestBody(#{body := Body}, Schema, Module, CheckFun, true) ->
|
|
|
_ -> Type0
|
|
|
end,
|
|
|
NewSchema = ?INIT_SCHEMA#{roots => [{root, Type}]},
|
|
|
- #{<<"root">> := NewBody} = CheckFun(NewSchema, #{<<"root">> => Body}, #{override_env => false}),
|
|
|
+ Option = #{override_env => false},
|
|
|
+ #{<<"root">> := NewBody} = CheckFun(NewSchema, #{<<"root">> => Body}, Option),
|
|
|
NewBody;
|
|
|
%% TODO not support nest object check yet, please use ref!
|
|
|
-%% RequestBody = [ {per_page, mk(integer(), #{}},
|
|
|
+%% 'requestBody' = [ {per_page, mk(integer(), #{}},
|
|
|
%% {nest_object, [
|
|
|
%% {good_nest_1, mk(integer(), #{})},
|
|
|
%% {good_nest_2, mk(ref(?MODULE, good_ref), #{})}
|
|
|
%% ]}
|
|
|
%% ]
|
|
|
-check_requestBody(#{body := Body}, Spec, _Module, CheckFun, false) ->
|
|
|
+check_request_body(#{body := Body}, Spec, _Module, CheckFun, false) ->
|
|
|
lists:foldl(fun({Name, Type}, Acc) ->
|
|
|
Schema = ?INIT_SCHEMA#{roots => [{Name, Type}]},
|
|
|
maps:merge(Acc, CheckFun(Schema, Body, #{}))
|
|
|
@@ -208,7 +219,7 @@ check_requestBody(#{body := Body}, Spec, _Module, CheckFun, false) ->
|
|
|
%% tags, description, summary, security, deprecated
|
|
|
meta_to_spec(Meta, Module) ->
|
|
|
{Params, Refs1} = parameters(maps:get(parameters, Meta, []), Module),
|
|
|
- {RequestBody, Refs2} = requestBody(maps:get(requestBody, Meta, []), Module),
|
|
|
+ {RequestBody, Refs2} = request_body(maps:get('requestBody', Meta, []), Module),
|
|
|
{Responses, Refs3} = responses(maps:get(responses, Meta, #{}), Module),
|
|
|
{
|
|
|
to_spec(Meta, Params, RequestBody, Responses),
|
|
|
@@ -216,25 +227,22 @@ meta_to_spec(Meta, Module) ->
|
|
|
}.
|
|
|
|
|
|
to_spec(Meta, Params, [], Responses) ->
|
|
|
- Spec = maps:without([parameters, requestBody, responses], Meta),
|
|
|
+ Spec = maps:without([parameters, 'requestBody', responses], Meta),
|
|
|
Spec#{parameters => Params, responses => Responses};
|
|
|
to_spec(Meta, Params, RequestBody, Responses) ->
|
|
|
Spec = to_spec(Meta, Params, [], Responses),
|
|
|
- maps:put(requestBody, RequestBody, Spec).
|
|
|
+ maps:put('requestBody', RequestBody, Spec).
|
|
|
|
|
|
parameters(Params, Module) ->
|
|
|
{SpecList, AllRefs} =
|
|
|
lists:foldl(fun(Param, {Acc, RefsAcc}) ->
|
|
|
case Param of
|
|
|
- ?REF(StructName) ->
|
|
|
- {[#{<<"$ref">> => ?TO_COMPONENTS_PARAM(Module, StructName)} | Acc],
|
|
|
- [{Module, StructName, parameter} | RefsAcc]};
|
|
|
- ?R_REF(RModule, StructName) ->
|
|
|
- {[#{<<"$ref">> => ?TO_COMPONENTS_PARAM(RModule, StructName)} | Acc],
|
|
|
- [{RModule, StructName, parameter} | RefsAcc]};
|
|
|
+ ?REF(StructName) -> to_ref(Module, StructName, Acc, RefsAcc);
|
|
|
+ ?R_REF(RModule, StructName) -> to_ref(RModule, StructName, Acc, RefsAcc);
|
|
|
{Name, Type} ->
|
|
|
In = hocon_schema:field_schema(Type, in),
|
|
|
- In =:= undefined andalso throw({error, <<"missing in:path/query field in parameters">>}),
|
|
|
+ In =:= undefined andalso
|
|
|
+ throw({error, <<"missing in:path/query field in parameters">>}),
|
|
|
Nullable = hocon_schema:field_schema(Type, nullable),
|
|
|
Default = hocon_schema:field_schema(Type, default),
|
|
|
HoconType = hocon_schema:field_schema(Type, type),
|
|
|
@@ -278,8 +286,8 @@ trans_desc(Spec, Hocon) ->
|
|
|
Desc -> Spec#{description => to_bin(Desc)}
|
|
|
end.
|
|
|
|
|
|
-requestBody([], _Module) -> {[], []};
|
|
|
-requestBody(Schema, Module) ->
|
|
|
+request_body([], _Module) -> {[], []};
|
|
|
+request_body(Schema, Module) ->
|
|
|
{{Props, Refs}, Examples} =
|
|
|
case hoconsc:is_schema(Schema) of
|
|
|
true ->
|
|
|
@@ -311,7 +319,10 @@ response(Status, Schema, {Acc, RefsAcc, Module}) ->
|
|
|
{Spec, Refs} = hocon_schema_to_spec(Hocon, Module),
|
|
|
Init = trans_desc(#{}, Schema),
|
|
|
Content = content(Spec, Examples),
|
|
|
- {Acc#{integer_to_binary(Status) => Init#{<<"content">> => Content}}, Refs ++ RefsAcc, Module};
|
|
|
+ {
|
|
|
+ Acc#{integer_to_binary(Status) => Init#{<<"content">> => Content}},
|
|
|
+ Refs ++ RefsAcc, Module
|
|
|
+ };
|
|
|
false ->
|
|
|
{Props, Refs} = parse_object(Schema, Module),
|
|
|
Content = #{<<"content">> => content(Props)},
|
|
|
@@ -401,11 +412,16 @@ typename_to_spec("timeout()", _Mod) -> #{<<"oneOf">> => [#{type => string, examp
|
|
|
typename_to_spec("bytesize()", _Mod) -> #{type => string, example => <<"32MB">>};
|
|
|
typename_to_spec("wordsize()", _Mod) -> #{type => string, example => <<"1024KB">>};
|
|
|
typename_to_spec("map()", _Mod) -> #{type => object, example => #{}};
|
|
|
-typename_to_spec("comma_separated_list()", _Mod) -> #{type => string, example => <<"item1,item2">>};
|
|
|
-typename_to_spec("comma_separated_atoms()", _Mod) -> #{type => string, example => <<"item1,item2">>};
|
|
|
-typename_to_spec("pool_type()", _Mod) -> #{type => string, enum => [random, hash], example => hash};
|
|
|
+typename_to_spec("comma_separated_list()", _Mod) ->
|
|
|
+ #{type => string, example => <<"item1,item2">>};
|
|
|
+typename_to_spec("comma_separated_atoms()", _Mod) ->
|
|
|
+ #{type => string, example => <<"item1,item2">>};
|
|
|
+typename_to_spec("pool_type()", _Mod) ->
|
|
|
+ #{type => string, enum => [random, hash], example => hash};
|
|
|
typename_to_spec("log_level()", _Mod) ->
|
|
|
- #{type => string, enum => [debug, info, notice, warning, error, critical, alert, emergency, all]};
|
|
|
+ #{ type => string,
|
|
|
+ enum => [debug, info, notice, warning, error, critical, alert, emergency, all]
|
|
|
+ };
|
|
|
typename_to_spec("rate()", _Mod) ->
|
|
|
#{type => string, example => <<"10M/s">>};
|
|
|
typename_to_spec("bucket_rate()", _Mod) ->
|
|
|
@@ -465,9 +481,12 @@ add_integer_prop(Schema, Key, Value) ->
|
|
|
{Int, []} -> Schema#{Key => Int}
|
|
|
end.
|
|
|
|
|
|
-to_bin([Atom | _] = List) when is_atom(Atom) -> iolist_to_binary(io_lib:format("~p", [List]));
|
|
|
-to_bin(List) when is_list(List) -> unicode:characters_to_binary(List);
|
|
|
-to_bin(B) when is_boolean(B) -> B;
|
|
|
+to_bin(List) when is_list(List) ->
|
|
|
+ case io_lib:printable_list(List) of
|
|
|
+ true -> unicode:characters_to_binary(List);
|
|
|
+ false -> List
|
|
|
+ end;
|
|
|
+to_bin(Boolean) when is_boolean(Boolean) -> Boolean;
|
|
|
to_bin(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8);
|
|
|
to_bin(X) -> X.
|
|
|
|
|
|
@@ -513,3 +532,7 @@ content(ApiSpec, undefined) ->
|
|
|
#{<<"application/json">> => #{<<"schema">> => ApiSpec}};
|
|
|
content(ApiSpec, Examples) when is_map(Examples) ->
|
|
|
#{<<"application/json">> => Examples#{<<"schema">> => ApiSpec}}.
|
|
|
+
|
|
|
+to_ref(Mod, StructName, Acc, RefsAcc) ->
|
|
|
+ Ref = #{<<"$ref">> => ?TO_COMPONENTS_PARAM(Mod, StructName)},
|
|
|
+ {[Ref | Acc], [{Mod, StructName, parameter} | RefsAcc]}.
|