Created
April 23, 2025 08:09
-
-
Save maxlapshin/35589b30cc9b13a08cbef84d188b2311 to your computer and use it in GitHub Desktop.
openapi
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-module(c2_schema). | |
-include_lib("kernel/include/logger.hrl"). | |
-export([json2config/1, json2config/2]). | |
-record(ctx, { | |
drop_unknown, | |
fill_defaults, | |
allow_miss_mandatory, | |
allow_forced_undefined, | |
validate, | |
add_compat, | |
records = #{}, | |
validators = #{} | |
}). | |
-type options() :: #{ | |
drop_unknown => boolean(), | |
add_position => boolean(), | |
validate => boolean(), | |
type_name => atom(), | |
add_compat => boolean(), | |
allow_miss_mandatory => boolean(), | |
allow_forced_undefined => boolean(), | |
fill_defaults => boolean(), | |
_ => _ | |
}. | |
-spec json2config(config2:json()|tuple()) -> config2:config() | {error, config2:error()}. | |
json2config(Object) -> | |
json2config(Object, #{}). | |
-spec json2config(config2:json()|tuple(), options()) -> config2:config() | {error, config2:error()}. | |
json2config(Object, #{} = Options) -> | |
RootType = maps:get(type_name, Options, server_config), | |
Ctx = #ctx{ | |
drop_unknown = maps:get(drop_unknown, Options, false), | |
fill_defaults = maps:get(fill_defaults, Options, false), | |
allow_miss_mandatory = maps:get(allow_miss_mandatory, Options, false), | |
allow_forced_undefined = maps:get(allow_forced_undefined, Options, false), | |
validate = maps:get(validate, Options, true), | |
add_compat = maps:get(add_compat, Options, false), | |
records = maps:get(records, Options, #{}), | |
validators = (server_config:validators())#{ | |
hexbinary => fun server_config:hexbinary/1, | |
query_session_key => fun server_config:session_key_query/1, | |
cidr => fun server_config:cidr/1, | |
url => fun server_config:url/1, | |
auth_url => fun server_config:auth_url/1, | |
agent_url => fun server_config:agent_url/1, | |
input_url => fun server_config:input_url/1, | |
push_url => fun server_config:push_url/1, | |
dvr_url => fun server_config:disk_path/1 | |
} | |
}, | |
Result1 = visit(Ctx, Object, config2_schema:schema(RootType), [RootType]), | |
AddPosition = maps:get(add_position, Options, false), | |
Result2 = case AddPosition of | |
true -> | |
NewResult1 = enumerate(vods, Result1), | |
enumerate(streams, NewResult1); | |
false -> | |
Result1 | |
end, | |
Result2. | |
visit(#ctx{records = Records} = Ctx, Object0, #{type := object} = Spec, Path) -> | |
Object = case Object0 of | |
#{} -> Object0; | |
_ when is_tuple(Object0) andalso is_map_key(element(1,Object0),Records) -> record2json(Ctx,Object0); | |
_ -> Object0 | |
end, | |
Nullable = maps:get(nullable, Spec, false), | |
case Object of | |
#{} -> | |
add_compat(Ctx, visit_object(Ctx, Object, Spec, Path), Spec); | |
_ when (Object == undefined orelse Object == null) andalso | |
(Ctx#ctx.allow_forced_undefined orelse Nullable) -> undefined; | |
_ when is_tuple(Object) -> | |
{error, #{error => non_converted_record, path => Path, what => element(1,Object)}}; | |
_ -> | |
{error, #{error => non_map_input, path => Path, what => Object}} | |
end; | |
visit(Ctx, Value, #{anyOf := Types}, Path) -> | |
visit_anyOf(Ctx, Value, Types, Path); | |
visit(#ctx{allow_miss_mandatory = true}, undefined, #{oneOf := _}, _Path) -> | |
undefined; | |
visit(Ctx, Value, #{oneOf := Types, discriminator := #{propertyName := DKey, mapping := DMap}}, Path) when is_map(Value) orelse is_tuple(Value) -> | |
%% Загадочно. Это делалось для того, что в зависимости от контента в track_info оставлялись разные поля. | |
%% В api_v3_handler:postprocess приходит уже провалидированная и отфильтрованная структура media_info в виде мапа (видно, что там уже нет pix_fmt) | |
%% Но до этой функции (visit) доходит опять рекорд #track_info{}, и вся фильтрация-валидация делается ещё раз, но другим кодом. | |
%% FIXME: разобраться в том, когда какой код вызывается на самом деле, и как можно убрать дублирующуюся логику. | |
% Приведём дискриминатор в окончательный вид, для этого найдем значение | |
% дискриминатора по ключу (или по ключу в бинарном виде) в Value и | |
% прогоним это значение через один из типов. На выходе получим дискриминатор | |
% в обработанном виде и по нему найдём верный тип в mapping | |
BinDKey = is_atom(DKey) andalso atom_to_binary(DKey), | |
Try0 = case Value of | |
#{DKey := Undef} when Undef == undefined orelse Undef == null -> | |
visit(Ctx#ctx{fill_defaults = true, validate = false}, #{}, hd(Types), Path); | |
#{<<BinDKey/binary>> := Undef} when Undef == undefined orelse Undef == null -> | |
visit(Ctx#ctx{fill_defaults = true, validate = false}, #{}, hd(Types), Path); | |
#{DKey := InDValue} -> | |
visit(Ctx#ctx{fill_defaults = true, validate = false}, #{DKey => InDValue}, hd(Types), Path); | |
#{<<BinDKey/binary>> := InDValue} -> | |
visit(Ctx#ctx{fill_defaults = true, validate = false}, #{DKey => InDValue}, hd(Types), Path); | |
#{} -> % в этом месте, попробуем подобрать дефолт для discriminator, когда в описании нет propertyName | |
visit(Ctx#ctx{fill_defaults = true, validate = false}, Value, hd(Types), Path); | |
_ -> | |
% Старое поведение. Сюда попадают Value = {track_info, ...} | |
% Это только ради приведения к мапу, в котором можно искать ключ-дискриминатор | |
visit_anyOf(Ctx, Value, Types, Path) | |
end, | |
case Try0 of | |
#{DKey := DValue} -> | |
case DMap of | |
#{DValue := DType} -> | |
visit(Ctx, Value, #{'$ref' => DType}, Path); | |
_ -> | |
{error, #{error => discriminator_unmapped, path => Path, what => DValue}} | |
end; | |
{error, _} = Error -> | |
Error; | |
_ -> | |
{error, #{error => discriminator_missing, path => Path, what => DKey}} | |
end; | |
visit(Ctx, Value, #{oneOf := Types, 'x-pattern-discriminator' := BinDKey} = Spec, Path) -> | |
DKey = binary_to_atom(BinDKey, latin1), | |
case Value of | |
#{DKey := InDValue} -> | |
visit_anyOf_with_pattern_discriminator(Ctx, Value, Types, Path, DKey, BinDKey, InDValue); | |
#{<<BinDKey/binary>> := InDValue} -> | |
visit_anyOf_with_pattern_discriminator(Ctx, Value, Types, Path, DKey, BinDKey, InDValue); | |
% Если нет данных по 'x-pattern-discriminator', то тут 2 варианта, либо забыли указать, либо это возможны специальные сервисные данные(delta) | |
% oneOf без `x-pattern-discriminator` либо вернет, что discriminator required(так как в схеме он должен быть required), либо пройдет без ошибок | |
_ -> | |
visit(Ctx, Value, maps:remove('x-pattern-discriminator', Spec), Path) | |
end; | |
visit(Ctx, Value, #{oneOf := Types}, Path) -> | |
visit_anyOf(Ctx, Value, Types, Path); | |
visit(Ctx, Object, #{'$ref' := <<"#/components/schemas/",RefType/binary>>} = Spec, Path) -> | |
Type = binary_to_atom(RefType,latin1), | |
Spec1 = maps:merge(maps:with([nullable],Spec),config2_schema:schema(Type)), | |
% если на ref_type есть валидатор - запустим его | |
case Ctx#ctx.validators of | |
_ when (Object == undefined orelse Object == null) andalso Ctx#ctx.allow_forced_undefined -> | |
visit(Ctx, Object, Spec1, Path); | |
#{Type := Validator} -> | |
case Validator(Object) of | |
{ok, Object1} -> visit(Ctx, Object1, Spec1, Path); | |
{error, E} -> {error, E#{path => Path}} | |
end; | |
_ -> | |
visit(Ctx, Object, Spec1, Path) | |
end; | |
visit(Ctx, Value, #{enum := Values, type := string}, Path) -> | |
case [V || V <- Values, V == Value orelse atom_to_binary(V,latin1) == Value] of | |
[V1] -> V1; | |
[] when (Value == undefined orelse Value == null) andalso Ctx#ctx.allow_forced_undefined -> undefined; | |
[] when is_binary(Value); is_atom(Value) -> {error, #{path => Path, error => unenumerated_value, what => Value}}; | |
[] -> {error, #{path => Path, error => unenumerated_value}} | |
end; | |
visit(Ctx, Value, #{enum := Values, type := integer} = Spec, Path) -> | |
Nullable = maps:get(nullable, Spec, false), | |
case lists:member(Value, Values) of | |
true -> Value; | |
false when (Value == undefined orelse Value == null) andalso | |
(Ctx#ctx.allow_forced_undefined orelse Nullable) -> undefined; | |
false -> {error, #{path => Path, error => unenumerated_value}} | |
end; | |
visit(Ctx, List, #{type := array, items := Spec} = ArraySpec, Path) -> | |
Nullable = maps:get(nullable, Spec, false), | |
MaxItems = maps:get(maxItems, ArraySpec, undefined), | |
case List of | |
_ when is_list(List) andalso is_integer(MaxItems) andalso length(List) > MaxItems -> | |
{error, #{path => Path, error => too_many_items}}; | |
_ when is_list(List) -> | |
visit_array(Ctx, List, Spec, Path, []); | |
_ when (List == undefined orelse List == null) andalso | |
(Ctx#ctx.allow_forced_undefined orelse Nullable) -> | |
undefined; | |
_ -> | |
{error, #{path => Path, error => non_list}} | |
end; | |
visit(Ctx, Int, #{type := integer} = Spec, Path) -> | |
Min = maps:get(minimum,Spec,undefined), | |
Max = maps:get(maximum,Spec,undefined), | |
Nullable = maps:get(nullable, Spec, false), | |
case Int of | |
_ when Int - Min < 0 -> {error, #{path => Path, error => too_small, what => Int}}; | |
_ when Int - Max > 0 -> {error, #{path => Path, error => too_big, what => Int}}; | |
_ when is_integer(Int) -> Int; | |
_ when (Int == undefined orelse Int == null) andalso | |
(Ctx#ctx.allow_forced_undefined orelse Nullable) -> undefined; | |
_ -> {error, #{path => Path, error => non_integer, what => Int}} | |
end; | |
visit(Ctx, Num, #{type := number} = Spec, Path) -> | |
Min = maps:get(minimum,Spec,undefined), | |
Max = maps:get(maximum,Spec,undefined), | |
Nullable = maps:get(nullable, Spec, false), | |
case Num of | |
_ when Num - Min < 0 -> {error, #{path => Path, error => too_small, what => Num}}; | |
_ when Num - Max > 0 -> {error, #{path => Path, error => too_big, what => Num}}; | |
_ when is_integer(Num) -> Num; | |
_ when is_number(Num) -> Num; | |
_ when (Num == undefined orelse Num == null) andalso | |
(Ctx#ctx.allow_forced_undefined orelse Nullable) -> undefined; | |
_ -> {error, #{path => Path, error => non_number}} | |
end; | |
visit(Ctx, Bool, #{type := boolean} = Spec, Path) -> | |
Nullable = maps:get(nullable, Spec, false), | |
case Bool of | |
true -> true; | |
false -> false; | |
_ when (Bool == undefined orelse Bool == null) andalso | |
(Ctx#ctx.allow_forced_undefined orelse Nullable) -> undefined; | |
_ -> {error, #{path => Path, error => non_boolean_value, what => Bool}} | |
end; | |
visit(_Ctx, Value, #{const := Value}, _Path) -> | |
Value; | |
visit(Ctx, Str, #{const := Value}, Path) when is_atom(Value) -> | |
case atom_to_binary(Value,latin1) of | |
Str -> Value; | |
_ when (Str == undefined orelse Str == null) andalso Ctx#ctx.allow_forced_undefined -> undefined; | |
_ when is_binary(Str); is_atom(Str) -> {error, #{path => Path, error => unenumerated_value, what => Str}}; | |
_ -> {error, #{path => Path, error => unenumerated_value}} | |
end; | |
visit(Ctx, Str, #{const := Value}, Path) when is_integer(Value) -> | |
case integer_to_binary(Value) of | |
Str -> Str; | |
_ when (Str == undefined orelse Str == null) andalso Ctx#ctx.allow_forced_undefined -> undefined; | |
_ when is_binary(Str); is_atom(Str) -> {error, #{path => Path, error => unenumerated_value, what => Str}}; | |
_ -> {error, #{path => Path, error => unenumerated_value}} | |
end; | |
visit(_Ctx, Str, #{type := atom} = Spec, _Path) -> | |
Nullable = maps:get(nullable, Spec, false), | |
case Str of | |
_ when is_binary(Str) -> binary_to_atom(Str,latin1); | |
undefined when Nullable -> undefined; | |
undefined when not Nullable -> {error, #{error => non_atom_value, what => Str}}; | |
_ when is_atom(Str) -> Str; | |
_ -> {error, #{error => non_atom_value, what => Str}} | |
end; | |
visit(Ctx = #ctx{validators = Validators}, Str, #{type := string} = Spec, Path) -> | |
Format = maps:get(format, Spec, undefined), | |
Pattern = maps:get(pattern, Spec, undefined), | |
Validator = maps:get(Format, Validators, undefined), | |
Nullable = maps:get(nullable, Spec, false), | |
MinLength = maps:get(minLength, Spec, undefined), | |
MaxLength = maps:get(maxLength, Spec, undefined), | |
Type = case Spec of | |
#{'x-atom' := true} -> atom; | |
_ -> binary | |
end, | |
Str1 = case Type of | |
atom when is_binary(Str) -> binary_to_atom(Str,latin1); | |
binary when is_atom(Str) -> atom_to_binary(Str,latin1); | |
_ -> Str | |
end, | |
try | |
case Str of | |
_ when (Str == undefined orelse Str == null) andalso | |
(Ctx#ctx.allow_forced_undefined orelse Nullable) -> throw({ok, undefined}); | |
_ -> | |
ok | |
end, | |
Str2 = case Str1 of | |
_ when is_binary(Str1) andalso Validator =/= undefined -> | |
case Validator(Str1) of | |
{ok, Str1_} -> Str1_; | |
{error, E} -> throw({error, E#{path => Path}}) | |
end; | |
_ -> | |
Str1 | |
end, | |
case Str2 of | |
_ when is_binary(Str2) andalso Pattern =/= undefined -> | |
case re:run(Str2, Pattern) of | |
{match, _} -> Str2; | |
nomatch -> throw({error, #{error => nomatch_pattern, path => Path}}) | |
end; | |
_ -> | |
ok | |
end, | |
case Str2 of | |
_ when is_binary(Str2) andalso is_integer(MinLength) andalso size(Str2) < MinLength -> | |
throw({error, #{error => too_short, path => Path}}); | |
_ -> | |
ok | |
end, | |
case Str2 of | |
_ when is_binary(Str2) andalso is_integer(MaxLength) andalso size(Str2) > MaxLength -> | |
throw({error, #{error => too_long, path => Path}}); | |
_ -> | |
ok | |
end, | |
case Str2 of | |
_ when is_binary(Str2) andalso Type == binary -> | |
Str2; | |
_ when is_atom(Str2) andalso Type == atom -> | |
Str2; | |
_ -> | |
{error, #{path => Path, error => non_binary, what => Str, spec => Spec}} | |
end | |
catch | |
throw:{ok, Result} -> | |
Result; | |
throw:{error, E0} -> | |
{error, E0} | |
end; | |
visit(_Ctx, Any, #{} = Spec, _Path) when not is_map_key(type,Spec) -> | |
Any. | |
visit_object(Ctx, Object, #{properties := Props} = ObjectSpec, Path) -> | |
KeysProps = [begin | |
KeyA = case Key of | |
_ when is_binary(Key) -> binary_to_atom(Key,latin1); | |
_ -> Key | |
end, | |
case Props of | |
#{Key := S} -> {Key,Value,S}; | |
#{KeyA := S} -> {KeyA,Value,S}; | |
_ -> {Key,Value,undefined} | |
end | |
end || {Key,Value} <- maps:to_list(Object)], | |
Default = case Ctx#ctx.fill_defaults of | |
true -> | |
Prefilled = lists:flatmap(fun({K,Spec}) -> | |
XDefault = case maps:get('x-default',Spec,undefined) of | |
undefined -> undefined; | |
#{'$ref' := _} -> #{} | |
end, | |
V = maps:get(default,Spec,XDefault), | |
K_bin = atom_to_binary(K,latin1), | |
case Object of | |
#{K := _} -> []; | |
#{K_bin := _} -> []; | |
#{} when V =/= undefined -> [{K,V}]; | |
_ -> [] | |
end | |
end, maps:to_list(Props)), | |
maps:from_list(Prefilled); | |
false -> | |
#{} | |
end, | |
case visit_object_fields(Ctx, KeysProps, Default, Path) of | |
{error, E} -> | |
{error, E}; | |
#{} = NewObject when Ctx#ctx.validate == false -> | |
NewObject; | |
#{} = NewObject when Ctx#ctx.allow_miss_mandatory == true -> | |
NewObject; | |
#{} = NewObject -> | |
RequiredProps = maps:get(required, ObjectSpec, []), | |
Missing = [K || K <- RequiredProps, not maps:is_key(K,NewObject)], | |
case Missing of | |
[] -> | |
NewObject; | |
[_|_] -> | |
{error, #{error => lacks_mandatory, path => Path, what => Missing}} | |
end | |
end; | |
visit_object(_Ctx, Object, #{additionalProperties := #{}, maxItems := MaxItems}, Path) when map_size(Object) > MaxItems -> | |
{error, #{error => too_many_items, path => Path}}; | |
visit_object(Ctx, Object, #{additionalProperties := #{} = Spec} = FullSpec, Path) -> | |
KeyTypeSpec = case FullSpec of | |
#{'x-key-type' := <<"string">>} -> | |
#{type => string}; | |
#{'x-key-type' := Other} -> | |
case config2_schema:schema(binary_to_atom(Other,latin1)) of | |
undefined -> #{type => string}; | |
#{} = OtherSpec -> OtherSpec | |
end; | |
#{} -> | |
#{type => atom} | |
end, | |
KeysProps = [begin | |
Key1 = case FullSpec of | |
% Этот хак только для #27111 для случая, когда мы удачно спроектировали | |
% число как ключ для объекта, что недопустимо в жсоне | |
#{'x-key-type' := <<"network_port">>} when is_binary(Key) -> | |
case c2_reader:to_int(Key) of | |
{ok, _, IntVal} -> IntVal; | |
_ -> Key | |
end; | |
_ -> | |
Key | |
end, | |
{visit(Ctx, Key1, KeyTypeSpec, Path),Value,Spec} | |
end || {Key,Value} <- maps:to_list(Object)], | |
visit_object_fields(Ctx, KeysProps, #{}, Path); | |
visit_object(_Ctx, #{} = Object, #{}, _Path) -> | |
Object. | |
visit_object_fields(_Ctx, [{{error, E},_Value,_Spec}|_], _, _Path) -> | |
{error, E}; | |
visit_object_fields(Ctx, [{Key,Value,Spec}|Keys], Object, Path) -> | |
case Spec of | |
undefined when (Key == '$delete' orelse Key == <<"$delete">>) andalso Value == true -> | |
visit_object_fields(Ctx, Keys, Object#{to_a(Key) => Value}, Path); | |
undefined when (Key == '$reset' orelse Key == <<"$reset">>) andalso Value == true -> | |
visit_object_fields(Ctx, Keys, Object#{to_a(Key) => Value}, Path); | |
undefined when (Key == '$index' orelse Key == <<"$index">>) andalso is_integer(Value) -> | |
visit_object_fields(Ctx, Keys, Object#{to_a(Key) => Value}, Path); | |
undefined when (Key == '$position' orelse Key == <<"$position">>) andalso is_integer(Value) -> | |
visit_object_fields(Ctx, Keys, Object#{to_a(Key) => Value}, Path); | |
undefined when Ctx#ctx.drop_unknown -> | |
visit_object_fields(Ctx, Keys, Object, Path); | |
undefined when Ctx#ctx.allow_forced_undefined andalso (Value =:= undefined orelse Value =:= null) -> | |
visit_object_fields(Ctx, Keys, Object#{to_a(Key) => undefined}, Path); | |
undefined when not Ctx#ctx.drop_unknown -> | |
{error, #{error => unknown_key, path => Path ++ [Key]}}; | |
#{} -> | |
case visit(Ctx, Value, Spec, Path ++ [Key]) of | |
{error, E} -> | |
{error, E}; | |
% FIXME: Буду рад предложениям, как обойтись без такого хака | |
Value2 when Key == session_keys -> | |
case server_config:session_key(Value2) of | |
{ok, Value3} -> | |
visit_object_fields(Ctx, Keys, Object#{Key => Value3}, Path); | |
{error, E2} -> | |
{error, E2} | |
end; | |
Value2 -> | |
visit_object_fields(Ctx, Keys, Object#{Key => Value2}, Path) | |
end | |
end; | |
visit_object_fields(_Ctx, [], Object, _) -> | |
Object. | |
to_a(Bin) when is_binary(Bin) -> binary_to_atom(Bin,latin1); | |
to_a(A) when is_atom(A) -> A. | |
visit_array(Ctx, [Value|List], Spec, Path, Acc) -> | |
case visit(Ctx, Value, Spec, Path ++ [length(Acc)]) of | |
{error, E} -> | |
{error, E}; | |
Value1 -> | |
visit_array(Ctx, List, Spec, Path, [Value1|Acc]) | |
end; | |
visit_array(_Ctx, [], _, _, Acc) -> | |
lists:reverse(Acc). | |
visit_anyOf(Ctx, Value, [Spec|Types], Path) -> | |
case visit(Ctx, Value, Spec, Path) of | |
{error, E} -> | |
% Эта хитрость здесь для того, чтобы не брать ошибки от энума, а брать от следующих | |
% То, что что-то не попало в энум означает, что нам следующие ошибки интереснее | |
IsEnum = case Spec of | |
#{enum := _} -> true; | |
#{oneOf := [#{const := _}|_]} -> true; | |
_ -> false | |
end, | |
case visit_anyOf(Ctx, Value, Types, Path) of | |
{error, E1} when IsEnum -> {error, E1#{what => Value}}; | |
{error, _} when is_binary(Value) -> {error, E#{what => Value}}; | |
{error, _} -> {error, E}; | |
Value1 -> Value1 | |
end; | |
Value2 -> | |
Value2 | |
end; | |
visit_anyOf(_Ctx, _, [], Path) -> | |
{error, #{error => unmatched_type, path => Path}}. | |
visit_anyOf_with_pattern_discriminator(Ctx, Value, [Spec|Types], Path, DKey, BinDKey, DValue) -> | |
case visit(Ctx, #{DKey => DValue}, Spec, Path) of | |
#{DKey := _} -> | |
visit(Ctx, Value, Spec, Path); | |
{error, #{error := nomatch_pattern}} -> | |
visit_anyOf_with_pattern_discriminator(Ctx, Value, Types, Path, DKey, BinDKey, DValue); | |
Err -> | |
Err | |
end; | |
visit_anyOf_with_pattern_discriminator(_Ctx, _, [], Path, DKey, BinDKey, DValue) -> | |
{error, #{error => <<"unmatched_", BinDKey/binary>>, path => Path, matched_by => DKey, what => DValue}}. | |
enumerate(Key, Config) -> | |
case Config of | |
#{Key := Streams} when is_list(Streams) -> | |
Streams1 = lists:zipwith(fun(I,S) -> S#{position => I} end, | |
lists:seq(0,length(Streams)-1), Streams), | |
Config#{Key => Streams1}; | |
_ -> | |
Config | |
end. | |
add_compat(#ctx{add_compat = AddCompat}, #{} = Object, #{'x-compat-map' := OldNew}) -> | |
Object2 = lists:foldl(fun({Old,New}, R) -> | |
case R of | |
#{New := Value} when AddCompat -> R#{Old => Value}; | |
#{New := _} -> maps:remove(Old,R); | |
#{Old := Value} when AddCompat -> R#{New => Value}; | |
#{Old := Value} -> (maps:remove(Old,R))#{New => Value}; | |
#{} -> R | |
end | |
end, Object, maps:to_list(OldNew)), | |
Object2; | |
add_compat(_Ctx, Else, _Spec) -> | |
Else. | |
record2json(#ctx{records = Records},Tuple) -> | |
Type = element(1,Tuple), | |
Keys = maps:get(Type, Records), | |
Values = lists:sublist(tl(tuple_to_list(Tuple)),length(Keys)), | |
case config2_schema:schema(Type) of | |
#{properties := Props} -> | |
maps:from_list([{K,V} || {K,V} <- lists:zip(Keys,Values), V =/= undefined, is_map_key(K,Props)]); | |
#{discriminator := #{propertyName := DKey, mapping := Mapping}} -> | |
%% Нетривиальный случай -- в зависимости от content разные наборы полей имеют значение. | |
%% В схеме это описано через дискриминатор с сопоставлением значения нужному типу | |
J0 = maps:from_list([{K,V} || {K,V} <- lists:zip(Keys,Values), V =/= undefined]), | |
DValue = maps:get(DKey, J0), | |
<<"#/components/schemas/", DType/binary>> = maps:get(DValue, Mapping), | |
#{properties := Props} = config2_schema:schema(binary_to_atom(DType)), | |
maps:with(maps:keys(Props), J0) | |
end. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment