isomorfeus-iodine 0.7.49 → 0.7.50

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +1 -1
  3. data/CHANGELOG.md +17 -3
  4. data/Rakefile +1 -9
  5. data/examples/etag.ru +16 -0
  6. data/ext/{iodine → iodine_ext}/extconf.rb +1 -1
  7. data/ext/{iodine → iodine_ext}/fio.c +0 -0
  8. data/ext/{iodine → iodine_ext}/fio.h +0 -0
  9. data/ext/{iodine → iodine_ext}/fio_cli.c +0 -0
  10. data/ext/{iodine → iodine_ext}/fio_cli.h +189 -189
  11. data/ext/{iodine → iodine_ext}/fio_json_parser.h +687 -687
  12. data/ext/{iodine → iodine_ext}/fio_siphash.c +157 -157
  13. data/ext/{iodine → iodine_ext}/fio_siphash.h +37 -37
  14. data/ext/{iodine → iodine_ext}/fio_tls.h +129 -129
  15. data/ext/{iodine → iodine_ext}/fio_tls_missing.c +0 -0
  16. data/ext/{iodine → iodine_ext}/fio_tls_openssl.c +0 -0
  17. data/ext/{iodine → iodine_ext}/fio_tmpfile.h +0 -0
  18. data/ext/{iodine → iodine_ext}/fiobj.h +44 -44
  19. data/ext/{iodine → iodine_ext}/fiobj4fio.h +21 -21
  20. data/ext/{iodine → iodine_ext}/fiobj_ary.c +333 -333
  21. data/ext/{iodine → iodine_ext}/fiobj_ary.h +139 -139
  22. data/ext/{iodine → iodine_ext}/fiobj_data.c +0 -0
  23. data/ext/{iodine → iodine_ext}/fiobj_data.h +0 -0
  24. data/ext/{iodine → iodine_ext}/fiobj_hash.c +0 -0
  25. data/ext/{iodine → iodine_ext}/fiobj_hash.h +176 -176
  26. data/ext/{iodine → iodine_ext}/fiobj_json.c +622 -622
  27. data/ext/{iodine → iodine_ext}/fiobj_json.h +68 -68
  28. data/ext/{iodine → iodine_ext}/fiobj_mem.h +71 -71
  29. data/ext/{iodine → iodine_ext}/fiobj_mustache.c +0 -0
  30. data/ext/{iodine → iodine_ext}/fiobj_mustache.h +62 -62
  31. data/ext/{iodine → iodine_ext}/fiobj_numbers.c +0 -0
  32. data/ext/{iodine → iodine_ext}/fiobj_numbers.h +127 -127
  33. data/ext/{iodine → iodine_ext}/fiobj_str.c +0 -0
  34. data/ext/{iodine → iodine_ext}/fiobj_str.h +172 -172
  35. data/ext/{iodine → iodine_ext}/fiobject.c +0 -0
  36. data/ext/{iodine → iodine_ext}/fiobject.h +0 -0
  37. data/ext/{iodine → iodine_ext}/hpack.h +1923 -1923
  38. data/ext/{iodine → iodine_ext}/http.c +14 -27
  39. data/ext/{iodine → iodine_ext}/http.h +1002 -1002
  40. data/ext/{iodine → iodine_ext}/http1.c +0 -0
  41. data/ext/{iodine → iodine_ext}/http1.h +29 -29
  42. data/ext/{iodine → iodine_ext}/http1_parser.h +0 -0
  43. data/ext/{iodine → iodine_ext}/http_internal.c +0 -0
  44. data/ext/{iodine → iodine_ext}/http_internal.h +0 -0
  45. data/ext/{iodine → iodine_ext}/http_mime_parser.h +350 -350
  46. data/ext/{iodine → iodine_ext}/iodine.c +1 -1
  47. data/ext/{iodine → iodine_ext}/iodine.h +0 -0
  48. data/ext/{iodine → iodine_ext}/iodine_caller.c +0 -0
  49. data/ext/{iodine → iodine_ext}/iodine_caller.h +0 -0
  50. data/ext/{iodine → iodine_ext}/iodine_connection.c +0 -0
  51. data/ext/{iodine → iodine_ext}/iodine_connection.h +55 -55
  52. data/ext/{iodine → iodine_ext}/iodine_defer.c +0 -0
  53. data/ext/{iodine → iodine_ext}/iodine_defer.h +6 -6
  54. data/ext/{iodine → iodine_ext}/iodine_fiobj2rb.h +120 -120
  55. data/ext/{iodine → iodine_ext}/iodine_helpers.c +0 -0
  56. data/ext/{iodine → iodine_ext}/iodine_helpers.h +12 -12
  57. data/ext/{iodine → iodine_ext}/iodine_http.c +0 -2
  58. data/ext/{iodine → iodine_ext}/iodine_http.h +23 -23
  59. data/ext/{iodine → iodine_ext}/iodine_json.c +302 -302
  60. data/ext/{iodine → iodine_ext}/iodine_json.h +6 -6
  61. data/ext/{iodine → iodine_ext}/iodine_mustache.c +0 -0
  62. data/ext/{iodine → iodine_ext}/iodine_mustache.h +6 -6
  63. data/ext/{iodine → iodine_ext}/iodine_pubsub.c +0 -0
  64. data/ext/{iodine → iodine_ext}/iodine_pubsub.h +26 -26
  65. data/ext/{iodine → iodine_ext}/iodine_rack_io.c +0 -0
  66. data/ext/{iodine → iodine_ext}/iodine_rack_io.h +20 -20
  67. data/ext/{iodine → iodine_ext}/iodine_store.c +0 -0
  68. data/ext/{iodine → iodine_ext}/iodine_store.h +20 -20
  69. data/ext/{iodine → iodine_ext}/iodine_tcp.c +0 -0
  70. data/ext/{iodine → iodine_ext}/iodine_tcp.h +0 -0
  71. data/ext/{iodine → iodine_ext}/iodine_tls.c +0 -0
  72. data/ext/{iodine → iodine_ext}/iodine_tls.h +13 -13
  73. data/ext/{iodine → iodine_ext}/mustache_parser.h +0 -0
  74. data/ext/{iodine → iodine_ext}/redis_engine.c +0 -0
  75. data/ext/{iodine → iodine_ext}/redis_engine.h +0 -0
  76. data/ext/{iodine → iodine_ext}/resp_parser.h +0 -0
  77. data/ext/{iodine → iodine_ext}/websocket_parser.h +505 -505
  78. data/ext/{iodine → iodine_ext}/websockets.c +0 -0
  79. data/ext/{iodine → iodine_ext}/websockets.h +185 -185
  80. data/isomorfeus-iodine.gemspec +1 -2
  81. data/lib/iodine/version.rb +1 -1
  82. data/lib/iodine.rb +1 -1
  83. metadata +79 -78
