Browse Source

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.
pull/72/head
Paul J. Davis 10 years ago
parent
commit
f9095c5258
6 changed files with 120 additions and 15 deletions
  1. +18
    -12
      c_src/encoder.c
  2. +2
    -0
      c_src/jiffy.h
  3. +8
    -0
      c_src/util.c
  4. +4
    -2
      src/jiffy.erl
  5. +1
    -1
      test/jiffy_04_string_tests.erl
  6. +87
    -0
      test/jiffy_12_error_tests.erl

+ 18
- 12
c_src/encoder.c View File

@ -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 class="p">) {
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)) {

+ 2
- 0
c_src/jiffy.h View File

@ -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);

+ 8
- 0
c_src/util.c View File

@ -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)
{

+ 4
- 2
src/jiffy.erl View File

@ -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 ->

+ 1
- 1
test/jiffy_04_string_tests.erl View File

@ -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))
]}.

+ 87
- 0
test/jiffy_12_error_tests.erl View File

@ -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))).

Loading…
Cancel
Save