From 3a7512d2b05af82c43578477941e92db72f0f57b Mon Sep 17 00:00:00 2001 From: Graeme Smecher Date: Fri, 14 Jan 2011 09:18:42 -0800 Subject: [PATCH] Make json_pack/json_unpack() recursive Note that we pass va_list pointers around instead of just va_lists, which would seem more intuitive. This is necessary since the behaviour of va_lists passed as function parameters is finicky. Quoth stdarg(3): If ap is passed to a function that uses va_arg(ap,type) then the value of ap is undefined after the return of that function. The pointer-passing strategy is used by Python's Py_BuildValue() for the same purpose. --- src/variadic.c | 814 ++++++++++++++++++++++-------------------- test/suites/api/test_pack.c | 95 +++-- test/suites/api/test_unpack.c | 77 +++- 3 files changed, 558 insertions(+), 428 deletions(-) diff --git a/src/variadic.c b/src/variadic.c index 89ff1da..aaa0495 100644 --- a/src/variadic.c +++ b/src/variadic.c @@ -13,89 +13,134 @@ #include #include "jansson_private.h" -json_t *json_pack(json_error_t *error, const char *fmt, ...) { - int fmt_length = strlen(fmt); - va_list ap; - - /* Keep a stack of containers (lists and objects) */ - int depth = 0; - json_t **stack = NULL; - - /* Keep a list of objects we create in case of error */ - int free_count = 0; - json_t **free_list = NULL; - - json_t *cur = NULL; /* Current container */ +static json_t *json_vnpack(json_error_t *error, ssize_t size, const char * const fmt, va_list *ap) +{ json_t *root = NULL; /* root object */ json_t *obj = NULL; + /* Scanner variables */ + const char *tok = fmt; + const char *etok; + int etok_depth; + char *key = NULL; /* Current key in an object */ char *s; - int line = 1; + int line=1; + int column=1; - /* Allocation provisioned for worst case */ - stack = calloc(fmt_length, sizeof(json_t *)); - free_list = calloc(fmt_length, sizeof(json_t *)); + /* Skip whitespace at the beginning of the string. */ + while(size && *tok == ' ') { + tok++; + size--; + column++; + } - jsonp_error_init(error, ""); + if(size <= 0) { + jsonp_error_set(error, 1, 1, "Empty format string!"); + return(NULL); + } - if(!stack || !free_list) - goto out; + /* tok must contain either a container type, or a length-1 string for a + * simple type. */ + if(*tok == '[') + root = json_array(); + else if(*tok == '{') + root = json_object(); + else + { + /* Simple object. Permit trailing spaces, otherwise complain. */ + if((ssize_t)strspn(tok+1, " ") < size-1) + { + jsonp_error_set(error, 1, 1, + "Expected a single object, got %i", size); + return(NULL); + } - va_start(ap, fmt); - while(*fmt) { - switch(*fmt) { + switch(*tok) + { + case 's': /* string */ + s = va_arg(*ap, char*); + if(!s) + { + jsonp_error_set(error, 1, 1, + "Refusing to handle a NULL string"); + return(NULL); + } + return(json_string(s)); + + case 'n': /* null */ + return(json_null()); + + case 'b': /* boolean */ + obj = va_arg(*ap, int) ? + json_true() : json_false(); + return(obj); + + case 'i': /* integer */ + return(json_integer(va_arg(*ap, int))); + + case 'f': /* double-precision float */ + return(json_real(va_arg(*ap, double))); + + case 'O': /* a json_t object; increments refcount */ + obj = va_arg(*ap, json_t *); + json_incref(obj); + return(obj); + + case 'o': /* a json_t object; doesn't increment refcount */ + obj = va_arg(*ap, json_t *); + return(obj); + + default: /* Whoops! */ + jsonp_error_set(error, 1, 1, + "Didn't understand format character '%c'", + *tok); + return(NULL); + } + } + + /* Move past container opening token */ + tok++; + column++; + + while(tok-fmt < size) { + switch(*tok) { case '\n': line++; + column=0; break; case ' ': /* Whitespace */ break; case ',': /* Element spacer */ - if(!root) - { - jsonp_error_set(error, line, -1, - "Unexpected COMMA precedes root element!"); - root = NULL; - goto out; - } - - if(!cur) - { - jsonp_error_set(error, line, -1, - "Unexpected COMMA outside a list or object!"); - root = NULL; - goto out; - } - if(key) { - jsonp_error_set(error, line, -1, + jsonp_error_set(error, line, column, "Expected KEY, got COMMA!"); - root = NULL; - goto out; + json_decref(root); + return(NULL); } break; case ':': /* Key/value separator */ if(!key) { - jsonp_error_set(error, line, -1, + jsonp_error_set(error, line, column, "Got key/value separator without " "a key preceding it!"); - root = NULL; - goto out; + json_decref(root); + return(NULL); } - if(!json_is_object(cur)) + if(!json_is_object(root)) { - jsonp_error_set(error, line, -1, + jsonp_error_set(error, line, column, "Got a key/value separator " "(':') outside an object!"); - root = NULL; - goto out; + json_decref(root); + return(NULL); } break; @@ -103,62 +148,85 @@ json_t *json_pack(json_error_t *error, const char *fmt, ...) { case ']': /* Close array or object */ case '}': - if(key) + if(tok-fmt + (ssize_t)strspn(tok+1, " ") != size-1) { - jsonp_error_set(error, line, -1, - "OBJECT or ARRAY ended with an " - "incomplete key/value pair!"); - root = NULL; - goto out; + jsonp_error_set(error, line, column, + "Unexpected close-bracket '%c'", *tok); + json_decref(root); + return(NULL); } - if(depth <= 0) + if((*tok == ']' && !json_is_array(root)) || + (*tok == '}' && !json_is_object(root))) { - jsonp_error_set(error, line, -1, - "Too many close-brackets '%c'", *fmt); - root = NULL; - goto out; + jsonp_error_set(error, line, column, + "Stray close-array '%c' character", *tok); + json_decref(root); + return(NULL); } + return(root); - if(*fmt == ']' && !json_is_array(cur)) - { - jsonp_error_set(error, line, -1, - "Stray close-array ']' character"); - root = NULL; - goto out; - } + case '[': + case '{': - if(*fmt == '}' && !json_is_object(cur)) - { - jsonp_error_set(error, line, -1, - "Stray close-object '}' character"); - root = NULL; - goto out; - } + /* Shortcut so we don't mess up the column count in error + * messages */ + if(json_is_object(root) && !key) + goto common; + + /* Find corresponding close bracket */ + etok = tok+1; + etok_depth = 1; + while(etok_depth) { + + if(!*etok || etok-fmt >= size) { + jsonp_error_set(error, line, column, + "Couldn't find matching close bracket for '%c'", + *tok); + json_decref(root); + return(NULL); + } - cur = stack[--depth]; - break; + if(*tok==*etok) + etok_depth++; + else if(*tok=='[' && *etok==']') { + etok_depth--; + break; + } else if(*tok=='{' && *etok=='}') { + etok_depth--; + break; + } - case '[': - obj = json_array(); - goto obj_common; + etok++; + } - case '{': - obj = json_object(); - goto obj_common; + /* Recurse */ + obj = json_vnpack(error, etok-tok+1, tok, ap); + if(!obj) { + /* error should already be set */ + error->column += column-1; + error->line += line-1; + json_decref(root); + return(NULL); + } + column += etok-tok; + tok = etok; + goto common; - case 's': /* string */ - s = va_arg(ap, char*); + case 's': + /* Handle strings specially, since they're used for both keys + * and values */ + s = va_arg(*ap, char*); if(!s) { - jsonp_error_set(error, line, -1, + jsonp_error_set(error, line, column, "Refusing to handle a NULL string"); - root = NULL; - goto out; + json_decref(root); + return(NULL); } - if(json_is_object(cur) && !key) + if(json_is_object(root) && !key) { /* It's a key */ key = s; @@ -166,400 +234,360 @@ json_t *json_pack(json_error_t *error, const char *fmt, ...) { } obj = json_string(s); - goto obj_common; - - case 'n': /* null */ - obj = json_null(); - goto obj_common; - - case 'b': /* boolean */ - obj = va_arg(ap, int) ? - json_true() : json_false(); - goto obj_common; - - case 'i': /* integer */ - obj = json_integer(va_arg(ap, int)); - goto obj_common; - - case 'f': /* double-precision float */ - obj = json_real(va_arg(ap, double)); - goto obj_common; - - case 'O': /* a json_t object; increments refcount */ - obj = va_arg(ap, json_t *); - json_incref(obj); - goto obj_common; - - case 'o': /* a json_t object; doesn't increment refcount */ - obj = va_arg(ap, json_t *); - goto obj_common; + goto common; -obj_common: free_list[free_count++] = obj; + default: + obj = json_vnpack(error, 1, tok, ap); + if(!obj) { + json_decref(root); + return(NULL); + } - /* Root this object to its parent */ - if(json_is_object(cur)) { +common: + /* Add to container */ + if(json_is_object(root)) { if(!key) { - jsonp_error_set(error, line, -1, - "Expected key, got identifier '%c'!", *fmt); - root = NULL; - goto out; + jsonp_error_set(error, line, column, + "Expected key, got identifier '%c'!", *tok); + json_decref(root); + return(NULL); } - json_object_set_new(cur, key, obj); + json_object_set_new(root, key, obj); key = NULL; } - else if(json_is_array(cur)) - { - json_array_append_new(cur, obj); - } - else if(!root) - { - printf("Rooting\n"); - root = obj; - } else { - jsonp_error_set(error, line, -1, - "Can't figure out where to attach " - "'%c' object!", *fmt); - root = NULL; - goto out; + json_array_append_new(root, obj); } - - /* If it was a container ('[' or '{'), descend on the stack */ - if(json_is_array(obj) || json_is_object(obj)) - { - stack[depth++] = cur; - cur = obj; - } - break; } - fmt++; + tok++; + column++; } - va_end(ap); - if(depth != 0) { - jsonp_error_set(error, line, -1, - "Missing object or array close-brackets in format string"); - root = NULL; - goto out; - } - - /* Success: don't free everything we just built! */ - free_count = 0; - -out: - while(free_count) - json_decref(free_list[--free_count]); - - if(free_list) - free(free_list); - - if(stack) - free(stack); - - return(root); + /* Whoops -- we didn't match the close bracket! */ + jsonp_error_set(error, line, column, "Missing close array or object!"); + json_decref(root); + return(NULL); } -int json_unpack(json_t *root, json_error_t *error, const char *fmt, ...) { - va_list ap; +static int json_vnunpack(json_t *root, json_error_t *error, ssize_t size, const char *fmt, va_list *ap) +{ int rv=0; /* Return value */ int line = 1; /* Line number */ + int column = 1; /* Column */ - /* Keep a stack of containers (lists and objects) */ - int depth = 0; - json_t **stack; - + /* Position markers for arrays or objects */ int array_index = 0; - char *key = NULL; /* Current key in an object */ + char *key = NULL; - json_t *cur = NULL; /* Current container */ - json_t *obj = NULL; + const char **s; - int fmt_length = strlen(fmt); + /* Scanner variables */ + const char *tok = fmt; + const char *etok; + int etok_depth; - jsonp_error_init(error, ""); + json_t *obj; - /* Allocation provisioned for worst case */ - stack = calloc(fmt_length, sizeof(json_t *)); - if(!stack) - { - jsonp_error_set(error, line, -1, "Out of memory!"); - rv = -1; - goto out; + /* If we're successful, we need to know if the number of arguments + * provided matches the number of JSON objects. We can do this by + * counting the elements in every array or object we open up, and + * decrementing the count as we visit their children. */ + int unvisited = 0; + + /* Skip whitespace at the beginning of the string. */ + while(size && *tok == ' ') { + tok++; + size--; + column++; } - /* Even if we're successful, we need to know if the number of - * arguments provided matches the number of JSON objects. - * We can do this by counting the elements in every array or - * object we open up, and decrementing the count as we visit - * their children. */ - int unvisited = 0; + if(size <= 0) { + jsonp_error_set(error, 1, 1, "Empty format string!"); + return(-2); + } - va_start(ap, fmt); - while(*fmt) + /* tok must contain either a container type, or a length-1 string for a + * simple type. */ + if(*tok != '[' && *tok != '{') { - switch(*fmt) + /* Simple object. Permit trailing spaces, otherwise complain. */ + if((ssize_t)strspn(tok+1, " ") < size-1) { - case ' ': /* Whitespace */ - break; - - case '\n': /* Line break */ - line++; - break; - - case ',': /* Element spacer */ + jsonp_error_set(error, 1, 1, + "Expected a single object, got %i", size); + return(-1); + } - if(!cur) + switch(*tok) + { + case 's': + if(!json_is_string(root)) { - jsonp_error_set(error, line, -1, - "Unexpected COMMA outside a list or object!"); - rv = -1; - goto out; + jsonp_error_set(error, line, column, + "Type mismatch! Object (%i) wasn't a string.", + json_typeof(root)); + return(-2); } - - if(key) - { - jsonp_error_set(error, line, -1, - "Expected KEY, got COMMA!"); - rv = -1; - goto out; + s = va_arg(*ap, const char **); + if(!s) { + jsonp_error_set(error, line, column, "Passed a NULL string pointer!"); + return(-2); } - break; + *s = json_string_value(root); + return(0); - case ':': /* Key/value separator */ - if(!json_is_object(cur) || !key) + case 'i': + if(!json_is_integer(root)) { - jsonp_error_set(error, line, -1, "Unexpected ':'"); - rv = -1; - goto out; + jsonp_error_set(error, line, column, + "Type mismatch! Object (%i) wasn't an integer.", + json_typeof(root)); + return(-2); } - break; + *va_arg(*ap, int*) = json_integer_value(root); + return(0); - case '[': - case '{': - /* Fetch object */ - if(!cur) - { - obj = root; - } - else if(json_is_object(cur)) - { - if(!key) - { - jsonp_error_set(error, line, -1, - "Objects can't be keys"); - rv = -1; - goto out; - } - obj = json_object_get(cur, key); - unvisited--; - key = NULL; - } - else if(json_is_array(cur)) + case 'b': + if(!json_is_boolean(root)) { - obj = json_array_get(cur, array_index); - unvisited--; - array_index++; - } - else - { - assert(0); + jsonp_error_set(error, line, column, + "Type mismatch! Object (%i) wasn't a boolean.", + json_typeof(root)); + return(-2); } + *va_arg(*ap, int*) = json_is_true(root); + return(0); - /* Make sure we got what we expected */ - if(*fmt=='{' && !json_is_object(obj)) + case 'f': + if(!json_is_number(root)) { - rv = -2; - goto out; + jsonp_error_set(error, line, column, + "Type mismatch! Object (%i) wasn't a real.", + json_typeof(root)); + return(-2); } + *va_arg(*ap, double*) = json_number_value(root); + return(0); - if(*fmt=='[' && !json_is_array(obj)) - { - rv = -2; - goto out; - } + case 'O': + json_incref(root); + /* Fall through */ + + case 'o': + *va_arg(*ap, json_t**) = root; + return(0); - unvisited += json_is_object(obj) ? - json_object_size(obj) : - json_array_size(obj); + case 'n': + /* Don't actually assign anything; we're just happy + * the null turned up as promised in the format + * string. */ + return(0); - /* Descend */ - stack[depth++] = cur; - cur = obj; + default: + jsonp_error_set(error, line, column, + "Unknown format character '%c'", *tok); + return(-1); + } + } - key = NULL; + /* Move past container opening token */ + tok++; + while(tok-fmt < size) { + switch(*tok) { + case '\n': + line++; + column=0; break; + case ' ': /* Whitespace */ + break; - case ']': - case '}': + case ',': /* Element spacer */ + if(key) + { + jsonp_error_set(error, line, column, + "Expected KEY, got COMMA!"); + return(-2); + } + break; - if(json_is_array(cur) && *fmt!=']') + case ':': /* Key/value separator */ + if(!key) { - jsonp_error_set(error, line, -1, "Missing ']'"); - rv = -1; - goto out; + jsonp_error_set(error, line, column, + "Got key/value separator without " + "a key preceding it!"); + return(-2); } - if(json_is_object(cur) && *fmt!='}') + if(!json_is_object(root)) { - jsonp_error_set(error, line, -1, "Missing '}'"); - rv = -1; - goto out; + jsonp_error_set(error, line, column, + "Got a key/value separator " + "(':') outside an object!"); + return(-2); } - if(key) + break; + + case ']': /* Close array or object */ + case '}': + + if(tok-fmt + (ssize_t)strspn(tok+1, " ") != size-1) { - jsonp_error_set(error, line, -1, "Unexpected '%c'", *fmt); - rv = -1; - goto out; + jsonp_error_set(error, line, column, + "Unexpected close-bracket '%c'", *tok); + return(-2); } - if(depth <= 0) + if((*tok == ']' && !json_is_array(root)) || + (*tok == '}' && !json_is_object(root))) { - jsonp_error_set(error, line, -1, "Unexpected '%c'", *fmt); - rv = -1; - goto out; + jsonp_error_set(error, line, column, + "Stray close-array '%c' character", *tok); + return(-2); } + return(unvisited); + + case '[': + case '{': - cur = stack[--depth]; + /* Find corresponding close bracket */ + etok = tok+1; + etok_depth = 1; + while(etok_depth) { - break; + if(!*etok || etok-fmt >= size) { + jsonp_error_set(error, line, column, + "Couldn't find matching close bracket for '%c'", + *tok); + return(-2); + } - case 's': - if(!key && json_is_object(cur)) - { - /* constant string for key */ - key = va_arg(ap, char*); - break; + if(*tok==*etok) + etok_depth++; + else if(*tok=='[' && *etok==']') { + etok_depth--; + break; + } else if(*tok=='{' && *etok=='}') { + etok_depth--; + break; + } + + etok++; } - /* fall through */ - case 'i': /* integer */ - case 'f': /* double-precision float */ - case 'O': /* a json_t object; increments refcount */ - case 'o': /* a json_t object; borrowed reference */ - case 'b': /* boolean */ - case 'n': /* null */ + /* Recurse */ + if(json_is_array(root)) { + rv = json_vnunpack(json_object_get(root, key), + error, etok-tok+1, tok, ap); + } else { + rv = json_vnunpack(json_array_get(root, array_index++), + error, etok-tok+1, tok, ap); + } - /* Fetch object */ - if(!cur) - { - obj = root; + if(rv < 0) { + /* error should already be set */ + error->column += column-1; + error->line += line-1; + return(rv); } - else if(json_is_object(cur)) + + unvisited += rv; + column += etok-tok; + tok = etok; + break; + + case 's': + /* Handle strings specially, since they're used for both keys + * and values */ + + if(json_is_object(root) && !key) { + /* It's a key */ + key = va_arg(*ap, char*); + printf("Got key '%s'\n", key); + if(!key) { - jsonp_error_set(error, line, -1, - "Only strings may be used as keys!"); - rv = -1; - goto out; + jsonp_error_set(error, line, column, + "Refusing to handle a NULL key"); + return(-2); } - - obj = json_object_get(cur, key); - unvisited--; - key = NULL; - } - else if(json_is_array(cur)) - { - obj = json_array_get(cur, array_index); - unvisited--; - array_index++; + break; } + + /* Fall through */ + + default: + + /* Fetch the element from the JSON container */ + if(json_is_object(root)) + obj = json_object_get(root, key); else - { - jsonp_error_set(error, line, -1, - "Unsure how to retrieve JSON object '%c'", - *fmt); - rv = -1; - goto out; + obj = json_array_get(root, array_index++); + + if(!obj) { + jsonp_error_set(error, line, column, + "Array/object entry didn't exist!"); + return(-1); } - switch(*fmt) - { - case 's': - if(!json_is_string(obj)) - { - jsonp_error_set(error, line, -1, - "Type mismatch! Object wasn't a string."); - rv = -2; - goto out; - } - *va_arg(ap, const char**) = json_string_value(obj); - break; + rv = json_vnunpack(obj, error, 1, tok, ap); + if(rv != 0) + return(rv); - case 'i': - if(!json_is_integer(obj)) - { - jsonp_error_set(error, line, -1, - "Type mismatch! Object wasn't an integer."); - rv = -2; - goto out; - } - *va_arg(ap, int*) = json_integer_value(obj); - break; + break; + } + tok++; + column++; + } - case 'b': - if(!json_is_boolean(obj)) - { - jsonp_error_set(error, line, -1, - "Type mismatch! Object wasn't a boolean."); - rv = -2; - goto out; - } - *va_arg(ap, int*) = json_is_true(obj); - break; + /* Whoops -- we didn't match the close bracket! */ + jsonp_error_set(error, line, column, "Missing close array or object!"); + return(-2); +} - case 'f': - if(!json_is_number(obj)) - { - jsonp_error_set(error, line, -1, - "Type mismatch! Object wasn't a real."); - rv = -2; - goto out; - } - *va_arg(ap, double*) = json_number_value(obj); - break; +json_t *json_pack(json_error_t *error, const char *fmt, ...) +{ + va_list ap; + json_t *obj; - case 'O': - json_incref(obj); - /* Fall through */ + jsonp_error_init(error, ""); - case 'o': - *va_arg(ap, json_t**) = obj; - break; + if(!fmt || !*fmt) { + jsonp_error_set(error, 1, 1, "Null or empty format string!"); + return(NULL); + } - case 'n': - /* Don't actually assign anything; we're just happy - * the null turned up as promised in the format - * string. */ - break; + va_start(ap, fmt); + obj = json_vnpack(error, strlen(fmt), fmt, &ap); + va_end(ap); - default: - jsonp_error_set(error, line, -1, - "Unknown format character '%c'", *fmt); - rv = -1; - goto out; - } - } - fmt++; - } + return(obj); +} - /* Return 0 if everything was matched; otherwise the number of JSON - * objects we didn't get to. */ - rv = unvisited; +int json_unpack(json_t *root, json_error_t *error, const char *fmt, ...) +{ + va_list ap; + int rv; -out: - va_end(ap); + jsonp_error_init(error, ""); + + if(!fmt || !*fmt) { + jsonp_error_set(error, 1, 1, "Null or empty format string!"); + return(-2);; + } - if(stack) - free(stack); + va_start(ap, fmt); + rv = json_vnunpack(root, error, strlen(fmt), fmt, &ap); + va_end(ap); return(rv); } diff --git a/test/suites/api/test_pack.c b/test/suites/api/test_pack.c index f1ca388..8968dc7 100644 --- a/test/suites/api/test_pack.c +++ b/test/suites/api/test_pack.c @@ -15,13 +15,14 @@ int main() { json_t *value; int i; + json_error_t error; /* * Simple, valid json_pack cases */ /* true */ - value = json_pack(NULL, "b", 1); + value = json_pack(&error, "b", 1); if(!json_is_true(value)) fail("json_pack boolean failed"); if(value->refcount != (ssize_t)-1) @@ -29,7 +30,7 @@ int main() json_decref(value); /* false */ - value = json_pack(NULL, "b", 0); + value = json_pack(&error, "b", 0); if(!json_is_false(value)) fail("json_pack boolean failed"); if(value->refcount != (ssize_t)-1) @@ -37,7 +38,7 @@ int main() json_decref(value); /* null */ - value = json_pack(NULL, "n"); + value = json_pack(&error, "n"); if(!json_is_null(value)) fail("json_pack null failed"); if(value->refcount != (ssize_t)-1) @@ -45,7 +46,7 @@ int main() json_decref(value); /* integer */ - value = json_pack(NULL, "i", 1); + value = json_pack(&error, "i", 1); if(!json_is_integer(value) || json_integer_value(value) != 1) fail("json_pack integer failed"); if(value->refcount != (ssize_t)1) @@ -54,7 +55,7 @@ int main() /* real */ - value = json_pack(NULL, "f", 1.0); + value = json_pack(&error, "f", 1.0); if(!json_is_real(value) || json_real_value(value) != 1.0) fail("json_pack real failed"); if(value->refcount != (ssize_t)1) @@ -62,7 +63,7 @@ int main() json_decref(value); /* string */ - value = json_pack(NULL, "s", "test"); + value = json_pack(&error, "s", "test"); if(!json_is_string(value) || strcmp("test", json_string_value(value))) fail("json_pack string failed"); if(value->refcount != (ssize_t)1) @@ -70,7 +71,7 @@ int main() json_decref(value); /* empty object */ - value = json_pack(NULL, "{}", 1.0); + value = json_pack(&error, "{}", 1.0); if(!json_is_object(value) || json_object_size(value) != 0) fail("json_pack empty object failed"); if(value->refcount != (ssize_t)1) @@ -78,7 +79,7 @@ int main() json_decref(value); /* empty list */ - value = json_pack(NULL, "[]", 1.0); + value = json_pack(&error, "[]", 1.0); if(!json_is_array(value) || json_array_size(value) != 0) fail("json_pack empty list failed"); if(value->refcount != (ssize_t)1) @@ -86,7 +87,7 @@ int main() json_decref(value); /* non-incref'd object */ - value = json_pack(NULL, "o", json_integer(1)); + value = json_pack(&error, "o", json_integer(1)); if(!json_is_integer(value) || json_integer_value(value) != 1) fail("json_pack object failed"); if(value->refcount != (ssize_t)1) @@ -94,7 +95,7 @@ int main() json_decref(value); /* incref'd object */ - value = json_pack(NULL, "O", json_integer(1)); + value = json_pack(&error, "O", json_integer(1)); if(!json_is_integer(value) || json_integer_value(value) != 1) fail("json_pack object failed"); if(value->refcount != (ssize_t)2) @@ -103,17 +104,17 @@ int main() json_decref(value); /* simple object */ - value = json_pack(NULL, "{s:[]}", "foo"); + value = json_pack(&error, "{s:[]}", "foo"); if(!json_is_object(value) || json_object_size(value) != 1) - fail("json_pack object failed"); + fail("json_pack array failed"); if(!json_is_array(json_object_get(value, "foo"))) - fail("json_pack object failed"); + fail("json_pack array failed"); if(json_object_get(value, "foo")->refcount != (ssize_t)1) fail("json_pack object refcount failed"); json_decref(value); /* simple array */ - value = json_pack(NULL, "[i,i,i]", 0, 1, 2); + value = json_pack(&error, "[i,i,i]", 0, 1, 2); if(!json_is_array(value) || json_array_size(value) != 3) fail("json_pack object failed"); for(i=0; i<3; i++) @@ -125,30 +126,82 @@ int main() } json_decref(value); + /* Whitespace; regular string */ + value = json_pack(&error, " s ", "test"); + if(!json_is_string(value) || strcmp("test", json_string_value(value))) + fail("json_pack string (with whitespace) failed"); + json_decref(value); + + /* Whitespace; empty array */ + value = json_pack(&error, "[ ]"); + if(!json_is_array(value) || json_array_size(value) != 0) + fail("json_pack empty array (with whitespace) failed"); + json_decref(value); + + /* Whitespace; array */ + value = json_pack(&error, "[ i , i, i ] ", 1, 2, 3); + if(!json_is_array(value) || json_array_size(value) != 3) + fail("json_pack array (with whitespace) failed"); + json_decref(value); + /* * Invalid cases */ - + /* mismatched open/close array/object */ - if(json_pack(NULL, "[}")) + if(json_pack(&error, "[}")) fail("json_pack failed to catch mismatched '}'"); + if(error.line != 1 || error.column != 2) + fail("json_pack didn't get the error coordinates right!"); - if(json_pack(NULL, "{]")) + if(json_pack(&error, "{]")) fail("json_pack failed to catch mismatched ']'"); + if(error.line != 1 || error.column != 2) + fail("json_pack didn't get the error coordinates right!"); /* missing close array */ - if(json_pack(NULL, "[")) + if(json_pack(&error, "[")) fail("json_pack failed to catch missing ']'"); + if(error.line != 1 || error.column != 2) + fail("json_pack didn't get the error coordinates right!"); /* missing close object */ - if(json_pack(NULL, "{")) + if(json_pack(&error, "{")) fail("json_pack failed to catch missing '}'"); + if(error.line != 1 || error.column != 2) + fail("json_pack didn't get the error coordinates right!"); /* NULL string */ - if(json_pack(NULL, "s", NULL)) - fail("json_pack failed to catch null string"); + if(json_pack(&error, "s", NULL)) + fail("json_pack failed to catch null argument string"); + if(error.line != 1 || error.column != 1) + fail("json_pack didn't get the error coordinates right!"); + + /* NULL format */ + if(json_pack(&error, NULL)) + fail("json_pack failed to catch NULL format string"); + if(error.line != 1 || error.column != 1) + fail("json_pack didn't get the error coordinates right!"); + + /* More complicated checks for row/columns */ + if(json_pack(&error, "{ {}: s }", "foo")) + fail("json_pack failed to catch object as key"); + if(error.line != 1 || error.column != 3) + fail("json_pack didn't get the error coordinates right!"); + + if(json_pack(&error, "{ s: {}, s:[ii{} }", "foo", "bar", 12, 13)) + fail("json_pack failed to catch missing ]"); + if(error.line != 1 || error.column != 13) + fail("json_pack didn't get the error coordinates right!"); + + if(json_pack(&error, "[[[[[ [[[[[ [[[[ }]]]] ]]]] ]]]]]")) + fail("json_pack failed to catch missing ]"); + if(error.line != 1 || error.column != 21) + fail("json_pack didn't get the error coordinates right!"); return(0); + + //fprintf(stderr, "%i/%i: %s %s\n", error.line, error.column, error.source, error.text); } /* vim: ts=4:expandtab:sw=4 diff --git a/test/suites/api/test_unpack.c b/test/suites/api/test_unpack.c index ea18ff8..6bc2e66 100644 --- a/test/suites/api/test_unpack.c +++ b/test/suites/api/test_unpack.c @@ -20,90 +20,139 @@ int main() double f; char *s; + json_error_t error; + /* * Simple, valid json_pack cases */ /* true */ - rv = json_unpack(json_true(), NULL, "b", &i1); + rv = json_unpack(json_true(), &error, "b", &i1); if(rv || !i1) fail("json_unpack boolean failed"); /* false */ - rv = json_unpack(json_false(), NULL, "b", &i1); + rv = json_unpack(json_false(), &error, "b", &i1); if(rv || i1) fail("json_unpack boolean failed"); /* null */ - rv = json_unpack(json_null(), NULL, "n"); + rv = json_unpack(json_null(), &error, "n"); if(rv) fail("json_unpack null failed"); /* integer */ j = json_integer(1); - rv = json_unpack(j, NULL, "i", &i1); + rv = json_unpack(j, &error, "i", &i1); if(rv || i1 != 1) fail("json_unpack integer failed"); json_decref(j); /* real */ j = json_real(1.0); - rv = json_unpack(j, NULL, "f", &f); + rv = json_unpack(j, &error, "f", &f); if(rv || f != 1.0) fail("json_unpack real failed"); json_decref(j); /* string */ j = json_string("foo"); - rv = json_unpack(j, NULL, "s", &s); + rv = json_unpack(j, &error, "s", &s); if(rv || strcmp(s, "foo")) fail("json_unpack string failed"); json_decref(j); /* empty object */ j = json_object(); - rv = json_unpack(j, NULL, "{}"); + rv = json_unpack(j, &error, "{}"); if(rv) fail("json_unpack empty object failed"); json_decref(j); /* empty list */ j = json_array(); - rv = json_unpack(j, NULL, "[]"); + rv = json_unpack(j, &error, "[]"); if(rv) fail("json_unpack empty list failed"); json_decref(j); /* non-incref'd object */ j = json_object(); - rv = json_unpack(j, NULL, "o", &j2); + rv = json_unpack(j, &error, "o", &j2); if(j2 != j || j->refcount != (ssize_t)1) fail("json_unpack object failed"); json_decref(j); /* incref'd object */ j = json_object(); - rv = json_unpack(j, NULL, "O", &j2); + rv = json_unpack(j, &error, "O", &j2); if(j2 != j || j->refcount != (ssize_t)2) fail("json_unpack object failed"); json_decref(j); json_decref(j); /* simple object */ - j = json_pack(NULL, "{s:i}", "foo", 1); - rv = json_unpack(j, NULL, "{s:i}", "foo", &i1); + j = json_pack(&error, "{s:i}", "foo", 1); + rv = json_unpack(j, &error, "{s:i}", "foo", &i1); if(rv || i1!=1) fail("json_unpack simple object failed"); json_decref(j); /* simple array */ - j = json_pack(NULL, "[iii]", 1, 2, 3); - rv = json_unpack(j, NULL, "[i,i,i]", &i1, &i2, &i3); + j = json_pack(&error, "[iii]", 1, 2, 3); + rv = json_unpack(j, &error, "[i,i,i]", &i1, &i2, &i3); if(rv || i1 != 1 || i2 != 2 || i3 != 3) fail("json_unpack simple array failed"); json_decref(j); + /* + * Invalid cases + */ + + /* mismatched open/close array/object */ + j = json_pack(&error, "[]"); + rv = json_unpack(j, &error, "[}"); + if(!rv) + fail("json_unpack failed to catch mismatched ']'"); + json_decref(j); + + j = json_pack(&error, "{}"); + rv = json_unpack(j, &error, "{]"); + if(!rv) + fail("json_unpack failed to catch mismatched '}'"); + json_decref(j); + + /* missing close array */ + j = json_pack(&error, "[]"); + rv = json_unpack(j, &error, "["); + if(rv >= 0) + fail("json_unpack failed to catch missing ']'"); + json_decref(j); + + /* missing close object */ + j = json_pack(&error, "{}"); + rv = json_unpack(j, &error, "{"); + if(rv >= 0) + fail("json_unpack failed to catch missing '}'"); + json_decref(j); + + /* NULL format string */ + j = json_pack(&error, "[]"); + rv =json_unpack(j, &error, NULL); + if(rv >= 0) + fail("json_unpack failed to catch null format string"); + json_decref(j); + + /* NULL string pointer */ + j = json_string("foobie"); + rv =json_unpack(j, &error, "s", NULL); + if(rv >= 0) + fail("json_unpack failed to catch null string pointer"); + json_decref(j); + return 0; + + //fprintf(stderr, "%i/%i: %s %s\n", error.line, error.column, error.source, error.text); } /* vim: ts=4:expandtab:sw=4 -- 2.7.4