]> git.ozlabs.org Git - ccan/blob - ccan/json_out/json_out.c
b8c798fc0dae7527cbf5986da619cdf7cb8ed437
[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. */
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         tal_resize(&p, newsize);
31         return p;
32 }
33
34 struct json_out *json_out_new(const tal_t *ctx)
35 {
36         struct json_out *jout = tal(ctx, struct json_out);
37
38         membuf_init(&jout->outbuf,
39                     tal_arr(jout, char, 64), 64, membuf_tal_realloc);
40 #ifdef CCAN_JSON_OUT_DEBUG
41         jout->wrapping = tal_arr(jout, char, 0);
42 #endif
43         jout->empty = true;
44         jout->move_cb = NULL;
45         return jout;
46 }
47
48 void json_out_call_on_move_(struct json_out *jout,
49                             void (*cb)(struct json_out *jout, ptrdiff_t delta,
50                                        void *arg),
51                             void *arg)
52 {
53         if (cb)
54                 assert(!jout->move_cb);
55         jout->move_cb = cb;
56         jout->cb_arg = arg;
57 }
58
59 struct json_out *json_out_dup(const tal_t *ctx, const struct json_out *src)
60 {
61         size_t num_elems = membuf_num_elems(&src->outbuf);
62         char *elems = membuf_elems(&src->outbuf);
63         struct json_out *jout = tal_dup(ctx, struct json_out, src);
64         membuf_init(&jout->outbuf,
65                     tal_dup_arr(jout, char, elems, num_elems, 0),
66                     num_elems, membuf_tal_realloc);
67         membuf_added(&jout->outbuf, num_elems);
68 #ifdef CCAN_JSON_OUT_DEBUG
69         jout->wrapping = tal_dup_arr(jout, char,
70                                      jout->wrapping, tal_count(jout->wrapping),
71                                      0);
72 #endif
73         return jout;
74 }
75
76 static void indent(struct json_out *jout, char type)
77 {
78 #ifdef CCAN_JSON_OUT_DEBUG
79         size_t n = tal_count(jout->wrapping);
80         tal_resize(&jout->wrapping, n+1);
81         jout->wrapping[n] = type;
82 #endif
83         jout->empty = true;
84 }
85
86 static void unindent(struct json_out *jout, char type)
87 {
88 #ifdef CCAN_JSON_OUT_DEBUG
89         size_t indent = tal_count(jout->wrapping);
90         assert(indent > 0);
91         /* Both [ and ] and { and } are two apart in ASCII */
92         assert(jout->wrapping[indent-1] == type - 2);
93         tal_resize(&jout->wrapping, indent-1);
94 #endif
95         jout->empty = false;
96 }
97
98 /* Make sure jout->outbuf has room for len: return pointer */
99 static char *mkroom(struct json_out *jout, size_t len)
100 {
101         ptrdiff_t delta = membuf_prepare_space(&jout->outbuf, len);
102
103         if (delta && jout->move_cb)
104                 jout->move_cb(jout, delta, jout->cb_arg);
105
106         return membuf_space(&jout->outbuf);
107 }
108
109 static void check_fieldname(const struct json_out *jout,
110                             const char *fieldname)
111 {
112 #ifdef CCAN_JSON_OUT_DEBUG
113         size_t n = tal_count(jout->wrapping);
114         if (n == 0)
115                 /* Can't have a fieldname if not in anything! */
116                 assert(!fieldname);
117         else if (jout->wrapping[n-1] == '[')
118                 /* No fieldnames in arrays. */
119                 assert(!fieldname);
120         else {
121                 /* Must have fieldnames in objects. */
122                 assert(fieldname);
123                 /* We don't escape this for you */
124                 assert(!json_escape_needed(fieldname, strlen(fieldname)));
125         }
126 #endif
127 }
128
129 char *json_out_member_direct(struct json_out *jout,
130                              const char *fieldname, size_t extra)
131 {
132         char *dest;
133
134         /* Prepend comma if required. */
135         if (!jout->empty)
136                 extra++;
137
138         check_fieldname(jout, fieldname);
139         if (fieldname)
140                 extra += 1 + strlen(fieldname) + 2;
141
142         if (!extra) {
143                 dest = NULL;
144                 goto out;
145         }
146
147         dest = mkroom(jout, extra);
148
149         if (!jout->empty)
150                 *(dest++) = ',';
151         if (fieldname) {
152                 *(dest++) = '"';
153                 memcpy(dest, fieldname, strlen(fieldname));
154                 dest += strlen(fieldname);
155                 *(dest++) = '"';
156                 *(dest++) = ':';
157         }
158         membuf_added(&jout->outbuf, extra);
159
160 out:
161         jout->empty = false;
162         return dest;
163 }
164
165 void json_out_start(struct json_out *jout, const char *fieldname, char type)
166 {
167         assert(type == '[' || type == '{');
168         json_out_member_direct(jout, fieldname, 1)[0] = type;
169         indent(jout, type);
170 }
171
172 void json_out_end(struct json_out *jout, char type)
173 {
174         assert(type == '}' || type == ']');
175         json_out_direct(jout, 1)[0] = type;
176         unindent(jout, type);
177 }
178
179 void json_out_addv(struct json_out *jout,
180                    const char *fieldname,
181                    bool quote,
182                    const char *fmt,
183                    va_list ap)
184 {
185         size_t fmtlen, avail;
186         va_list ap2;
187         char *dst;
188
189         json_out_member_direct(jout, fieldname, 0);
190
191         /* Make a copy in case we need it below. */
192         va_copy(ap2, ap);
193
194         /* We can use any additional space, but need room for ". */
195         avail = membuf_num_space(&jout->outbuf);
196         if (quote) {
197                 if (avail < 2)
198                         avail = 0;
199                 else
200                         avail -= 2;
201         }
202
203         /* Try printing in place first. */
204         dst = membuf_space(&jout->outbuf);
205         fmtlen = vsnprintf(dst + quote, avail, fmt, ap);
206
207         /* Horrible subtlety: vsnprintf *will* NUL terminate, even if it means
208          * chopping off the last character.  So if fmtlen ==
209          * membuf_num_space(&jout->outbuf), the result was truncated! */
210         if (fmtlen + (int)quote*2 >= membuf_num_space(&jout->outbuf)) {
211                 /* Make room for NUL terminator, even though we don't want it */
212                 dst = mkroom(jout, fmtlen + 1 + (int)quote*2);
213                 vsprintf(dst + quote, fmt, ap2);
214         }
215
216         /* Of course, if we need to escape it, we have to redo it all. */
217         if (json_escape_needed(dst + quote, fmtlen)) {
218                 struct json_escape *e;
219                 e = json_escape_len(NULL, dst + quote, fmtlen);
220                 fmtlen = strlen(e->s);
221                 dst = mkroom(jout, fmtlen + (int)quote*2);
222                 memcpy(dst + quote, e, fmtlen);
223                 tal_free(e);
224         }
225
226         if (quote) {
227                 dst[0] = '"';
228                 dst[fmtlen+1] = '"';
229         }
230         membuf_added(&jout->outbuf, fmtlen + (int)quote*2);
231         va_end(ap2);
232 }
233
234 void json_out_add(struct json_out *jout,
235                   const char *fieldname,
236                   bool quote,
237                   const char *fmt, ...)
238 {
239         va_list ap;
240
241         va_start(ap, fmt);
242         json_out_addv(jout, fieldname, quote, fmt, ap);
243         va_end(ap);
244 }
245
246 void json_out_addstr(struct json_out *jout,
247                      const char *fieldname,
248                      const char *str)
249 {
250         size_t len = strlen(str);
251         char *p;
252
253         p = json_out_member_direct(jout, fieldname, len + 2);
254         p[0] = p[1+len] = '"';
255         memcpy(p+1, str, len);
256 }
257
258 void json_out_add_splice(struct json_out *jout,
259                          const char *fieldname,
260                          const struct json_out *src)
261 {
262         const char *p;
263         size_t len;
264
265         p = json_out_contents(src, &len);
266         memcpy(json_out_member_direct(jout, fieldname, len), p, len);
267 }
268
269 char *json_out_direct(struct json_out *jout, size_t len)
270 {
271         char *p = mkroom(jout, len);
272         membuf_added(&jout->outbuf, len);
273         return p;
274 }
275
276 void json_out_finished(const struct json_out *jout)
277 {
278 #ifdef CCAN_JSON_OUT_DEBUG
279         assert(tal_count(jout->wrapping) == 0);
280 #endif
281 }
282
283 const char *json_out_contents(const struct json_out *jout, size_t *len)
284 {
285         *len = membuf_num_elems(&jout->outbuf);
286         return *len ? membuf_elems(&jout->outbuf) : NULL;
287 }
288
289 void json_out_consume(struct json_out *jout, size_t len)
290 {
291         membuf_consume(&jout->outbuf, len);
292 }