|
@@ -11,20 +11,32 @@
|
|
|
|
|
|
|
|
%% API
|
|
%% API
|
|
|
-export([
|
|
-export([
|
|
|
- decode/2,
|
|
|
|
|
- decode/3,
|
|
|
|
|
- encode/2,
|
|
|
|
|
- encode/3,
|
|
|
|
|
make_serde/3,
|
|
make_serde/3,
|
|
|
handle_rule_function/2,
|
|
handle_rule_function/2,
|
|
|
destroy/1
|
|
destroy/1
|
|
|
]).
|
|
]).
|
|
|
|
|
|
|
|
|
|
+%% Tests
|
|
|
-export([
|
|
-export([
|
|
|
|
|
+ decode/2,
|
|
|
|
|
+ decode/3,
|
|
|
|
|
+ encode/2,
|
|
|
|
|
+ encode/3,
|
|
|
eval_decode/2,
|
|
eval_decode/2,
|
|
|
eval_encode/2
|
|
eval_encode/2
|
|
|
]).
|
|
]).
|
|
|
|
|
|
|
|
|
|
+-define(BOOL(SerdeName, EXPR),
|
|
|
|
|
+ try
|
|
|
|
|
+ _ = EXPR,
|
|
|
|
|
+ true
|
|
|
|
|
+ catch
|
|
|
|
|
+ error:Reason ->
|
|
|
|
|
+ ?SLOG(debug, #{msg => "schema_check_failed", schema => SerdeName, reason => Reason}),
|
|
|
|
|
+ false
|
|
|
|
|
+ end
|
|
|
|
|
+).
|
|
|
|
|
+
|
|
|
%%------------------------------------------------------------------------------
|
|
%%------------------------------------------------------------------------------
|
|
|
%% API
|
|
%% API
|
|
|
%%------------------------------------------------------------------------------
|
|
%%------------------------------------------------------------------------------
|
|
@@ -40,10 +52,6 @@ handle_rule_function(sparkplug_decode, [Data | MoreArgs]) ->
|
|
|
schema_decode,
|
|
schema_decode,
|
|
|
[?EMQX_SCHEMA_REGISTRY_SPARKPLUGB_SCHEMA_NAME, Data | MoreArgs]
|
|
[?EMQX_SCHEMA_REGISTRY_SPARKPLUGB_SCHEMA_NAME, Data | MoreArgs]
|
|
|
);
|
|
);
|
|
|
-handle_rule_function(schema_decode, [SchemaId, Data | MoreArgs]) ->
|
|
|
|
|
- decode(SchemaId, Data, MoreArgs);
|
|
|
|
|
-handle_rule_function(schema_decode, Args) ->
|
|
|
|
|
- error({args_count_error, {schema_decode, Args}});
|
|
|
|
|
handle_rule_function(sparkplug_encode, [Term]) ->
|
|
handle_rule_function(sparkplug_encode, [Term]) ->
|
|
|
handle_rule_function(
|
|
handle_rule_function(
|
|
|
schema_encode,
|
|
schema_encode,
|
|
@@ -54,6 +62,10 @@ handle_rule_function(sparkplug_encode, [Term | MoreArgs]) ->
|
|
|
schema_encode,
|
|
schema_encode,
|
|
|
[?EMQX_SCHEMA_REGISTRY_SPARKPLUGB_SCHEMA_NAME, Term | MoreArgs]
|
|
[?EMQX_SCHEMA_REGISTRY_SPARKPLUGB_SCHEMA_NAME, Term | MoreArgs]
|
|
|
);
|
|
);
|
|
|
|
|
+handle_rule_function(schema_decode, [SchemaId, Data | MoreArgs]) ->
|
|
|
|
|
+ decode(SchemaId, Data, MoreArgs);
|
|
|
|
|
+handle_rule_function(schema_decode, Args) ->
|
|
|
|
|
+ error({args_count_error, {schema_decode, Args}});
|
|
|
handle_rule_function(schema_encode, [SchemaId, Term | MoreArgs]) ->
|
|
handle_rule_function(schema_encode, [SchemaId, Term | MoreArgs]) ->
|
|
|
%% encode outputs iolists, but when the rule actions process those
|
|
%% encode outputs iolists, but when the rule actions process those
|
|
|
%% it might wrongly encode them as JSON lists, so we force them to
|
|
%% it might wrongly encode them as JSON lists, so we force them to
|
|
@@ -62,33 +74,56 @@ handle_rule_function(schema_encode, [SchemaId, Term | MoreArgs]) ->
|
|
|
iolist_to_binary(IOList);
|
|
iolist_to_binary(IOList);
|
|
|
handle_rule_function(schema_encode, Args) ->
|
|
handle_rule_function(schema_encode, Args) ->
|
|
|
error({args_count_error, {schema_encode, Args}});
|
|
error({args_count_error, {schema_encode, Args}});
|
|
|
|
|
+handle_rule_function(schema_check, [SchemaId, Data | MoreArgs]) ->
|
|
|
|
|
+ schema_check(SchemaId, Data, MoreArgs);
|
|
|
handle_rule_function(_, _) ->
|
|
handle_rule_function(_, _) ->
|
|
|
{error, no_match_for_function}.
|
|
{error, no_match_for_function}.
|
|
|
|
|
|
|
|
|
|
+-spec schema_check(schema_name(), decoded_data() | encoded_data(), [term()]) -> decoded_data().
|
|
|
|
|
+schema_check(SerdeName, Data, VarArgs) when is_list(VarArgs), is_binary(Data) ->
|
|
|
|
|
+ with_serde(
|
|
|
|
|
+ SerdeName,
|
|
|
|
|
+ fun(Serde) ->
|
|
|
|
|
+ ?BOOL(SerdeName, eval_decode(Serde, [Data | VarArgs]))
|
|
|
|
|
+ end
|
|
|
|
|
+ );
|
|
|
|
|
+schema_check(SerdeName, Data, VarArgs) when is_list(VarArgs), is_map(Data) ->
|
|
|
|
|
+ with_serde(
|
|
|
|
|
+ SerdeName,
|
|
|
|
|
+ fun(Serde) ->
|
|
|
|
|
+ ?BOOL(SerdeName, eval_encode(Serde, [Data | VarArgs]))
|
|
|
|
|
+ end
|
|
|
|
|
+ ).
|
|
|
|
|
+
|
|
|
-spec decode(schema_name(), encoded_data()) -> decoded_data().
|
|
-spec decode(schema_name(), encoded_data()) -> decoded_data().
|
|
|
decode(SerdeName, RawData) ->
|
|
decode(SerdeName, RawData) ->
|
|
|
decode(SerdeName, RawData, []).
|
|
decode(SerdeName, RawData, []).
|
|
|
|
|
|
|
|
-spec decode(schema_name(), encoded_data(), [term()]) -> decoded_data().
|
|
-spec decode(schema_name(), encoded_data(), [term()]) -> decoded_data().
|
|
|
decode(SerdeName, RawData, VarArgs) when is_list(VarArgs) ->
|
|
decode(SerdeName, RawData, VarArgs) when is_list(VarArgs) ->
|
|
|
- case emqx_schema_registry:get_serde(SerdeName) of
|
|
|
|
|
- {error, not_found} ->
|
|
|
|
|
- error({serde_not_found, SerdeName});
|
|
|
|
|
- {ok, Serde} ->
|
|
|
|
|
- eval_decode(Serde, [RawData | VarArgs])
|
|
|
|
|
- end.
|
|
|
|
|
|
|
+ with_serde(SerdeName, fun(Serde) ->
|
|
|
|
|
+ eval_decode(Serde, [RawData | VarArgs])
|
|
|
|
|
+ end).
|
|
|
|
|
|
|
|
-spec encode(schema_name(), decoded_data()) -> encoded_data().
|
|
-spec encode(schema_name(), decoded_data()) -> encoded_data().
|
|
|
encode(SerdeName, RawData) ->
|
|
encode(SerdeName, RawData) ->
|
|
|
encode(SerdeName, RawData, []).
|
|
encode(SerdeName, RawData, []).
|
|
|
|
|
|
|
|
-spec encode(schema_name(), decoded_data(), [term()]) -> encoded_data().
|
|
-spec encode(schema_name(), decoded_data(), [term()]) -> encoded_data().
|
|
|
-encode(SerdeName, EncodedData, VarArgs) when is_list(VarArgs) ->
|
|
|
|
|
- case emqx_schema_registry:get_serde(SerdeName) of
|
|
|
|
|
- {error, not_found} ->
|
|
|
|
|
- error({serde_not_found, SerdeName});
|
|
|
|
|
|
|
+encode(SerdeName, Data, VarArgs) when is_list(VarArgs) ->
|
|
|
|
|
+ with_serde(
|
|
|
|
|
+ SerdeName,
|
|
|
|
|
+ fun(Serde) ->
|
|
|
|
|
+ eval_encode(Serde, [Data | VarArgs])
|
|
|
|
|
+ end
|
|
|
|
|
+ ).
|
|
|
|
|
+
|
|
|
|
|
+with_serde(Name, F) ->
|
|
|
|
|
+ case emqx_schema_registry:get_serde(Name) of
|
|
|
{ok, Serde} ->
|
|
{ok, Serde} ->
|
|
|
- eval_encode(Serde, [EncodedData | VarArgs])
|
|
|
|
|
|
|
+ F(Serde);
|
|
|
|
|
+ {error, not_found} ->
|
|
|
|
|
+ error({serde_not_found, Name})
|
|
|
end.
|
|
end.
|
|
|
|
|
|
|
|
-spec make_serde(serde_type(), schema_name(), schema_source()) -> serde().
|
|
-spec make_serde(serde_type(), schema_name(), schema_source()) -> serde().
|
|
@@ -108,7 +143,17 @@ make_serde(protobuf, Name, Source) ->
|
|
|
name = Name,
|
|
name = Name,
|
|
|
type = protobuf,
|
|
type = protobuf,
|
|
|
eval_context = SerdeMod
|
|
eval_context = SerdeMod
|
|
|
- }.
|
|
|
|
|
|
|
+ };
|
|
|
|
|
+make_serde(json, Name, Source) ->
|
|
|
|
|
+ case json_decode(Source) of
|
|
|
|
|
+ SchemaObj when is_map(SchemaObj) ->
|
|
|
|
|
+ %% jesse:add_schema adds any map() without further validation
|
|
|
|
|
+ %% if it's not a map, then case_clause
|
|
|
|
|
+ ok = jesse_add_schema(Name, SchemaObj),
|
|
|
|
|
+ #serde{name = Name, type = json};
|
|
|
|
|
+ _NotMap ->
|
|
|
|
|
+ error({invalid_json_schema, bad_schema_object})
|
|
|
|
|
+ end.
|
|
|
|
|
|
|
|
eval_decode(#serde{type = avro, name = Name, eval_context = Store}, [Data]) ->
|
|
eval_decode(#serde{type = avro, name = Name, eval_context = Store}, [Data]) ->
|
|
|
Opts = avro:make_decoder_options([{map_type, map}, {record_type, map}]),
|
|
Opts = avro:make_decoder_options([{map_type, map}, {record_type, map}]),
|
|
@@ -116,14 +161,29 @@ eval_decode(#serde{type = avro, name = Name, eval_context = Store}, [Data]) ->
|
|
|
eval_decode(#serde{type = protobuf, eval_context = SerdeMod}, [EncodedData, MessageName0]) ->
|
|
eval_decode(#serde{type = protobuf, eval_context = SerdeMod}, [EncodedData, MessageName0]) ->
|
|
|
MessageName = binary_to_existing_atom(MessageName0, utf8),
|
|
MessageName = binary_to_existing_atom(MessageName0, utf8),
|
|
|
Decoded = apply(SerdeMod, decode_msg, [EncodedData, MessageName]),
|
|
Decoded = apply(SerdeMod, decode_msg, [EncodedData, MessageName]),
|
|
|
- emqx_utils_maps:binary_key_map(Decoded).
|
|
|
|
|
|
|
+ emqx_utils_maps:binary_key_map(Decoded);
|
|
|
|
|
+eval_decode(#serde{type = json, name = Name}, [Data]) ->
|
|
|
|
|
+ true = is_binary(Data),
|
|
|
|
|
+ Term = json_decode(Data),
|
|
|
|
|
+ {ok, NewTerm} = jesse_validate(Name, Term),
|
|
|
|
|
+ NewTerm.
|
|
|
|
|
|
|
|
eval_encode(#serde{type = avro, name = Name, eval_context = Store}, [Data]) ->
|
|
eval_encode(#serde{type = avro, name = Name, eval_context = Store}, [Data]) ->
|
|
|
avro_binary_encoder:encode(Store, Name, Data);
|
|
avro_binary_encoder:encode(Store, Name, Data);
|
|
|
eval_encode(#serde{type = protobuf, eval_context = SerdeMod}, [DecodedData0, MessageName0]) ->
|
|
eval_encode(#serde{type = protobuf, eval_context = SerdeMod}, [DecodedData0, MessageName0]) ->
|
|
|
DecodedData = emqx_utils_maps:safe_atom_key_map(DecodedData0),
|
|
DecodedData = emqx_utils_maps:safe_atom_key_map(DecodedData0),
|
|
|
MessageName = binary_to_existing_atom(MessageName0, utf8),
|
|
MessageName = binary_to_existing_atom(MessageName0, utf8),
|
|
|
- apply(SerdeMod, encode_msg, [DecodedData, MessageName]).
|
|
|
|
|
|
|
+ apply(SerdeMod, encode_msg, [DecodedData, MessageName]);
|
|
|
|
|
+eval_encode(#serde{type = json, name = Name}, [Map]) ->
|
|
|
|
|
+ %% The input Map may not be a valid JSON term for jesse
|
|
|
|
|
+ Data = iolist_to_binary(emqx_utils_json:encode(Map)),
|
|
|
|
|
+ NewMap = json_decode(Data),
|
|
|
|
|
+ case jesse_validate(Name, NewMap) of
|
|
|
|
|
+ {ok, _} ->
|
|
|
|
|
+ Data;
|
|
|
|
|
+ {error, Reason} ->
|
|
|
|
|
+ error(Reason)
|
|
|
|
|
+ end.
|
|
|
|
|
|
|
|
destroy(#serde{type = avro, name = _Name}) ->
|
|
destroy(#serde{type = avro, name = _Name}) ->
|
|
|
?tp(serde_destroyed, #{type => avro, name => _Name}),
|
|
?tp(serde_destroyed, #{type => avro, name => _Name}),
|
|
@@ -131,12 +191,31 @@ destroy(#serde{type = avro, name = _Name}) ->
|
|
|
destroy(#serde{type = protobuf, name = _Name, eval_context = SerdeMod}) ->
|
|
destroy(#serde{type = protobuf, name = _Name, eval_context = SerdeMod}) ->
|
|
|
unload_code(SerdeMod),
|
|
unload_code(SerdeMod),
|
|
|
?tp(serde_destroyed, #{type => protobuf, name => _Name}),
|
|
?tp(serde_destroyed, #{type => protobuf, name => _Name}),
|
|
|
|
|
+ ok;
|
|
|
|
|
+destroy(#serde{type = json, name = Name}) ->
|
|
|
|
|
+ ok = jesse_del_schema(Name),
|
|
|
|
|
+ ?tp(serde_destroyed, #{type => json, name => Name}),
|
|
|
ok.
|
|
ok.
|
|
|
|
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%%------------------------------------------------------------------------------
|
|
|
%% Internal fns
|
|
%% Internal fns
|
|
|
%%------------------------------------------------------------------------------
|
|
%%------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
+json_decode(Data) ->
|
|
|
|
|
+ emqx_utils_json:decode(Data, [return_maps]).
|
|
|
|
|
+
|
|
|
|
|
+jesse_add_schema(Name, Obj) ->
|
|
|
|
|
+ jesse:add_schema(jesse_name(Name), Obj).
|
|
|
|
|
+
|
|
|
|
|
+jesse_del_schema(Name) ->
|
|
|
|
|
+ jesse:del_schema(jesse_name(Name)).
|
|
|
|
|
+
|
|
|
|
|
+jesse_validate(Name, Map) ->
|
|
|
|
|
+ jesse:validate(jesse_name(Name), Map, []).
|
|
|
|
|
+
|
|
|
|
|
+jesse_name(Str) ->
|
|
|
|
|
+ unicode:characters_to_list(Str).
|
|
|
|
|
+
|
|
|
-spec make_protobuf_serde_mod(schema_name(), schema_source()) -> module().
|
|
-spec make_protobuf_serde_mod(schema_name(), schema_source()) -> module().
|
|
|
make_protobuf_serde_mod(Name, Source) ->
|
|
make_protobuf_serde_mod(Name, Source) ->
|
|
|
{SerdeMod0, SerdeModFileName} = protobuf_serde_mod_name(Name),
|
|
{SerdeMod0, SerdeModFileName} = protobuf_serde_mod_name(Name),
|