From f9095c52581867938e1ea1eb695235994f585e89 Mon Sep 17 00:00:00 2001 From: "Paul J. Davis" Date: Sat, 23 Aug 2014 05:47:59 -0500 Subject: [PATCH] Improved encoder errors This updates encoder errors to report the actual Erlang value that caused the error. This should make it easier to debug errors when generating JSON. --- c_src/encoder.c | 30 +++++++----- c_src/jiffy.h | 2 + c_src/util.c | 8 ++++ src/jiffy.erl | 6 ++- test/jiffy_04_string_tests.erl | 2 +- test/jiffy_12_error_tests.erl | 87 ++++++++++++++++++++++++++++++++++ 6 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 test/jiffy_12_error_tests.erl diff --git a/c_src/encoder.c b/c_src/encoder.c index 992b5d5..ac4935d 100644 --- a/c_src/encoder.c +++ b/c_src/encoder.c @@ -122,6 +122,12 @@ enc_error(Encoder* e, const char* msg) return make_error(e->atoms, e->env, msg); } +ERL_NIF_TERM +enc_obj_error(Encoder* e, const char* msg, ERL_NIF_TERM obj) +{ + return make_obj_error(e->atoms, e->env, msg, obj); +} + static inline int enc_ensure(Encoder* e, size_t req) { @@ -667,11 +673,11 @@ encode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) goto done; } if(!enif_get_tuple(env, item, &arity, &tuple)) { - ret = enc_error(e, "invalid_object_pair"); + ret = enc_obj_error(e, "invalid_object_member", item); goto done; } if(arity != 2) { - ret = enc_error(e, "invalid_object_pair"); + ret = enc_obj_error(e, "invalid_object_member_arity", item); goto done; } if(!enc_comma(e)) { @@ -679,7 +685,7 @@ encode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) goto done; } if(!enc_string(e, tuple[0])) { - ret = enc_error(e, "invalid_object_key"); + ret = enc_obj_error(e, "invalid_object_member_key", tuple[0]); goto done; } if(!enc_colon(e)) { @@ -712,12 +718,12 @@ encode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) stack = enif_make_list_cell(env, curr, stack); stack = enif_make_list_cell(env, e->atoms->ref_array, stack); stack = enif_make_list_cell(env, item, stack); - } else if(enif_compare(curr, e->atoms->atom_null) == 0 + } else if(enif_compare(curr, e->atoms->atom_null) == 0) { if(!enc_literal(e, "null", 4)) { ret = enc_error(e, "null"); goto done; } - } else if(e->use_nil && enif_compare(curr, e->atoms->atom_nil) == 0)) { + } else if(e->use_nil && enif_compare(curr, e->atoms->atom_nil) == 0) { if(!enc_literal(e, "null", 4)) { ret = enc_error(e, "null"); goto done; @@ -734,12 +740,12 @@ encode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) } } else if(enif_is_binary(env, curr)) { if(!enc_string(e, curr)) { - ret = enc_error(e, "invalid_string"); + ret = enc_obj_error(e, "invalid_string", curr); goto done; } } else if(enif_is_atom(env, curr)) { if(!enc_string(e, curr)) { - ret = enc_error(e, "invalid_string"); + ret = enc_obj_error(e, "invalid_string", curr); goto done; } } else if(enif_get_int64(env, curr, &lval)) { @@ -754,11 +760,11 @@ encode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) } } else if(enif_get_tuple(env, curr, &arity, &tuple)) { if(arity != 1) { - ret = enc_error(e, "invalid_ejson"); + ret = enc_obj_error(e, "invalid_ejson", curr); goto done; } if(!enif_is_list(env, tuple[0])) { - ret = enc_error(e, "invalid_object"); + ret = enc_obj_error(e, "invalid_object", curr); goto done; } if(!enc_start_object(e)) { @@ -777,15 +783,15 @@ encode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) goto done; } if(!enif_get_tuple(env, item, &arity, &tuple)) { - ret = enc_error(e, "invalid_object_member"); + ret = enc_obj_error(e, "invalid_object_member", item); goto done; } if(arity != 2) { - ret = enc_error(e, "invalid_object_member_arity"); + ret = enc_obj_error(e, "invalid_object_member_arity", item); goto done; } if(!enc_string(e, tuple[0])) { - ret = enc_error(e, "invalid_object_member_key"); + ret = enc_obj_error(e, "invalid_object_member_key", tuple[0]); goto done; } if(!enc_colon(e)) { diff --git a/c_src/jiffy.h b/c_src/jiffy.h index 34dff75..6d869a5 100644 --- a/c_src/jiffy.h +++ b/c_src/jiffy.h @@ -41,6 +41,8 @@ typedef struct { ERL_NIF_TERM make_atom(ErlNifEnv* env, const char* name); ERL_NIF_TERM make_ok(jiffy_st* st, ErlNifEnv* env, ERL_NIF_TERM data); ERL_NIF_TERM make_error(jiffy_st* st, ErlNifEnv* env, const char* error); +ERL_NIF_TERM make_obj_error(jiffy_st* st, ErlNifEnv* env, const char* error, + ERL_NIF_TERM obj); int get_bytes_per_iter(ErlNifEnv* env, ERL_NIF_TERM val, size_t* bpi); int should_yield(size_t used, size_t limit); int consume_timeslice(ErlNifEnv* env, size_t used, size_t limit); diff --git a/c_src/util.c b/c_src/util.c index 7436add..c30a6d7 100644 --- a/c_src/util.c +++ b/c_src/util.c @@ -25,6 +25,14 @@ make_error(jiffy_st* st, ErlNifEnv* env, const char* error) return enif_make_tuple2(env, st->atom_error, make_atom(env, error)); } +ERL_NIF_TERM +make_obj_error(jiffy_st* st, ErlNifEnv* env, + const char* error, ERL_NIF_TERM obj) +{ + ERL_NIF_TERM reason = enif_make_tuple2(env, make_atom(env, error), obj); + return enif_make_tuple2(env, st->atom_error, reason); +} + int get_bytes_per_iter(ErlNifEnv* env, ERL_NIF_TERM val, size_t* bpi) { diff --git a/src/jiffy.erl b/src/jiffy.erl index 8903831..9642b25 100644 --- a/src/jiffy.erl +++ b/src/jiffy.erl @@ -34,7 +34,7 @@ encode(Data) -> encode(Data, Options) -> ForceUTF8 = lists:member(force_utf8, Options), case nif_encode_init(Data, Options) of - {error, invalid_string} when ForceUTF8 == true -> + {error, {invalid_string, _}} when ForceUTF8 == true -> FixedData = jiffy_utf8:fix(Data), encode(FixedData, Options -- [force_utf8]); {error, _} = Error -> @@ -102,6 +102,8 @@ finish_encode([<<_/binary>>=B | Rest], Acc) -> finish_encode([Val | Rest], Acc) when is_integer(Val) -> Bin = list_to_binary(integer_to_list(Val)), finish_encode(Rest, [Bin | Acc]); +finish_encode([InvalidEjson | _], _) -> + throw({error, {invalid_ejson, InvalidEjson}}); finish_encode(_, _) -> throw({error, invalid_ejson}). @@ -134,7 +136,7 @@ decode_loop(Data, Decoder, Objs, Curr) -> encode_loop(Data, Options, Encoder, Stack, IOBuf) -> ForceUTF8 = lists:member(force_utf8, Options), case nif_encode_iter(Encoder, Stack, IOBuf) of - {error, invalid_string} when ForceUTF8 == true -> + {error, {invalid_string, _}} when ForceUTF8 == true -> FixedData = jiffy_utf8:fix(Data), encode(FixedData, Options -- [force_utf8]); {error, _} = Error -> diff --git a/test/jiffy_04_string_tests.erl b/test/jiffy_04_string_tests.erl index 46f15ff..7238f7c 100644 --- a/test/jiffy_04_string_tests.erl +++ b/test/jiffy_04_string_tests.erl @@ -48,7 +48,7 @@ gen(utf8, {Case, Fixed}) -> Case2 = <<34, Case/binary, 34>>, Fixed2 = <<34, Fixed/binary, 34>>, {msg("UTF-8: ~s", [hex(Case)]), [ - ?_assertThrow({error, invalid_string}, jiffy:encode(Case)), + ?_assertThrow({error, {invalid_string, _}}, jiffy:encode(Case)), ?_assertEqual(Fixed2, jiffy:encode(Case, [force_utf8])), ?_assertThrow({error, {_, invalid_string}}, jiffy:decode(Case2)) ]}. diff --git a/test/jiffy_12_error_tests.erl b/test/jiffy_12_error_tests.erl new file mode 100644 index 0000000..a7d4ed6 --- /dev/null +++ b/test/jiffy_12_error_tests.erl @@ -0,0 +1,87 @@ +% This file is part of Jiffy released under the MIT license. +% See the LICENSE file for more information. + +-module(jiffy_12_error_tests). + +-include_lib("eunit/include/eunit.hrl"). + + +enc_invalid_ejson_test_() -> + Type = invalid_ejson, + Ref = make_ref(), + {"invalid_ejson", [ + {"Basic", enc_error(Type, Ref, Ref)}, + {"Nested", enc_error(Type, {Ref, Ref}, {Ref, Ref})} + ]}. + + +enc_invalid_string_test_() -> + Type = invalid_string, + {"invalid_string", [ + {"Bare strign", enc_error(Type, <<143>>, <<143>>)}, + {"List element", enc_error(Type, <<143>>, [<<143>>])}, + {"Bad obj value", enc_error(Type, <<143>>, {[{foo, <<143>>}]})} + ]}. + +enc_invalid_object_test_() -> + Type = invalid_object, + Ref = make_ref(), + {"invalid_object", [ + {"Number", enc_error(Type, {1}, {1})}, + {"Ref", enc_error(Type, {Ref}, {Ref})}, + {"Tuple", enc_error(Type, {{[]}}, {{[]}})}, + {"Atom", enc_error(Type, {foo}, {foo})} + ]}. + + +enc_invalid_object_member_test_() -> + Type = invalid_object_member, + {"invalid_object_member", [ + {"Basic", enc_error(Type, foo, {[foo]})}, + {"Basic", enc_error(Type, foo, {[{bar, baz}, foo]})}, + {"Nested", enc_error(Type, foo, {[{bar,{[foo]}}]})}, + {"Nested", enc_error(Type, foo, {[{bar,{[{baz, 1}, foo]}}]})}, + {"In List", enc_error(Type, foo, [{[foo]}])}, + {"In List", enc_error(Type, foo, [{[{bang, true}, foo]}])} + ]}. + + +enc_invalid_object_member_arity_test_() -> + Type = invalid_object_member_arity, + E1 = {foo}, + E2 = {x, y, z}, + {"invalid_object_member", [ + {"Basic", enc_error(Type, E1, {[E1]})}, + {"Basic", enc_error(Type, E2, {[E2]})}, + {"Basic", enc_error(Type, E1, {[{bar, baz}, E1]})}, + {"Basic", enc_error(Type, E2, {[{bar, baz}, E2]})}, + {"Nested", enc_error(Type, E1, {[{bar,{[E1]}}]})}, + {"Nested", enc_error(Type, E2, {[{bar,{[E2]}}]})}, + {"Nested", enc_error(Type, E1, {[{bar,{[{baz, 1}, E1]}}]})}, + {"Nested", enc_error(Type, E2, {[{bar,{[{baz, 1}, E2]}}]})}, + {"In List", enc_error(Type, E1, [{[E1]}])}, + {"In List", enc_error(Type, E2, [{[E2]}])}, + {"In List", enc_error(Type, E1, [{[{bang, true}, E1]}])}, + {"In List", enc_error(Type, E2, [{[{bang, true}, E2]}])} + ]}. + + +enc_invalid_object_member_key_test_() -> + Type = invalid_object_member_key, + E1 = {1, true}, + {"invalid_object_member_key", [ + {"Bad string", enc_error(Type, <<143>>, {[{<<143>>, true}]})}, + {"Basic", enc_error(Type, 1, {[{1, true}]})}, + {"Basic", enc_error(Type, [1], {[{[1], true}]})}, + {"Basic", enc_error(Type, {[{foo,bar}]}, {[{{[{foo,bar}]}, true}]})}, + {"Second", enc_error(Type, 1, {[{bar, baz}, E1]})}, + {"Nested", enc_error(Type, 1, {[{bar,{[E1]}}]})}, + {"Nested", enc_error(Type, 1, {[{bar,{[{baz, 1}, E1]}}]})}, + {"In List", enc_error(Type, 1, [{[E1]}])}, + {"In List", enc_error(Type, 1, [{[{bang, true}, E1]}])} + ]}. + + + +enc_error(Type, Obj, Case) -> + ?_assertEqual({error, {Type, Obj}}, (catch jiffy:encode(Case))).