Skip to content

Commit e387a7c

Browse files
committed
feat: add jsonb_reset() for streaming purposes, add test at test/test.c
1 parent 81984c4 commit e387a7c

File tree

3 files changed

+113
-28
lines changed

3 files changed

+113
-28
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ for multiple C files, to avoid duplication of symbols you may define `JSONB_HEAD
5555
## API
5656

5757
* `jsonb_init()` - initialize a jsonb handle
58+
* `jsonb_reset()` - reset the buffer's position tracker for streaming purposes
5859
* `jsonb_object()` - push an object to the builder stack
5960
* `jsonb_object_pop()` - pop an object from the builder stack
6061
* `jsonb_key()` - push an object key field to the builder stack
@@ -75,8 +76,9 @@ The following are the possible return codes for the builder functions:
7576

7677
Its worth mentioning that all `JSONB_ERROR_` prefixed codes are negative.
7778

78-
If you get `JSONB_ERROR_NOMEM` you can re-allocate a larger buffer and call
79-
the builder function once more.
79+
If you get `JSONB_ERROR_NOMEM` you can either:
80+
1. re-allocate a larger buffer and call the builder function once more
81+
2. call `jsonb_reset()` to reset the buffer's position tracker and call the builder function once more (useful for streaming with a fixed sized buffer!)
8082

8183
## Other info
8284

json-build.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ extern "C" {
2626
*
2727
* #define JSONB_MAX_DEPTH 256
2828
* #include "json-build.h"
29-
* */
29+
*/
3030
#define JSONB_MAX_DEPTH 128
3131
#endif /* JSONB_MAX_DEPTH */
3232

@@ -59,14 +59,23 @@ enum jsonbstate {
5959

6060
/** @brief Handle for building a JSON string */
6161
typedef struct jsonb {
62-
/** expected next input */
62+
/** state stack to keep track and enforce next inputs */
6363
enum jsonbstate stack[JSONB_MAX_DEPTH + 1];
6464
/** pointer to stack top */
6565
enum jsonbstate *top;
6666
/** offset in the JSON buffer (current length) */
6767
size_t pos;
6868
} jsonb;
6969

70+
/**
71+
* @brief Reset a jsonb handle buffer's position tracker (for streaming purposes)
72+
* @note Should be used in conjunction with @ref JSONB_ERROR_NOMEM if the
73+
* buffer is meant to be used as a stream
74+
*
75+
* @param builder pointer to the @ref jsonb handle
76+
*/
77+
#define jsonb_reset(builder) ((builder)->pos = 0)
78+
7079
/**
7180
* @brief Initialize a jsonb handle
7281
*
@@ -235,6 +244,7 @@ _jsonb_eval_state(enum jsonbstate state)
235244
#define BUFFER_COPY_CHAR(b, c, _pos, buf, bufsize) \
236245
do { \
237246
if ((b)->pos + (_pos) + 1 + 1 > (bufsize)) { \
247+
(buf)[(b)->pos] = '\0'; \
238248
return JSONB_ERROR_NOMEM; \
239249
} \
240250
(buf)[(b)->pos + (_pos)++] = (c); \
@@ -244,6 +254,7 @@ _jsonb_eval_state(enum jsonbstate state)
244254
do { \
245255
size_t i; \
246256
if ((b)->pos + (_pos) + (len) + 1 > (bufsize)) { \
257+
(buf)[(b)->pos] = '\0'; \
247258
return JSONB_ERROR_NOMEM; \
248259
} \
249260
for (i = 0; i < (len); ++i) \

test/test.c

Lines changed: 96 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ check_valid_singles(void)
3737
ASSERT_STR_EQ("10", buf);
3838

3939
jsonb_init(&b);
40-
ASSERT_EQm(buf,
41-
JSONB_END, jsonb_string(&b, buf, sizeof(buf), "hi", 2));
40+
ASSERT_EQm(buf, JSONB_END, jsonb_string(&b, buf, sizeof(buf), "hi", 2));
4241
ASSERT_STR_EQ("\"hi\"", buf);
4342

4443
jsonb_init(&b);
@@ -61,8 +60,8 @@ check_valid_array(void)
6160
ASSERT_EQm(buf, JSONB_OK, jsonb_bool(&b, buf, sizeof(buf), 0));
6261
ASSERT_EQm(buf, JSONB_OK, jsonb_null(&b, buf, sizeof(buf)));
6362
ASSERT_EQm(buf, JSONB_OK, jsonb_number(&b, buf, sizeof(buf), 10));
64-
ASSERT_EQm(buf,
65-
JSONB_OK, jsonb_string(&b, buf, sizeof(buf), "foo", 3));
63+
ASSERT_EQm(buf, JSONB_OK,
64+
jsonb_string(&b, buf, sizeof(buf), "foo", 3));
6665
ASSERT_EQm(buf, JSONB_OK, jsonb_object(&b, buf, sizeof(buf)));
6766
ASSERT_EQm(buf, JSONB_OK, jsonb_object_pop(&b, buf, sizeof(buf)));
6867
ASSERT_EQm(buf, JSONB_END, jsonb_array_pop(&b, buf, sizeof(buf)));
@@ -91,8 +90,8 @@ check_valid_object(void)
9190
ASSERT_EQm(buf, JSONB_OK, jsonb_key(&b, buf, sizeof(buf), "d", 1));
9291
ASSERT_EQm(buf, JSONB_OK, jsonb_number(&b, buf, sizeof(buf), 10));
9392
ASSERT_EQm(buf, JSONB_OK, jsonb_key(&b, buf, sizeof(buf), "e", 1));
94-
ASSERT_EQm(buf,
95-
JSONB_OK, jsonb_string(&b, buf, sizeof(buf), "foo", 3));
93+
ASSERT_EQm(buf, JSONB_OK,
94+
jsonb_string(&b, buf, sizeof(buf), "foo", 3));
9695
ASSERT_EQm(buf, JSONB_OK, jsonb_key(&b, buf, sizeof(buf), "f", 1));
9796
ASSERT_EQm(buf, JSONB_OK, jsonb_array(&b, buf, sizeof(buf)));
9897
ASSERT_EQm(buf, JSONB_OK, jsonb_array_pop(&b, buf, sizeof(buf)));
@@ -161,8 +160,7 @@ check_deep_nesting_object_and_array(void)
161160
for (i = 0; i < JSONB_MAX_DEPTH; ++i) {
162161
if (i % 2 == 0) {
163162
ASSERT_EQm(buf, JSONB_OK, jsonb_object(&b, buf, sizeof(buf)));
164-
ASSERT_EQm(buf,
165-
JSONB_OK, jsonb_key(&b, buf, sizeof(buf), "a", 1));
163+
ASSERT_EQm(buf, JSONB_OK, jsonb_key(&b, buf, sizeof(buf), "a", 1));
166164
}
167165
else {
168166
ASSERT_EQm(buf, JSONB_OK, jsonb_array(&b, buf, sizeof(buf)));
@@ -173,21 +171,21 @@ check_deep_nesting_object_and_array(void)
173171
ASSERT_EQm(buf, JSONB_OK, jsonb_null(&b, buf, sizeof(buf)));
174172
for (i = 0; i < JSONB_MAX_DEPTH - 1; ++i) {
175173
if (i % 2 == 0)
176-
ASSERT_EQm(buf,
177-
JSONB_OK, jsonb_object_pop(&b, buf, sizeof(buf)));
174+
ASSERT_EQm(buf, JSONB_OK,
175+
jsonb_object_pop(&b, buf, sizeof(buf)));
178176
else
179-
ASSERT_EQm(buf,
180-
JSONB_OK, jsonb_array_pop(&b, buf, sizeof(buf)));
177+
ASSERT_EQm(buf, JSONB_OK,
178+
jsonb_array_pop(&b, buf, sizeof(buf)));
181179
}
182180
}
183181
else {
184182
for (i = 0; i < JSONB_MAX_DEPTH - 1; ++i) {
185183
if (i % 2 == 0)
186-
ASSERT_EQm(buf,
187-
JSONB_OK, jsonb_array_pop(&b, buf, sizeof(buf)));
184+
ASSERT_EQm(buf, JSONB_OK,
185+
jsonb_array_pop(&b, buf, sizeof(buf)));
188186
else
189-
ASSERT_EQm(buf,
190-
JSONB_OK, jsonb_object_pop(&b, buf, sizeof(buf)));
187+
ASSERT_EQm(buf, JSONB_OK,
188+
jsonb_object_pop(&b, buf, sizeof(buf)));
191189
}
192190
}
193191
ASSERT_EQm(buf, JSONB_END, jsonb_object_pop(&b, buf, sizeof(buf)));
@@ -222,18 +220,94 @@ check_string_escaping(void)
222220
for (i = 0; i < sizeof(strs) / sizeof(char *); ++i) {
223221
size_t len = strlen(strs[i]);
224222
size_t prev_pos = b.pos;
225-
ASSERT_GTEm(buf,
226-
jsonb_string(&b, buf, sizeof(buf), strs[i], len), 0);
223+
ASSERT_GTEm(buf, jsonb_string(&b, buf, sizeof(buf), strs[i], len), 0);
227224
ASSERT_STR_EQ(expect[i], buf + prev_pos);
228225
}
229226
ASSERT_EQm(buf, JSONB_END, jsonb_array_pop(&b, buf, sizeof(buf)));
230227

231228
PASS();
232229
}
233230

231+
TEST
232+
check_string_streaming(void)
233+
{
234+
const char expect[] =
235+
"{\"foo\":null,\"bar\":0,\"baz\":\"\",\"tuna\":{},\"spam\":[]}";
236+
char buf[10] = { 0 }, dest[1024] = { 0 };
237+
enum jsonbcode code;
238+
int k;
239+
jsonb b;
240+
241+
jsonb_init(&b);
242+
/* 1 < 10 : '{' */
243+
ASSERT_EQ(JSONB_OK, jsonb_object(&b, buf, sizeof(buf)));
244+
/* 7 < 10 : '{"foo":' */
245+
ASSERT_EQ(JSONB_OK, jsonb_key(&b, buf, sizeof(buf), "foo", 3));
246+
/* 11 > 10: '{"foo":null' */
247+
for (k = 0; (code = jsonb_null(&b, buf, sizeof(buf))); k = 1) {
248+
ASSERT_EQm("token can't fit alone in buffer", 0, k);
249+
ASSERT_EQ(JSONB_ERROR_NOMEM, code);
250+
strcat(dest, buf);
251+
jsonb_reset(&b);
252+
}
253+
/* 10 == 10 : 'null,"bar":' */
254+
for (k = 0; (code = jsonb_key(&b, buf, sizeof(buf), "bar", 3)); k = 1) {
255+
ASSERT_EQm("token can't fit alone in buffer", 0, k);
256+
ASSERT_EQ(JSONB_ERROR_NOMEM, code);
257+
strcat(dest, buf);
258+
jsonb_reset(&b);
259+
}
260+
/* 8 < 10 : ',"bar":0' */
261+
ASSERT_EQ(JSONB_OK, jsonb_number(&b, buf, sizeof(buf), 0));
262+
/* 15 > 10 : ',"bar":0,"baz":' */
263+
for (k = 0; (code = jsonb_key(&b, buf, sizeof(buf), "baz", 3)); k = 1) {
264+
ASSERT_EQm("token can't fit alone in buffer", 0, k);
265+
ASSERT_EQ(JSONB_ERROR_NOMEM, code);
266+
strcat(dest, buf);
267+
jsonb_reset(&b);
268+
}
269+
/* 9 < 10 : ',"baz":""' */
270+
ASSERT_EQ(JSONB_OK, jsonb_string(&b, buf, sizeof(buf), "", 0));
271+
/* 17 > 10 : ',"baz":"","tuna":' */
272+
for (k = 0; (code = jsonb_key(&b, buf, sizeof(buf), "tuna", 4)); k = 1) {
273+
ASSERT_EQm("token can't fit alone in buffer", 0, k);
274+
ASSERT_EQ(JSONB_ERROR_NOMEM, code);
275+
strcat(dest, buf);
276+
jsonb_reset(&b);
277+
}
278+
/* 9 < 10 : ',"tuna":{' */
279+
ASSERT_EQ(JSONB_OK, jsonb_object(&b, buf, sizeof(buf)));
280+
/* 10 == 10 : ',"tuna":{}' */
281+
for (k = 0; (code = jsonb_object_pop(&b, buf, sizeof(buf))); k = 1) {
282+
ASSERT_EQm("token can't fit alone in buffer", 0, k);
283+
ASSERT_EQ(JSONB_ERROR_NOMEM, code);
284+
strcat(dest, buf);
285+
jsonb_reset(&b);
286+
}
287+
/* 9 < 10 : '},"spam":' */
288+
ASSERT_EQ(JSONB_OK, jsonb_key(&b, buf, sizeof(buf), "spam", 4));
289+
/* 10 == 10 : '},"spam":[' */
290+
for (k = 0; (code = jsonb_array(&b, buf, sizeof(buf))); k = 1) {
291+
ASSERT_EQm("token can't fit alone in buffer", 0, k);
292+
ASSERT_EQ(JSONB_ERROR_NOMEM, code);
293+
strcat(dest, buf);
294+
jsonb_reset(&b);
295+
}
296+
/* 2 < 10 : '[]' */
297+
ASSERT_EQ(JSONB_OK, jsonb_array_pop(&b, buf, sizeof(buf)));
298+
/* 3 < 10 : '[]}' */
299+
ASSERT_EQ(JSONB_END, jsonb_object_pop(&b, buf, sizeof(buf)));
300+
strcat(dest, buf);
301+
302+
ASSERT_STR_EQ(expect, dest);
303+
304+
PASS();
305+
}
306+
234307
SUITE(string)
235308
{
236309
RUN_TEST(check_string_escaping);
310+
RUN_TEST(check_string_streaming);
237311
}
238312

239313
TEST
@@ -244,8 +318,7 @@ check_invalid_top_level_tokens_in_sequence(void)
244318

245319
jsonb_init(&b);
246320
jsonb_bool(&b, buf, sizeof(buf), 1);
247-
ASSERT_EQm(buf,
248-
JSONB_ERROR_INPUT, jsonb_bool(&b, buf, sizeof(buf), 0));
321+
ASSERT_EQm(buf, JSONB_ERROR_INPUT, jsonb_bool(&b, buf, sizeof(buf), 0));
249322

250323
jsonb_init(&b);
251324
jsonb_array(&b, buf, sizeof(buf));
@@ -255,17 +328,16 @@ check_invalid_top_level_tokens_in_sequence(void)
255328
jsonb_init(&b);
256329
jsonb_array(&b, buf, sizeof(buf));
257330
jsonb_array_pop(&b, buf, sizeof(buf));
258-
ASSERT_EQm(buf,
259-
JSONB_ERROR_INPUT, jsonb_bool(&b, buf, sizeof(buf), 1));
331+
ASSERT_EQm(buf, JSONB_ERROR_INPUT, jsonb_bool(&b, buf, sizeof(buf), 1));
260332

261333
jsonb_init(&b);
262334
jsonb_bool(&b, buf, sizeof(buf), 1);
263335
ASSERT_EQm(buf, JSONB_ERROR_INPUT, jsonb_array(&b, buf, sizeof(buf)));
264336

265337
jsonb_init(&b);
266338
jsonb_bool(&b, buf, sizeof(buf), 1);
267-
ASSERT_EQm(buf,
268-
JSONB_ERROR_INPUT, jsonb_string(&b, buf, sizeof(buf), "", 0));
339+
ASSERT_EQm(buf, JSONB_ERROR_INPUT,
340+
jsonb_string(&b, buf, sizeof(buf), "", 0));
269341

270342
PASS();
271343
}

0 commit comments

Comments
 (0)