Make json_error_t opaque

All decoding functions now accept a json_error_t** parameter and set
it to point to a heap-allocated json_error_t structure if an error
occurs. The contents of json_error_t are no longer exposed directly, a
few functions to do it have been added instead. If an error occurs,
the user must free the json_error_t value.

This makes it possible to enhance the error reporting facilities in
the future without breaking ABI compatibility with older versions.

This is a backwards incompatible change.
This commit is contained in:
Petri Lehtinen 2010-10-14 20:57:55 +03:00
parent 781bda1404
commit 23dd078c8d
6 changed files with 144 additions and 98 deletions

View File

@ -715,74 +715,93 @@ affect especially the behavior of the decoder.
.. type:: json_error_t .. type:: json_error_t
This data structure is used to return information on decoding This opaque structure is used to return information on errors from
errors from the decoding functions. Its definition is repeated the decoding functions. See below for more discussion on error
here:: reporting.
#define JSON_ERROR_TEXT_LENGTH 160 The following functions perform the JSON decoding:
typedef struct { .. function:: json_t *json_loads(const char *input, size_t flags, json_error_t **error)
char text[JSON_ERROR_TEXT_LENGTH];
int line;
} json_error_t;
*line* is the line number on which the error occurred, or -1 if
this information is not available. *text* contains the error
message (in UTF-8), or an empty string if a message is not
available.
The normal usef of :type:`json_error_t` is to allocate it normally
on the stack, and pass a pointer to a decoding function. Example::
int main() {
json_t *json;
json_error_t error;
json = json_load_file("/path/to/file.json", 0, &error);
if(!json) {
/* the error variable contains error information */
}
...
}
Also note that if the decoding succeeded (``json != NULL`` in the
above example), the contents of ``error`` are unspecified.
All decoding functions also accept *NULL* as the
:type:`json_error_t` pointer, in which case no error information
is returned to the caller.
The following functions perform the actual JSON decoding.
.. function:: json_t *json_loads(const char *input, size_t flags, json_error_t *error)
.. refcounting:: new .. refcounting:: new
Decodes the JSON string *input* and returns the array or object it Decodes the JSON string *input* and returns the array or object it
contains, or *NULL* on error, in which case *error* is filled with contains, or *NULL* on error. If *error* is non-*NULL*, it's used
information about the error. See above for discussion on the to return error information. See below for more discussion on error
*error* parameter. *flags* is currently unused, and should be set reporting. *flags* is currently unused, and should be set to 0.
to 0.
.. function:: json_t *json_loadf(FILE *input, size_t flags, json_error_t *error) .. function:: json_t *json_loadf(FILE *input, size_t flags, json_error_t **error)
.. refcounting:: new .. refcounting:: new
Decodes the JSON text in stream *input* and returns the array or Decodes the JSON text in stream *input* and returns the array or
object it contains, or *NULL* on error, in which case *error* is object it contains, or *NULL* on error. If *error* is non-*NULL*,
filled with information about the error. See above for discussion it's used to return error information. See below for more
on the *error* parameter. *flags* is currently unused, and should discussion on error reporting. *flags* is currently unused, and
be set to 0. should be set to 0.
.. function:: json_t *json_load_file(const char *path, size_t flags, json_error_t *error) .. function:: json_t *json_load_file(const char *path, size_t flags, json_error_t **error)
.. refcounting:: new .. refcounting:: new
Decodes the JSON text in file *path* and returns the array or Decodes the JSON text in file *path* and returns the array or
object it contains, or *NULL* on error, in which case *error* is object it contains, or *NULL* on error. If *error* is non-*NULL*,
filled with information about the error. See above for discussion it's used to return error information. See below for more
on the *error* parameter. *flags* is currently unused, and should discussion on error reporting. *flags* is currently unused, and
be set to 0. should be set to 0.
The :type:`json_error_t` parameter, that all decoding function accept
as their last parameter, is used to return information on decoding
errors to the caller. It is used by having a ``json_error_t *``
variable and passing a pointer to this variable to a decoding
function. Example::
int main() {
json_t *json;
json_error_t *error;
json = json_load_file("/path/to/file.json", 0, &error);
if(!json) {
/* the error variable contains error information */
fprintf(stderr, "Decoding error occured on line %d: %s\n", json_error_line(error), json_error_msg(error));
free(error);
}
/* ... */
}
Note that **the caller must free the error structure** after use if a
decoding error occurs. If decoding is succesfully finished, *error* is
simply set to *NULL* by the decoding function.
All decoding functions also accept *NULL* as the :type:`json_error_t`
pointer, in which case no error information is returned to the caller.
Example::
int main() {
json_t *json;
json = json_load_file("/path/to/file.json", 0, NULL);
if(!json) {
/* A decoding error occured but no error information is available */
}
/* ... */
}
:type:`json_error_t` is totally opaque and must be queried using the
following functions:
.. function:: const char *json_error_msg(const json_error_t *error)
Return a pointer to an UTF-8 encoded string that describes the
error in human-readable text, or *NULL* if *error* is *NULL*.
.. function:: int json_error_line(const json_error_t *error)
Return the line numer on which the error occurred, or -1 if this
information is not available or if *error* is *NULL*.
Equality Equality

