Files
openapi-generator/samples/server/echo_api/erlang-server/src/openapi_api.erl
Nelson Vides 83f6d592a2 Erlang server validation bugfixes and capability extensions (#19750)
* Bugfix: exclusiveMinimum/Maximum must be booleans

* Add support for validating and decoding base64 byte strings

* Add support for validating RFC3339 datetime

* Simplify validate function for performance

* Regenerate erlang-server handlers
2024-10-03 14:24:00 +08:00

916 lines
28 KiB
Erlang

-module(openapi_api).
-moduledoc """
This module offers an API for JSON schema validation, using `jesse` under the hood.
If validation is desired, a jesse state can be loaded using `prepare_validator/1`,
and request and response can be validated using `populate_request/3`
and `validate_response/4` respectively.
For example, the user-defined `Module:accept_callback/4` can be implemented as follows:
```
-spec accept_callback(atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
{cowboy:http_status(), cowboy:http_headers(), json:encode_value()}.
accept_callback(Class, OperationID, Req, Context) ->
ValidatorState = openapi_api:prepare_validator(),
case openapi_api:populate_request(OperationID, Req0, ValidatorState) of
{ok, Populated, Req1} ->
{Code, Headers, Body} = openapi_logic_handler:handle_request(
LogicHandler,
OperationID,
Req1,
maps:merge(State#state.context, Populated)
),
_ = openapi_api:validate_response(
OperationID,
Code,
Body,
ValidatorState
),
PreparedBody = prepare_body(Code, Body),
Response = {ok, {Code, Headers, PreparedBody}},
process_response(Response, Req1, State);
{error, Reason, Req1} ->
process_response({error, Reason}, Req1, State)
end.
```
""".
-export([prepare_validator/0, prepare_validator/1, prepare_validator/2]).
-export([populate_request/3, validate_response/4]).
-ignore_xref([populate_request/3, validate_response/4]).
-ignore_xref([prepare_validator/0, prepare_validator/1, prepare_validator/2]).
-type operation_id() :: atom().
-type request_param() :: atom().
-export_type([operation_id/0]).
-dialyzer({nowarn_function, [validate_response_body/4]}).
-type rule() ::
{type, binary} |
{type, byte} |
{type, integer} |
{type, float} |
{type, boolean} |
{type, date} |
{type, datetime} |
{enum, [atom()]} |
{max, Max :: number()} |
{exclusive_max, Max :: number()} |
{min, Min :: number()} |
{exclusive_min, Min :: number()} |
{max_length, MaxLength :: integer()} |
{min_length, MaxLength :: integer()} |
{pattern, Pattern :: string()} |
schema |
required |
not_required.
-doc #{equiv => prepare_validator/2}.
-spec prepare_validator() -> jesse_state:state().
prepare_validator() ->
prepare_validator(<<"http://json-schema.org/draft-06/schema#">>).
-doc #{equiv => prepare_validator/2}.
-spec prepare_validator(binary()) -> jesse_state:state().
prepare_validator(SchemaVer) ->
prepare_validator(get_openapi_path(), SchemaVer).
-doc """
Loads the JSON schema and the desired validation draft into a `t:jesse_state:state()`.
""".
-spec prepare_validator(file:name_all(), binary()) -> jesse_state:state().
prepare_validator(OpenApiPath, SchemaVer) ->
{ok, FileContents} = file:read_file(OpenApiPath),
R = json:decode(FileContents),
jesse_state:new(R, [{default_schema_ver, SchemaVer}]).
-doc """
Automatically loads the entire body from the cowboy req
and validates the JSON body against the schema.
""".
-spec populate_request(
OperationID :: operation_id(),
Req :: cowboy_req:req(),
ValidatorState :: jesse_state:state()) ->
{ok, Model :: #{}, Req :: cowboy_req:req()} |
{error, Reason :: any(), Req :: cowboy_req:req()}.
populate_request(OperationID, Req, ValidatorState) ->
Params = request_params(OperationID),
populate_request_params(OperationID, Params, Req, ValidatorState, #{}).
-doc """
Validates that the provided `Code` and `Body` comply with the `ValidatorState` schema
for the `OperationID` operation.
""".
-spec validate_response(
OperationID :: operation_id(),
Code :: 200..599,
Body :: jesse:json_term(),
ValidatorState :: jesse_state:state()) ->
ok | {ok, term()} | [ok | {ok, term()}] | no_return().
validate_response('TestAuthHttpBasic', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestAuthHttpBearer', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestBinaryGif', 200, Body, ValidatorState) ->
validate_response_body('file', 'file', Body, ValidatorState);
validate_response('TestBodyApplicationOctetstreamBinary', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestBodyMultipartFormdataArrayOfBinary', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestBodyMultipartFormdataSingleBinary', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestEchoBodyAllOfPet', 200, Body, ValidatorState) ->
validate_response_body('Pet', 'Pet', Body, ValidatorState);
validate_response('TestEchoBodyFreeFormObjectResponseString', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestEchoBodyPet', 200, Body, ValidatorState) ->
validate_response_body('Pet', 'Pet', Body, ValidatorState);
validate_response('TestEchoBodyPetResponseString', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestEchoBodyStringEnum', 200, Body, ValidatorState) ->
validate_response_body('StringEnumRef', 'StringEnumRef', Body, ValidatorState);
validate_response('TestEchoBodyTagResponseString', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestFormIntegerBooleanString', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestFormObjectMultipart', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestFormOneof', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestHeaderIntegerBooleanStringEnums', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestEnumRefString', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestQueryDatetimeDateString', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestQueryIntegerBooleanString', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestQueryStyleDeepObjectExplodeTrueObject', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestQueryStyleDeepObjectExplodeTrueObjectAllOf', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestQueryStyleFormExplodeFalseArrayInteger', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestQueryStyleFormExplodeFalseArrayString', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestQueryStyleFormExplodeTrueArrayString', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestQueryStyleFormExplodeTrueObject', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response('TestQueryStyleFormExplodeTrueObjectAllOf', 200, Body, ValidatorState) ->
validate_response_body('binary', 'string', Body, ValidatorState);
validate_response(_OperationID, _Code, _Body, _ValidatorState) ->
ok.
%%%
-spec request_params(OperationID :: operation_id()) -> [Param :: request_param()].
request_params('TestAuthHttpBasic') ->
[
];
request_params('TestAuthHttpBearer') ->
[
];
request_params('TestBinaryGif') ->
[
];
request_params('TestBodyApplicationOctetstreamBinary') ->
[
'file'
];
request_params('TestBodyMultipartFormdataArrayOfBinary') ->
[
'files'
];
request_params('TestBodyMultipartFormdataSingleBinary') ->
[
'my-file'
];
request_params('TestEchoBodyAllOfPet') ->
[
'Pet'
];
request_params('TestEchoBodyFreeFormObjectResponseString') ->
[
'object'
];
request_params('TestEchoBodyPet') ->
[
'Pet'
];
request_params('TestEchoBodyPetResponseString') ->
[
'Pet'
];
request_params('TestEchoBodyStringEnum') ->
[
'binary'
];
request_params('TestEchoBodyTagResponseString') ->
[
'Tag'
];
request_params('TestFormIntegerBooleanString') ->
[
'integer_form',
'boolean_form',
'string_form'
];
request_params('TestFormObjectMultipart') ->
[
'marker'
];
request_params('TestFormOneof') ->
[
'form1',
'form2',
'form3',
'form4',
'id',
'name'
];
request_params('TestHeaderIntegerBooleanStringEnums') ->
[
'integer_header',
'boolean_header',
'string_header',
'enum_nonref_string_header',
'enum_ref_string_header'
];
request_params('TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}') ->
[
'path_string',
'path_integer',
'enum_nonref_string_path',
'enum_ref_string_path'
];
request_params('TestEnumRefString') ->
[
'enum_nonref_string_query',
'enum_ref_string_query'
];
request_params('TestQueryDatetimeDateString') ->
[
'datetime_query',
'date_query',
'string_query'
];
request_params('TestQueryIntegerBooleanString') ->
[
'integer_query',
'boolean_query',
'string_query'
];
request_params('TestQueryStyleDeepObjectExplodeTrueObject') ->
[
'query_object'
];
request_params('TestQueryStyleDeepObjectExplodeTrueObjectAllOf') ->
[
'query_object'
];
request_params('TestQueryStyleFormExplodeFalseArrayInteger') ->
[
'query_object'
];
request_params('TestQueryStyleFormExplodeFalseArrayString') ->
[
'query_object'
];
request_params('TestQueryStyleFormExplodeTrueArrayString') ->
[
'query_object'
];
request_params('TestQueryStyleFormExplodeTrueObject') ->
[
'query_object'
];
request_params('TestQueryStyleFormExplodeTrueObjectAllOf') ->
[
'query_object'
];
request_params(_) ->
error(unknown_operation).
-spec request_param_info(OperationID :: operation_id(), Name :: request_param()) ->
#{source => qs_val | binding | header | body, rules => [rule()]}.
request_param_info('TestBodyApplicationOctetstreamBinary', 'file') ->
#{
source => body,
rules => [
{type, binary},
schema,
not_required
]
};
request_param_info('TestBodyMultipartFormdataArrayOfBinary', 'files') ->
#{
source => body,
rules => [
required
]
};
request_param_info('TestBodyMultipartFormdataSingleBinary', 'my-file') ->
#{
source => body,
rules => [
{type, binary},
not_required
]
};
request_param_info('TestEchoBodyAllOfPet', 'Pet') ->
#{
source => body,
rules => [
schema,
not_required
]
};
request_param_info('TestEchoBodyFreeFormObjectResponseString', 'object') ->
#{
source => body,
rules => [
schema,
not_required
]
};
request_param_info('TestEchoBodyPet', 'Pet') ->
#{
source => body,
rules => [
schema,
not_required
]
};
request_param_info('TestEchoBodyPetResponseString', 'Pet') ->
#{
source => body,
rules => [
schema,
not_required
]
};
request_param_info('TestEchoBodyStringEnum', 'binary') ->
#{
source => body,
rules => [
schema,
not_required
]
};
request_param_info('TestEchoBodyTagResponseString', 'Tag') ->
#{
source => body,
rules => [
schema,
not_required
]
};
request_param_info('TestFormIntegerBooleanString', 'integer_form') ->
#{
source => body,
rules => [
{type, integer},
not_required
]
};
request_param_info('TestFormIntegerBooleanString', 'boolean_form') ->
#{
source => body,
rules => [
{type, boolean},
not_required
]
};
request_param_info('TestFormIntegerBooleanString', 'string_form') ->
#{
source => body,
rules => [
{type, binary},
not_required
]
};
request_param_info('TestFormObjectMultipart', 'marker') ->
#{
source => body,
rules => [
required
]
};
request_param_info('TestFormOneof', 'form1') ->
#{
source => body,
rules => [
{type, binary},
not_required
]
};
request_param_info('TestFormOneof', 'form2') ->
#{
source => body,
rules => [
{type, integer},
not_required
]
};
request_param_info('TestFormOneof', 'form3') ->
#{
source => body,
rules => [
{type, binary},
not_required
]
};
request_param_info('TestFormOneof', 'form4') ->
#{
source => body,
rules => [
{type, boolean},
not_required
]
};
request_param_info('TestFormOneof', 'id') ->
#{
source => body,
rules => [
{type, integer},
not_required
]
};
request_param_info('TestFormOneof', 'name') ->
#{
source => body,
rules => [
{type, binary},
not_required
]
};
request_param_info('TestHeaderIntegerBooleanStringEnums', 'integer_header') ->
#{
source => header,
rules => [
{type, integer},
not_required
]
};
request_param_info('TestHeaderIntegerBooleanStringEnums', 'boolean_header') ->
#{
source => header,
rules => [
{type, boolean},
not_required
]
};
request_param_info('TestHeaderIntegerBooleanStringEnums', 'string_header') ->
#{
source => header,
rules => [
{type, binary},
not_required
]
};
request_param_info('TestHeaderIntegerBooleanStringEnums', 'enum_nonref_string_header') ->
#{
source => header,
rules => [
{type, binary},
{enum, ['success', 'failure', 'unclassified'] },
not_required
]
};
request_param_info('TestHeaderIntegerBooleanStringEnums', 'enum_ref_string_header') ->
#{
source => header,
rules => [
not_required
]
};
request_param_info('TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}', 'path_string') ->
#{
source => binding,
rules => [
{type, binary},
required
]
};
request_param_info('TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}', 'path_integer') ->
#{
source => binding,
rules => [
{type, integer},
required
]
};
request_param_info('TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}', 'enum_nonref_string_path') ->
#{
source => binding,
rules => [
{type, binary},
{enum, ['success', 'failure', 'unclassified'] },
required
]
};
request_param_info('TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}', 'enum_ref_string_path') ->
#{
source => binding,
rules => [
required
]
};
request_param_info('TestEnumRefString', 'enum_nonref_string_query') ->
#{
source => qs_val,
rules => [
{type, binary},
{enum, ['success', 'failure', 'unclassified'] },
not_required
]
};
request_param_info('TestEnumRefString', 'enum_ref_string_query') ->
#{
source => qs_val,
rules => [
not_required
]
};
request_param_info('TestQueryDatetimeDateString', 'datetime_query') ->
#{
source => qs_val,
rules => [
{type, datetime},
not_required
]
};
request_param_info('TestQueryDatetimeDateString', 'date_query') ->
#{
source => qs_val,
rules => [
{type, date},
not_required
]
};
request_param_info('TestQueryDatetimeDateString', 'string_query') ->
#{
source => qs_val,
rules => [
{type, binary},
not_required
]
};
request_param_info('TestQueryIntegerBooleanString', 'integer_query') ->
#{
source => qs_val,
rules => [
{type, integer},
not_required
]
};
request_param_info('TestQueryIntegerBooleanString', 'boolean_query') ->
#{
source => qs_val,
rules => [
{type, boolean},
not_required
]
};
request_param_info('TestQueryIntegerBooleanString', 'string_query') ->
#{
source => qs_val,
rules => [
{type, binary},
not_required
]
};
request_param_info('TestQueryStyleDeepObjectExplodeTrueObject', 'query_object') ->
#{
source => qs_val,
rules => [
not_required
]
};
request_param_info('TestQueryStyleDeepObjectExplodeTrueObjectAllOf', 'query_object') ->
#{
source => qs_val,
rules => [
not_required
]
};
request_param_info('TestQueryStyleFormExplodeFalseArrayInteger', 'query_object') ->
#{
source => qs_val,
rules => [
not_required
]
};
request_param_info('TestQueryStyleFormExplodeFalseArrayString', 'query_object') ->
#{
source => qs_val,
rules => [
not_required
]
};
request_param_info('TestQueryStyleFormExplodeTrueArrayString', 'query_object') ->
#{
source => qs_val,
rules => [
not_required
]
};
request_param_info('TestQueryStyleFormExplodeTrueObject', 'query_object') ->
#{
source => qs_val,
rules => [
not_required
]
};
request_param_info('TestQueryStyleFormExplodeTrueObjectAllOf', 'query_object') ->
#{
source => qs_val,
rules => [
not_required
]
};
request_param_info(OperationID, Name) ->
error({unknown_param, OperationID, Name}).
-spec populate_request_params(
operation_id(), [request_param()], cowboy_req:req(), jesse_state:state(), map()) ->
{ok, map(), cowboy_req:req()} | {error, _, cowboy_req:req()}.
populate_request_params(_, [], Req, _, Model) ->
{ok, Model, Req};
populate_request_params(OperationID, [ReqParamName | T], Req0, ValidatorState, Model0) ->
case populate_request_param(OperationID, ReqParamName, Req0, ValidatorState) of
{ok, V, Req} ->
Model = maps:put(ReqParamName, V, Model0),
populate_request_params(OperationID, T, Req, ValidatorState, Model);
Error ->
Error
end.
-spec populate_request_param(
operation_id(), request_param(), cowboy_req:req(), jesse_state:state()) ->
{ok, term(), cowboy_req:req()} | {error, term(), cowboy_req:req()}.
populate_request_param(OperationID, ReqParamName, Req0, ValidatorState) ->
#{rules := Rules, source := Source} = request_param_info(OperationID, ReqParamName),
case get_value(Source, ReqParamName, Req0) of
{error, Reason, Req} ->
{error, Reason, Req};
{Value, Req} ->
case prepare_param(Rules, ReqParamName, Value, ValidatorState) of
{ok, Result} -> {ok, Result, Req};
{error, Reason} ->
{error, Reason, Req}
end
end.
-include_lib("kernel/include/logger.hrl").
validate_response_body(list, ReturnBaseType, Body, ValidatorState) ->
[
validate(schema, Item, ReturnBaseType, ValidatorState)
|| Item <- Body];
validate_response_body(_, ReturnBaseType, Body, ValidatorState) ->
validate(schema, Body, ReturnBaseType, ValidatorState).
-spec validate(rule(), term(), request_param(), jesse_state:state()) ->
ok | {ok, term()}.
validate(required, undefined, ReqParamName, _) ->
validation_error(required, ReqParamName, undefined);
validate(required, _Value, _, _) ->
ok;
validate(not_required, _Value, _, _) ->
ok;
validate(_, undefined, _, _) ->
ok;
validate({type, boolean}, Value, _, _) when is_boolean(Value) ->
ok;
validate({type, integer}, Value, _, _) when is_integer(Value) ->
ok;
validate({type, float}, Value, _, _) when is_float(Value) ->
ok;
validate({type, binary}, Value, _, _) when is_binary(Value) ->
ok;
validate({max, Max}, Value, _, _) when Value =< Max ->
ok;
validate({min, Min}, Value, _, _) when Min =< Value ->
ok;
validate({exclusive_max, Max}, Value, _, _) when Value < Max ->
ok;
validate({exclusive_min, Min}, Value, _, _) when Min < Value ->
ok;
validate({max_length, MaxLength}, Value, _, _) when is_binary(Value), byte_size(Value) =< MaxLength ->
ok;
validate({min_length, MinLength}, Value, _, _) when is_binary(Value), MinLength =< byte_size(Value) ->
ok;
validate(Rule = {type, byte}, Value, ReqParamName, _) when is_binary(Value) ->
try base64:decode(Value) of
Decoded -> {ok, Decoded}
catch error:_Error -> validation_error(Rule, ReqParamName, Value)
end;
validate(Rule = {type, boolean}, Value, ReqParamName, _) when is_binary(Value) ->
case to_binary(string:lowercase(Value)) of
<<"true">> -> {ok, true};
<<"false">> -> {ok, false};
_ -> validation_error(Rule, ReqParamName, Value)
end;
validate(Rule = {type, integer}, Value, ReqParamName, _) when is_binary(Value) ->
try
{ok, binary_to_integer(Value)}
catch
error:badarg ->
validation_error(Rule, ReqParamName, Value)
end;
validate(Rule = {type, float}, Value, ReqParamName, _) when is_binary(Value) ->
try
{ok, binary_to_float(Value)}
catch
error:badarg ->
validation_error(Rule, ReqParamName, Value)
end;
validate(Rule = {type, date}, Value, ReqParamName, _) ->
case is_binary(Value) of
true -> ok;
false -> validation_error(Rule, ReqParamName, Value)
end;
validate(Rule = {type, datetime}, Value, ReqParamName, _) ->
try calendar:rfc3339_to_system_time(binary_to_list(Value)) of
_ -> ok
catch error:_Error -> validation_error(Rule, ReqParamName, Value)
end;
validate(Rule = {enum, Values}, Value, ReqParamName, _) ->
try
FormattedValue = erlang:binary_to_existing_atom(Value, utf8),
case lists:member(FormattedValue, Values) of
true -> {ok, FormattedValue};
false -> validation_error(Rule, ReqParamName, Value)
end
catch
error:badarg ->
validation_error(Rule, ReqParamName, Value)
end;
validate(Rule = {pattern, Pattern}, Value, ReqParamName, _) ->
{ok, MP} = re:compile(Pattern),
case re:run(Value, MP) of
{match, _} -> ok;
_ -> validation_error(Rule, ReqParamName, Value)
end;
validate(Rule = schema, Value, ReqParamName, ValidatorState) ->
Definition = iolist_to_binary(["#/components/schemas/", atom_to_binary(ReqParamName, utf8)]),
try
_ = validate_with_schema(Value, Definition, ValidatorState),
ok
catch
throw:[{schema_invalid, _, Error} | _] ->
Info = #{
type => schema_invalid,
error => Error
},
validation_error(Rule, ReqParamName, Value, Info);
throw:[{data_invalid, Schema, Error, _, Path} | _] ->
Info = #{
type => data_invalid,
error => Error,
schema => Schema,
path => Path
},
validation_error(Rule, ReqParamName, Value, Info)
end;
validate(Rule, Value, ReqParamName, _) ->
validation_error(Rule, ReqParamName, Value).
-spec validation_error(rule(), request_param(), term()) -> no_return().
validation_error(ViolatedRule, Name, Value) ->
validation_error(ViolatedRule, Name, Value, #{}).
-spec validation_error(rule(), request_param(), term(), Info :: #{_ := _}) -> no_return().
validation_error(ViolatedRule, Name, Value, Info) ->
throw({wrong_param, Name, Value, ViolatedRule, Info}).
-spec get_value(body | qs_val | header | binding, request_param(), cowboy_req:req()) ->
{any(), cowboy_req:req()} |
{error, any(), cowboy_req:req()}.
get_value(body, _Name, Req0) ->
{ok, Body, Req} = read_entire_body(Req0),
case prepare_body(Body) of
{error, Reason} ->
{error, Reason, Req};
Value ->
{Value, Req}
end;
get_value(qs_val, Name, Req) ->
QS = cowboy_req:parse_qs(Req),
Value = get_opt(to_qs(Name), QS),
{Value, Req};
get_value(header, Name, Req) ->
Headers = cowboy_req:headers(Req),
Value = maps:get(to_header(Name), Headers, undefined),
{Value, Req};
get_value(binding, Name, Req) ->
Value = cowboy_req:binding(Name, Req),
{Value, Req}.
-spec read_entire_body(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}.
read_entire_body(Req) ->
read_entire_body(Req, []).
-spec read_entire_body(cowboy_req:req(), iodata()) -> {ok, binary(), cowboy_req:req()}.
read_entire_body(Request, Acc) -> % {
case cowboy_req:read_body(Request) of
{ok, Data, NewRequest} ->
{ok, iolist_to_binary(lists:reverse([Data | Acc])), NewRequest};
{more, Data, NewRequest} ->
read_entire_body(NewRequest, [Data | Acc])
end.
prepare_body(<<>>) ->
<<>>;
prepare_body(Body) ->
try
json:decode(Body)
catch
error:Error ->
{error, {invalid_json, Body, Error}}
end.
validate_with_schema(Body, Definition, ValidatorState) ->
jesse_schema_validator:validate_with_state(
[{<<"$ref">>, Definition}],
Body,
ValidatorState
).
-spec prepare_param([rule()], request_param(), term(), jesse_state:state()) ->
{ok, term()} | {error, Reason :: any()}.
prepare_param(Rules, ReqParamName, Value, ValidatorState) ->
Fun = fun(Rule, Acc) ->
case validate(Rule, Acc, ReqParamName, ValidatorState) of
ok -> Acc;
{ok, Prepared} -> Prepared
end
end,
try
Result = lists:foldl(Fun, Value, Rules),
{ok, Result}
catch
throw:Reason ->
{error, Reason}
end.
-spec to_binary(iodata()) -> binary().
to_binary(V) when is_binary(V) -> V;
to_binary(V) when is_list(V) -> iolist_to_binary(V).
-spec to_header(request_param()) -> binary().
to_header(Name) ->
to_binary(string:lowercase(atom_to_binary(Name, utf8))).
-spec to_qs(request_param()) -> binary().
to_qs(Name) ->
atom_to_binary(Name, utf8).
-spec get_opt(any(), []) -> any().
get_opt(Key, Opts) ->
get_opt(Key, Opts, undefined).
-spec get_opt(any(), [], any()) -> any().
get_opt(Key, Opts, Default) ->
case lists:keyfind(Key, 1, Opts) of
{_, Value} -> Value;
false -> Default
end.
get_openapi_path() ->
{ok, AppName} = application:get_application(?MODULE),
filename:join(priv_dir(AppName), "openapi.json").
-include_lib("kernel/include/file.hrl").
-spec priv_dir(Application :: atom()) -> file:name_all().
priv_dir(AppName) ->
case code:priv_dir(AppName) of
Value when is_list(Value) ->
Value ++ "/";
_Error ->
select_priv_dir([filename:join(["apps", atom_to_list(AppName), "priv"]), "priv"])
end.
select_priv_dir(Paths) ->
case lists:dropwhile(fun test_priv_dir/1, Paths) of
[Path | _] -> Path;
_ -> exit(no_priv_dir)
end.
test_priv_dir(Path) ->
case file:read_file_info(Path) of
{ok, #file_info{type = directory}} ->
false;
_ ->
true
end.