@@ -1,622 +1,622 @@
1
- /*
2
- Copyright: Boaz Segev, 2017-2019
3
- License: MIT
4
- */
5
- #include <fiobj_json.h>
6
- #define FIO_ARY_NAME fio_json_stack
7
- #define FIO_ARY_TYPE FIOBJ
8
- #include <fio.h>
9
-
10
- #include <fio_json_parser.h>
11
-
12
- #include <assert.h>
13
- #include <ctype.h>
14
- #include <math.h>
15
- #include <stdlib.h>
16
- #include <string.h>
17
-
18
- /* *****************************************************************************
19
- JSON API
20
- ***************************************************************************** */
21
-
22
- /**
23
- * Parses JSON, setting `pobj` to point to the new Object.
24
- *
25
- * Returns the number of bytes consumed. On Error, 0 is returned and no data is
26
- * consumed.
27
- */
28
- size_t fiobj_json2obj(FIOBJ *pobj, const void *data, size_t len);
29
- /* Formats an object into a JSON string. Remember to `fiobj_free`. */
30
- FIOBJ fiobj_obj2json(FIOBJ, uint8_t);
31
-
32
- /* *****************************************************************************
33
- FIOBJ Parser
34
- ***************************************************************************** */
35
-
36
- typedef struct {
37
- json_parser_s p;
38
- FIOBJ key;
39
- FIOBJ top;
40
- FIOBJ target;
41
- fio_json_stack_s stack;
42
- uint8_t is_hash;
43
- } fiobj_json_parser_s;
44
-
45
- /* *****************************************************************************
46
- FIOBJ Callacks
47
- ***************************************************************************** */
48
-
49
- static inline void fiobj_json_add2parser(fiobj_json_parser_s *p, FIOBJ o) {
50
- if (p->top) {
51
- if (p->is_hash) {
52
- if (p->key) {
53
- fiobj_hash_set(p->top, p->key, o);
54
- fiobj_free(p->key);
55
- p->key = FIOBJ_INVALID;
56
- } else {
57
- p->key = o;
58
- }
59
- } else {
60
- fiobj_ary_push(p->top, o);
61
- }
62
- } else {
63
- p->top = o;
64
- }
65
- }
66
-
67
- /** a NULL object was detected */
68
- static void fio_json_on_null(json_parser_s *p) {
69
- fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_null());
70
- }
71
- /** a TRUE object was detected */
72
- static void fio_json_on_true(json_parser_s *p) {
73
- fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_true());
74
- }
75
- /** a FALSE object was detected */
76
- static void fio_json_on_false(json_parser_s *p) {
77
- fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_false());
78
- }
79
- /** a Numberl was detected (long long). */
80
- static void fio_json_on_number(json_parser_s *p, long long i) {
81
- fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_num_new(i));
82
- }
83
- /** a Float was detected (double). */
84
- static void fio_json_on_float(json_parser_s *p, double f) {
85
- fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_float_new(f));
86
- }
87
- /** a String was detected (int / float). update `pos` to point at ending */
88
- static void fio_json_on_string(json_parser_s *p, void *start, size_t length) {
89
- FIOBJ str = fiobj_str_buf(length);
90
- fiobj_str_resize(
91
- str, fio_json_unescape_str(fiobj_obj2cstr(str).data, start, length));
92
- fiobj_json_add2parser((fiobj_json_parser_s *)p, str);
93
- }
94
- /** a dictionary object was detected */
95
- static int fio_json_on_start_object(json_parser_s *p) {
96
- fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
97
- if (pr->target) {
98
- /* push NULL, don't free the objects */
99
- fio_json_stack_push(&pr->stack, pr->top);
100
- pr->top = pr->target;
101
- pr->target = FIOBJ_INVALID;
102
- } else {
103
- FIOBJ hash = fiobj_hash_new();
104
- fiobj_json_add2parser(pr, hash);
105
- fio_json_stack_push(&pr->stack, pr->top);
106
- pr->top = hash;
107
- }
108
- pr->is_hash = 1;
109
- return 0;
110
- }
111
- /** a dictionary object closure detected */
112
- static void fio_json_on_end_object(json_parser_s *p) {
113
- fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
114
- if (pr->key) {
115
- FIO_LOG_WARNING("(JSON parsing) malformed JSON, "
116
- "ignoring dangling Hash key.");
117
- fiobj_free(pr->key);
118
- pr->key = FIOBJ_INVALID;
119
- }
120
- pr->top = FIOBJ_INVALID;
121
- fio_json_stack_pop(&pr->stack, &pr->top);
122
- pr->is_hash = FIOBJ_TYPE_IS(pr->top, FIOBJ_T_HASH);
123
- }
124
- /** an array object was detected */
125
- static int fio_json_on_start_array(json_parser_s *p) {
126
- fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
127
- if (pr->target)
128
- return -1;
129
- FIOBJ ary = fiobj_ary_new();
130
- fiobj_json_add2parser(pr, ary);
131
- fio_json_stack_push(&pr->stack, pr->top);
132
- pr->top = ary;
133
- pr->is_hash = 0;
134
- return 0;
135
- }
136
- /** an array closure was detected */
137
- static void fio_json_on_end_array(json_parser_s *p) {
138
- fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
139
- pr->top = FIOBJ_INVALID;
140
- fio_json_stack_pop(&pr->stack, &pr->top);
141
- pr->is_hash = FIOBJ_TYPE_IS(pr->top, FIOBJ_T_HASH);
142
- }
143
- /** the JSON parsing is complete */
144
- static void fio_json_on_json(json_parser_s *p) {
145
- // fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
146
- // FIO_ARY_FOR(&pr->stack, pos) { fiobj_free((FIOBJ)pos.obj); }
147
- // fio_json_stack_free(&pr->stack);
148
- (void)p; /* nothing special... right? */
149
- }
150
- /** the JSON parsing is complete */
151
- static void fio_json_on_error(json_parser_s *p) {
152
- fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
153
- #if DEBUG
154
- FIO_LOG_DEBUG("JSON on error called.");
155
- #endif
156
- fiobj_free((FIOBJ)fio_json_stack_get(&pr->stack, 0));
157
- fiobj_free(pr->key);
158
- fio_json_stack_free(&pr->stack);
159
- *pr = (fiobj_json_parser_s){.top = FIOBJ_INVALID};
160
- }
161
-
162
- /* *****************************************************************************
163
- JSON formatting
164
- ***************************************************************************** */
165
-
166
- /** Writes a JSON friendly version of the src String */
167
- static void write_safe_str(FIOBJ dest, const FIOBJ str) {
168
- fio_str_info_s s = fiobj_obj2cstr(str);
169
- fio_str_info_s t = fiobj_obj2cstr(dest);
170
- t.data[t.len] = '"';
171
- t.len++;
172
- fiobj_str_resize(dest, t.len);
173
- t = fiobj_obj2cstr(dest);
174
- const uint8_t *restrict src = (const uint8_t *)s.data;
175
- size_t len = s.len;
176
- uint64_t end = t.len;
177
- /* make sure we have some room */
178
- size_t added = 0;
179
- size_t capa = fiobj_str_capa(dest);
180
- if (capa <= end + s.len + 64) {
181
- if (0) {
182
- capa = (((capa >> 12) + 1) << 12) - 1;
183
- capa = fiobj_str_capa_assert(dest, capa);
184
- } else {
185
- capa = fiobj_str_capa_assert(dest, (end + s.len + 64));
186
- }
187
- fio_str_info_s tmp = fiobj_obj2cstr(dest);
188
- t = tmp;
189
- }
190
- while (len) {
191
- char *restrict writer = (char *)t.data;
192
- while (len && src[0] > 32 && src[0] != '"' && src[0] != '\\') {
193
- len--;
194
- writer[end++] = *(src++);
195
- }
196
- if (!len)
197
- break;
198
- switch (src[0]) {
199
- case '\b':
200
- writer[end++] = '\\';
201
- writer[end++] = 'b';
202
- added++;
203
- break; /* from switch */
204
- case '\f':
205
- writer[end++] = '\\';
206
- writer[end++] = 'f';
207
- added++;
208
- break; /* from switch */
209
- case '\n':
210
- writer[end++] = '\\';
211
- writer[end++] = 'n';
212
- added++;
213
- break; /* from switch */
214
- case '\r':
215
- writer[end++] = '\\';
216
- writer[end++] = 'r';
217
- added++;
218
- break; /* from switch */
219
- case '\t':
220
- writer[end++] = '\\';
221
- writer[end++] = 't';
222
- added++;
223
- break; /* from switch */
224
- case '"':
225
- case '\\':
226
- case '/':
227
- writer[end++] = '\\';
228
- writer[end++] = src[0];
229
- added++;
230
- break; /* from switch */
231
- default:
232
- if (src[0] <= 31) {
233
- /* MUST escape all control values less than 32 */
234
- writer[end++] = '\\';
235
- writer[end++] = 'u';
236
- writer[end++] = '0';
237
- writer[end++] = '0';
238
- writer[end++] = hex_chars[src[0] >> 4];
239
- writer[end++] = hex_chars[src[0] & 15];
240
- added += 4;
241
- } else
242
- writer[end++] = src[0];
243
- break; /* from switch */
244
- }
245
- src++;
246
- len--;
247
- if (added >= 48 && capa <= end + len + 64) {
248
- writer[end] = 0;
249
- fiobj_str_resize(dest, end);
250
- fiobj_str_capa_assert(dest, (end + len + 64));
251
- t = fiobj_obj2cstr(dest);
252
- writer = (char *)t.data;
253
- capa = t.capa;
254
- added = 0;
255
- }
256
- }
257
- t.data[end++] = '"';
258
- fiobj_str_resize(dest, end);
259
- }
260
-
261
- typedef struct {
262
- FIOBJ dest;
263
- FIOBJ parent;
264
- fio_json_stack_s *stack;
265
- uintptr_t count;
266
- uint8_t pretty;
267
- } obj2json_data_s;
268
-
269
- static int fiobj_obj2json_task(FIOBJ o, void *data_) {
270
- obj2json_data_s *data = data_;
271
- uint8_t add_seperator = 1;
272
- if (fiobj_hash_key_in_loop()) {
273
- write_safe_str(data->dest, fiobj_hash_key_in_loop());
274
- fiobj_str_write(data->dest, ":", 1);
275
- }
276
- switch (FIOBJ_TYPE(o)) {
277
- case FIOBJ_T_NUMBER:
278
- case FIOBJ_T_NULL:
279
- case FIOBJ_T_TRUE:
280
- case FIOBJ_T_FALSE:
281
- case FIOBJ_T_FLOAT:
282
- fiobj_str_join(data->dest, o);
283
- --data->count;
284
- break;
285
-
286
- case FIOBJ_T_DATA:
287
- case FIOBJ_T_UNKNOWN:
288
- case FIOBJ_T_STRING:
289
- write_safe_str(data->dest, o);
290
- --data->count;
291
- break;
292
-
293
- case FIOBJ_T_ARRAY:
294
- --data->count;
295
- fio_json_stack_push(data->stack, data->parent);
296
- fio_json_stack_push(data->stack, (FIOBJ)data->count);
297
- data->parent = o;
298
- data->count = fiobj_ary_count(o);
299
- fiobj_str_write(data->dest, "[", 1);
300
- add_seperator = 0;
301
- break;
302
-
303
- case FIOBJ_T_HASH:
304
- --data->count;
305
- fio_json_stack_push(data->stack, data->parent);
306
- fio_json_stack_push(data->stack, (FIOBJ)data->count);
307
- data->parent = o;
308
- data->count = fiobj_hash_count(o);
309
- fiobj_str_write(data->dest, "{", 1);
310
- add_seperator = 0;
311
- break;
312
- }
313
- if (data->pretty) {
314
- fiobj_str_capa_assert(data->dest,
315
- fiobj_obj2cstr(data->dest).len +
316
- (fio_json_stack_count(data->stack) * 5));
317
- while (!data->count && data->parent) {
318
- if (FIOBJ_TYPE_IS(data->parent, FIOBJ_T_HASH)) {
319
- fiobj_str_write(data->dest, "}", 1);
320
- } else {
321
- fiobj_str_write(data->dest, "]", 1);
322
- }
323
- add_seperator = 1;
324
- data->count = 0;
325
- fio_json_stack_pop(data->stack, &data->count);
326
- data->parent = FIOBJ_INVALID;
327
- fio_json_stack_pop(data->stack, &data->parent);
328
- }
329
-
330
- if (add_seperator && data->parent) {
331
- fiobj_str_write(data->dest, ",\n", 2);
332
- uintptr_t indent = fio_json_stack_count(data->stack) - 1;
333
- fiobj_str_capa_assert(data->dest,
334
- fiobj_obj2cstr(data->dest).len + (indent * 2));
335
- fio_str_info_s buf = fiobj_obj2cstr(data->dest);
336
- while (indent--) {
337
- buf.data[buf.len++] = ' ';
338
- buf.data[buf.len++] = ' ';
339
- }
340
- fiobj_str_resize(data->dest, buf.len);
341
- }
342
- } else {
343
- fiobj_str_capa_assert(data->dest,
344
- fiobj_obj2cstr(data->dest).len +
345
- (fio_json_stack_count(data->stack) << 1));
346
- while (!data->count && data->parent) {
347
- if (FIOBJ_TYPE_IS(data->parent, FIOBJ_T_HASH)) {
348
- fiobj_str_write(data->dest, "}", 1);
349
- } else {
350
- fiobj_str_write(data->dest, "]", 1);
351
- }
352
- add_seperator = 1;
353
- data->count = 0;
354
- data->parent = FIOBJ_INVALID;
355
- fio_json_stack_pop(data->stack, &data->count);
356
- fio_json_stack_pop(data->stack, &data->parent);
357
- }
358
-
359
- if (add_seperator && data->parent) {
360
- fiobj_str_write(data->dest, ",", 1);
361
- }
362
- }
363
-
364
- return 0;
365
- }
366
-
367
- /* *****************************************************************************
368
- FIOBJ API
369
- ***************************************************************************** */
370
-
371
- /**
372
- * Parses JSON, setting `pobj` to point to the new Object.
373
- *
374
- * Returns the number of bytes consumed. On Error, 0 is returned and no data is
375
- * consumed.
376
- */
377
- size_t fiobj_json2obj(FIOBJ *pobj, const void *data, size_t len) {
378
- fiobj_json_parser_s p = {.top = FIOBJ_INVALID};
379
- size_t consumed = fio_json_parse(&p.p, data, len);
380
- if (!consumed || p.p.depth) {
381
- fiobj_free(fio_json_stack_get(&p.stack, 0));
382
- p.top = FIOBJ_INVALID;
383
- }
384
- fio_json_stack_free(&p.stack);
385
- fiobj_free(p.key);
386
- *pobj = p.top;
387
- return consumed;
388
- }
389
-
390
- /**
391
- * Updates a Hash using JSON data.
392
- *
393
- * Parsing errors and non-dictionar object JSON data are silently ignored,
394
- * attempting to update the Hash as much as possible before any errors
395
- * encountered.
396
- *
397
- * Conflicting Hash data is overwritten (prefering the new over the old).
398
- *
399
- * Returns the number of bytes consumed. On Error, 0 is returned and no data is
400
- * consumed.
401
- */
402
- size_t fiobj_hash_update_json(FIOBJ hash, const void *data, size_t len) {
403
- if (!hash)
404
- return 0;
405
- fiobj_json_parser_s p = {.top = FIOBJ_INVALID, .target = hash};
406
- size_t consumed = fio_json_parse(&p.p, data, len);
407
- fio_json_stack_free(&p.stack);
408
- fiobj_free(p.key);
409
- if (p.top != hash)
410
- fiobj_free(p.top);
411
- return consumed;
412
- }
413
-
414
- /**
415
- * Formats an object into a JSON string, appending the JSON string to an
416
- * existing String. Remember to `fiobj_free`.
417
- */
418
- FIOBJ fiobj_obj2json2(FIOBJ dest, FIOBJ o, uint8_t pretty) {
419
- assert(dest && FIOBJ_TYPE_IS(dest, FIOBJ_T_STRING));
420
- if (!o) {
421
- fiobj_str_write(dest, "null", 4);
422
- return 0;
423
- }
424
- fio_json_stack_s stack = FIO_ARY_INIT;
425
- obj2json_data_s data = {
426
- .dest = dest,
427
- .stack = &stack,
428
- .pretty = pretty,
429
- .count = 1,
430
- };
431
- if (!o || !FIOBJ_IS_ALLOCATED(o) || !FIOBJECT2VTBL(o)->each) {
432
- fiobj_obj2json_task(o, &data);
433
- return dest;
434
- }
435
- fiobj_each2(o, fiobj_obj2json_task, &data);
436
- fio_json_stack_free(&stack);
437
- return dest;
438
- }
439
-
440
- /* Formats an object into a JSON string. Remember to `fiobj_free`. */
441
- FIOBJ fiobj_obj2json(FIOBJ obj, uint8_t pretty) {
442
- return fiobj_obj2json2(fiobj_str_buf(128), obj, pretty);
443
- }
444
-
445
- /* *****************************************************************************
446
- Test
447
- ***************************************************************************** */
448
-
449
- #if DEBUG
450
- void fiobj_test_json(void) {
451
- fprintf(stderr, "=== Testing JSON parser (simple test)\n");
452
- #define TEST_ASSERT(cond, ...) \
453
- if (!(cond)) { \
454
- fprintf(stderr, "* " __VA_ARGS__); \
455
- fprintf(stderr, "\n !!! Testing failed !!!\n"); \
456
- exit(-1); \
457
- }
458
- char json_str[] = "{\"array\":[1,2,3,\"boom\"],\"my\":{\"secret\":42},"
459
- "\"true\":true,\"false\":false,\"null\":null,\"float\":-2."
460
- "2,\"string\":\"I \\\"wrote\\\" this.\"}";
461
- char json_str_update[] = "{\"array\":[1,2,3]}";
462
- char json_str2[] =
463
- "[\n \"JSON Test Pattern pass1\",\n {\"object with 1 "
464
- "member\":[\"array with 1 element\"]},\n {},\n [],\n -42,\n "
465
- "true,\n false,\n null,\n {\n \"integer\": 1234567890,\n "
466
- " \"real\": -9876.543210,\n \"e\": 0.123456789e-12,\n "
467
- " \"E\": 1.234567890E+34,\n \"\": 23456789012E66,\n "
468
- "\"zero\": 0,\n \"one\": 1,\n \"space\": \" \",\n "
469
- "\"quote\": \"\\\"\",\n \"backslash\": \"\\\\\",\n "
470
- "\"controls\": \"\\b\\f\\n\\r\\t\",\n \"slash\": \"/ & \\/\",\n "
471
- " \"alpha\": \"abcdefghijklmnopqrstuvwyz\",\n \"ALPHA\": "
472
- "\"ABCDEFGHIJKLMNOPQRSTUVWYZ\",\n \"digit\": \"0123456789\",\n "
473
- " \"0123456789\": \"digit\",\n \"special\": "
474
- "\"`1~!@#$%^&*()_+-={':[,]}|;.</>?\",\n \"hex\": "
475
- "\"\\u0123\\u4567\\u89AB\\uCDEF\\uabcd\\uef4A\",\n \"true\": "
476
- "true,\n \"false\": false,\n \"null\": null,\n "
477
- "\"array\":[ ],\n \"object\":{ },\n \"address\": \"50 "
478
- "St. James Street\",\n \"url\": \"http://www.JSON.org/\",\n "
479
- " \"comment\": \"// /* <!-- --\",\n \"# -- --> */\": \" \",\n "
480
- " \" s p a c e d \" :[1,2 , 3\n\n,\n\n4 , 5 , 6 "
481
- " ,7 ],\"compact\":[1,2,3,4,5,6,7],\n \"jsontext\": "
482
- "\"{\\\"object with 1 member\\\":[\\\"array with 1 element\\\"]}\",\n "
483
- " \"quotes\": \"&#34; \\u0022 %22 0x22 034 &#x22;\",\n "
484
- "\"\\/"
485
- "\\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t`1~!@#$"
486
- "%^&*()_+-=[]{}|;:',./<>?\"\n: \"A key can be any string\"\n },\n "
487
- "0.5 "
488
- ",98.6\n,\n99.44\n,\n\n1066,\n1e1,\n0.1e1,\n1e-1,\n1e00,2e+00,2e-00\n,"
489
- "\"rosebud\"]";
490
-
491
- FIOBJ o = 0;
492
- TEST_ASSERT(fiobj_json2obj(&o, "1", 2) == 1,
493
- "JSON number parsing failed to run!\n");
494
- TEST_ASSERT(o, "JSON (single) object missing!\n");
495
- TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_NUMBER),
496
- "JSON (single) not a number!\n");
497
- TEST_ASSERT(fiobj_obj2num(o) == 1, "JSON (single) not == 1!\n");
498
- fiobj_free(o);
499
-
500
- TEST_ASSERT(fiobj_json2obj(&o, "2.0", 5) == 3,
501
- "JSON float parsing failed to run!\n");
502
- TEST_ASSERT(o, "JSON (float) object missing!\n");
503
- TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_FLOAT), "JSON (float) not a float!\n");
504
- TEST_ASSERT(fiobj_obj2float(o) == 2, "JSON (float) not == 2!\n");
505
- fiobj_free(o);
506
-
507
- TEST_ASSERT(fiobj_json2obj(&o, json_str, sizeof(json_str)) ==
508
- (sizeof(json_str) - 1),
509
- "JSON parsing failed to run!\n");
510
- TEST_ASSERT(o, "JSON object missing!\n");
511
- TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_HASH),
512
- "JSON root not a dictionary (not a hash)!\n");
513
- FIOBJ tmp = fiobj_hash_get2(o, fiobj_hash_string("array", 5));
514
- TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_ARRAY),
515
- "JSON 'array' not an Array!\n");
516
- TEST_ASSERT(fiobj_obj2num(fiobj_ary_index(tmp, 0)) == 1,
517
- "JSON 'array' index 0 error!\n");
518
- TEST_ASSERT(fiobj_obj2num(fiobj_ary_index(tmp, 1)) == 2,
519
- "JSON 'array' index 1 error!\n");
520
- TEST_ASSERT(fiobj_obj2num(fiobj_ary_index(tmp, 2)) == 3,
521
- "JSON 'array' index 2 error!\n");
522
- TEST_ASSERT(FIOBJ_TYPE_IS(fiobj_ary_index(tmp, 3), FIOBJ_T_STRING),
523
- "JSON 'array' index 3 type error!\n");
524
- TEST_ASSERT(!memcmp("boom", fiobj_obj2cstr(fiobj_ary_index(tmp, 3)).data, 4),
525
- "JSON 'array' index 3 error!\n");
526
- tmp = fiobj_hash_get2(o, fiobj_hash_string("my", 2));
527
- TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_HASH),
528
- "JSON 'my:secret' not a Hash!\n");
529
- TEST_ASSERT(
530
- FIOBJ_TYPE_IS(fiobj_hash_get2(tmp, fiobj_hash_string("secret", 6)),
531
- FIOBJ_T_NUMBER),
532
- "JSON 'my:secret' doesn't hold a number!\n");
533
- TEST_ASSERT(
534
- fiobj_obj2num(fiobj_hash_get2(tmp, fiobj_hash_string("secret", 6))) == 42,
535
- "JSON 'my:secret' not 42!\n");
536
- TEST_ASSERT(fiobj_hash_get2(o, fiobj_hash_string("true", 4)) == fiobj_true(),
537
- "JSON 'true' not true!\n");
538
- TEST_ASSERT(fiobj_hash_get2(o, fiobj_hash_string("false", 5)) ==
539
- fiobj_false(),
540
- "JSON 'false' not false!\n");
541
- TEST_ASSERT(fiobj_hash_get2(o, fiobj_hash_string("null", 4)) == fiobj_null(),
542
- "JSON 'null' not null!\n");
543
- tmp = fiobj_hash_get2(o, fiobj_hash_string("float", 5));
544
- TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_FLOAT), "JSON 'float' not a float!\n");
545
- tmp = fiobj_hash_get2(o, fiobj_hash_string("string", 6));
546
- TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_STRING),
547
- "JSON 'string' not a string!\n");
548
- TEST_ASSERT(!strcmp(fiobj_obj2cstr(tmp).data, "I \"wrote\" this."),
549
- "JSON 'string' incorrect!\n");
550
- fprintf(stderr, "* passed.\n");
551
- fprintf(stderr, "=== Testing JSON formatting (simple test)\n");
552
- tmp = fiobj_obj2json(o, 0);
553
- fprintf(stderr, "* data (%p):\n%.*s\n", (void *)fiobj_obj2cstr(tmp).data,
554
- (int)fiobj_obj2cstr(tmp).len, fiobj_obj2cstr(tmp).data);
555
- if (!strcmp(fiobj_obj2cstr(tmp).data, json_str))
556
- fprintf(stderr, "* Stringify == Original.\n");
557
- TEST_ASSERT(
558
- fiobj_hash_update_json(o, json_str_update, strlen(json_str_update)),
559
- "JSON update failed to parse data.");
560
- fiobj_free(tmp);
561
-
562
- tmp = fiobj_hash_get2(o, fiobj_hash_string("array", 5));
563
- TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_ARRAY),
564
- "JSON updated 'array' not an Array!\n");
565
- TEST_ASSERT(fiobj_ary_count(tmp) == 3, "JSON updated 'array' not updated?");
566
- tmp = fiobj_hash_get2(o, fiobj_hash_string("float", 5));
567
- TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_FLOAT),
568
- "JSON updated (old) 'float' missing!\n");
569
- fiobj_free(o);
570
- fprintf(stderr, "* passed.\n");
571
-
572
- fprintf(stderr, "=== Testing JSON parsing (UTF-8 and special cases)\n");
573
- fiobj_json2obj(&o, "[\"\\uD834\\uDD1E\"]", 16);
574
- TEST_ASSERT(o, "JSON G clef String failed to parse!\n");
575
- TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY),
576
- "JSON G clef container has an incorrect type! (%s)\n",
577
- fiobj_type_name(o));
578
- tmp = o;
579
- o = fiobj_ary_pop(o);
580
- fiobj_free(tmp);
581
- TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING),
582
- "JSON G clef String incorrect type! %p => %s\n", (void *)o,
583
- fiobj_type_name(o));
584
- TEST_ASSERT((!strcmp(fiobj_obj2cstr(o).data, "\xF0\x9D\x84\x9E")),
585
- "JSON G clef String incorrect %s !\n", fiobj_obj2cstr(o).data);
586
- fiobj_free(o);
587
-
588
- fiobj_json2obj(&o, "\"\\uD834\\uDD1E\"", 14);
589
- TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING),
590
- "JSON direct G clef String incorrect type! %p => %s\n", (void *)o,
591
- fiobj_type_name(o));
592
- TEST_ASSERT((!strcmp(fiobj_obj2cstr(o).data, "\xF0\x9D\x84\x9E")),
593
- "JSON direct G clef String incorrect %s !\n",
594
- fiobj_obj2cstr(o).data);
595
- fiobj_free(o);
596
- fiobj_json2obj(&o, "\"Hello\\u0000World\"", 19);
597
- TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING),
598
- "JSON NUL containing String incorrect type! %p => %s\n",
599
- (void *)o, fiobj_type_name(o));
600
- TEST_ASSERT(
601
- (!memcmp(fiobj_obj2cstr(o).data, "Hello\0World", fiobj_obj2cstr(o).len)),
602
- "JSON NUL containing String incorrect! (%u): %s . %s\n",
603
- (int)fiobj_obj2cstr(o).len, fiobj_obj2cstr(o).data,
604
- fiobj_obj2cstr(o).data + 3);
605
- fiobj_free(o);
606
- size_t consumed = fiobj_json2obj(&o, json_str2, sizeof(json_str2));
607
- TEST_ASSERT(
608
- consumed == (sizeof(json_str2) - 1),
609
- "JSON messy string failed to parse (consumed %lu instead of %lu\n",
610
- (unsigned long)consumed, (unsigned long)(sizeof(json_str2) - 1));
611
- TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY),
612
- "JSON messy string object error\n");
613
- tmp = fiobj_obj2json(o, 1);
614
- TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_STRING),
615
- "JSON messy string isn't a string\n");
616
- fprintf(stderr, "Messy JSON:\n%s\n", fiobj_obj2cstr(tmp).data);
617
- fiobj_free(o);
618
- fiobj_free(tmp);
619
- fprintf(stderr, "* passed.\n");
620
- }
621
-
622
- #endif
1
+ /*
2
+ Copyright: Boaz Segev, 2017-2019
3
+ License: MIT
4
+ */
5
+ #include <fiobj_json.h>
6
+ #define FIO_ARY_NAME fio_json_stack
7
+ #define FIO_ARY_TYPE FIOBJ
8
+ #include <fio.h>
9
+
10
+ #include <fio_json_parser.h>
11
+
12
+ #include <assert.h>
13
+ #include <ctype.h>
14
+ #include <math.h>
15
+ #include <stdlib.h>
16
+ #include <string.h>
17
+
18
+ /* *****************************************************************************
19
+ JSON API
20
+ ***************************************************************************** */
21
+
22
+ /**
23
+ * Parses JSON, setting `pobj` to point to the new Object.
24
+ *
25
+ * Returns the number of bytes consumed. On Error, 0 is returned and no data is
26
+ * consumed.
27
+ */
28
+ size_t fiobj_json2obj(FIOBJ *pobj, const void *data, size_t len);
29
+ /* Formats an object into a JSON string. Remember to `fiobj_free`. */
30
+ FIOBJ fiobj_obj2json(FIOBJ, uint8_t);
31
+
32
+ /* *****************************************************************************
33
+ FIOBJ Parser
34
+ ***************************************************************************** */
35
+
36
+ typedef struct {
37
+ json_parser_s p;
38
+ FIOBJ key;
39
+ FIOBJ top;
40
+ FIOBJ target;
41
+ fio_json_stack_s stack;
42
+ uint8_t is_hash;
43
+ } fiobj_json_parser_s;
44
+
45
+ /* *****************************************************************************
46
+ FIOBJ Callacks
47
+ ***************************************************************************** */
48
+
49
+ static inline void fiobj_json_add2parser(fiobj_json_parser_s *p, FIOBJ o) {
50
+ if (p->top) {
51
+ if (p->is_hash) {
52
+ if (p->key) {
53
+ fiobj_hash_set(p->top, p->key, o);
54
+ fiobj_free(p->key);
55
+ p->key = FIOBJ_INVALID;
56
+ } else {
57
+ p->key = o;
58
+ }
59
+ } else {
60
+ fiobj_ary_push(p->top, o);
61
+ }
62
+ } else {
63
+ p->top = o;
64
+ }
65
+ }
66
+
67
+ /** a NULL object was detected */
68
+ static void fio_json_on_null(json_parser_s *p) {
69
+ fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_null());
70
+ }
71
+ /** a TRUE object was detected */
72
+ static void fio_json_on_true(json_parser_s *p) {
73
+ fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_true());
74
+ }
75
+ /** a FALSE object was detected */
76
+ static void fio_json_on_false(json_parser_s *p) {
77
+ fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_false());
78
+ }
79
+ /** a Numberl was detected (long long). */
80
+ static void fio_json_on_number(json_parser_s *p, long long i) {
81
+ fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_num_new(i));
82
+ }
83
+ /** a Float was detected (double). */
84
+ static void fio_json_on_float(json_parser_s *p, double f) {
85
+ fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_float_new(f));
86
+ }
87
+ /** a String was detected (int / float). update `pos` to point at ending */
88
+ static void fio_json_on_string(json_parser_s *p, void *start, size_t length) {
89
+ FIOBJ str = fiobj_str_buf(length);
90
+ fiobj_str_resize(
91
+ str, fio_json_unescape_str(fiobj_obj2cstr(str).data, start, length));
92
+ fiobj_json_add2parser((fiobj_json_parser_s *)p, str);
93
+ }
94
+ /** a dictionary object was detected */
95
+ static int fio_json_on_start_object(json_parser_s *p) {
96
+ fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
97
+ if (pr->target) {
98
+ /* push NULL, don't free the objects */
99
+ fio_json_stack_push(&pr->stack, pr->top);
100
+ pr->top = pr->target;
101
+ pr->target = FIOBJ_INVALID;
102
+ } else {
103
+ FIOBJ hash = fiobj_hash_new();
104
+ fiobj_json_add2parser(pr, hash);
105
+ fio_json_stack_push(&pr->stack, pr->top);
106
+ pr->top = hash;
107
+ }
108
+ pr->is_hash = 1;
109
+ return 0;
110
+ }
111
+ /** a dictionary object closure detected */
112
+ static void fio_json_on_end_object(json_parser_s *p) {
113
+ fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
114
+ if (pr->key) {
115
+ FIO_LOG_WARNING("(JSON parsing) malformed JSON, "
116
+ "ignoring dangling Hash key.");
117
+ fiobj_free(pr->key);
118
+ pr->key = FIOBJ_INVALID;
119
+ }
120
+ pr->top = FIOBJ_INVALID;
121
+ fio_json_stack_pop(&pr->stack, &pr->top);
122
+ pr->is_hash = FIOBJ_TYPE_IS(pr->top, FIOBJ_T_HASH);
123
+ }
124
+ /** an array object was detected */
125
+ static int fio_json_on_start_array(json_parser_s *p) {
126
+ fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
127
+ if (pr->target)
128
+ return -1;
129
+ FIOBJ ary = fiobj_ary_new();
130
+ fiobj_json_add2parser(pr, ary);
131
+ fio_json_stack_push(&pr->stack, pr->top);
132
+ pr->top = ary;
133
+ pr->is_hash = 0;
134
+ return 0;
135
+ }
136
+ /** an array closure was detected */
137
+ static void fio_json_on_end_array(json_parser_s *p) {
138
+ fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
139
+ pr->top = FIOBJ_INVALID;
140
+ fio_json_stack_pop(&pr->stack, &pr->top);
141
+ pr->is_hash = FIOBJ_TYPE_IS(pr->top, FIOBJ_T_HASH);
142
+ }
143
+ /** the JSON parsing is complete */
144
+ static void fio_json_on_json(json_parser_s *p) {
145
+ // fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
146
+ // FIO_ARY_FOR(&pr->stack, pos) { fiobj_free((FIOBJ)pos.obj); }
147
+ // fio_json_stack_free(&pr->stack);
148
+ (void)p; /* nothing special... right? */
149
+ }
150
+ /** the JSON parsing is complete */
151
+ static void fio_json_on_error(json_parser_s *p) {
152
+ fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
153
+ #if DEBUG
154
+ FIO_LOG_DEBUG("JSON on error called.");
155
+ #endif
156
+ fiobj_free((FIOBJ)fio_json_stack_get(&pr->stack, 0));
157
+ fiobj_free(pr->key);
158
+ fio_json_stack_free(&pr->stack);
159
+ *pr = (fiobj_json_parser_s){.top = FIOBJ_INVALID};
160
+ }
161
+
162
+ /* *****************************************************************************
163
+ JSON formatting
164
+ ***************************************************************************** */
165
+
166
+ /** Writes a JSON friendly version of the src String */
167
+ static void write_safe_str(FIOBJ dest, const FIOBJ str) {
168
+ fio_str_info_s s = fiobj_obj2cstr(str);
169
+ fio_str_info_s t = fiobj_obj2cstr(dest);
170
+ t.data[t.len] = '"';
171
+ t.len++;
172
+ fiobj_str_resize(dest, t.len);
173
+ t = fiobj_obj2cstr(dest);
174
+ const uint8_t *restrict src = (const uint8_t *)s.data;
175
+ size_t len = s.len;
176
+ uint64_t end = t.len;
177
+ /* make sure we have some room */
178
+ size_t added = 0;
179
+ size_t capa = fiobj_str_capa(dest);
180
+ if (capa <= end + s.len + 64) {
181
+ if (0) {
182
+ capa = (((capa >> 12) + 1) << 12) - 1;
183
+ capa = fiobj_str_capa_assert(dest, capa);
184
+ } else {
185
+ capa = fiobj_str_capa_assert(dest, (end + s.len + 64));
186
+ }
187
+ fio_str_info_s tmp = fiobj_obj2cstr(dest);
188
+ t = tmp;
189
+ }
190
+ while (len) {
191
+ char *restrict writer = (char *)t.data;
192
+ while (len && src[0] > 32 && src[0] != '"' && src[0] != '\\') {
193
+ len--;
194
+ writer[end++] = *(src++);
195
+ }
196
+ if (!len)
197
+ break;
198
+ switch (src[0]) {
199
+ case '\b':
200
+ writer[end++] = '\\';
201
+ writer[end++] = 'b';
202
+ added++;
203
+ break; /* from switch */
204
+ case '\f':
205
+ writer[end++] = '\\';
206
+ writer[end++] = 'f';
207
+ added++;
208
+ break; /* from switch */
209
+ case '\n':
210
+ writer[end++] = '\\';
211
+ writer[end++] = 'n';
212
+ added++;
213
+ break; /* from switch */
214
+ case '\r':
215
+ writer[end++] = '\\';
216
+ writer[end++] = 'r';
217
+ added++;
218
+ break; /* from switch */
219
+ case '\t':
220
+ writer[end++] = '\\';
221
+ writer[end++] = 't';
222
+ added++;
223
+ break; /* from switch */
224
+ case '"':
225
+ case '\\':
226
+ case '/':
227
+ writer[end++] = '\\';
228
+ writer[end++] = src[0];
229
+ added++;
230
+ break; /* from switch */
231
+ default:
232
+ if (src[0] <= 31) {
233
+ /* MUST escape all control values less than 32 */
234
+ writer[end++] = '\\';
235
+ writer[end++] = 'u';
236
+ writer[end++] = '0';
237
+ writer[end++] = '0';
238
+ writer[end++] = hex_chars[src[0] >> 4];
239
+ writer[end++] = hex_chars[src[0] & 15];
240
+ added += 4;
241
+ } else
242
+ writer[end++] = src[0];
243
+ break; /* from switch */
244
+ }
245
+ src++;
246
+ len--;
247
+ if (added >= 48 && capa <= end + len + 64) {
248
+ writer[end] = 0;
249
+ fiobj_str_resize(dest, end);
250
+ fiobj_str_capa_assert(dest, (end + len + 64));
251
+ t = fiobj_obj2cstr(dest);
252
+ writer = (char *)t.data;
253
+ capa = t.capa;
254
+ added = 0;
255
+ }
256
+ }
257
+ t.data[end++] = '"';
258
+ fiobj_str_resize(dest, end);
259
+ }
260
+
261
+ typedef struct {
262
+ FIOBJ dest;
263
+ FIOBJ parent;
264
+ fio_json_stack_s *stack;
265
+ uintptr_t count;
266
+ uint8_t pretty;
267
+ } obj2json_data_s;
268
+
269
+ static int fiobj_obj2json_task(FIOBJ o, void *data_) {
270
+ obj2json_data_s *data = data_;
271
+ uint8_t add_seperator = 1;
272
+ if (fiobj_hash_key_in_loop()) {
273
+ write_safe_str(data->dest, fiobj_hash_key_in_loop());
274
+ fiobj_str_write(data->dest, ":", 1);
275
+ }
276
+ switch (FIOBJ_TYPE(o)) {
277
+ case FIOBJ_T_NUMBER:
278
+ case FIOBJ_T_NULL:
279
+ case FIOBJ_T_TRUE:
280
+ case FIOBJ_T_FALSE:
281
+ case FIOBJ_T_FLOAT:
282
+ fiobj_str_join(data->dest, o);
283
+ --data->count;
284
+ break;
285
+
286
+ case FIOBJ_T_DATA:
287
+ case FIOBJ_T_UNKNOWN:
288
+ case FIOBJ_T_STRING:
289
+ write_safe_str(data->dest, o);
290
+ --data->count;
291
+ break;
292
+
293
+ case FIOBJ_T_ARRAY:
294
+ --data->count;
295
+ fio_json_stack_push(data->stack, data->parent);
296
+ fio_json_stack_push(data->stack, (FIOBJ)data->count);
297
+ data->parent = o;
298
+ data->count = fiobj_ary_count(o);
299
+ fiobj_str_write(data->dest, "[", 1);
300
+ add_seperator = 0;
301
+ break;
302
+
303
+ case FIOBJ_T_HASH:
304
+ --data->count;
305
+ fio_json_stack_push(data->stack, data->parent);
306
+ fio_json_stack_push(data->stack, (FIOBJ)data->count);
307
+ data->parent = o;
308
+ data->count = fiobj_hash_count(o);
309
+ fiobj_str_write(data->dest, "{", 1);
310
+ add_seperator = 0;
311
+ break;
312
+ }
313
+ if (data->pretty) {
314
+ fiobj_str_capa_assert(data->dest,
315
+ fiobj_obj2cstr(data->dest).len +
316
+ (fio_json_stack_count(data->stack) * 5));
317
+ while (!data->count && data->parent) {
318
+ if (FIOBJ_TYPE_IS(data->parent, FIOBJ_T_HASH)) {
319
+ fiobj_str_write(data->dest, "}", 1);
320
+ } else {
321
+ fiobj_str_write(data->dest, "]", 1);
322
+ }
323
+ add_seperator = 1;
324
+ data->count = 0;
325
+ fio_json_stack_pop(data->stack, &data->count);
326
+ data->parent = FIOBJ_INVALID;
327
+ fio_json_stack_pop(data->stack, &data->parent);
328
+ }
329
+
330
+ if (add_seperator && data->parent) {
331
+ fiobj_str_write(data->dest, ",\n", 2);
332
+ uintptr_t indent = fio_json_stack_count(data->stack) - 1;
333
+ fiobj_str_capa_assert(data->dest,
334
+ fiobj_obj2cstr(data->dest).len + (indent * 2));
335
+ fio_str_info_s buf = fiobj_obj2cstr(data->dest);
336
+ while (indent--) {
337
+ buf.data[buf.len++] = ' ';
338
+ buf.data[buf.len++] = ' ';
339
+ }
340
+ fiobj_str_resize(data->dest, buf.len);
341
+ }
342
+ } else {
343
+ fiobj_str_capa_assert(data->dest,
344
+ fiobj_obj2cstr(data->dest).len +
345
+ (fio_json_stack_count(data->stack) << 1));
346
+ while (!data->count && data->parent) {
347
+ if (FIOBJ_TYPE_IS(data->parent, FIOBJ_T_HASH)) {
348
+ fiobj_str_write(data->dest, "}", 1);
349
+ } else {
350
+ fiobj_str_write(data->dest, "]", 1);
351
+ }
352
+ add_seperator = 1;
353
+ data->count = 0;
354
+ data->parent = FIOBJ_INVALID;
355
+ fio_json_stack_pop(data->stack, &data->count);
356
+ fio_json_stack_pop(data->stack, &data->parent);
357
+ }
358
+
359
+ if (add_seperator && data->parent) {
360
+ fiobj_str_write(data->dest, ",", 1);
361
+ }
362
+ }
363
+
364
+ return 0;
365
+ }
366
+
367
+ /* *****************************************************************************
368
+ FIOBJ API
369
+ ***************************************************************************** */
370
+
371
+ /**
372
+ * Parses JSON, setting `pobj` to point to the new Object.
373
+ *
374
+ * Returns the number of bytes consumed. On Error, 0 is returned and no data is
375
+ * consumed.
376
+ */
377
+ size_t fiobj_json2obj(FIOBJ *pobj, const void *data, size_t len) {
378
+ fiobj_json_parser_s p = {.top = FIOBJ_INVALID};
379
+ size_t consumed = fio_json_parse(&p.p, data, len);
380
+ if (!consumed || p.p.depth) {
381
+ fiobj_free(fio_json_stack_get(&p.stack, 0));
382
+ p.top = FIOBJ_INVALID;
383
+ }
384
+ fio_json_stack_free(&p.stack);
385
+ fiobj_free(p.key);
386
+ *pobj = p.top;
387
+ return consumed;
388
+ }
389
+
390
+ /**
391
+ * Updates a Hash using JSON data.
392
+ *
393
+ * Parsing errors and non-dictionar object JSON data are silently ignored,
394
+ * attempting to update the Hash as much as possible before any errors
395
+ * encountered.
396
+ *
397
+ * Conflicting Hash data is overwritten (prefering the new over the old).
398
+ *
399
+ * Returns the number of bytes consumed. On Error, 0 is returned and no data is
400
+ * consumed.
401
+ */
402
+ size_t fiobj_hash_update_json(FIOBJ hash, const void *data, size_t len) {
403
+ if (!hash)
404
+ return 0;
405
+ fiobj_json_parser_s p = {.top = FIOBJ_INVALID, .target = hash};
406
+ size_t consumed = fio_json_parse(&p.p, data, len);
407
+ fio_json_stack_free(&p.stack);
408
+ fiobj_free(p.key);
409
+ if (p.top != hash)
410
+ fiobj_free(p.top);
411
+ return consumed;
412
+ }
413
+
414
+ /**
415
+ * Formats an object into a JSON string, appending the JSON string to an
416
+ * existing String. Remember to `fiobj_free`.
417
+ */
418
+ FIOBJ fiobj_obj2json2(FIOBJ dest, FIOBJ o, uint8_t pretty) {
419
+ assert(dest && FIOBJ_TYPE_IS(dest, FIOBJ_T_STRING));
420
+ if (!o) {
421
+ fiobj_str_write(dest, "null", 4);
422
+ return 0;
423
+ }
424
+ fio_json_stack_s stack = FIO_ARY_INIT;
425
+ obj2json_data_s data = {
426
+ .dest = dest,
427
+ .stack = &stack,
428
+ .pretty = pretty,
429
+ .count = 1,
430
+ };
431
+ if (!o || !FIOBJ_IS_ALLOCATED(o) || !FIOBJECT2VTBL(o)->each) {
432
+ fiobj_obj2json_task(o, &data);
433
+ return dest;
434
+ }
435
+ fiobj_each2(o, fiobj_obj2json_task, &data);
436
+ fio_json_stack_free(&stack);
437
+ return dest;
438
+ }
439
+
440
+ /* Formats an object into a JSON string. Remember to `fiobj_free`. */
441
+ FIOBJ fiobj_obj2json(FIOBJ obj, uint8_t pretty) {
442
+ return fiobj_obj2json2(fiobj_str_buf(128), obj, pretty);
443
+ }
444
+
445
+ /* *****************************************************************************
446
+ Test
447
+ ***************************************************************************** */
448
+
449
+ #if DEBUG
450
+ void fiobj_test_json(void) {
451
+ fprintf(stderr, "=== Testing JSON parser (simple test)\n");
452
+ #define TEST_ASSERT(cond, ...) \
453
+ if (!(cond)) { \
454
+ fprintf(stderr, "* " __VA_ARGS__); \
455
+ fprintf(stderr, "\n !!! Testing failed !!!\n"); \
456
+ exit(-1); \
457
+ }
458
+ char json_str[] = "{\"array\":[1,2,3,\"boom\"],\"my\":{\"secret\":42},"
459
+ "\"true\":true,\"false\":false,\"null\":null,\"float\":-2."
460
+ "2,\"string\":\"I \\\"wrote\\\" this.\"}";
461
+ char json_str_update[] = "{\"array\":[1,2,3]}";
462
+ char json_str2[] =
463
+ "[\n \"JSON Test Pattern pass1\",\n {\"object with 1 "
464
+ "member\":[\"array with 1 element\"]},\n {},\n [],\n -42,\n "
465
+ "true,\n false,\n null,\n {\n \"integer\": 1234567890,\n "
466
+ " \"real\": -9876.543210,\n \"e\": 0.123456789e-12,\n "
467
+ " \"E\": 1.234567890E+34,\n \"\": 23456789012E66,\n "
468
+ "\"zero\": 0,\n \"one\": 1,\n \"space\": \" \",\n "
469
+ "\"quote\": \"\\\"\",\n \"backslash\": \"\\\\\",\n "
470
+ "\"controls\": \"\\b\\f\\n\\r\\t\",\n \"slash\": \"/ & \\/\",\n "
471
+ " \"alpha\": \"abcdefghijklmnopqrstuvwyz\",\n \"ALPHA\": "
472
+ "\"ABCDEFGHIJKLMNOPQRSTUVWYZ\",\n \"digit\": \"0123456789\",\n "
473
+ " \"0123456789\": \"digit\",\n \"special\": "
474
+ "\"`1~!@#$%^&*()_+-={':[,]}|;.</>?\",\n \"hex\": "
475
+ "\"\\u0123\\u4567\\u89AB\\uCDEF\\uabcd\\uef4A\",\n \"true\": "
476
+ "true,\n \"false\": false,\n \"null\": null,\n "
477
+ "\"array\":[ ],\n \"object\":{ },\n \"address\": \"50 "
478
+ "St. James Street\",\n \"url\": \"http://www.JSON.org/\",\n "
479
+ " \"comment\": \"// /* <!-- --\",\n \"# -- --> */\": \" \",\n "
480
+ " \" s p a c e d \" :[1,2 , 3\n\n,\n\n4 , 5 , 6 "
481
+ " ,7 ],\"compact\":[1,2,3,4,5,6,7],\n \"jsontext\": "
482
+ "\"{\\\"object with 1 member\\\":[\\\"array with 1 element\\\"]}\",\n "
483
+ " \"quotes\": \"&#34; \\u0022 %22 0x22 034 &#x22;\",\n "
484
+ "\"\\/"
485
+ "\\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t`1~!@#$"
486
+ "%^&*()_+-=[]{}|;:',./<>?\"\n: \"A key can be any string\"\n },\n "
487
+ "0.5 "
488
+ ",98.6\n,\n99.44\n,\n\n1066,\n1e1,\n0.1e1,\n1e-1,\n1e00,2e+00,2e-00\n,"
489
+ "\"rosebud\"]";
490
+
491
+ FIOBJ o = 0;
492
+ TEST_ASSERT(fiobj_json2obj(&o, "1", 2) == 1,
493
+ "JSON number parsing failed to run!\n");
494
+ TEST_ASSERT(o, "JSON (single) object missing!\n");
495
+ TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_NUMBER),
496
+ "JSON (single) not a number!\n");
497
+ TEST_ASSERT(fiobj_obj2num(o) == 1, "JSON (single) not == 1!\n");
498
+ fiobj_free(o);
499
+
500
+ TEST_ASSERT(fiobj_json2obj(&o, "2.0", 5) == 3,
501
+ "JSON float parsing failed to run!\n");
502
+ TEST_ASSERT(o, "JSON (float) object missing!\n");
503
+ TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_FLOAT), "JSON (float) not a float!\n");
504
+ TEST_ASSERT(fiobj_obj2float(o) == 2, "JSON (float) not == 2!\n");
505
+ fiobj_free(o);
506
+
507
+ TEST_ASSERT(fiobj_json2obj(&o, json_str, sizeof(json_str)) ==
508
+ (sizeof(json_str) - 1),
509
+ "JSON parsing failed to run!\n");
510
+ TEST_ASSERT(o, "JSON object missing!\n");
511
+ TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_HASH),
512
+ "JSON root not a dictionary (not a hash)!\n");
513
+ FIOBJ tmp = fiobj_hash_get2(o, fiobj_hash_string("array", 5));
514
+ TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_ARRAY),
515
+ "JSON 'array' not an Array!\n");
516
+ TEST_ASSERT(fiobj_obj2num(fiobj_ary_index(tmp, 0)) == 1,
517
+ "JSON 'array' index 0 error!\n");
518
+ TEST_ASSERT(fiobj_obj2num(fiobj_ary_index(tmp, 1)) == 2,
519
+ "JSON 'array' index 1 error!\n");
520
+ TEST_ASSERT(fiobj_obj2num(fiobj_ary_index(tmp, 2)) == 3,
521
+ "JSON 'array' index 2 error!\n");
522
+ TEST_ASSERT(FIOBJ_TYPE_IS(fiobj_ary_index(tmp, 3), FIOBJ_T_STRING),
523
+ "JSON 'array' index 3 type error!\n");
524
+ TEST_ASSERT(!memcmp("boom", fiobj_obj2cstr(fiobj_ary_index(tmp, 3)).data, 4),
525
+ "JSON 'array' index 3 error!\n");
526
+ tmp = fiobj_hash_get2(o, fiobj_hash_string("my", 2));
527
+ TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_HASH),
528
+ "JSON 'my:secret' not a Hash!\n");
529
+ TEST_ASSERT(
530
+ FIOBJ_TYPE_IS(fiobj_hash_get2(tmp, fiobj_hash_string("secret", 6)),
531
+ FIOBJ_T_NUMBER),
532
+ "JSON 'my:secret' doesn't hold a number!\n");
533
+ TEST_ASSERT(
534
+ fiobj_obj2num(fiobj_hash_get2(tmp, fiobj_hash_string("secret", 6))) == 42,
535
+ "JSON 'my:secret' not 42!\n");
536
+ TEST_ASSERT(fiobj_hash_get2(o, fiobj_hash_string("true", 4)) == fiobj_true(),
537
+ "JSON 'true' not true!\n");
538
+ TEST_ASSERT(fiobj_hash_get2(o, fiobj_hash_string("false", 5)) ==
539
+ fiobj_false(),
540
+ "JSON 'false' not false!\n");
541
+ TEST_ASSERT(fiobj_hash_get2(o, fiobj_hash_string("null", 4)) == fiobj_null(),
542
+ "JSON 'null' not null!\n");
543
+ tmp = fiobj_hash_get2(o, fiobj_hash_string("float", 5));
544
+ TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_FLOAT), "JSON 'float' not a float!\n");
545
+ tmp = fiobj_hash_get2(o, fiobj_hash_string("string", 6));
546
+ TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_STRING),
547
+ "JSON 'string' not a string!\n");
548
+ TEST_ASSERT(!strcmp(fiobj_obj2cstr(tmp).data, "I \"wrote\" this."),
549
+ "JSON 'string' incorrect!\n");
550
+ fprintf(stderr, "* passed.\n");
551
+ fprintf(stderr, "=== Testing JSON formatting (simple test)\n");
552
+ tmp = fiobj_obj2json(o, 0);
553
+ fprintf(stderr, "* data (%p):\n%.*s\n", (void *)fiobj_obj2cstr(tmp).data,
554
+ (int)fiobj_obj2cstr(tmp).len, fiobj_obj2cstr(tmp).data);
555
+ if (!strcmp(fiobj_obj2cstr(tmp).data, json_str))
556
+ fprintf(stderr, "* Stringify == Original.\n");
557
+ TEST_ASSERT(
558
+ fiobj_hash_update_json(o, json_str_update, strlen(json_str_update)),
559
+ "JSON update failed to parse data.");
560
+ fiobj_free(tmp);
561
+
562
+ tmp = fiobj_hash_get2(o, fiobj_hash_string("array", 5));
563
+ TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_ARRAY),
564
+ "JSON updated 'array' not an Array!\n");
565
+ TEST_ASSERT(fiobj_ary_count(tmp) == 3, "JSON updated 'array' not updated?");
566
+ tmp = fiobj_hash_get2(o, fiobj_hash_string("float", 5));
567
+ TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_FLOAT),
568
+ "JSON updated (old) 'float' missing!\n");
569
+ fiobj_free(o);
570
+ fprintf(stderr, "* passed.\n");
571
+
572
+ fprintf(stderr, "=== Testing JSON parsing (UTF-8 and special cases)\n");
573
+ fiobj_json2obj(&o, "[\"\\uD834\\uDD1E\"]", 16);
574
+ TEST_ASSERT(o, "JSON G clef String failed to parse!\n");
575
+ TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY),
576
+ "JSON G clef container has an incorrect type! (%s)\n",
577
+ fiobj_type_name(o));
578
+ tmp = o;
579
+ o = fiobj_ary_pop(o);
580
+ fiobj_free(tmp);
581
+ TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING),
582
+ "JSON G clef String incorrect type! %p => %s\n", (void *)o,
583
+ fiobj_type_name(o));
584
+ TEST_ASSERT((!strcmp(fiobj_obj2cstr(o).data, "\xF0\x9D\x84\x9E")),
585
+ "JSON G clef String incorrect %s !\n", fiobj_obj2cstr(o).data);
586
+ fiobj_free(o);
587
+
588
+ fiobj_json2obj(&o, "\"\\uD834\\uDD1E\"", 14);
589
+ TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING),
590
+ "JSON direct G clef String incorrect type! %p => %s\n", (void *)o,
591
+ fiobj_type_name(o));
592
+ TEST_ASSERT((!strcmp(fiobj_obj2cstr(o).data, "\xF0\x9D\x84\x9E")),
593
+ "JSON direct G clef String incorrect %s !\n",
594
+ fiobj_obj2cstr(o).data);
595
+ fiobj_free(o);
596
+ fiobj_json2obj(&o, "\"Hello\\u0000World\"", 19);
597
+ TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING),
598
+ "JSON NUL containing String incorrect type! %p => %s\n",
599
+ (void *)o, fiobj_type_name(o));
600
+ TEST_ASSERT(
601
+ (!memcmp(fiobj_obj2cstr(o).data, "Hello\0World", fiobj_obj2cstr(o).len)),
602
+ "JSON NUL containing String incorrect! (%u): %s . %s\n",
603
+ (int)fiobj_obj2cstr(o).len, fiobj_obj2cstr(o).data,
604
+ fiobj_obj2cstr(o).data + 3);
605
+ fiobj_free(o);
606
+ size_t consumed = fiobj_json2obj(&o, json_str2, sizeof(json_str2));
607
+ TEST_ASSERT(
608
+ consumed == (sizeof(json_str2) - 1),
609
+ "JSON messy string failed to parse (consumed %lu instead of %lu\n",
610
+ (unsigned long)consumed, (unsigned long)(sizeof(json_str2) - 1));
611
+ TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY),
612
+ "JSON messy string object error\n");
613
+ tmp = fiobj_obj2json(o, 1);
614
+ TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_STRING),
615
+ "JSON messy string isn't a string\n");
616
+ fprintf(stderr, "Messy JSON:\n%s\n", fiobj_obj2cstr(tmp).data);
617
+ fiobj_free(o);
618
+ fiobj_free(tmp);
619
+ fprintf(stderr, "* passed.\n");
620
+ }
621
+
622
+ #endif