%%%-------------------------------------------------------------------
%% @doc Response Decoding Utils
%% @end
%% @since 3.0.0
%%%-------------------------------------------------------------------
-module(oidcc_decode_util).
-export([extract/3]).
-export([parse_setting_binary/2]).
-export([parse_setting_binary_list/2]).
-export([parse_setting_boolean/2]).
-export([parse_setting_list_enum/3]).
-export([parse_setting_number/2]).
-export([parse_setting_uri/2]).
-export([parse_setting_uri_https/2]).
-export_type([error/0]).
-type error() ::
{missing_config_property, Key :: atom()}
| {invalid_config_property, {
Type ::
uri
| uri_https
| binary
| number
| list_of_binaries
| boolean
| scopes_including_openid
| enum
| alg_no_none,
Field :: atom()
}}.
%% @private
-spec extract(
Map :: #{binary() => term()},
Keys :: [{required, Key, ParseFn} | {optional, Key, Default, ParseFn}],
Acc :: #{atom() => term()}
) ->
{ok, {Matched, Rest}} | {error, error()}
when
Key :: atom(),
Default :: term(),
ParseFn :: fun((Setting :: term(), Key) -> {ok, term()} | {error, error()}),
Matched :: #{Key => Default | undefined | term()},
Rest :: #{binary() => term()}.
extract(Map1, [{required, Key, ParseFn} | RestKeys], Acc) ->
case maps:take(atom_to_binary(Key), Map1) of
{Value, Map2} ->
case ParseFn(Value, Key) of
{ok, Parsed} ->
extract(Map2, RestKeys, maps:put(Key, Parsed, Acc));
{error, Reason} ->
{error, Reason}
end;
error ->
{error, {missing_config_property, Key}}
end;
extract(Map1, [{optional, Key, Default, ParseFn} | RestKeys], Acc) ->
case maps:take(atom_to_binary(Key), Map1) of
{Value, Map2} ->
case ParseFn(Value, Key) of
{ok, Parsed} ->
extract(Map2, RestKeys, maps:put(Key, Parsed, Acc));
{error, Reason} ->
{error, Reason}
end;
error ->
extract(Map1, RestKeys, maps:put(Key, Default, Acc))
end;
extract(Map, [], Acc) ->
{ok, {Acc, Map}}.
%% @private
-spec parse_setting_uri(Setting :: term(), Field :: atom()) ->
{ok, uri_string:uri_string()} | {error, error()}.
parse_setting_uri(Setting, _Field) when is_binary(Setting) ->
{ok, Setting};
parse_setting_uri(_Setting, Field) ->
{error, {invalid_config_property, {uri, Field}}}.
%% @private
-spec parse_setting_uri_https(Setting :: term(), Field :: atom()) ->
{ok, uri_string:uri_string()} | {error, error()}.
parse_setting_uri_https(Setting, Field) when is_binary(Setting) ->
case uri_string:parse(Setting) of
#{scheme := <<"https">>} ->
{ok, Setting};
#{scheme := _Scheme} ->
{error, {invalid_config_property, {uri_https, Field}}}
end;
parse_setting_uri_https(_Setting, Field) ->
{error, {invalid_config_property, {uri_https, Field}}}.
%% @private
-spec parse_setting_binary(Setting :: term(), Field :: atom()) ->
{ok, binary()} | {error, error()}.
parse_setting_binary(Setting, _Field) when is_binary(Setting) ->
{ok, Setting};
parse_setting_binary(_Setting, Field) ->
{error, {invalid_config_property, {binary, Field}}}.
%% @private
-spec parse_setting_binary_list(Setting :: term(), Field :: atom()) ->
{ok, [binary()]} | {error, error()}.
parse_setting_binary_list(Setting, Field) when is_list(Setting) ->
case lists:all(fun is_binary/1, Setting) of
true ->
{ok, Setting};
false ->
{error, {invalid_config_property, {list_of_binaries, Field}}}
end;
parse_setting_binary_list(_Setting, Field) ->
{error, {invalid_config_property, {list_of_binaries, Field}}}.
%% @private
-spec parse_setting_number(Setting :: term(), Field :: atom()) ->
{ok, integer()} | {error, error()}.
parse_setting_number(Setting, _Field) when is_integer(Setting) ->
{ok, Setting};
parse_setting_number(_Setting, Field) ->
{error, {invalid_config_property, {number, Field}}}.
%% @private
-spec parse_setting_boolean(Setting :: term(), Field :: atom()) ->
{ok, boolean()} | {error, error()}.
parse_setting_boolean(Setting, _Field) when is_boolean(Setting) ->
{ok, Setting};
parse_setting_boolean(_Setting, Field) ->
{error, {invalid_config_property, {boolean, Field}}}.
%% @private
-spec parse_setting_list_enum(
Setting :: term(),
Field :: atom(),
Parse :: fun((binary()) -> {ok, Value} | error)
) ->
{ok, [Value]} | {error, error()}
when
Value :: term().
parse_setting_list_enum(Setting, Field, Parse) ->
case parse_setting_binary_list(Setting, Field) of
{ok, Values} ->
Parsed =
lists:map(
fun(Value) ->
case Parse(Value) of
{ok, ParsedValue} ->
{ok, ParsedValue};
error ->
{error, Value}
end
end,
Values
),
case
lists:filter(
fun
({ok, _Value}) ->
false;
({error, _Value}) ->
true
end,
Parsed
)
of
[] ->
{ok, lists:map(fun({ok, Value}) -> Value end, Parsed)};
[{error, _InvalidValue} | _Rest] ->
{error, {invalid_config_property, {enum, Field}}}
end;
{error, Reason} ->
{error, Reason}
end.