From 5cead13b2b9be96461b4681004518791b49986f2 Mon Sep 17 00:00:00 2001 From: "Paul J. Davis" Date: Wed, 15 Jul 2015 16:18:13 -0500 Subject: [PATCH] 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. --- c_src/decoder.c | 18 ++++++++++++++++-- c_src/jiffy.c | 2 ++ c_src/jiffy.h | 2 ++ test/jiffy_11_proper_tests.erl | 21 +++++++++++++++++++++ test/jiffy_15_return_trailer_tests.erl | 19 +++++++++++++++++++ 5 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 test/jiffy_15_return_trailer_tests.erl diff --git a/c_src/decoder.c b/c_src/decoder.c index f39575a..60b3257 100644 --- a/c_src/decoder.c +++ b/c_src/decoder.c @@ -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) { diff --git a/c_src/jiffy.c b/c_src/jiffy.c index 9048f5e..1ea60a3 100644 --- a/c_src/jiffy.c +++ b/c_src/jiffy.c @@ -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"); diff --git a/c_src/jiffy.h b/c_src/jiffy.h index 7a2b7d5..61c7b70 100644 --- a/c_src/jiffy.h +++ b/c_src/jiffy.h @@ -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; diff --git a/test/jiffy_11_proper_tests.erl b/test/jiffy_11_proper_tests.erl index 2c254bf..23aa38d 100644 --- a/test/jiffy_11_proper_tests.erl +++ b/test/jiffy_11_proper_tests.erl @@ -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 = <>, + {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], diff --git a/test/jiffy_15_return_trailer_tests.erl b/test/jiffy_15_return_trailer_tests.erl new file mode 100644 index 0000000..af80a46 --- /dev/null +++ b/test/jiffy_15_return_trailer_tests.erl @@ -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)}. \ No newline at end of file