Skip to content

Commit

Permalink
refactor: enhance ndto_parser interface
Browse files Browse the repository at this point in the history
  • Loading branch information
javiergarea committed Jan 9, 2024
1 parent d8dd357 commit 489a50b
Show file tree
Hide file tree
Showing 10 changed files with 821 additions and 446 deletions.
16 changes: 8 additions & 8 deletions rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

{project_plugins, [
erlfmt,
{eqwalizer_rebar3,
{git_subdir, "https://github.com/whatsapp/eqwalizer.git", {branch, "main"},
"eqwalizer_rebar3"}},
{gradualizer, {git, "[email protected]:josefs/Gradualizer.git", {branch, "master"}}},
rebar3_ex_doc
]}.
Expand All @@ -16,6 +19,9 @@
{test, [
{erl_opts, [nowarn_export_all]},
{deps, [
{eqwalizer_support,
{git_subdir, "https://github.com/whatsapp/eqwalizer.git", {branch, "main"},
"eqwalizer_support"}},
{nct_util, {git, "[email protected]:nomasystems/nct_util.git", {branch, "main"}}},
{triq, {git, "[email protected]:nomasystems/triq.git", {branch, "master"}}}
]}
Expand Down Expand Up @@ -54,12 +60,6 @@

{xref_ignores, [
ndto,
ndto_parser
]}.

%% TODO: address this
{gradualizer_opts, [
{exclude, [
"src/ndto_parser_json_schema_draft_04.erl"
]}
ndto_parser,
ndto_parser_json_schema
]}.
2 changes: 1 addition & 1 deletion rebar.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
0},
{<<"njson">>,
{git,"[email protected]:nomasystems/njson.git",
{ref,"338ecbac922343874765abebfbcc8af131724732"}},
{ref,"b230b3e6fb5e35320aeaa203762f3f12277c9970"}},
0}].
6 changes: 4 additions & 2 deletions src/ndto.erl
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@
}.
-type array_schema() :: #{
type := array,
items => schema(),
items => schema() | [schema()],
additional_items => schema(),
min_items => non_neg_integer(),
max_items => non_neg_integer(),
unique_items => boolean(),
Expand Down Expand Up @@ -124,6 +125,7 @@
}.
-type symmetric_difference_schema() :: #{
one_of := [schema()],
optional => boolean(),
nullable => boolean()
}.

Expand All @@ -139,7 +141,7 @@
-type null() :: null.
-type object() :: #{binary() => value()}.
-type format() :: iso8601 | base64.
% TODO: use openapi defined formats
% TODO: support json_schema and openapi defined formats
-type pattern() :: binary().

