Browse Source

Add new return_trailer option

Previously Jiffy would throw an error about trailing data if there is
any non-whitespace character encounter after the first term had been
decoded.

This patch adds a decoder option `return_trailer` that will instead
return a sub-binary starting at the first non-whitespace character. This
allows users to be able to decode multiple terms from a single iodata()
term.

Thanks to @vlm for the original patch.
pull/98/head
Paul J. Davis 9 years ago
parent
commit
6d2278e906
7 changed files with 70 additions and 4 deletions
  1. +4
    -0
      README.md
  2. +16
    -2
      c_src/decoder.c
  3. +2
    -0
      c_src/jiffy.c
  4. +2
    -0
      c_src/jiffy.h
  5. +6
    -2
      src/jiffy.erl
  6. +21
    -0
      test/jiffy_11_proper_tests.erl
  7. +19
    -0
      test/jiffy_15_return_trailer_tests.erl

+ 4
- 0
README.md View File

@ -47,6 +47,10 @@ The options for decode are:
instead of `null`.
* `use_nil` - Returns the atom `nil` instead of `null` when decoding
JSON. This is a short hand for `{null_term, nil}`.
* `return_trailer` - If any non-whitespace is found after the first
JSON term is decoded the return value of decode/2 becomes
`{has_trailer, FirstTerm, RestData::iodata()}`. This is useful to
decode multiple terms in a single binary.
`jiffy:encode/1,2`
------------------

+ 16
- 2
c_src/decoder.c View File

@ -52,6 +52,7 @@ typedef struct {
size_t bytes_per_iter;
int is_partial;
int return_maps;
int return_trailer;
ERL_NIF_TERM null_term;
char* p;
@ -80,6 +81,7 @@ dec_new(ErlNifEnv* env)
d->bytes_per_iter = DEFAULT_BYTES_PER_ITER;
d->is_partial = 0;
d->return_maps = 0;
d->return_trailer = 0;
d->null_term = d->atoms->atom_null;
d->p = NULL;
@ -710,6 +712,8 @@ decode_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
#else
return enif_make_badarg(env);
#endif
} else if(enif_compare(val, d->atoms->atom_return_trailer) == 0) {
d->return_trailer = 1;
} else if(enif_compare(val, d->atoms->atom_use_nil) == 0) {
d->null_term = d->atoms->atom_nil;
} else if(get_null_term(env, val, &(d->null_term))) {
@ -733,6 +737,7 @@ decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
ERL_NIF_TERM objs;
ERL_NIF_TERM curr;
ERL_NIF_TERM val = argv[2];
ERL_NIF_TERM trailer;
ERL_NIF_TERM ret;
size_t start;
@ -1030,8 +1035,7 @@ decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
d->i++;
break;
default:
ret = dec_error(d, "invalid_trailing_data");
goto done;
goto decode_done;
}
break;
@ -1041,6 +1045,16 @@ decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
}
}
decode_done:
if(d->i < bin.size && d->return_trailer) {
trailer = enif_make_sub_binary(env, argv[0], d->i, bin.size - d->i);
val = enif_make_tuple3(env, d->atoms->atom_has_trailer, val, trailer);
} else if(d->i < bin.size) {
ret = dec_error(d, "invalid_trailing_data");
goto done;
}
if(dec_curr(d) != st_done) {
ret = dec_error(d, "truncated_json");
} else if(d->is_partial) {

+ 2
- 0
c_src/jiffy.c View File

@ -26,6 +26,8 @@ load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info)
st->atom_iter = make_atom(env, "iter");
st->atom_bytes_per_iter = make_atom(env, "bytes_per_iter");
st->atom_return_maps = make_atom(env, "return_maps");
st->atom_return_trailer = make_atom(env, "return_trailer");
st->atom_has_trailer = make_atom(env, "has_trailer");
st->atom_nil = make_atom(env, "nil");
st->atom_use_nil = make_atom(env, "use_nil");
st->atom_null_term = make_atom(env, "null_term");

+ 2
- 0
c_src/jiffy.h View File

@ -28,6 +28,8 @@ typedef struct {
ERL_NIF_TERM atom_iter;
ERL_NIF_TERM atom_bytes_per_iter;
ERL_NIF_TERM atom_return_maps;
ERL_NIF_TERM atom_return_trailer;
ERL_NIF_TERM atom_has_trailer;
ERL_NIF_TERM atom_nil;
ERL_NIF_TERM atom_use_nil;
ERL_NIF_TERM atom_null_term;

+ 6
- 2
src/jiffy.erl View File

@ -23,8 +23,12 @@
-type json_number() :: integer() | float().
-type json_object() :: {[{json_string(),json_value()}]}.
-type decode_result() :: json_value()
| {has_trailer, json_value(), binary()}.
-type decode_option() :: return_maps
| use_nil
| return_trailer
| {null_term, any()}
| {bytes_per_iter, non_neg_integer()}.
@ -37,10 +41,10 @@
-type decode_options() :: [decode_option()].
-type encode_options() :: [encode_option()].
-export_type([json_value/0]).
-export_type([json_value/0, jiffy_decode_result/0]).
-spec decode(iolist() | binary()) -> json_value().
-spec decode(iolist() | binary()) -> jiffy_decode_result().
decode(Data) ->
decode(Data, []).

+ 21
- 0
test/jiffy_11_proper_tests.erl View File

@ -24,6 +24,7 @@ proper_encode_decode_test_() ->
[
run(prop_enc_dec),
run(prop_enc_dec_pretty),
run(prop_dec_trailer),
run(prop_enc_no_crash),
run(prop_dec_no_crash_bin),
run(prop_dec_no_crash_any)
@ -37,6 +38,26 @@ prop_enc_dec() ->
end
).
prop_dec_trailer() ->
?FORALL({T1, T2}, {json(), json()},
begin
B1 = jiffy:encode(T1),
B2 = jiffy:encode(T2),
Combiners = [
<<" ">>,
<<"\r\t">>,
<<"\n \t">>,
<<" ">>
],
lists:foreach(fun(Comb) ->
Bin = <<B1/binary, Comb/binary, B2/binary>>,
{has_trailer, T1, Rest} = jiffy:decode(Bin, [return_trailer]),
T2 = jiffy:decode(Rest)
end, Combiners),
true
end
).
-ifndef(JIFFY_NO_MAPS).
to_map_ejson({Props}) ->
NewProps = [{K, to_map_ejson(V)} || {K, V} <- Props],

+ 19
- 0
test/jiffy_15_return_trailer_tests.erl View File

@ -0,0 +1,19 @@
% This file is part of Jiffy released under the MIT license.
% See the LICENSE file for more information.
-module(jiffy_15_return_trailer_tests).
-include_lib("eunit/include/eunit.hrl").
trailer_test_() ->
Opts = [return_trailer],
Cases = [
{<<"true">>, true},
{<<"true;">>, {has_trailer, true, <<";">>}},
{<<"true[]">>, {has_trailer, true, <<"[]">>}},
{<<"[]{}">>, {has_trailer, [], <<"{}">>}},
{<<"1 2 3">>, {has_trailer, 1, <<"2 3">>}}
],
{"Test return_trailer", lists:map(fun({Data, Result}) ->
?_assertEqual(Result, jiffy:decode(Data, Opts))
end, Cases)}.

Loading…
Cancel
Save