From 2fd44331077429da94f8f7eb43513068257742f8 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 21 May 2019 16:11:58 +0930 Subject: [PATCH 1/1] json_out: new module for authoring JSON strings. Signed-off-by: Rusty Russell --- ccan/json_out/LICENSE | 1 + ccan/json_out/_info | 82 ++++++++ ccan/json_out/json_out.c | 292 +++++++++++++++++++++++++++++ ccan/json_out/json_out.h | 189 +++++++++++++++++++ ccan/json_out/test/run-debugging.c | 2 + ccan/json_out/test/run-move_cb.c | 48 +++++ ccan/json_out/test/run.c | 141 ++++++++++++++ 7 files changed, 755 insertions(+) create mode 120000 ccan/json_out/LICENSE create mode 100644 ccan/json_out/_info create mode 100644 ccan/json_out/json_out.c create mode 100644 ccan/json_out/json_out.h create mode 100644 ccan/json_out/test/run-debugging.c create mode 100644 ccan/json_out/test/run-move_cb.c create mode 100644 ccan/json_out/test/run.c diff --git a/ccan/json_out/LICENSE b/ccan/json_out/LICENSE new file mode 120000 index 00000000..2354d129 --- /dev/null +++ b/ccan/json_out/LICENSE @@ -0,0 +1 @@ +../../licenses/BSD-MIT \ No newline at end of file diff --git a/ccan/json_out/_info b/ccan/json_out/_info new file mode 100644 index 00000000..fbb4fa56 --- /dev/null +++ b/ccan/json_out/_info @@ -0,0 +1,82 @@ +#include "config.h" +#include +#include + +/** + * json_out - Code for creating simple JSON output. + * + * This code helps you create well-formed JSON strings. + * + * Author: Rusty Russell + * License: BSD-MIT + * + * Example: + * // Given "a 1 true" outputs {"argv1":"a","argv2":1,"argv3":true} + * // Print arguments as a JSON array. + * #include + * #include + * #include + * #include + * + * // Simplistic test to see if str needs quotes. + * static bool can_be_json_literal(const char *str) + * { + * char *endp; + * if (strtol(str, &endp, 10) != LONG_MIN + * && endp != str + * && *endp == '\0') + * return true; + * return !strcmp(str, "true") + * || !strcmp(str, "false") + * || !strcmp(str, "null"); + * } + * + * int main(int argc, char *argv[]) + * { + * struct json_out *jout = json_out_new(NULL); + * size_t len; + * const char *p; + * + * json_out_start(jout, NULL, '{'); + * for (int i = 1; i < argc; i++) { + * char fieldname[80]; + * sprintf(fieldname, "argv%i", i); + * json_out_add(jout, fieldname, + * !can_be_json_literal(argv[i]), + * "%s", argv[i]); + * } + * json_out_end(jout, '}'); + * // Force appending of \n + * json_out_direct(jout, 1)[0] = '\n'; + * json_out_finished(jout); + * + * // Now write it out. + * while ((p = json_out_contents(jout, &len)) != NULL) { + * int i = write(STDOUT_FILENO, p, len); + * if (i <= 0) + * exit(1); + * json_out_consume(jout, i); + * } + * + * tal_free(jout); + * return 0; + * } + * + */ +int main(int argc, char *argv[]) +{ + /* Expect exactly one argument */ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + printf("ccan/compiler\n"); + printf("ccan/json_escape\n"); + printf("ccan/membuf\n"); + printf("ccan/tal\n"); + printf("ccan/typesafe_cb\n"); + return 0; + } + + return 1; +} diff --git a/ccan/json_out/json_out.c b/ccan/json_out/json_out.c new file mode 100644 index 00000000..b8c798fc --- /dev/null +++ b/ccan/json_out/json_out.c @@ -0,0 +1,292 @@ +/* MIT (BSD) license - see LICENSE file for details */ +#include +#include +#include +#include +#include + +struct json_out { + /* Callback if we reallocate. */ + void (*move_cb)(struct json_out *jout, ptrdiff_t delta, void *arg); + void *cb_arg; + +#ifdef CCAN_JSON_OUT_DEBUG + /* tal_arr of types ( or [ we're enclosed in. */ + char *wrapping; +#endif + /* True if we haven't yet put an element in current wrapping */ + bool empty; + + /* Output. */ + MEMBUF(char) outbuf; +}; + +/* Realloc helper for tal membufs */ +static void *membuf_tal_realloc(struct membuf *mb, + void *rawelems, size_t newsize) +{ + char *p = rawelems; + + tal_resize(&p, newsize); + return p; +} + +struct json_out *json_out_new(const tal_t *ctx) +{ + struct json_out *jout = tal(ctx, struct json_out); + + membuf_init(&jout->outbuf, + tal_arr(jout, char, 64), 64, membuf_tal_realloc); +#ifdef CCAN_JSON_OUT_DEBUG + jout->wrapping = tal_arr(jout, char, 0); +#endif + jout->empty = true; + jout->move_cb = NULL; + return jout; +} + +void json_out_call_on_move_(struct json_out *jout, + void (*cb)(struct json_out *jout, ptrdiff_t delta, + void *arg), + void *arg) +{ + if (cb) + assert(!jout->move_cb); + jout->move_cb = cb; + jout->cb_arg = arg; +} + +struct json_out *json_out_dup(const tal_t *ctx, const struct json_out *src) +{ + size_t num_elems = membuf_num_elems(&src->outbuf); + char *elems = membuf_elems(&src->outbuf); + struct json_out *jout = tal_dup(ctx, struct json_out, src); + membuf_init(&jout->outbuf, + tal_dup_arr(jout, char, elems, num_elems, 0), + num_elems, membuf_tal_realloc); + membuf_added(&jout->outbuf, num_elems); +#ifdef CCAN_JSON_OUT_DEBUG + jout->wrapping = tal_dup_arr(jout, char, + jout->wrapping, tal_count(jout->wrapping), + 0); +#endif + return jout; +} + +static void indent(struct json_out *jout, char type) +{ +#ifdef CCAN_JSON_OUT_DEBUG + size_t n = tal_count(jout->wrapping); + tal_resize(&jout->wrapping, n+1); + jout->wrapping[n] = type; +#endif + jout->empty = true; +} + +static void unindent(struct json_out *jout, char type) +{ +#ifdef CCAN_JSON_OUT_DEBUG + size_t indent = tal_count(jout->wrapping); + assert(indent > 0); + /* Both [ and ] and { and } are two apart in ASCII */ + assert(jout->wrapping[indent-1] == type - 2); + tal_resize(&jout->wrapping, indent-1); +#endif + jout->empty = false; +} + +/* Make sure jout->outbuf has room for len: return pointer */ +static char *mkroom(struct json_out *jout, size_t len) +{ + ptrdiff_t delta = membuf_prepare_space(&jout->outbuf, len); + + if (delta && jout->move_cb) + jout->move_cb(jout, delta, jout->cb_arg); + + return membuf_space(&jout->outbuf); +} + +static void check_fieldname(const struct json_out *jout, + const char *fieldname) +{ +#ifdef CCAN_JSON_OUT_DEBUG + size_t n = tal_count(jout->wrapping); + if (n == 0) + /* Can't have a fieldname if not in anything! */ + assert(!fieldname); + else if (jout->wrapping[n-1] == '[') + /* No fieldnames in arrays. */ + assert(!fieldname); + else { + /* Must have fieldnames in objects. */ + assert(fieldname); + /* We don't escape this for you */ + assert(!json_escape_needed(fieldname, strlen(fieldname))); + } +#endif +} + +char *json_out_member_direct(struct json_out *jout, + const char *fieldname, size_t extra) +{ + char *dest; + + /* Prepend comma if required. */ + if (!jout->empty) + extra++; + + check_fieldname(jout, fieldname); + if (fieldname) + extra += 1 + strlen(fieldname) + 2; + + if (!extra) { + dest = NULL; + goto out; + } + + dest = mkroom(jout, extra); + + if (!jout->empty) + *(dest++) = ','; + if (fieldname) { + *(dest++) = '"'; + memcpy(dest, fieldname, strlen(fieldname)); + dest += strlen(fieldname); + *(dest++) = '"'; + *(dest++) = ':'; + } + membuf_added(&jout->outbuf, extra); + +out: + jout->empty = false; + return dest; +} + +void json_out_start(struct json_out *jout, const char *fieldname, char type) +{ + assert(type == '[' || type == '{'); + json_out_member_direct(jout, fieldname, 1)[0] = type; + indent(jout, type); +} + +void json_out_end(struct json_out *jout, char type) +{ + assert(type == '}' || type == ']'); + json_out_direct(jout, 1)[0] = type; + unindent(jout, type); +} + +void json_out_addv(struct json_out *jout, + const char *fieldname, + bool quote, + const char *fmt, + va_list ap) +{ + size_t fmtlen, avail; + va_list ap2; + char *dst; + + json_out_member_direct(jout, fieldname, 0); + + /* Make a copy in case we need it below. */ + va_copy(ap2, ap); + + /* We can use any additional space, but need room for ". */ + avail = membuf_num_space(&jout->outbuf); + if (quote) { + if (avail < 2) + avail = 0; + else + avail -= 2; + } + + /* Try printing in place first. */ + dst = membuf_space(&jout->outbuf); + fmtlen = vsnprintf(dst + quote, avail, fmt, ap); + + /* Horrible subtlety: vsnprintf *will* NUL terminate, even if it means + * chopping off the last character. So if fmtlen == + * membuf_num_space(&jout->outbuf), the result was truncated! */ + if (fmtlen + (int)quote*2 >= membuf_num_space(&jout->outbuf)) { + /* Make room for NUL terminator, even though we don't want it */ + dst = mkroom(jout, fmtlen + 1 + (int)quote*2); + vsprintf(dst + quote, fmt, ap2); + } + + /* Of course, if we need to escape it, we have to redo it all. */ + if (json_escape_needed(dst + quote, fmtlen)) { + struct json_escape *e; + e = json_escape_len(NULL, dst + quote, fmtlen); + fmtlen = strlen(e->s); + dst = mkroom(jout, fmtlen + (int)quote*2); + memcpy(dst + quote, e, fmtlen); + tal_free(e); + } + + if (quote) { + dst[0] = '"'; + dst[fmtlen+1] = '"'; + } + membuf_added(&jout->outbuf, fmtlen + (int)quote*2); + va_end(ap2); +} + +void json_out_add(struct json_out *jout, + const char *fieldname, + bool quote, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + json_out_addv(jout, fieldname, quote, fmt, ap); + va_end(ap); +} + +void json_out_addstr(struct json_out *jout, + const char *fieldname, + const char *str) +{ + size_t len = strlen(str); + char *p; + + p = json_out_member_direct(jout, fieldname, len + 2); + p[0] = p[1+len] = '"'; + memcpy(p+1, str, len); +} + +void json_out_add_splice(struct json_out *jout, + const char *fieldname, + const struct json_out *src) +{ + const char *p; + size_t len; + + p = json_out_contents(src, &len); + memcpy(json_out_member_direct(jout, fieldname, len), p, len); +} + +char *json_out_direct(struct json_out *jout, size_t len) +{ + char *p = mkroom(jout, len); + membuf_added(&jout->outbuf, len); + return p; +} + +void json_out_finished(const struct json_out *jout) +{ +#ifdef CCAN_JSON_OUT_DEBUG + assert(tal_count(jout->wrapping) == 0); +#endif +} + +const char *json_out_contents(const struct json_out *jout, size_t *len) +{ + *len = membuf_num_elems(&jout->outbuf); + return *len ? membuf_elems(&jout->outbuf) : NULL; +} + +void json_out_consume(struct json_out *jout, size_t len) +{ + membuf_consume(&jout->outbuf, len); +} diff --git a/ccan/json_out/json_out.h b/ccan/json_out/json_out.h new file mode 100644 index 00000000..a3ebdb13 --- /dev/null +++ b/ccan/json_out/json_out.h @@ -0,0 +1,189 @@ +/* MIT (BSD) license - see LICENSE file for details */ +#ifndef CCAN_JSON_OUT_H +#define CCAN_JSON_OUT_H +#include +#include +#include +#include + +struct json_out; + +/** + * json_out_new - allocate a json_out stream. + * @ctx: the tal_context to allocate from, or NULL + */ +struct json_out *json_out_new(const tal_t *ctx); + +/** + * json_out_call_on_move - callback for when buffer is reallocated. + * @jout: the json_out object to attach to. + * @cb: the callback to call. + * @arg: the argument to @cb (must match type). + * + * A NULL @cb disables. You can't currently have more than one callback. + * The @delta argument to @cb is the difference between the old location + * and the new one, and is never zero. + */ +#define json_out_call_on_move(jout, cb, arg) \ + json_out_call_on_move_((jout), \ + typesafe_cb_preargs(void, void *, \ + (cb), (arg), \ + struct json_out *, \ + ptrdiff_t), \ + (arg)) + +void json_out_call_on_move_(struct json_out *jout, + void (*cb)(struct json_out *jout, ptrdiff_t delta, + void *arg), + void *arg); + +/** + * json_out_dup - duplicate a json_out stream. + * @ctx: the tal_context to allocate from, or NULL + * @src: the json_out to copy. + */ +struct json_out *json_out_dup(const tal_t *ctx, const struct json_out *src); + +/** + * json_out_start - start an array or object. + * @jout: the json_out object to write into. + * @fieldname: the fieldname, if inside an object, or NULL if inside an array. + * @type: '[' or '{' to start an array or object, respectively. + * + * Literally writes '"@fieldname": @type' or '@type ' if fieldname is NULL. + * @fieldname must not need JSON escaping. + */ +void json_out_start(struct json_out *jout, const char *fieldname, char type); + +/** + * json_out_end - end an array or object. + * @jout: the json_out object to write into. + * @type: '}' or ']' to end an array or object, respectively. + * + * Literally writes ']' or '}', keeping track of whether we need to append + * a comma. + */ +void json_out_end(struct json_out *jout, char type); + +/** + * json_out_add - add a formatted member. + * @jout: the json_out object to write into. + * @fieldname: optional fieldname to prepend (must not need escaping). + * @quote: if true, surround fmt by " and ". + * @fmt...: the printf-style format + * + * If you're in an array, @fieldname must be NULL. If you're in an + * object, @fieldname must be non-NULL. This is checked if + * CCAN_JSON_OUT_DEBUG is defined. + * @fieldname must not need JSON escaping. + * + * If the resulting string requires escaping, we call json_escape(). + */ +PRINTF_FMT(4,5) +void json_out_add(struct json_out *jout, + const char *fieldname, + bool quote, + const char *fmt, ...); + +/** + * json_out_addv - add a formatted member (vararg variant) + * @jout: the json_out object to write into. + * @fieldname: optional fieldname to prepend. + * @quote: if true, surround fmt by " and ". + * @fmt: the printf-style format + * @ap: the argument list. + * + * See json_out_add() above. + */ +void json_out_addv(struct json_out *jout, + const char *fieldname, + bool quote, + const char *fmt, + va_list ap); + +/** + * json_out_addstr - convenience helper to add a string field. + * @jout: the json_out object to write into. + * @fieldname: optional fieldname to prepend. + * @str: the string to add (must not be NULL). + * + * Equivalent to json_out_add(@jout, @fieldname, true, "%s", @str); + */ +void json_out_addstr(struct json_out *jout, + const char *fieldname, + const char *str); + +/** + * json_out_member_direct - add a field, with direct access. + * @jout: the json_out object to write into. + * @fieldname: optional fieldname to prepend. + * @extra: how many bytes to allocate. + * + * @fieldname must not need JSON escaping. Returns a direct pointer into + * the @extra bytes. + * + * This allows you to write your own efficient type-specific helpers. + */ +char *json_out_member_direct(struct json_out *jout, + const char *fieldname, size_t extra); + +/** + * json_out_direct - make room in output and access directly. + * @jout: the json_out object to write into. + * @len: the length to allocate. + * + * This lets you access the json_out stream directly, to save a copy, + * if you know exactly how much you will write. + * + * Returns a pointer to @len bytes at the end of @jout. + * + * This is dangerous, since it doesn't automatically prepend a "," + * like the internal logic does, but can be used (carefully) to add + * entire objects, or whitespace. + */ +char *json_out_direct(struct json_out *jout, size_t extra); + +/** + * json_out_add_splice - copy a field from another json_out. + * @jout: the json_out object to write into. + * @fieldname: optional fieldname to prepend. + * @src: the json_out object to copy from. + * + * This asserts that @src is well-formed (as per json_out_finished()), + * then places it into @jout with optional @fieldname prepended. This + * can be used to assemble sub-objects for your JSON and then copy + * them in. + * + * Note that it will call json_out_contents(@src), so it expects that + * object to be unconsumed. + */ +void json_out_add_splice(struct json_out *jout, + const char *fieldname, + const struct json_out *src); + +/** + * json_out_finished - assert that the json buffer is finished. + * @jout: the json_out object written to. + * + * This simply causes internal assertions that all arrays and objects are + * finished. It needs CCAN_JSON_OUT_DEBUG defined to have any effect. + */ +void json_out_finished(const struct json_out *jout); + +/** + * json_out_contents - read contents from json_out stream. + * @jout: the json_out object we want to read from. + * @len: set to the length of the buffer returned. + * + * This returns a pointer into the JSON written so far. Returns NULL + * and sets @len to 0 if there's nothing left in the buffer. + */ +const char *json_out_contents(const struct json_out *jout, size_t *len); + +/** + * json_out_consume - discard contents from json_out stream. + * @jout: the json_out object we read from. + * @len: the length to consume (must be <= @len from json_out_contents) + */ +void json_out_consume(struct json_out *jout, size_t len); +#endif /* CCAN_JSON_OUT_H */ diff --git a/ccan/json_out/test/run-debugging.c b/ccan/json_out/test/run-debugging.c new file mode 100644 index 00000000..e239d8e1 --- /dev/null +++ b/ccan/json_out/test/run-debugging.c @@ -0,0 +1,2 @@ +#define CCAN_JSON_OUT_DEBUG 1 +#include "run.c" diff --git a/ccan/json_out/test/run-move_cb.c b/ccan/json_out/test/run-move_cb.c new file mode 100644 index 00000000..445b4262 --- /dev/null +++ b/ccan/json_out/test/run-move_cb.c @@ -0,0 +1,48 @@ +#include +/* Include the C files directly. */ +#include +#include + +static const char *ptr; +static bool called = false; + +static void move_cb(struct json_out *jout, ptrdiff_t delta, + struct json_out *arg) +{ + ptr += delta; + called = true; + ok1(arg == jout); +} + +int main(void) +{ + const tal_t *ctx = tal(NULL, char); + struct json_out *jout; + char *p; + size_t len; + + /* This is how many tests you plan to run */ + plan_tests(3); + + /* Test nested arrays. */ + jout = json_out_new(ctx); + json_out_call_on_move(jout, move_cb, jout); + + json_out_start(jout, NULL, '{'); + ptr = json_out_contents(jout, &len); + + p = json_out_member_direct(jout, "fieldname", 102); + p[0] = '"'; + p[101] = '"'; + memset(p+1, 'p', 100); + + json_out_finished(jout); + ok1(called); + /* Contents should have moved correctly. */ + ok1(json_out_contents(jout, &len) == ptr); + + tal_free(ctx); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/json_out/test/run.c b/ccan/json_out/test/run.c new file mode 100644 index 00000000..f6f9d41e --- /dev/null +++ b/ccan/json_out/test/run.c @@ -0,0 +1,141 @@ +#include +/* Include the C files directly. */ +#include +#include + +static void test_json_out_add(const tal_t *ctx, + char c, bool quote, const char *escaped) +{ + /* 64 is the size of the initial buf, so we test that. */ + for (size_t i = 1; i < 64; i++) { + struct json_out *jout; + char str[64 + 1]; + const char *r; + size_t len; + char fieldname[64 + 1]; + + jout = json_out_new(ctx); + json_out_start(jout, NULL, '{'); + memset(str, c, i); + str[i] = '\0'; + memset(fieldname, 'f', i); + fieldname[i] = '\0'; + json_out_add(jout, fieldname, quote, "%s", str); + json_out_end(jout, '}'); + json_out_finished(jout); + + r = json_out_contents(jout, &len); + ok1(len == strlen("{\"") + i + strlen("\":") + + quote * 2 + strlen(escaped) * i + strlen("}")); + + ok1(len > strlen("{\"")); + ok1(memcmp(r, "{\"", strlen("{\"")) == 0); + json_out_consume(jout, strlen("{\"")); + + r = json_out_contents(jout, &len); + ok1(len > strlen(fieldname)); + ok1(memcmp(r, fieldname, strlen(fieldname)) == 0); + json_out_consume(jout, strlen(fieldname)); + + r = json_out_contents(jout, &len); + ok1(len > strlen("\":")); + ok1(memcmp(r, "\":", strlen("\":")) == 0); + json_out_consume(jout, strlen("\":")); + + r = json_out_contents(jout, &len); + if (quote) { + ok1(len > 0); + ok1(r[0] == '"'); + json_out_consume(jout, 1); + } + for (size_t n = 0; n < i; n++) { + r = json_out_contents(jout, &len); + ok1(len > strlen(escaped)); + ok1(memcmp(r, escaped, strlen(escaped)) == 0); + json_out_consume(jout, strlen(escaped)); + } + r = json_out_contents(jout, &len); + if (quote) { + ok1(len > 0); + ok1(r[0] == '"'); + json_out_consume(jout, 1); + } + r = json_out_contents(jout, &len); + ok1(len == 1); + ok1(memcmp(r, "}", 1) == 0); + json_out_consume(jout, 1); + ok1(!json_out_contents(jout, &len)); + ok1(len == 0); + } +} + +static void json_eq(const struct json_out *jout, const char *expect) +{ + size_t len; + const char *p; + + json_out_finished(jout); + p = json_out_contents(jout, &len); + ok1(len == strlen(expect)); + ok1(memcmp(expect, p, len) == 0); +} + +int main(void) +{ + const tal_t *ctx = tal(NULL, char); + struct json_out *jout; + char *p; + + /* This is how many tests you plan to run */ + plan_tests(14689); + + /* Simple tests */ + test_json_out_add(ctx, '1', false, "1"); + test_json_out_add(ctx, 'x', true, "x"); + test_json_out_add(ctx, '\n', true, "\\n"); + + /* Test nested arrays. */ + jout = json_out_new(ctx); + for (size_t i = 0; i < 64; i++) + json_out_start(jout, NULL, '['); + for (size_t i = 0; i < 64; i++) + json_out_end(jout, ']'); + json_eq(jout, "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"); + + /* Test nested objects. */ + jout = json_out_new(ctx); + json_out_start(jout, NULL, '{'); + for (size_t i = 0; i < 63; i++) + json_out_start(jout, "x", '{'); + for (size_t i = 0; i < 64; i++) + json_out_end(jout, '}'); + json_eq(jout, "{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}"); + + jout = json_out_new(ctx); + json_out_start(jout, NULL, '{'); + p = json_out_member_direct(jout, "x", 7); + memcpy(p, "\"hello\"", 7); + json_out_end(jout, '}'); + json_eq(jout, "{\"x\":\"hello\"}"); + + jout = json_out_new(ctx); + p = json_out_direct(jout, strlen("{\"x\":\"hello\"}\n")); + memcpy(p, "{\"x\":\"hello\"}\n", strlen("{\"x\":\"hello\"}\n")); + json_eq(jout, "{\"x\":\"hello\"}\n"); + + jout = json_out_new(ctx); + json_out_start(jout, NULL, '{'); + struct json_out *jout2 = json_out_new(ctx); + json_out_start(jout2, NULL, '{'); + json_out_addstr(jout2, "x", "hello"); + json_out_end(jout2, '}'); + json_out_finished(jout2); + json_out_add_splice(jout, "inner", jout2); + json_out_end(jout, '}'); + json_eq(jout, "{\"inner\":{\"x\":\"hello\"}}"); + + tal_free(ctx); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} -- 2.39.2