diff --git a/rebar.config b/rebar.config index 543589b..57d87d3 100644 --- a/rebar.config +++ b/rebar.config @@ -1,11 +1,14 @@ {erl_opts, [debug_info]}. {deps, [ - {nova, "0.12.1"}, + {nova, "0.13.7"}, {jesse, "1.8.1"} ]}. {xref_ignores, [ - {nova_json_schemas, load_local_schemas, 0} + {nova_json_schemas, init, 0}, + {nova_json_schemas, load_local_schemas, 0}, + {nova_json_schemas, pre_request, 4}, + {nova_json_schemas, post_request, 4} ]}. {dialyzer, [ diff --git a/rebar.lock b/rebar.lock index 44b518e..569747f 100644 --- a/rebar.lock +++ b/rebar.lock @@ -3,8 +3,8 @@ {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.16.0">>},2}, {<<"erlydtl">>,{pkg,<<"erlydtl">>,<<"0.14.0">>},1}, {<<"jesse">>,{pkg,<<"jesse">>,<<"1.8.1">>},0}, - {<<"jhn_stdlib">>,{pkg,<<"jhn_stdlib">>,<<"5.3.3">>},1}, - {<<"nova">>,{pkg,<<"nova">>,<<"0.12.1">>},0}, + {<<"jhn_stdlib">>,{pkg,<<"jhn_stdlib">>,<<"5.4.0">>},1}, + {<<"nova">>,{pkg,<<"nova">>,<<"0.13.7">>},0}, {<<"ranch">>,{pkg,<<"ranch">>,<<"2.2.0">>},2}, {<<"routing_tree">>,{pkg,<<"routing_tree">>,<<"1.0.11">>},1}, {<<"thoas">>,{pkg,<<"thoas">>,<<"1.2.1">>},1}]}. @@ -14,8 +14,8 @@ {<<"cowlib">>, <<"54592074EBBBB92EE4746C8A8846E5605052F29309D3A873468D76CDF932076F">>}, {<<"erlydtl">>, <<"964B2DC84F8C17ACFAA69C59BA129EF26AC45D2BA898C3C6AD9B5BDC8BA13CED">>}, {<<"jesse">>, <<"C9E3670C7EE40F719734E3BC716578143AABA93FC7525A02A7D5CB300B3AD71E">>}, - {<<"jhn_stdlib">>, <<"3E50C560334A85EE5B6C645D2E1BCD35E7BE667E5FCB62AA364F00737C2ADC61">>}, - {<<"nova">>, <<"78B60221F632C5C7D1B0EEEABCB7370764304C32C757C0343513DA4108326BD3">>}, + {<<"jhn_stdlib">>, <<"FAC6F19B35351278F1CB156E23A5B2A6047A9DD5AB1FD9E1189A7918006DF7ED">>}, + {<<"nova">>, <<"C37A2161EA1EE643635739282D1DF9880D20D9521B6AD3ABE6A71C6A6867C842">>}, {<<"ranch">>, <<"25528F82BC8D7C6152C57666CA99EC716510FE0925CB188172F41CE93117B1B0">>}, {<<"routing_tree">>, <<"72ACEF2095F0EC804F7AFD07EF781DDE5009425A1CA0A28F0706B1DB334A4812">>}, {<<"thoas">>, <<"19A25F31177A17E74004D4840F66D791D4298C5738790FA2CC73731EB911F195">>}]}, @@ -24,8 +24,8 @@ {<<"cowlib">>, <<"7F478D80D66B747344F0EA7708C187645CFCC08B11AA424632F78E25BF05DB51">>}, {<<"erlydtl">>, <<"D80EC044CD8F58809C19D29AC5605BE09E955040911B644505E31E9DD8143431">>}, {<<"jesse">>, <<"0EDED3F18623FDA2F25989804A06CF518B4ACF2E9365B18C8E8C013D7E3C906F">>}, - {<<"jhn_stdlib">>, <<"2CB184C505397B62A842AB3DE13F21B83ADF62364BD35A572191629E30E0258E">>}, - {<<"nova">>, <<"C43DC05F0B0F31FD3CF3B0415F2772BFD9FFD75DFEBFFE0B806DE85B6581F4C7">>}, + {<<"jhn_stdlib">>, <<"7EABD1B01D2DEFF495BF7C5CA1DBA4D3FA0B84DC3AF03CA85F31D52EBB03C6FC">>}, + {<<"nova">>, <<"22F4C3FFDC08DB8568F3DA198647823D63123520F69C7D718A8DB468F4C7A1EA">>}, {<<"ranch">>, <<"FA0B99A1780C80218A4197A59EA8D3BDAE32FBFF7E88527D7D8A4787EFF4F8E7">>}, {<<"routing_tree">>, <<"85982C7AC502892C5179CD2A591331003BACD2D2A71723640BA7D23F45408E6E">>}, {<<"thoas">>, <<"E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A">>}]} diff --git a/src/nova_json_schemas.erl b/src/nova_json_schemas.erl index a6313ea..01c97d6 100644 --- a/src/nova_json_schemas.erl +++ b/src/nova_json_schemas.erl @@ -2,14 +2,20 @@ -behaviour(nova_plugin). -export([ + init/0, load_local_schemas/0, - pre_request/2, - post_request/2, + pre_request/4, + post_request/4, plugin_info/0 ]). -include_lib("kernel/include/logger.hrl"). +init() -> + jesse_database:load_all(), + load_local_schemas(), + #{}. + %%-------------------------------------------------------------------- %% @doc %% Load all local JSON schemas from the main application's @@ -34,69 +40,59 @@ load_local_schemas() -> %% Pre-request callback %% @end %%-------------------------------------------------------------------- --spec pre_request(Req :: cowboy_req:req(), Options :: map()) -> - {ok, Req0 :: cowboy_req:req()} - | {stop, Req0 :: cowboy_req:req()} +-spec pre_request(Req :: cowboy_req:req(), Env :: any(), Options :: map(), State :: any()) -> + {ok, Req0 :: cowboy_req:req(), NewState :: any()} + | {stop, Req0 :: cowboy_req:req(), NewState :: any()} | {error, Reason :: term()}. pre_request( - Req = #{extra_state := #{json_schema := SchemaLocation} = Extra, json := JSON}, Options + Req = #{extra_state := #{json_schema := SchemaLocation} = Extra, json := JSON}, + _Env, + Options, + State ) -> JesseOpts = maps:get(jesse_options, Extra, []), - %% JSON have already been parsed so we can just continue with the validation case validate_json(SchemaLocation, JSON, JesseOpts) of ok -> ?LOG_DEBUG("Schema validation on JSON body successful"), - {ok, Req}; + {ok, Req, State}; {error, Errors} -> ?LOG_DEBUG("Got validation-errors on JSON body. Errors: ~p", [Errors]), case maps:get(render_errors, Options, false) of true -> ?LOG_DEBUG("Rendering validation-errors and send back to requester"), - JsonLib = nova:get_env(json_lib, thoas), Req0 = cowboy_req:set_resp_headers( #{<<"content-type">> => <<"application/json">>}, Req ), ErrorStruct = render_error(Errors), - ErrorJson = erlang:apply(JsonLib, encode, [ErrorStruct]), + ErrorJson = json:encode(ErrorStruct), Req1 = cowboy_req:set_resp_body(ErrorJson, Req0), Req2 = cowboy_req:reply(400, Req1), - {stop, Req2}; + {stop, Req2, State}; _ -> ?LOG_DEBUG( "render_errors-option not set for plugin nova_json_schemas - returning plain 400-status to requester" ), Req0 = cowboy_req:reply(400, Req), - {stop, Req0} + {stop, Req0, State} end end; -pre_request(#{extra_state := #{json_schema := _SchemaLocation}}, _Options) -> - %% The body have not been parsed. Log and error and stop +pre_request(#{extra_state := #{json_schema := _SchemaLocation}}, _Env, _Options, _State) -> ?LOG_ERROR( "JSON Schema is set in 'extra_state' but body have not yet been parsed - rearrange your plugins so that JSON plugin is ran before this.." ), {error, body_not_parsed}; -pre_request(Req, _Options) -> - %% 'json_schema' is not set or 'extra_state' is completly missing. Just continue. - HasBody = cowboy_req:has_body(Req), - if - HasBody -> - ?LOG_DEBUG("No schema is set for this route so will continue executing"); - true -> - ok - end, - {ok, Req}. +pre_request(Req, _Env, _Options, State) -> + {ok, Req, State}. %%-------------------------------------------------------------------- %% @doc %% Post-request callback %% @end %%-------------------------------------------------------------------- --spec post_request(Req :: cowboy_req:req(), Options :: map()) -> - {ok, Req0 :: cowboy_req:req()} - | {stop, Req0 :: cowboy_req:req()} - | {error, Reason :: term()}. -post_request(Req, _Options) -> - {ok, Req}. +-spec post_request(Req :: cowboy_req:req(), Env :: any(), Options :: map(), State :: any()) -> + {ok, Req0 :: cowboy_req:req(), NewState :: any()}. +post_request(Req, _Env, _Options, State) -> + {ok, Req, State}. %%-------------------------------------------------------------------- %% @doc @@ -104,18 +100,25 @@ post_request(Req, _Options) -> %% @end %%-------------------------------------------------------------------- -spec plugin_info() -> - {Title :: binary(), Version :: binary(), Author :: binary(), Description :: binary(), [ - {Key :: atom(), OptionDescription :: binary()} - ]}. + #{ + title := binary(), + version := binary(), + url := binary(), + authors := [binary()], + description := binary(), + options := [{Key :: atom(), OptionDescription :: binary()}] + }. plugin_info() -> - {ok, Vsn} = application:get_key(nova_json_schemas, vsn), - {ok, Desc} = application:get_key(nova_json_schemas, description), - - {<<"JSON schema plugin">>, list_to_binary(Vsn), <<"Niclas Axelsson >, - list_to_binary(Desc), [ - {render_errors, <<"If this is set, validation-errors is returned to the requester">>} - %% Options is specified as {Key, Description} - ]}. + #{ + title => <<"Nova JSON Schema plugin">>, + version => <<"0.2.0">>, + url => <<"https://github.com/novaframework/nova_json_schemas">>, + authors => [<<"Niclas Axelsson ">>], + description => <<"Validates JSON request bodies against JSON schemas using jesse">>, + options => [ + {render_errors, <<"If true, validation errors are returned as JSON to the requester">>} + ] + }. validate_json(SchemaLocation, Json, JesseOpts) -> case jesse:validate(SchemaLocation, Json, JesseOpts) of @@ -125,8 +128,7 @@ validate_json(SchemaLocation, Json, JesseOpts) -> PrivDir = code:priv_dir(MainApp), SchemaLocation0 = filename:join([PrivDir, SchemaLocation]), {ok, Filecontent} = file:read_file(SchemaLocation0), - JsonLib = nova:get_env(json_lib, thoas), - {ok, Schema} = erlang:apply(JsonLib, decode, [Filecontent]), + Schema = json:decode(Filecontent), jesse:add_schema(SchemaLocation, Schema), validate_json(SchemaLocation, Json, JesseOpts); {error, ValidationError} -> @@ -178,12 +180,12 @@ load_schemas_from_dir(Dir, RelativePrefix) -> load_schema_file(FilePath, RelativePath) -> case file:read_file(FilePath) of {ok, FileContent} -> - JsonLib = nova:get_env(json_lib, thoas), - case erlang:apply(JsonLib, decode, [FileContent]) of - {ok, Schema} -> + try json:decode(FileContent) of + Schema -> jesse:add_schema(RelativePath, Schema), - ?LOG_DEBUG("Loaded JSON schema: ~s", [RelativePath]); - {error, Reason} -> + ?LOG_DEBUG("Loaded JSON schema: ~s", [RelativePath]) + catch + error:Reason -> ?LOG_ERROR("Failed to decode JSON schema ~s: ~p", [FilePath, Reason]) end; {error, Reason} ->