Enhance error reporting
[jansson.git] / src / variadic.c
1 /*
2  * Copyright (c) 2009, 2010 Petri Lehtinen <petri@digip.org>
3  * Copyright (c) 2010 Graeme Smecher <graeme.smecher@mail.mcgill.ca>
4  *
5  * Jansson is free software; you can redistribute it and/or modify
6  * it under the terms of the MIT license. See LICENSE for details.
7  */
8
9 #include <stdarg.h>
10 #include <string.h>
11 #include <assert.h>
12
13 #include <jansson.h>
14 #include "jansson_private.h"
15
16 json_t *json_pack(json_error_t *error, const char *fmt, ...) {
17     int fmt_length = strlen(fmt);
18     va_list ap;
19
20     /* Keep a stack of containers (lists and objects) */
21     int depth = 0;
22     json_t **stack = NULL;
23
24     /* Keep a list of objects we create in case of error */
25     int free_count = 0;
26     json_t **free_list = NULL;
27
28     json_t *cur = NULL; /* Current container */
29     json_t *root = NULL; /* root object */
30     json_t *obj = NULL;
31
32     char *key = NULL; /* Current key in an object */
33     char *s;
34
35     int line = 1;
36
37     /* Allocation provisioned for worst case */
38     stack = calloc(fmt_length, sizeof(json_t *));
39     free_list = calloc(fmt_length, sizeof(json_t *));
40
41     jsonp_error_init(error, "");
42
43     if(!stack || !free_list)
44         goto out;
45
46     va_start(ap, fmt);
47     while(*fmt) {
48         switch(*fmt) {
49             case '\n':
50                 line++;
51                 break;
52
53             case ' ': /* Whitespace */
54                 break;
55
56             case ',': /* Element spacer */
57                 if(!root)
58                 {
59                     jsonp_error_set(error, line, -1,
60                               "Unexpected COMMA precedes root element!");
61                     root = NULL;
62                     goto out;
63                 }
64
65                 if(!cur)
66                 {
67                     jsonp_error_set(error, line, -1,
68                               "Unexpected COMMA outside a list or object!");
69                     root = NULL;
70                     goto out;
71                 }
72
73                 if(key)
74                 {
75                     jsonp_error_set(error, line, -1,
76                               "Expected KEY, got COMMA!");
77                     root = NULL;
78                     goto out;
79                 }
80                 break;
81
82             case ':': /* Key/value separator */
83                 if(!key)
84                 {
85                     jsonp_error_set(error, line, -1,
86                               "Got key/value separator without "
87                               "a key preceding it!");
88                     root = NULL;
89                     goto out;
90                 }
91
92                 if(!json_is_object(cur))
93                 {
94                     jsonp_error_set(error, line, -1,
95                               "Got a key/value separator "
96                               "(':') outside an object!");
97                     root = NULL;
98                     goto out;
99                 }
100
101                 break;
102
103             case ']': /* Close array or object */
104             case '}':
105
106                 if(key)
107                 {
108                     jsonp_error_set(error, line, -1,
109                               "OBJECT or ARRAY ended with an "
110                               "incomplete key/value pair!");
111                     root = NULL;
112                     goto out;
113                 }
114
115                 if(depth <= 0)
116                 {
117                     jsonp_error_set(error, line, -1,
118                               "Too many close-brackets '%c'", *fmt);
119                     root = NULL;
120                     goto out;
121                 }
122
123                 if(*fmt == ']' && !json_is_array(cur))
124                 {
125                     jsonp_error_set(error, line, -1,
126                               "Stray close-array ']' character");
127                     root = NULL;
128                     goto out;
129                 }
130
131                 if(*fmt == '}' && !json_is_object(cur))
132                 {
133                     jsonp_error_set(error, line, -1,
134                               "Stray close-object '}' character");
135                     root = NULL;
136                     goto out;
137                 }
138
139                 cur = stack[--depth];
140                 break;
141
142             case '[':
143                 obj = json_array();
144                 goto obj_common;
145
146             case '{':
147                 obj = json_object();
148                 goto obj_common;
149
150             case 's': /* string */
151                 s = va_arg(ap, char*);
152
153                 if(!s)
154                 {
155                     jsonp_error_set(error, line, -1,
156                               "Refusing to handle a NULL string");
157                     root = NULL;
158                     goto out;
159                 }
160
161                 if(json_is_object(cur) && !key)
162                 {
163                     /* It's a key */
164                     key = s;
165                     break;
166                 }
167
168                 obj = json_string(s);
169                 goto obj_common;
170
171             case 'n': /* null */
172                 obj = json_null();
173                 goto obj_common;
174
175             case 'b': /* boolean */
176                 obj = va_arg(ap, int) ?
177                     json_true() : json_false();
178                 goto obj_common;
179
180             case 'i': /* integer */
181                 obj = json_integer(va_arg(ap, int));
182                 goto obj_common;
183
184             case 'f': /* double-precision float */
185                 obj = json_real(va_arg(ap, double));
186                 goto obj_common;
187
188             case 'O': /* a json_t object; increments refcount */
189                 obj = va_arg(ap, json_t *);
190                 json_incref(obj);
191                 goto obj_common;
192
193             case 'o': /* a json_t object; doesn't increment refcount */
194                 obj = va_arg(ap, json_t *);
195                 goto obj_common;
196
197 obj_common:     free_list[free_count++] = obj;
198
199                 /* Root this object to its parent */
200                 if(json_is_object(cur)) {
201                     if(!key)
202                     {
203                         jsonp_error_set(error, line, -1,
204                               "Expected key, got identifier '%c'!", *fmt);
205                         root = NULL;
206                         goto out;
207                     }
208
209                     json_object_set_new(cur, key, obj);
210                     key = NULL;
211                 }
212                 else if(json_is_array(cur))
213                 {
214                     json_array_append_new(cur, obj);
215                 }
216                 else if(!root)
217                 {
218                     printf("Rooting\n");
219                     root = obj;
220                 }
221                 else
222                 {
223                     jsonp_error_set(error, line, -1,
224                               "Can't figure out where to attach "
225                               "'%c' object!", *fmt);
226                     root = NULL;
227                     goto out;
228                 }
229
230                 /* If it was a container ('[' or '{'), descend on the stack */
231                 if(json_is_array(obj) || json_is_object(obj))
232                 {
233                     stack[depth++] = cur;
234                     cur = obj;
235                 }
236
237                 break;
238         }
239         fmt++;
240     }
241     va_end(ap);
242
243     if(depth != 0) {
244         jsonp_error_set(error, line, -1,
245                 "Missing object or array close-brackets in format string");
246         root = NULL;
247         goto out;
248     }
249
250     /* Success: don't free everything we just built! */
251     free_count = 0;
252
253 out:
254     while(free_count)
255         json_decref(free_list[--free_count]);
256
257     if(free_list)
258         free(free_list);
259
260     if(stack)
261         free(stack);
262
263     return(root);
264 }
265
266 int json_unpack(json_t *root, json_error_t *error, const char *fmt, ...) {
267     va_list ap;
268
269     int rv=0; /* Return value */
270     int line = 1; /* Line number */
271
272     /* Keep a stack of containers (lists and objects) */
273     int depth = 0;
274     json_t **stack;
275
276     int array_index = 0;
277     char *key = NULL; /* Current key in an object */
278
279     json_t *cur = NULL; /* Current container */
280     json_t *obj = NULL;
281
282     int fmt_length = strlen(fmt);
283
284     jsonp_error_init(error, "");
285
286     /* Allocation provisioned for worst case */
287     stack = calloc(fmt_length, sizeof(json_t *));
288     if(!stack)
289     {
290         jsonp_error_set(error, line, -1, "Out of memory!");
291         rv = -1;
292         goto out;
293     }
294
295     /* Even if we're successful, we need to know if the number of
296      * arguments provided matches the number of JSON objects.
297      * We can do this by counting the elements in every array or
298      * object we open up, and decrementing the count as we visit
299      * their children. */
300     int unvisited = 0;
301
302     va_start(ap, fmt);
303     while(*fmt)
304     {
305         switch(*fmt)
306         {
307             case ' ': /* Whitespace */
308                 break;
309
310             case '\n': /* Line break */
311                 line++;
312                 break;
313
314             case ',': /* Element spacer */
315
316                 if(!cur)
317                 {
318                     jsonp_error_set(error, line, -1,
319                               "Unexpected COMMA outside a list or object!");
320                     rv = -1;
321                     goto out;
322                 }
323
324                 if(key)
325                 {
326                     jsonp_error_set(error, line, -1,
327                               "Expected KEY, got COMMA!");
328                     rv = -1;
329                     goto out;
330                 }
331                 break;
332
333             case ':': /* Key/value separator */
334                 if(!json_is_object(cur) || !key)
335                 {
336                     jsonp_error_set(error, line, -1, "Unexpected ':'");
337                     rv = -1;
338                     goto out;
339                 }
340                 break;
341
342             case '[':
343             case '{':
344                 /* Fetch object */
345                 if(!cur)
346                 {
347                     obj = root;
348                 }
349                 else if(json_is_object(cur))
350                 {
351                     if(!key)
352                     {
353                         jsonp_error_set(error, line, -1,
354                               "Objects can't be keys");
355                         rv = -1;
356                         goto out;
357                     }
358                     obj = json_object_get(cur, key);
359                     unvisited--;
360                     key = NULL;
361                 }
362                 else if(json_is_array(cur))
363                 {
364                     obj = json_array_get(cur, array_index);
365                     unvisited--;
366                     array_index++;
367                 }
368                 else
369                 {
370                     assert(0);
371                 }
372
373                 /* Make sure we got what we expected */
374                 if(*fmt=='{' && !json_is_object(obj))
375                 {
376                     rv = -2;
377                     goto out;
378                 }
379
380                 if(*fmt=='[' && !json_is_array(obj))
381                 {
382                     rv = -2;
383                     goto out;
384                 }
385
386                 unvisited += json_is_object(obj) ?
387                     json_object_size(obj) :
388                     json_array_size(obj);
389
390                 /* Descend */
391                 stack[depth++] = cur;
392                 cur = obj;
393
394                 key = NULL;
395
396                 break;
397
398
399             case ']':
400             case '}':
401
402                 if(json_is_array(cur) && *fmt!=']')
403                 {
404                     jsonp_error_set(error, line, -1, "Missing ']'");
405                     rv = -1;
406                     goto out;
407                 }
408
409                 if(json_is_object(cur) && *fmt!='}')
410                 {
411                     jsonp_error_set(error, line, -1, "Missing '}'");
412                     rv = -1;
413                     goto out;
414                 }
415
416                 if(key)
417                 {
418                     jsonp_error_set(error, line, -1, "Unexpected '%c'", *fmt);
419                     rv = -1;
420                     goto out;
421                 }
422
423                 if(depth <= 0)
424                 {
425                     jsonp_error_set(error, line, -1, "Unexpected '%c'", *fmt);
426                     rv = -1;
427                     goto out;
428                 }
429
430                 cur = stack[--depth];
431
432                 break;
433
434             case 's':
435                 if(!key && json_is_object(cur))
436                 {
437                     /* constant string for key */
438                     key = va_arg(ap, char*);
439                     break;
440                 }
441                 /* fall through */
442
443             case 'i': /* integer */
444             case 'f': /* double-precision float */
445             case 'O': /* a json_t object; increments refcount */
446             case 'o': /* a json_t object; borrowed reference */
447             case 'b': /* boolean */
448             case 'n': /* null */
449
450                 /* Fetch object */
451                 if(!cur)
452                 {
453                     obj = root;
454                 }
455                 else if(json_is_object(cur))
456                 {
457                     if(!key)
458                     {
459                         jsonp_error_set(error, line, -1,
460                                   "Only strings may be used as keys!");
461                         rv = -1;
462                         goto out;
463                     }
464
465                     obj = json_object_get(cur, key);
466                     unvisited--;
467                     key = NULL;
468                 }
469                 else if(json_is_array(cur))
470                 {
471                     obj = json_array_get(cur, array_index);
472                     unvisited--;
473                     array_index++;
474                 }
475                 else
476                 {
477                     jsonp_error_set(error, line, -1,
478                               "Unsure how to retrieve JSON object '%c'",
479                               *fmt);
480                     rv = -1;
481                     goto out;
482                 }
483
484                 switch(*fmt)
485                 {
486                     case 's':
487                         if(!json_is_string(obj))
488                         {
489                             jsonp_error_set(error, line, -1,
490                                     "Type mismatch! Object wasn't a string.");
491                             rv = -2;
492                             goto out;
493                         }
494                         *va_arg(ap, const char**) = json_string_value(obj);
495                         break;
496
497                     case 'i':
498                         if(!json_is_integer(obj))
499                         {
500                             jsonp_error_set(error, line, -1,
501                                     "Type mismatch! Object wasn't an integer.");
502                             rv = -2;
503                             goto out;
504                         }
505                         *va_arg(ap, int*) = json_integer_value(obj);
506                         break;
507
508                     case 'b':
509                         if(!json_is_boolean(obj))
510                         {
511                             jsonp_error_set(error, line, -1,
512                                     "Type mismatch! Object wasn't a boolean.");
513                             rv = -2;
514                             goto out;
515                         }
516                         *va_arg(ap, int*) = json_is_true(obj);
517                         break;
518
519                     case 'f':
520                         if(!json_is_number(obj))
521                         {
522                             jsonp_error_set(error, line, -1,
523                                     "Type mismatch! Object wasn't a real.");
524                             rv = -2;
525                             goto out;
526                         }
527                         *va_arg(ap, double*) = json_number_value(obj);
528                         break;
529
530                     case 'O':
531                         json_incref(obj);
532                         /* Fall through */
533
534                     case 'o':
535                         *va_arg(ap, json_t**) = obj;
536                         break;
537
538                     case 'n':
539                         /* Don't actually assign anything; we're just happy
540                          * the null turned up as promised in the format
541                          * string. */
542                         break;
543
544                     default:
545                         jsonp_error_set(error, line, -1,
546                                 "Unknown format character '%c'", *fmt);
547                         rv = -1;
548                         goto out;
549                 }
550         }
551         fmt++;
552     }
553
554     /* Return 0 if everything was matched; otherwise the number of JSON
555      * objects we didn't get to. */
556     rv = unvisited;
557
558 out:
559     va_end(ap);
560
561     if(stack)
562         free(stack);
563
564     return(rv);
565 }
566
567 /* vim: ts=4:expandtab:sw=4
568  */