src/utils_parse_json.c: Implement parse_json().
[collectd.git] / src / utils_parse_json.c
1 /**
2  * collectd - src/utils_parse_json.c
3  * Copyright (C) 2018  Florian Forster
4  *
5  * MIT License
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining a copy
8  * of this software and associated documentation files (the "Software"), to deal
9  * in the Software without restriction, including without limitation the rights
10  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11  * copies of the Software, and to permit persons to whom the Software is
12  * furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in
15  * all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23  * SOFTWARE.
24  *
25  * Authors:
26  *   Florian Forster <octo at collectd.org>
27  **/
28
29 #include "collectd.h"
30
31 #include "common.h"
32 #include "plugin.h"
33 #include "utils_parse_json.h"
34
35 #include <yajl/yajl_tree.h>
36
37 static int parse_json_gauge(yajl_val value_val, value_t *v) /* {{{ */
38 {
39   if (YAJL_IS_DOUBLE(value_val))
40     v->gauge = (gauge_t)YAJL_GET_DOUBLE(value_val);
41   else if (YAJL_IS_NULL(value_val))
42     v->gauge = NAN;
43   else
44     return EINVAL;
45
46   return 0;
47 } /* }}} int parse_json_gauge */
48
49 static int parse_json_derive(yajl_val value_val, value_t *v) /* {{{ */
50 {
51   if (!YAJL_IS_INTEGER(value_val)) {
52     if (YAJL_IS_NUMBER(value_val)) {
53       WARNING("parse_json_derive: %s is not an integer. The most common cause "
54               "is that the publisher has the StoreRates option enabled.",
55               YAJL_GET_NUMBER(value_val));
56     }
57     return EINVAL;
58   }
59
60   v->derive = (derive_t)YAJL_GET_INTEGER(value_val);
61   return 0;
62 } /* }}} int parse_json_derive */
63
64 static int parse_json_counter(yajl_val value_val, value_t *v) /* {{{ */
65 {
66   if (!YAJL_IS_INTEGER(value_val)) {
67     if (YAJL_IS_NUMBER(value_val)) {
68       WARNING("parse_json_counter: %s is not an integer. The most common cause "
69               "is that the publisher has the StoreRates option enabled.",
70               YAJL_GET_NUMBER(value_val));
71     }
72     return EINVAL;
73   }
74
75   v->counter = (counter_t)YAJL_GET_INTEGER(value_val);
76   return 0;
77 } /* }}} int parse_json_counter */
78
79 static int parse_json_absolute(yajl_val value_val, value_t *v) /* {{{ */
80 {
81   if (!YAJL_IS_INTEGER(value_val)) {
82     if (YAJL_IS_NUMBER(value_val)) {
83       WARNING("parse_json_absolute: %s is not an integer. The most common "
84               "cause is that the publisher has the StoreRates option enabled.",
85               YAJL_GET_NUMBER(value_val));
86     }
87     return EINVAL;
88   }
89
90   v->absolute = (absolute_t)YAJL_GET_INTEGER(value_val);
91   return 0;
92 } /* }}} int parse_json_absolute */
93
94 static int parse_json_value(yajl_val value_val, yajl_val dstype_val,
95                             value_t *v) /* {{{ */
96 {
97   char const *dstype = YAJL_GET_STRING(dstype_val);
98
99   if (dstype == NULL) {
100     NOTICE("parse_json_value: unable to get data source type");
101     return EINVAL;
102   } else if (strcasecmp("gauge", dstype) == 0)
103     return parse_json_gauge(value_val, v);
104   else if (strcasecmp("derive", dstype) == 0)
105     return parse_json_derive(value_val, v);
106   else if (strcasecmp("counter", dstype) == 0)
107     return parse_json_counter(value_val, v);
108   else if (strcasecmp("absolute", dstype) == 0)
109     return parse_json_absolute(value_val, v);
110   else {
111     NOTICE("parse_json_value: unexpected data source type \"%s\"", dstype);
112     return EINVAL;
113   }
114 } /* }}} int parse_json_value */
115
116 static int parse_json_values(yajl_val root, value_list_t *vl) /* {{{ */
117 {
118   char const *values_path[] = {"values", NULL};
119   char const *dstypes_path[] = {"dstypes", NULL};
120
121   yajl_val values_val = yajl_tree_get(root, values_path, yajl_t_array);
122   yajl_val dstypes_val = yajl_tree_get(root, dstypes_path, yajl_t_array);
123
124   size_t i;
125
126   if (values_val == NULL) {
127     NOTICE("parse_json_values: key \"values\" not found (or not an array)");
128     return EINVAL;
129   }
130   if (dstypes_val == NULL) {
131     NOTICE("parse_json_values: key \"dstypes\" not found (or not an array)");
132     return EINVAL;
133   }
134   if (YAJL_GET_ARRAY(values_val)->len != YAJL_GET_ARRAY(dstypes_val)->len) {
135     NOTICE("parse_json_values: lengths of \"values\" (%zu) and "
136            "\"dstypes\" (%zu) differ",
137            YAJL_GET_ARRAY(values_val)->len, YAJL_GET_ARRAY(dstypes_val)->len);
138     return EINVAL;
139   }
140
141   vl->values = calloc(YAJL_GET_ARRAY(values_val)->len, sizeof(*vl->values));
142   if (vl->values == NULL)
143     return ENOMEM;
144   vl->values_len = (int)YAJL_GET_ARRAY(values_val)->len;
145
146   for (i = 0; i < YAJL_GET_ARRAY(values_val)->len; i++) {
147     int status = parse_json_value(YAJL_GET_ARRAY(values_val)->values[i],
148                                   YAJL_GET_ARRAY(dstypes_val)->values[i],
149                                   &vl->values[i]);
150     if (status != 0) {
151       sfree(vl->values);
152       vl->values = NULL;
153       vl->values_len = 0;
154       return status;
155     }
156   }
157
158   return 0;
159 } /* }}} int parse_json_values */
160
161 static int parse_json_string(yajl_val root, char *key, char *buffer,
162                              size_t buffer_size) /* {{{ */
163 {
164   char const *path[] = {key, NULL};
165   yajl_val string_val = yajl_tree_get(root, path, yajl_t_string);
166
167   if (!YAJL_IS_STRING(string_val))
168     return EINVAL;
169
170   sstrncpy(buffer, YAJL_GET_STRING(string_val), buffer_size);
171   return 0;
172 } /* }}} int parse_json_string */
173
174 static int parse_json_time(yajl_val root, char *key, cdtime_t *t) /* {{{ */
175 {
176   char const *path[] = {key, NULL};
177   yajl_val double_val = yajl_tree_get(root, path, yajl_t_number);
178
179   if (!YAJL_IS_DOUBLE(double_val))
180     return EINVAL;
181
182   *t = DOUBLE_TO_CDTIME_T(YAJL_GET_DOUBLE(double_val));
183   return 0;
184 } /* }}} int parse_json_time */
185
186 static int parse_json_vl(yajl_val root, value_list_t *vl) /* {{{ */
187 {
188   int status;
189
190   memset(vl, 0, sizeof(*vl));
191
192   status = parse_json_string(root, "host", vl->host, sizeof(vl->host));
193   if (status != 0) {
194     NOTICE("parse_json: parsing key \"host\" failed");
195     return status;
196   }
197
198   status = parse_json_string(root, "plugin", vl->plugin, sizeof(vl->plugin));
199   if (status != 0) {
200     NOTICE("parse_json: parsing key \"plugin\" failed");
201     return status;
202   }
203   parse_json_string(root, "plugin_instance", vl->plugin_instance,
204                     sizeof(vl->plugin_instance));
205
206   status = parse_json_string(root, "type", vl->type, sizeof(vl->type));
207   if (status != 0) {
208     NOTICE("parse_json: parsing key \"type\" failed");
209     return status;
210   }
211   parse_json_string(root, "type_instance", vl->type_instance,
212                     sizeof(vl->type_instance));
213
214   status = parse_json_time(root, "time", &vl->time);
215   if (status != 0) {
216     NOTICE("parse_json: parsing key \"time\" failed");
217     return status;
218   }
219
220   status = parse_json_time(root, "interval", &vl->interval);
221   if (status != 0) {
222     NOTICE("parse_json: parsing key \"interval\" failed");
223     return status;
224   }
225
226   status = parse_json_values(root, vl);
227   if (status != 0) {
228     return status;
229   }
230
231   return 0;
232 } /* }}} int parse_json_vl */
233
234 int parse_json(char const *json, value_list_t ***ret_vls,
235                size_t *ret_vls_num) /* {{{ */
236 {
237   yajl_val root;
238   char errbuf[1024];
239   size_t i;
240
241   value_list_t **vls = NULL;
242   size_t vls_num = 0;
243
244   root = yajl_tree_parse(json, errbuf, sizeof(errbuf));
245   if (root == NULL) {
246     ERROR("parse_json: yajl_tree_parse failed: %s", errbuf);
247     return -1;
248   }
249
250   if (!YAJL_IS_ARRAY(root)) {
251     NOTICE("parse_json: root is not an array");
252     return -1;
253   }
254
255   for (i = 0; i < YAJL_GET_ARRAY(root)->len; i++) {
256     yajl_val vl_json = YAJL_GET_ARRAY(root)->values[i];
257     value_list_t *vl;
258
259     vl = malloc(sizeof(*vl));
260     if (vl == NULL)
261       continue;
262
263     int status = parse_json_vl(vl_json, vl);
264     if (status != 0) {
265       sfree(vl);
266       continue;
267     }
268
269     value_list_t **tmp = realloc(vls, sizeof(*vls) * (vls_num + 1));
270     if (tmp == NULL) {
271       ERROR("parse_json: realloc failed");
272       sfree(vl->values);
273       sfree(vl);
274       continue;
275     }
276     vls = tmp;
277
278     vls[vls_num] = vl;
279     vls_num++;
280   }
281
282   *ret_vls = vls;
283   *ret_vls_num = vls_num;
284   return 0;
285 } /* }}} int parse_json */