]> git.ozlabs.org Git - ccan/blob - ccan/json_out/json_out.c
87deaca85f9ce0375ff9bd299ced923669f7c739
[ccan] / ccan / json_out / json_out.c
1 /* MIT (BSD) license - see LICENSE file for details */
2 #include <ccan/json_escape/json_escape.h>
3 #include <ccan/json_out/json_out.h>
4 #include <ccan/membuf/membuf.h>
5 #include <stdarg.h>
6 #include <stdio.h>
7
8 struct json_out {
9         /* Callback if we reallocate. */
10         void (*move_cb)(struct json_out *jout, ptrdiff_t delta, void *arg);
11         void *cb_arg;
12         
13 #ifdef CCAN_JSON_OUT_DEBUG
14         /* tal_arr of types ( or [ we're enclosed in.  NULL if oom. */
15         char *wrapping;
16 #endif
17         /* True if we haven't yet put an element in current wrapping */
18         bool empty;
19
20         /* Output. */
21         MEMBUF(char) outbuf;
22 };
23
24 /* Realloc helper for tal membufs */
25 static void *membuf_tal_realloc(struct membuf *mb,
26                                 void *rawelems, size_t newsize)
27 {
28         char *p = rawelems;
29
30         if (!tal_resize(&p, newsize))
31                 return NULL;
32         return p;
33 }
34
35 struct json_out *json_out_new(const tal_t *ctx)
36 {
37         struct json_out *jout = tal(ctx, struct json_out);
38         char *pool;
39
40         if (!jout)
41                 return NULL;
42         pool = tal_arr(jout, char, 64);
43         if (!pool)
44                 return tal_free(jout);
45
46         membuf_init(&jout->outbuf, pool, tal_count(pool), membuf_tal_realloc);
47 #ifdef CCAN_JSON_OUT_DEBUG
48         jout->wrapping = tal_arr(jout, char, 0);
49 #endif
50         jout->empty = true;
51         jout->move_cb = NULL;
52         return jout;
53 }
54
55 void json_out_call_on_move_(struct json_out *jout,
56                             void (*cb)(struct json_out *jout, ptrdiff_t delta,
57                                        void *arg),
58                             void *arg)
59 {
60         if (cb)
61                 assert(!jout->move_cb);
62         jout->move_cb = cb;
63         jout->cb_arg = arg;
64 }
65
66 struct json_out *json_out_dup(const tal_t *ctx, const struct json_out *src)
67 {
68         size_t num_elems = membuf_num_elems(&src->outbuf);
69         char *elems = membuf_elems(&src->outbuf);
70         struct json_out *jout = tal_dup(ctx, struct json_out, src);
71         char *pool;
72
73         if (!jout)
74                 return NULL;
75         pool = tal_dup_arr(jout, char, elems, num_elems, 0);
76         if (!pool)
77                 return tal_free(jout);
78         membuf_init(&jout->outbuf, pool, num_elems, membuf_tal_realloc);
79         membuf_added(&jout->outbuf, num_elems);
80 #ifdef CCAN_JSON_OUT_DEBUG
81         jout->wrapping = tal_dup_arr(jout, char,
82                                      jout->wrapping, tal_count(jout->wrapping),
83                                      0);
84 #endif
85         return jout;
86 }
87
88 static void indent(struct json_out *jout, char type)
89 {
90 #ifdef CCAN_JSON_OUT_DEBUG
91         /* Can't check if we ran out of memory. */
92         if (jout->wrapping) {
93                 size_t n = tal_count(jout->wrapping);
94                 if (!tal_resize(&jout->wrapping, n+1))
95                         jout->wrapping = tal_free(jout->wrapping);
96                 else
97                         jout->wrapping[n] = type;
98         }
99 #endif
100         jout->empty = true;
101 }
102
103 static void unindent(struct json_out *jout, char type)
104 {
105 #ifdef CCAN_JSON_OUT_DEBUG
106         /* Can't check if we ran out of memory. */
107         if (jout->wrapping) {
108                 size_t indent = tal_count(jout->wrapping);
109                 assert(indent > 0);
110                 /* Both [ and ] and { and } are two apart in ASCII */
111                 assert(jout->wrapping[indent-1] == type - 2);
112                 tal_resize(&jout->wrapping, indent-1);
113         }
114 #endif
115         jout->empty = false;
116 }
117
118 /* Make sure jout->outbuf has room for len: return pointer */
119 static char *mkroom(struct json_out *jout, size_t len)
120 {
121         ptrdiff_t delta = membuf_prepare_space(&jout->outbuf, len);
122
123         if (delta && jout->move_cb)
124                 jout->move_cb(jout, delta, jout->cb_arg);
125
126         return membuf_space(&jout->outbuf);
127 }
128
129 static void check_fieldname(const struct json_out *jout,
130                             const char *fieldname)
131 {
132 #ifdef CCAN_JSON_OUT_DEBUG
133         /* We don't escape this for you */
134         assert(!fieldname || !json_escape_needed(fieldname, strlen(fieldname)));
135
136         /* Can't check anything else if we ran out of memory. */
137         if (jout->wrapping) {
138                 size_t n = tal_count(jout->wrapping);
139                 if (n == 0)
140                         /* Can't have a fieldname if not in anything! */
141                         assert(!fieldname);
142                 else if (jout->wrapping[n-1] == '[')
143                         /* No fieldnames in arrays. */
144                         assert(!fieldname);
145                 else {
146                         /* Must have fieldnames in objects. */
147                         assert(fieldname);
148                 }
149         }
150 #endif
151 }
152
153 char *json_out_member_direct(struct json_out *jout,
154                              const char *fieldname, size_t extra)
155 {
156         char *dest;
157
158         /* Prepend comma if required. */
159         if (!jout->empty)
160                 extra++;
161
162         check_fieldname(jout, fieldname);
163         if (fieldname)
164                 extra += 1 + strlen(fieldname) + 2;
165
166         dest = mkroom(jout, extra);
167         if (!dest)
168                 goto out;
169
170         if (!jout->empty)
171                 *(dest++) = ',';
172         if (fieldname) {
173                 *(dest++) = '"';
174                 memcpy(dest, fieldname, strlen(fieldname));
175                 dest += strlen(fieldname);
176                 *(dest++) = '"';
177                 *(dest++) = ':';
178         }
179         membuf_added(&jout->outbuf, extra);
180
181 out:
182         jout->empty = false;
183         return dest;
184 }
185
186 bool json_out_start(struct json_out *jout, const char *fieldname, char type)
187 {
188         char *p;
189
190         assert(type == '[' || type == '{');
191         p = json_out_member_direct(jout, fieldname, 1);
192         if (p)
193                 p[0] = type;
194         indent(jout, type);
195
196         return p != NULL;
197 }
198
199 bool json_out_end(struct json_out *jout, char type)
200 {
201         char *p;
202
203         assert(type == '}' || type == ']');
204         p = json_out_direct(jout, 1);
205         if (p)
206                 p[0] = type;
207         unindent(jout, type);
208
209         return p != NULL;
210 }
211
212 bool json_out_addv(struct json_out *jout,
213                    const char *fieldname,
214                    bool quote,
215                    const char *fmt,
216                    va_list ap)
217 {
218         size_t fmtlen, avail;
219         va_list ap2;
220         char *dst;
221
222         if (!json_out_member_direct(jout, fieldname, 0))
223                 return false;
224
225         /* Make a copy in case we need it below. */
226         va_copy(ap2, ap);
227
228         /* We can use any additional space, but need room for ". */
229         avail = membuf_num_space(&jout->outbuf);
230         if (quote) {
231                 if (avail < 2)
232                         avail = 0;
233                 else
234                         avail -= 2;
235         }
236
237         /* Try printing in place first. */
238         dst = membuf_space(&jout->outbuf);
239         fmtlen = vsnprintf(dst + quote, avail, fmt, ap);
240
241         /* Horrible subtlety: vsnprintf *will* NUL terminate, even if it means
242          * chopping off the last character.  So if fmtlen ==
243          * membuf_num_space(&jout->outbuf), the result was truncated! */
244         if (fmtlen + (int)quote*2 >= membuf_num_space(&jout->outbuf)) {
245                 /* Make room for NUL terminator, even though we don't want it */
246                 dst = mkroom(jout, fmtlen + 1 + (int)quote*2);
247                 if (!dst)
248                         goto out;
249                 vsprintf(dst + quote, fmt, ap2);
250         }
251
252 #ifdef CCAN_JSON_OUT_DEBUG
253         /* You're not inserting junk here, are you? */
254         assert(quote || !json_escape_needed(dst, fmtlen));
255 #endif
256
257         /* Of course, if we need to escape it, we have to redo it all. */
258         if (quote) {
259                 if (json_escape_needed(dst + quote, fmtlen)) {
260                         struct json_escape *e;
261                         e = json_escape_len(NULL, dst + quote, fmtlen);
262                         fmtlen = strlen(e->s);
263                         dst = mkroom(jout, fmtlen + (int)quote*2);
264                         if (!dst)
265                                 goto out;
266                         memcpy(dst + quote, e, fmtlen);
267                         tal_free(e);
268                 }
269                 dst[0] = '"';
270                 dst[fmtlen+1] = '"';
271         }
272         membuf_added(&jout->outbuf, fmtlen + (int)quote*2);
273
274 out:
275         va_end(ap2);
276         return dst != NULL;
277 }
278
279 bool json_out_add(struct json_out *jout,
280                   const char *fieldname,
281                   bool quote,
282                   const char *fmt, ...)
283 {
284         va_list ap;
285         bool ret;
286
287         va_start(ap, fmt);
288         ret = json_out_addv(jout, fieldname, quote, fmt, ap);
289         va_end(ap);
290         return ret;
291 }
292
293 bool json_out_addstr(struct json_out *jout,
294                      const char *fieldname,
295                      const char *str)
296 {
297         size_t len = strlen(str);
298         char *p;
299         struct json_escape *e;
300
301         if (json_escape_needed(str, len)) {
302                 e = json_escape(NULL, str);
303                 str = e->s;
304                 len = strlen(str);
305         } else
306                 e = NULL;
307
308         p = json_out_member_direct(jout, fieldname, len + 2);
309         if (p) {
310                 p[0] = p[1+len] = '"';
311                 memcpy(p+1, str, len);
312         }
313         tal_free(e);
314
315         return p != NULL;
316 }
317
318 bool json_out_add_splice(struct json_out *jout,
319                          const char *fieldname,
320                          const struct json_out *src)
321 {
322         const char *p;
323         size_t len;
324
325         p = json_out_contents(src, &len);
326         if (!p)
327                 return false;
328         memcpy(json_out_member_direct(jout, fieldname, len), p, len);
329         return true;
330 }
331
332 char *json_out_direct(struct json_out *jout, size_t len)
333 {
334         char *p = mkroom(jout, len);
335         if (p)
336                 membuf_added(&jout->outbuf, len);
337         return p;
338 }
339
340 void json_out_finished(const struct json_out *jout)
341 {
342 #ifdef CCAN_JSON_OUT_DEBUG
343         assert(tal_count(jout->wrapping) == 0);
344 #endif
345 }
346
347 const char *json_out_contents(const struct json_out *jout, size_t *len)
348 {
349         *len = membuf_num_elems(&jout->outbuf);
350         return *len ? membuf_elems(&jout->outbuf) : NULL;
351 }
352
353 void json_out_consume(struct json_out *jout, size_t len)
354 {
355         membuf_consume(&jout->outbuf, len);
356 }