From 23dd078c8dcb17fd29d3b69f082b5b93f5f13b8f Mon Sep 17 00:00:00 2001 From: Petri Lehtinen Date: Thu, 14 Oct 2010 20:57:55 +0300 Subject: [PATCH] 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. --- doc/apiref.rst | 123 ++++++++++++++++++++-------------- src/jansson.h | 21 +++--- src/load.c | 80 +++++++++++++--------- test/bin/json_process.c | 7 +- test/suites/api/check-exports | 2 + test/suites/api/test_load.c | 9 ++- 6 files changed, 144 insertions(+), 98 deletions(-) diff --git a/doc/apiref.rst b/doc/apiref.rst index 397f418..84a0233 100644 --- a/doc/apiref.rst +++ b/doc/apiref.rst @@ -715,74 +715,93 @@ affect especially the behavior of the decoder. .. type:: json_error_t - This data structure is used to return information on decoding - errors from the decoding functions. Its definition is repeated - here:: + This opaque structure is used to return information on errors from + the decoding functions. See below for more discussion on error + reporting. - #define JSON_ERROR_TEXT_LENGTH 160 +The following functions perform the JSON decoding: - typedef struct { - 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) +.. function:: json_t *json_loads(const char *input, size_t flags, json_error_t **error) .. refcounting:: new Decodes the JSON string *input* and returns the array or object it - contains, or *NULL* on error, in which case *error* is filled with - information about the error. See above for discussion on the - *error* parameter. *flags* is currently unused, and should be set - to 0. + contains, or *NULL* on error. If *error* is non-*NULL*, it's used + to return error information. See below for more discussion on error + reporting. *flags* is currently unused, and should be set 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 Decodes the JSON text in stream *input* and returns the array or - object it contains, or *NULL* on error, in which case *error* is - filled with information about the error. See above for discussion - on the *error* parameter. *flags* is currently unused, and should - be set to 0. + object it contains, or *NULL* on error. If *error* is non-*NULL*, + it's used to return error information. See below for more + discussion on error reporting. *flags* is currently unused, and + 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 Decodes the JSON text in file *path* and returns the array or - object it contains, or *NULL* on error, in which case *error* is - filled with information about the error. See above for discussion - on the *error* parameter. *flags* is currently unused, and should - be set to 0. + object it contains, or *NULL* on error. If *error* is non-*NULL*, + it's used to return error information. See below for more + discussion on error reporting. *flags* is currently unused, and + 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 diff --git a/src/jansson.h b/src/jansson.h index 78a2222..e463f81 100644 --- a/src/jansson.h +++ b/src/jansson.h @@ -168,18 +168,19 @@ json_t *json_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 */ -#define JSON_ERROR_TEXT_LENGTH 160 - -typedef struct { - 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); +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_COMPACT 0x20 diff --git a/src/load.c b/src/load.c index 925a850..a34fd0c 100644 --- a/src/load.c +++ b/src/load.c @@ -60,53 +60,71 @@ typedef struct { /*** 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) - { - error->text[0] = '\0'; - error->line = -1; - } + return error ? error->msg : NULL; } -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, ...) { va_list ap; - char text[JSON_ERROR_TEXT_LENGTH]; + char text[JSON_ERROR_MSG_LENGTH]; - if(!error || error->text[0] != '\0') { - /* error already set */ + if(!error || *error) { + /* error not given or already set */ return; } + *error = malloc(sizeof(json_error_t)); + if(!(*error)) + return; + va_start(ap, msg); - vsnprintf(text, JSON_ERROR_TEXT_LENGTH, msg, ap); + vsnprintf(text, JSON_ERROR_MSG_LENGTH, msg, ap); va_end(ap); if(lex) { const char *saved_text = strbuffer_value(&lex->saved_text); - error->line = lex->line; + (*error)->line = lex->line; if(saved_text && saved_text[0]) { 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); } else - snprintf(error->text, JSON_ERROR_TEXT_LENGTH, "%s", text); + snprintf((*error)->msg, JSON_ERROR_MSG_LENGTH, "%s", text); } else { - snprintf(error->text, JSON_ERROR_TEXT_LENGTH, + snprintf((*error)->msg, JSON_ERROR_MSG_LENGTH, "%s near end of file", text); } } else { - error->line = -1; - snprintf(error->text, JSON_ERROR_TEXT_LENGTH, "%s", text); + (*error)->line = -1; + 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; } -static char stream_get(stream_t *stream, json_error_t *error) +static char stream_get(stream_t *stream, json_error_t **error) { 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); } @@ -197,7 +215,7 @@ static void lex_save(lex_t *lex, char 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); lex_save(lex, c); @@ -245,7 +263,7 @@ static int32_t decode_unicode_escape(const char *str) 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; const char *p; @@ -407,7 +425,7 @@ out: #define json_strtoint strtol #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; char *end; @@ -504,7 +522,7 @@ out: 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; @@ -610,9 +628,9 @@ static void lex_close(lex_t *lex) /*** 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(); if(!object) @@ -677,7 +695,7 @@ error: 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(); if(!array) @@ -717,7 +735,7 @@ error: 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; @@ -772,7 +790,7 @@ static json_t *parse_value(lex_t *lex, json_error_t *error) 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); @@ -811,7 +829,7 @@ static int string_eof(void *data) 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; json_t *result; @@ -838,7 +856,7 @@ out: 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; json_t *result; @@ -863,7 +881,7 @@ out: 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; FILE *fp; diff --git a/test/bin/json_process.c b/test/bin/json_process.c index cff820b..671127a 100644 --- a/test/bin/json_process.c +++ b/test/bin/json_process.c @@ -31,7 +31,7 @@ int main(int argc, char *argv[]) size_t flags = 0; json_t *json; - json_error_t error; + json_error_t *error; if(argc != 1) { fprintf(stderr, "usage: %s\n", argv[0]); @@ -61,7 +61,10 @@ int main(int argc, char *argv[]) json = json_loadf(stdin, 0, &error); 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; } diff --git a/test/suites/api/check-exports b/test/suites/api/check-exports index a6d877f..b1ef400 100755 --- a/test/suites/api/check-exports +++ b/test/suites/api/check-exports @@ -47,6 +47,8 @@ json_object_iter_set_new json_dumps json_dumpf json_dump_file +json_error_line +json_error_msg json_loads json_loadf json_load_file diff --git a/test/suites/api/test_load.c b/test/suites/api/test_load.c index b022a3a..e121de3 100644 --- a/test/suites/api/test_load.c +++ b/test/suites/api/test_load.c @@ -12,13 +12,16 @@ int main() { json_t *json; - json_error_t error; + json_error_t *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"); - 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"); + free(error); return 0; }