%%% TYPE EXPORTS
Expand Down
142 changes: 135 additions & 7 deletions src/ndto_generator.erl
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ generate(Name, Schema) ->
Result :: {IsValidFun, ExtraFuns},
IsValidFun :: erl_syntax:syntaxTree(),
ExtraFuns :: [erl_syntax:syntaxTree()].
is_valid(Prefix, false) ->
FunName = <<Prefix/binary, "false">>,
FalseClause = false_clause(),
Fun = erl_syntax:function(
erl_syntax:atom(erlang:binary_to_atom(FunName)),
[FalseClause]
),
{Fun, []};
is_valid(Prefix, #{ref := Ref} = Schema) ->
FunName = <<Prefix/binary, "ref_", Ref/binary>>,
DTO = erlang:binary_to_atom(Ref),
Expand Down Expand Up @@ -302,8 +310,8 @@ is_valid(Prefix, #{type := array} = Schema) ->
case maps:get(Keyword, Schema, undefined) of
undefined ->
Acc;
Value ->
case is_valid_array(<<FunName/binary, "_">>, Keyword, Value) of
_Value ->
case is_valid_array(<<FunName/binary, "_">>, Keyword, Schema) of
{undefined, _EmptyList} ->
Acc;
{NewIsValidFun, NewExtraFuns} ->
Expand Down Expand Up @@ -554,7 +562,7 @@ is_valid(Prefix, _Schema) ->
Result :: {Fun, ExtraFuns},
Fun :: erl_syntax:syntaxTree() | undefined,
ExtraFuns :: [erl_syntax:syntaxTree()].
is_valid_array(Prefix, items, Items) ->
is_valid_array(Prefix, items, #{items := Items}) when is_map(Items) ->
FunName = <<Prefix/binary, "items">>,
{IsValidFun, ExtraFuns} = is_valid(<<FunName/binary, "_">>, Items),
TrueClause = erl_syntax:clause(
Expand Down Expand Up @@ -587,7 +595,125 @@ is_valid_array(Prefix, items, Items) ->
[TrueClause]
),
{Fun, [IsValidFun | ExtraFuns]};
is_valid_array(Prefix, min_items, MinItems) ->
is_valid_array(Prefix, items, #{items := Items} = Schema) when is_list(Items) ->
{_Size, IsValidFuns, ExtraFuns} = lists:foldl(
fun(Item, {Idx, IsValidFunsAcc, ExtraFunsAcc}) ->
ItemFunName = <<Prefix/binary, "item_", (erlang:integer_to_binary(Idx))/binary, "_">>,
{ItemIsValidFun, ItemExtraFuns} = is_valid(ItemFunName, Item),
{Idx + 1, [{Idx, ItemIsValidFun} | IsValidFunsAcc], ItemExtraFuns ++ ExtraFunsAcc}
end,
{1, [], []},
Items
),
FunName = <<Prefix/binary, "items">>,
AdditionalItems = maps:get(additional_items, Schema, true),
{IsValidAdditionalItemsFun, AdditionalItemsExtraFuns} =
is_valid(<<FunName/binary, "_additional_items_">>, AdditionalItems),
TrueClause = erl_syntax:clause(
[erl_syntax:variable('Val')],
none,
[
erl_syntax:match_expr(
erl_syntax:variable('FunsMap'),
erl_syntax:map_expr(
lists:map(
fun({Idx, IsValidFun}) ->
erl_syntax:map_field_assoc(
erl_syntax:integer(Idx),
erl_syntax:fun_expr(erl_syntax:function_clauses(IsValidFun))
)
end,
IsValidFuns
)
)
),
erl_syntax:application(
erl_syntax:atom(lists),
erl_syntax:atom(all),
[
erl_syntax:fun_expr([
erl_syntax:clause(
[
erl_syntax:tuple([
erl_syntax:variable('Item'),
erl_syntax:variable('FunKey')
])
],
none,
[
erl_syntax:case_expr(
erl_syntax:application(
erl_syntax:atom(maps),
erl_syntax:atom(get),
[
erl_syntax:variable('FunKey'),
erl_syntax:variable('FunsMap'),
erl_syntax:atom(undefined)
]
),
[
erl_syntax:clause(
[erl_syntax:atom(undefined)],
none,
[
erl_syntax:application(
erl_syntax:function_name(
IsValidAdditionalItemsFun
),
[erl_syntax:variable('Item')]
)
]
),
erl_syntax:clause(
[erl_syntax:variable('IsValidItemFun')],
none,
[
erl_syntax:application(
erl_syntax:variable(
'IsValidItemFun'
),
[
erl_syntax:variable(
'Item'
)
]
)
]
)
]
)
]
)
]),
erl_syntax:application(
erl_syntax:atom(lists),
erl_syntax:atom(zip),
[
erl_syntax:variable('Val'),
erl_syntax:application(
erl_syntax:atom(lists),
erl_syntax:atom(seq),
[
erl_syntax:integer(1),
erl_syntax:application(
erl_syntax:atom(erlang),
erl_syntax:atom(length),
[erl_syntax:variable('Val')]
)
]
)
]
)
]
)
]
),
Fun = erl_syntax:function(
erl_syntax:atom(erlang:binary_to_atom(FunName)),
[TrueClause]
),
{Fun, ExtraFuns ++ [IsValidAdditionalItemsFun | AdditionalItemsExtraFuns]};
is_valid_array(Prefix, min_items, #{min_items := MinItems}) ->
FunName = <<Prefix/binary, "min_items">>,
TrueClause = erl_syntax:clause(
[erl_syntax:variable('Val')],
Expand All @@ -604,7 +730,7 @@ is_valid_array(Prefix, min_items, MinItems) ->
[TrueClause, FalseClause]
),
{Fun, []};
is_valid_array(Prefix, max_items, MaxItems) ->
is_valid_array(Prefix, max_items, #{max_items := MaxItems}) ->
FunName = <<Prefix/binary, "max_items">>,
TrueClause = erl_syntax:clause(
[erl_syntax:variable('Val')],
Expand All @@ -621,7 +747,7 @@ is_valid_array(Prefix, max_items, MaxItems) ->
[TrueClause, FalseClause]
),
{Fun, []};
is_valid_array(Prefix, unique_items, true) ->
is_valid_array(Prefix, unique_items, #{unique_items := true}) ->
FunName = <<Prefix/binary, "unique_items">>,
TrueClause = erl_syntax:clause(
[erl_syntax:variable('Val')],
Expand Down Expand Up @@ -649,7 +775,7 @@ is_valid_array(Prefix, unique_items, true) ->
[TrueClause]
),
{Fun, []};
is_valid_array(_Prefix, unique_items, false) ->
is_valid_array(_Prefix, unique_items, #{unique_items := false}) ->
{undefined, []}.

-spec is_valid_number(Type, Prefix, Keyword, Value, Schema) -> Result when
Expand Down Expand Up @@ -1576,6 +1702,8 @@ false_clause() ->
guard(Pred, Var) ->
erl_syntax:application(erl_syntax:atom(Pred), [erl_syntax:variable(Var)]).

literal(null) ->
erl_syntax:atom(null);
literal(Val) when is_boolean(Val) ->
erl_syntax:atom(Val);
literal(Val) when is_integer(Val) ->
Expand Down
41 changes: 27 additions & 14 deletions src/ndto_parser.erl
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,59 @@
%% See the License for the specific language governing permissions and
%% limitations under the License

%% @doc An <code>ndto</code> behaviour for schema parsers.
%% @doc An <code>ndto</code> interface and behaviour for schema parsers.
-module(ndto_parser).

%%% EXTERNAL EXPORTS
-export([
parse/2,
parse/3
]).

%%% TYPES
-type ctx() :: term().
-type spec() :: term().
-opaque t() :: module().
% A parser is a module that implements the <code>ndto_parser</code> behaviour.

%%% EXPORT TYPES
-export_type([
ctx/0,
spec/0,
t/0
]).

%%%-----------------------------------------------------------------------------
%%% BEHAVIOUR CALLBACKS
%%%-----------------------------------------------------------------------------
-callback parse(Namespace, SpecPath) -> Result when
Namespace :: atom(),
SpecPath :: binary(),
-callback parse(SpecPath, Opts) -> Result when
SpecPath :: file:filename_all(),
Opts :: map(),
Result :: {ok, Schemas} | {error, Reason},
Schemas :: [{SchemaName, ndto:schema()}],
SchemaName :: ndto:name(),
Schemas :: [{ndto:name(), ndto:schema()}],
Reason :: term().
% Parses a specification into a <code>ndto:schema()</code>
% Parses a specification into a list of <code>ndto:schema()</code> values.

%%%-----------------------------------------------------------------------------
%%% EXTERNAL EXPORTS
%%%-----------------------------------------------------------------------------
-spec parse(Parser, Namespace, SpecPath) -> Result when
-spec parse(Parser, SpecPath) -> Result when
Parser :: t(),
Namespace :: atom(),
SpecPath :: binary(),
SpecPath :: file:filename_all(),
Result :: {ok, Schemas} | {error, Reason},
Schemas :: [{SchemaName, ndto:schema()}],
SchemaName :: ndto:name(),
Schemas :: [{ndto:name(), ndto:schema()}],
Reason :: term().
%% @equiv parse(Parser, SpecPath, #{})
parse(Parser, SpecPath) ->
parse(Parser, SpecPath, #{}).

-spec parse(Parser, SpecPath, Opts) -> Result when
Parser :: t(),
SpecPath :: file:filename_all(),
Opts :: map(),
Result :: {ok, Schemas} | {error, Reason},
Schemas :: [{ndto:name(), ndto:schema()}],
Reason :: term().
%% @doc Parses a schema specification into a <code>ndto:schema()</code> using the given parser.
parse(Parser, Namespace, SpecPath) ->
Parser:parse(Namespace, SpecPath).
parse(Parser, SpecPath, Opts) ->
Parser:parse(SpecPath, Opts).
Loading

0 comments on commit 489a50b

Please sign in to comment.