]> git.ozlabs.org Git - ccan/blobdiff - ccan/json_out/json_out.c
json_out: new module for authoring JSON strings.
[ccan] / ccan / json_out / json_out.c
diff --git a/ccan/json_out/json_out.c b/ccan/json_out/json_out.c
new file mode 100644 (file)
index 0000000..b8c798f
--- /dev/null
@@ -0,0 +1,292 @@
+/* MIT (BSD) license - see LICENSE file for details */
+#include <ccan/json_escape/json_escape.h>
+#include <ccan/json_out/json_out.h>
+#include <ccan/membuf/membuf.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+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);
+}