View File

@ -168,18 +168,19 @@ json_t *json_copy(json_t *value);
json_t *json_deep_copy(json_t *value); json_t *json_deep_copy(json_t *value);
/* error reporting */
typedef struct json_error_t json_error_t;
const char *json_error_msg(const json_error_t *error);
int json_error_line(const json_error_t *error);
/* loading, printing */ /* loading, printing */
#define JSON_ERROR_TEXT_LENGTH 160 json_t *json_loads(const char *input, size_t flags, json_error_t **error);
json_t *json_loadf(FILE *input, size_t flags, json_error_t **error);
typedef struct { json_t *json_load_file(const char *path, size_t flags, json_error_t **error);
char text[JSON_ERROR_TEXT_LENGTH];
int line;
} json_error_t;
json_t *json_loads(const char *input, size_t flags, json_error_t *error);
json_t *json_loadf(FILE *input, size_t flags, json_error_t *error);
json_t *json_load_file(const char *path, size_t flags, json_error_t *error);
#define JSON_INDENT(n) (n & 0x1F) #define JSON_INDENT(n) (n & 0x1F)
#define JSON_COMPACT 0x20 #define JSON_COMPACT 0x20

View File

@ -60,53 +60,71 @@ typedef struct {
/*** error reporting ***/ /*** error reporting ***/
static void error_init(json_error_t *error) #define JSON_ERROR_MSG_LENGTH 160
struct json_error_t {
char msg[JSON_ERROR_MSG_LENGTH];
int line;
};
const char *json_error_msg(const json_error_t *error)
{ {
if(error) return error ? error->msg : NULL;
{
error->text[0] = '\0';
error->line = -1;
}
} }
static void error_set(json_error_t *error, const lex_t *lex, int json_error_line(const json_error_t *error)
{
return error ? error->line : -1;
}
static void error_init(json_error_t **error)
{
if(error)
*error = NULL;
}
static void error_set(json_error_t **error, const lex_t *lex,
const char *msg, ...) const char *msg, ...)
{ {
va_list ap; va_list ap;
char text[JSON_ERROR_TEXT_LENGTH]; char text[JSON_ERROR_MSG_LENGTH];
if(!error || error->text[0] != '\0') { if(!error || *error) {
/* error already set */ /* error not given or already set */
return; return;
} }
*error = malloc(sizeof(json_error_t));
if(!(*error))
return;
va_start(ap, msg); va_start(ap, msg);
vsnprintf(text, JSON_ERROR_TEXT_LENGTH, msg, ap); vsnprintf(text, JSON_ERROR_MSG_LENGTH, msg, ap);
va_end(ap); va_end(ap);
if(lex) if(lex)
{ {
const char *saved_text = strbuffer_value(&lex->saved_text); const char *saved_text = strbuffer_value(&lex->saved_text);
error->line = lex->line; (*error)->line = lex->line;
if(saved_text && saved_text[0]) if(saved_text && saved_text[0])
{ {
if(lex->saved_text.length <= 20) { if(lex->saved_text.length <= 20) {
snprintf(error->text, JSON_ERROR_TEXT_LENGTH, snprintf((*error)->msg, JSON_ERROR_MSG_LENGTH,
"%s near '%s'", text, saved_text); "%s near '%s'", text, saved_text);
} }
else else
snprintf(error->text, JSON_ERROR_TEXT_LENGTH, "%s", text); snprintf((*error)->msg, JSON_ERROR_MSG_LENGTH, "%s", text);
} }
else else
{ {
snprintf(error->text, JSON_ERROR_TEXT_LENGTH, snprintf((*error)->msg, JSON_ERROR_MSG_LENGTH,
"%s near end of file", text); "%s near end of file", text);
} }
} }
else else
{ {
error->line = -1; (*error)->line = -1;
snprintf(error->text, JSON_ERROR_TEXT_LENGTH, "%s", text); snprintf((*error)->msg, JSON_ERROR_MSG_LENGTH, "%s", text);
} }
} }
@ -124,7 +142,7 @@ stream_init(stream_t *stream, get_func get, eof_func eof, void *data)
stream->buffer_pos = 0; stream->buffer_pos = 0;
} }
static char stream_get(stream_t *stream, json_error_t *error) static char stream_get(stream_t *stream, json_error_t **error)
{ {
char c; char c;
@ -182,7 +200,7 @@ static void stream_unget(stream_t *stream, char c)
} }
static int lex_get(lex_t *lex, json_error_t *error) static int lex_get(lex_t *lex, json_error_t **error)
{ {
return stream_get(&lex->stream, error); return stream_get(&lex->stream, error);
} }
@ -197,7 +215,7 @@ static void lex_save(lex_t *lex, char c)
strbuffer_append_byte(&lex->saved_text, c); strbuffer_append_byte(&lex->saved_text, c);
} }
static int lex_get_save(lex_t *lex, json_error_t *error) static int lex_get_save(lex_t *lex, json_error_t **error)
{ {
char c = stream_get(&lex->stream, error); char c = stream_get(&lex->stream, error);
lex_save(lex, c); lex_save(lex, c);
@ -245,7 +263,7 @@ static int32_t decode_unicode_escape(const char *str)
return value; return value;
} }
static void lex_scan_string(lex_t *lex, json_error_t *error) static void lex_scan_string(lex_t *lex, json_error_t **error)
{ {
char c; char c;
const char *p; const char *p;
@ -407,7 +425,7 @@ out:
#define json_strtoint strtol #define json_strtoint strtol
#endif #endif
static int lex_scan_number(lex_t *lex, char c, json_error_t *error) static int lex_scan_number(lex_t *lex, char c, json_error_t **error)
{ {
const char *saved_text; const char *saved_text;
char *end; char *end;
@ -504,7 +522,7 @@ out:
return -1; return -1;
} }
static int lex_scan(lex_t *lex, json_error_t *error) static int lex_scan(lex_t *lex, json_error_t **error)
{ {
char c; char c;
@ -610,9 +628,9 @@ static void lex_close(lex_t *lex)
/*** parser ***/ /*** parser ***/
static json_t *parse_value(lex_t *lex, json_error_t *error); static json_t *parse_value(lex_t *lex, json_error_t **error);
static json_t *parse_object(lex_t *lex, json_error_t *error) static json_t *parse_object(lex_t *lex, json_error_t **error)
{ {
json_t *object = json_object(); json_t *object = json_object();
if(!object) if(!object)
@ -677,7 +695,7 @@ error:
return NULL; return NULL;
} }
static json_t *parse_array(lex_t *lex, json_error_t *error) static json_t *parse_array(lex_t *lex, json_error_t **error)
{ {
json_t *array = json_array(); json_t *array = json_array();
if(!array) if(!array)
@ -717,7 +735,7 @@ error:
return NULL; return NULL;
} }
static json_t *parse_value(lex_t *lex, json_error_t *error) static json_t *parse_value(lex_t *lex, json_error_t **error)
{ {
json_t *json; json_t *json;
@ -772,7 +790,7 @@ static json_t *parse_value(lex_t *lex, json_error_t *error)
return json; return json;
} }
static json_t *parse_json(lex_t *lex, json_error_t *error) static json_t *parse_json(lex_t *lex, json_error_t **error)
{ {
error_init(error); error_init(error);
@ -811,7 +829,7 @@ static int string_eof(void *data)
return (stream->data[stream->pos] == '\0'); return (stream->data[stream->pos] == '\0');
} }
json_t *json_loads(const char *string, size_t flags, json_error_t *error) json_t *json_loads(const char *string, size_t flags, json_error_t **error)
{ {
lex_t lex; lex_t lex;
json_t *result; json_t *result;
@ -838,7 +856,7 @@ out:
return result; return result;
} }
json_t *json_loadf(FILE *input, size_t flags, json_error_t *error) json_t *json_loadf(FILE *input, size_t flags, json_error_t **error)
{ {
lex_t lex; lex_t lex;
json_t *result; json_t *result;
@ -863,7 +881,7 @@ out:
return result; return result;
} }
json_t *json_load_file(const char *path, size_t flags, json_error_t *error) json_t *json_load_file(const char *path, size_t flags, json_error_t **error)
{ {
json_t *result; json_t *result;
FILE *fp; FILE *fp;

View File

@ -31,7 +31,7 @@ int main(int argc, char *argv[])
size_t flags = 0; size_t flags = 0;
json_t *json; json_t *json;
json_error_t error; json_error_t *error;
if(argc != 1) { if(argc != 1) {
fprintf(stderr, "usage: %s\n", argv[0]); fprintf(stderr, "usage: %s\n", argv[0]);
@ -61,7 +61,10 @@ int main(int argc, char *argv[])
json = json_loadf(stdin, 0, &error); json = json_loadf(stdin, 0, &error);
if(!json) { if(!json) {
fprintf(stderr, "%d\n%s\n", error.line, error.text); fprintf(stderr, "%d\n%s\n",
json_error_line(error),
json_error_msg(error));
free(error);
return 1; return 1;
} }

View File

@ -47,6 +47,8 @@ json_object_iter_set_new
json_dumps json_dumps
json_dumpf json_dumpf
json_dump_file json_dump_file
json_error_line
json_error_msg
json_loads json_loads
json_loadf json_loadf
json_load_file json_load_file

View File

@ -12,13 +12,16 @@
int main() int main()
{ {
json_t *json; json_t *json;
json_error_t error; json_error_t *error;
json = json_load_file("/path/to/nonexistent/file.json", 0, &error); json = json_load_file("/path/to/nonexistent/file.json", 0, &error);
if(error.line != -1) if(json)
fail("json_load didn't return an error!");
if(json_error_line(error) != -1)
fail("json_load_file returned an invalid line number"); fail("json_load_file returned an invalid line number");
if(strcmp(error.text, "unable to open /path/to/nonexistent/file.json: No such file or directory") != 0) if(strcmp(json_error_msg(error), "unable to open /path/to/nonexistent/file.json: No such file or directory") != 0)
fail("json_load_file returned an invalid error message"); fail("json_load_file returned an invalid error message");
free(error);
return 0; return 0;
} }