1 /**
2 * Copyright (c) 2015, Timothy Stack
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * * Redistributions of source code must retain the above copyright notice, this
10 * list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 * * Neither the name of Timothy Stack nor the names of its contributors
15 * may be used to endorse or promote products derived from this software
16 * without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 *
29 * @file yajlpp.cc
30 */
31
32 #include "config.h"
33
34 #include <regex>
35 #include <utility>
36
37 #include "fmt/format.h"
38
39 #include "yajlpp.hh"
40 #include "yajlpp_def.hh"
41 #include "yajl/api/yajl_parse.h"
42
43 using namespace std;
44
45 const json_path_handler_base::enum_value_t json_path_handler_base::ENUM_TERMINATOR((const char *) nullptr, 0);
46
json_path_handler_base(const string & property)47 json_path_handler_base::json_path_handler_base(const string &property)
48 : jph_property(property.back() == '#' ?
49 property.substr(0, property.size() - 1) :
50 property),
51 jph_regex(pcrepp::quote(property), PCRE_ANCHORED),
52 jph_is_array(property.back() == '#')
53 {
54 memset(&this->jph_callbacks, 0, sizeof(this->jph_callbacks));
55 }
56
scrub_pattern(const std::string & pattern)57 static std::string scrub_pattern(const std::string &pattern)
58 {
59 static const std::regex CAPTURE(R"(\(\?\<\w+\>)");
60
61 return std::regex_replace(pattern, CAPTURE, "(");
62 }
63
json_path_handler_base(const pcrepp & property)64 json_path_handler_base::json_path_handler_base(const pcrepp &property)
65 : jph_property(scrub_pattern(property.p_pattern)),
66 jph_regex(property),
67 jph_is_array(property.p_pattern.back() == '#'),
68 jph_is_pattern_property(true)
69 {
70 memset(&this->jph_callbacks, 0, sizeof(this->jph_callbacks));
71 }
72
json_path_handler_base(string property,const pcrepp & property_re)73 json_path_handler_base::json_path_handler_base(string property,
74 const pcrepp &property_re)
75 : jph_property(std::move(property)),
76 jph_regex(property_re),
77 jph_is_array(property_re.p_pattern.find('#') != string::npos)
78 {
79 memset(&this->jph_callbacks, 0, sizeof(this->jph_callbacks));
80 }
81
gen(yajlpp_gen_context & ygc,yajl_gen handle) const82 yajl_gen_status json_path_handler_base::gen(yajlpp_gen_context &ygc, yajl_gen handle) const
83 {
84 vector<string> local_paths;
85
86 if (this->jph_path_provider) {
87 this->jph_path_provider(ygc.ygc_obj_stack.top(), local_paths);
88 }
89 else {
90 local_paths.emplace_back(this->jph_property);
91 }
92
93 if (this->jph_children) {
94 for (const auto &lpath : local_paths) {
95 string full_path = lpath;
96 if (this->jph_path_provider) {
97 full_path += "/";
98 }
99 int start_depth = ygc.ygc_depth;
100
101 yajl_gen_string(handle, lpath);
102 yajl_gen_map_open(handle);
103 ygc.ygc_depth += 1;
104
105 if (this->jph_obj_provider) {
106 pcre_context_static<30> pc;
107 pcre_input pi(full_path);
108
109 this->jph_regex.match(pc, pi);
110 ygc.ygc_obj_stack.push(this->jph_obj_provider(
111 {{pc, pi}, -1}, ygc.ygc_obj_stack.top()
112 ));
113 if (!ygc.ygc_default_stack.empty()) {
114 ygc.ygc_default_stack.push(this->jph_obj_provider(
115 {{pc, pi}, -1}, ygc.ygc_default_stack.top()
116 ));
117 }
118 }
119
120 for (auto &jph : this->jph_children->jpc_children) {
121 yajl_gen_status status = jph.gen(ygc, handle);
122
123 const unsigned char *buf;
124 size_t len;
125 yajl_gen_get_buf(handle, &buf, &len);
126 if (status != yajl_gen_status_ok) {
127 log_error("yajl_gen failure for: %s -- %d",
128 jph.jph_property.c_str(),
129 status);
130 return status;
131 }
132 }
133
134 if (this->jph_obj_provider) {
135 ygc.ygc_obj_stack.pop();
136 if (!ygc.ygc_default_stack.empty()) {
137 ygc.ygc_default_stack.pop();
138 }
139 }
140
141 while (ygc.ygc_depth > start_depth) {
142 yajl_gen_map_close(handle);
143 ygc.ygc_depth -= 1;
144 }
145 }
146 }
147 else if (this->jph_gen_callback != nullptr) {
148 return this->jph_gen_callback(ygc, *this, handle);
149 }
150
151 return yajl_gen_status_ok;
152 }
153
154 const char *SCHEMA_TYPE_STRINGS[] = {
155 "any",
156 "boolean",
157 "integer",
158 "number",
159 "string",
160 "array",
161 "object",
162 };
163
gen_schema(yajlpp_gen_context & ygc) const164 yajl_gen_status json_path_handler_base::gen_schema(yajlpp_gen_context &ygc) const
165 {
166 if (this->jph_children) {
167 {
168 yajlpp_map schema(ygc.ygc_handle);
169
170 if (this->jph_description && this->jph_description[0]) {
171 schema.gen("description");
172 schema.gen(this->jph_description);
173 }
174 if (this->jph_is_pattern_property) {
175 ygc.ygc_path.emplace_back(fmt::format("<{}>", this->jph_regex.name_for_capture(0)));
176 } else {
177 ygc.ygc_path.emplace_back(this->jph_property);
178 }
179 if (this->jph_children->jpc_definition_id.empty()) {
180 schema.gen("title");
181 schema.gen(fmt::format("/{}", fmt::join(ygc.ygc_path, "/")));
182 schema.gen("type");
183 if (this->jph_is_array) {
184 if (this->jph_regex.p_pattern.find("#?") == string::npos) {
185 schema.gen("array");
186 } else {
187 yajlpp_array type_array(ygc.ygc_handle);
188
189 type_array.gen("array");
190 for (auto schema_type: this->get_types()) {
191 type_array.gen(SCHEMA_TYPE_STRINGS[(int) schema_type]);
192 }
193 }
194 schema.gen("items");
195 yajl_gen_map_open(ygc.ygc_handle);
196 yajl_gen_string(ygc.ygc_handle, "type");
197 this->gen_schema_type(ygc);
198 } else {
199 this->gen_schema_type(ygc);
200 }
201 this->jph_children->gen_schema(ygc);
202 if (this->jph_is_array) {
203 yajl_gen_map_close(ygc.ygc_handle);
204 }
205 } else {
206 schema.gen("title");
207 schema.gen(fmt::format("/{}", fmt::join(ygc.ygc_path, "/")));
208 this->jph_children->gen_schema(ygc);
209 }
210 ygc.ygc_path.pop_back();
211 }
212 } else {
213 yajlpp_map schema(ygc.ygc_handle);
214
215 if (this->jph_is_pattern_property) {
216 ygc.ygc_path.emplace_back(fmt::format("<{}>", this->jph_regex.name_for_capture(0)));
217 } else {
218 ygc.ygc_path.emplace_back(this->jph_property);
219 }
220
221 schema.gen("title");
222 schema.gen(fmt::format("/{}", fmt::join(ygc.ygc_path, "/")));
223 if (this->jph_description && this->jph_description[0]) {
224 schema.gen("description");
225 schema.gen(this->jph_description);
226 }
227
228 schema.gen("type");
229
230 if (this->jph_is_array) {
231 if (this->jph_regex.p_pattern.find("#?") == string::npos) {
232 schema.gen("array");
233 } else {
234 yajlpp_array type_array(ygc.ygc_handle);
235
236 type_array.gen("array");
237 for (auto schema_type: this->get_types()) {
238 type_array.gen(SCHEMA_TYPE_STRINGS[(int) schema_type]);
239 }
240 }
241 yajl_gen_string(ygc.ygc_handle, "items");
242 yajl_gen_map_open(ygc.ygc_handle);
243 yajl_gen_string(ygc.ygc_handle, "type");
244 }
245
246 this->gen_schema_type(ygc);
247
248 if (!this->jph_examples.empty()) {
249 schema.gen("examples");
250
251 yajlpp_array example_array(ygc.ygc_handle);
252 for (auto &ex : this->jph_examples) {
253 example_array.gen(ex);
254 }
255 }
256
257 if (this->jph_is_array) {
258 yajl_gen_map_close(ygc.ygc_handle);
259 }
260
261 ygc.ygc_path.pop_back();
262 }
263
264 return yajl_gen_status_ok;
265 }
266
gen_schema_type(yajlpp_gen_context & ygc) const267 yajl_gen_status json_path_handler_base::gen_schema_type(yajlpp_gen_context &ygc) const
268 {
269 yajlpp_generator schema(ygc.ygc_handle);
270
271 auto types = this->get_types();
272 if (types.size() == 1) {
273 yajl_gen_string(ygc.ygc_handle, SCHEMA_TYPE_STRINGS[(int) types[0]]);
274 } else {
275 yajlpp_array type_array(ygc.ygc_handle);
276
277 for (auto schema_type: types) {
278 type_array.gen(SCHEMA_TYPE_STRINGS[(int) schema_type]);
279 }
280 }
281
282 for (auto &schema_type : types) {
283 switch (schema_type) {
284 case schema_type_t::STRING:
285 if (this->jph_min_length > 0) {
286 schema("minLength");
287 schema(this->jph_min_length);
288 }
289 if (this->jph_max_length < INT_MAX) {
290 schema("maxLength");
291 schema(this->jph_max_length);
292 }
293 if (this->jph_pattern_re) {
294 schema("pattern");
295 schema(this->jph_pattern_re);
296 }
297 if (this->jph_enum_values) {
298 schema("enum");
299
300 yajlpp_array enum_array(ygc.ygc_handle);
301 for (int lpc = 0; this->jph_enum_values[lpc].first; lpc++) {
302 enum_array.gen(this->jph_enum_values[lpc].first);
303 }
304 }
305 break;
306 case schema_type_t::INTEGER:
307 case schema_type_t::NUMBER:
308 if (this->jph_min_value > LLONG_MIN) {
309 schema("minimum");
310 schema(this->jph_min_value);
311 }
312 break;
313 default:
314 break;
315 }
316 }
317
318 return yajl_gen_keys_must_be_strings;
319 }
320
walk(const std::function<void (const json_path_handler_base &,const std::string &,void *)> & cb,void * root,const string & base) const321 void json_path_handler_base::walk(
322 const std::function<void(const json_path_handler_base &,
323 const std::string &,
324 void *)> &cb,
325 void *root, const string &base) const
326 {
327 vector<string> local_paths;
328
329 if (this->jph_path_provider) {
330 this->jph_path_provider(root, local_paths);
331
332 for (auto &lpath : local_paths) {
333 cb(*this, lpath, nullptr);
334 }
335 }
336 else {
337 local_paths.emplace_back(this->jph_property);
338
339 string full_path = base + this->jph_property;
340 if (this->jph_children) {
341 full_path += "/";
342 }
343 cb(*this, full_path, nullptr);
344 }
345
346 if (this->jph_children) {
347 for (const auto &lpath : local_paths) {
348 for (auto &jph : this->jph_children->jpc_children) {
349 string full_path = base + lpath;
350 if (this->jph_children) {
351 full_path += "/";
352 }
353 json_path_container dummy = {
354 json_path_handler(this->jph_property)
355 };
356 dummy.jpc_children[0].jph_callbacks = this->jph_callbacks;
357
358 yajlpp_parse_context ypc("possibilities", &dummy);
359 void *child_root = root;
360
361 ypc.set_path(full_path)
362 .with_obj(root)
363 .update_callbacks();
364 if (this->jph_obj_provider) {
365 string full_path = lpath + "/";
366 pcre_input pi(full_path);
367
368 if (!this->jph_regex.match(ypc.ypc_pcre_context, pi)) {
369 ensure(false);
370 }
371 child_root = this->jph_obj_provider(
372 {{ypc.ypc_pcre_context, pi}, -1}, root);
373 }
374
375 jph.walk(cb, child_root, full_path);
376 }
377 }
378 }
379 else {
380 for (auto &lpath : local_paths) {
381 void *field = nullptr;
382
383 if (this->jph_field_getter) {
384 field = this->jph_field_getter(root, lpath);
385 }
386 cb(*this, base + lpath, field);
387 }
388 }
389 }
390
yajlpp_parse_context(std::string source,const struct json_path_container * handlers)391 yajlpp_parse_context::yajlpp_parse_context(std::string source,
392 const struct json_path_container *handlers)
393 : ypc_source(std::move(source)),
394 ypc_handlers(handlers)
395 {
396 this->ypc_path.reserve(4096);
397 this->ypc_path.push_back('/');
398 this->ypc_path.push_back('\0');
399 this->ypc_callbacks = DEFAULT_CALLBACKS;
400 memset(&this->ypc_alt_callbacks, 0, sizeof(this->ypc_alt_callbacks));
401 }
402
map_start(void * ctx)403 int yajlpp_parse_context::map_start(void *ctx)
404 {
405 yajlpp_parse_context *ypc = (yajlpp_parse_context *)ctx;
406 int retval = 1;
407
408 require(ypc->ypc_path.size() >= 2);
409
410 ypc->ypc_path_index_stack.push_back(ypc->ypc_path.size() - 1);
411
412 if (ypc->ypc_path.size() > 1 &&
413 ypc->ypc_path[ypc->ypc_path.size() - 2] == '#') {
414 ypc->ypc_array_index.back() += 1;
415 }
416
417 if (ypc->ypc_alt_callbacks.yajl_start_map != nullptr) {
418 retval = ypc->ypc_alt_callbacks.yajl_start_map(ypc);
419 }
420
421 return retval;
422 }
423
map_key(void * ctx,const unsigned char * key,size_t len)424 int yajlpp_parse_context::map_key(void *ctx,
425 const unsigned char *key,
426 size_t len)
427 {
428 yajlpp_parse_context *ypc = (yajlpp_parse_context *)ctx;
429 int retval = 1;
430
431 require(ypc->ypc_path.size() >= 2);
432
433 ypc->ypc_path.resize(ypc->ypc_path_index_stack.back());
434 if (ypc->ypc_path.back() != '/') {
435 ypc->ypc_path.push_back('/');
436 }
437 if (ypc->ypc_handlers != nullptr) {
438 for (size_t lpc = 0; lpc < len; lpc++) {
439 switch (key[lpc]) {
440 case '~':
441 ypc->ypc_path.push_back('~');
442 ypc->ypc_path.push_back('0');
443 break;
444 case '/':
445 ypc->ypc_path.push_back('~');
446 ypc->ypc_path.push_back('1');
447 break;
448 case '#':
449 ypc->ypc_path.push_back('~');
450 ypc->ypc_path.push_back('2');
451 break;
452 default:
453 ypc->ypc_path.push_back(key[lpc]);
454 break;
455 }
456 }
457 }
458 else {
459 size_t start = ypc->ypc_path.size();
460 ypc->ypc_path.resize(ypc->ypc_path.size() + len);
461 memcpy(&ypc->ypc_path[start], key, len);
462 }
463 ypc->ypc_path.push_back('\0');
464
465 if (ypc->ypc_alt_callbacks.yajl_map_key != nullptr) {
466 retval = ypc->ypc_alt_callbacks.yajl_map_key(ctx, key, len);
467 }
468
469 if (ypc->ypc_handlers != nullptr) {
470 ypc->update_callbacks();
471 }
472
473 ensure(ypc->ypc_path.size() >= 2);
474
475 return retval;
476 }
477
update_callbacks(const json_path_container * orig_handlers,int child_start)478 void yajlpp_parse_context::update_callbacks(const json_path_container *orig_handlers, int child_start)
479 {
480 const json_path_container *handlers = orig_handlers;
481
482 this->ypc_current_handler = nullptr;
483
484 if (this->ypc_handlers == nullptr) {
485 return;
486 }
487
488 this->ypc_sibling_handlers = orig_handlers;
489
490 pcre_input pi(&this->ypc_path[0], 0, this->ypc_path.size() - 1);
491
492 this->ypc_callbacks = DEFAULT_CALLBACKS;
493
494 if (handlers == nullptr) {
495 handlers = this->ypc_handlers;
496 this->ypc_handler_stack.clear();
497 }
498
499 if (!this->ypc_active_paths.empty()) {
500 string curr_path(&this->ypc_path[0], this->ypc_path.size() - 1);
501
502 if (this->ypc_active_paths.find(curr_path) ==
503 this->ypc_active_paths.end()) {
504 return;
505 }
506 }
507
508 if (child_start == 0 && !this->ypc_obj_stack.empty()) {
509 while (this->ypc_obj_stack.size() > 1) {
510 this->ypc_obj_stack.pop();
511 }
512 }
513
514 for (auto &jph : handlers->jpc_children) {
515 pi.reset(&this->ypc_path[1 + child_start],
516 0,
517 this->ypc_path.size() - 2 - child_start);
518 if (jph.jph_regex.match(this->ypc_pcre_context, pi)) {
519 pcre_context::capture_t *cap = this->ypc_pcre_context.all();
520
521 if (jph.jph_obj_provider) {
522 int index = this->index_for_provider();
523
524 if ((1 + child_start + cap->c_end != (int)this->ypc_path.size() - 1) &&
525 (!jph.is_array() || index >= 0)) {
526 this->ypc_obj_stack.push(jph.jph_obj_provider(
527 {{this->ypc_pcre_context, pi}, index},
528 this->ypc_obj_stack.top()));
529 }
530 }
531
532 if (jph.jph_children) {
533 this->ypc_handler_stack.emplace_back(&jph);
534
535 if (1 + child_start + cap->c_end != (int)this->ypc_path.size() - 1) {
536 this->update_callbacks(jph.jph_children,
537 1 + child_start + cap->c_end);
538 return;
539 }
540 }
541 else {
542 if (1 + child_start + cap->c_end != (int)this->ypc_path.size() - 1) {
543 continue;
544 }
545
546 this->ypc_current_handler = &jph;
547 }
548
549 if (jph.jph_callbacks.yajl_null != nullptr)
550 this->ypc_callbacks.yajl_null = jph.jph_callbacks.yajl_null;
551 if (jph.jph_callbacks.yajl_boolean != nullptr)
552 this->ypc_callbacks.yajl_boolean = jph.jph_callbacks.yajl_boolean;
553 if (jph.jph_callbacks.yajl_integer != nullptr)
554 this->ypc_callbacks.yajl_integer = jph.jph_callbacks.yajl_integer;
555 if (jph.jph_callbacks.yajl_double != nullptr)
556 this->ypc_callbacks.yajl_double = jph.jph_callbacks.yajl_double;
557 if (jph.jph_callbacks.yajl_string != nullptr)
558 this->ypc_callbacks.yajl_string = jph.jph_callbacks.yajl_string;
559
560 return;
561 }
562 }
563
564 this->ypc_handler_stack.emplace_back(nullptr);
565 }
566
map_end(void * ctx)567 int yajlpp_parse_context::map_end(void *ctx)
568 {
569 yajlpp_parse_context *ypc = (yajlpp_parse_context *)ctx;
570 int retval = 1;
571
572 ypc->ypc_path.resize(ypc->ypc_path_index_stack.back());
573 ypc->ypc_path.push_back('\0');
574 ypc->ypc_path_index_stack.pop_back();
575
576 if (ypc->ypc_alt_callbacks.yajl_end_map != nullptr) {
577 retval = ypc->ypc_alt_callbacks.yajl_end_map(ctx);
578 }
579
580 ypc->update_callbacks();
581
582 ensure(ypc->ypc_path.size() >= 2);
583
584 return retval;
585 }
586
array_start(void * ctx)587 int yajlpp_parse_context::array_start(void *ctx)
588 {
589 yajlpp_parse_context *ypc = (yajlpp_parse_context *)ctx;
590 int retval = 1;
591
592 ypc->ypc_path_index_stack.push_back(ypc->ypc_path.size() - 1);
593 ypc->ypc_path[ypc->ypc_path.size() - 1] = '#';
594 ypc->ypc_path.push_back('\0');
595 ypc->ypc_array_index.push_back(-1);
596
597 if (ypc->ypc_alt_callbacks.yajl_start_array != nullptr) {
598 retval = ypc->ypc_alt_callbacks.yajl_start_array(ctx);
599 }
600
601 ypc->update_callbacks();
602
603 ensure(ypc->ypc_path.size() >= 2);
604
605 return retval;
606 }
607
array_end(void * ctx)608 int yajlpp_parse_context::array_end(void *ctx)
609 {
610 yajlpp_parse_context *ypc = (yajlpp_parse_context *)ctx;
611 int retval = 1;
612
613 ypc->ypc_path.resize(ypc->ypc_path_index_stack.back());
614 ypc->ypc_path.push_back('\0');
615 ypc->ypc_path_index_stack.pop_back();
616 ypc->ypc_array_index.pop_back();
617
618 if (ypc->ypc_alt_callbacks.yajl_end_array != nullptr) {
619 retval = ypc->ypc_alt_callbacks.yajl_end_array(ctx);
620 }
621
622 ypc->update_callbacks();
623
624 ensure(ypc->ypc_path.size() >= 2);
625
626 return retval;
627 }
628
handle_unused(void * ctx)629 int yajlpp_parse_context::handle_unused(void *ctx)
630 {
631 yajlpp_parse_context *ypc = (yajlpp_parse_context *)ctx;
632
633 if (ypc->ypc_ignore_unused) {
634 return 1;
635 }
636
637 const json_path_handler_base *handler = ypc->ypc_current_handler;
638
639 int line_number = ypc->get_line_number();
640
641 if (handler != nullptr && strlen(handler->jph_synopsis) > 0 &&
642 strlen(handler->jph_description) > 0) {
643
644 ypc->report_error(
645 lnav_log_level_t::WARNING,
646 "%s:line %d",
647 ypc->ypc_source.c_str(),
648 line_number);
649 ypc->report_error(lnav_log_level_t::WARNING, " unexpected data for path");
650
651 ypc->report_error(lnav_log_level_t::WARNING,
652 " %s %s -- %s",
653 &ypc->ypc_path[0],
654 handler->jph_synopsis,
655 handler->jph_description);
656 }
657 else if (ypc->ypc_path[1]) {
658 ypc->report_error(lnav_log_level_t::WARNING,
659 "%s:line %d",
660 ypc->ypc_source.c_str(),
661 line_number);
662 ypc->report_error(lnav_log_level_t::WARNING, " unexpected path --");
663
664 ypc->report_error(lnav_log_level_t::WARNING, " %s", &ypc->ypc_path[0]);
665 } else {
666 ypc->report_error(lnav_log_level_t::WARNING,
667 "%s:line %d\n unexpected JSON value",
668 ypc->ypc_source.c_str(),
669 line_number);
670 }
671
672 if (ypc->ypc_callbacks.yajl_boolean != (int (*)(void *, int))yajlpp_parse_context::handle_unused ||
673 ypc->ypc_callbacks.yajl_integer != (int (*)(void *, long long))yajlpp_parse_context::handle_unused ||
674 ypc->ypc_callbacks.yajl_double != (int (*)(void *, double))yajlpp_parse_context::handle_unused ||
675 ypc->ypc_callbacks.yajl_string != (int (*)(void *, const unsigned char *, size_t))yajlpp_parse_context::handle_unused) {
676 ypc->report_error(lnav_log_level_t::WARNING, " expecting one of the following data types --");
677 }
678
679 if (ypc->ypc_callbacks.yajl_boolean != (int (*)(void *, int))yajlpp_parse_context::handle_unused) {
680 ypc->report_error(lnav_log_level_t::WARNING, " boolean");
681 }
682 if (ypc->ypc_callbacks.yajl_integer != (int (*)(void *, long long))yajlpp_parse_context::handle_unused) {
683 ypc->report_error(lnav_log_level_t::WARNING, " integer");
684 }
685 if (ypc->ypc_callbacks.yajl_double != (int (*)(void *, double))yajlpp_parse_context::handle_unused) {
686 ypc->report_error(lnav_log_level_t::WARNING, " float");
687 }
688 if (ypc->ypc_callbacks.yajl_string != (int (*)(void *, const unsigned char *, size_t))yajlpp_parse_context::handle_unused) {
689 ypc->report_error(lnav_log_level_t::WARNING, " string");
690 }
691
692 if (handler == nullptr) {
693 const json_path_container *accepted_handlers;
694
695 if (ypc->ypc_sibling_handlers) {
696 accepted_handlers = ypc->ypc_sibling_handlers;
697 } else {
698 accepted_handlers = ypc->ypc_handlers;
699 }
700
701 ypc->report_error(lnav_log_level_t::WARNING, " accepted paths --");
702 for (auto &jph : accepted_handlers->jpc_children) {
703 ypc->report_error(lnav_log_level_t::WARNING, " %s %s -- %s",
704 jph.jph_property.c_str(),
705 jph.jph_synopsis,
706 jph.jph_description);
707 }
708 }
709
710 return 1;
711 }
712
713 const yajl_callbacks yajlpp_parse_context::DEFAULT_CALLBACKS = {
714 yajlpp_parse_context::handle_unused,
715 (int (*)(void *, int))yajlpp_parse_context::handle_unused,
716 (int (*)(void *, long long))yajlpp_parse_context::handle_unused,
717 (int (*)(void *, double))yajlpp_parse_context::handle_unused,
718 nullptr,
719 (int (*)(void *, const unsigned char *, size_t))
720 yajlpp_parse_context::handle_unused,
721 yajlpp_parse_context::map_start,
722 yajlpp_parse_context::map_key,
723 yajlpp_parse_context::map_end,
724 yajlpp_parse_context::array_start,
725 yajlpp_parse_context::array_end,
726 };
727
728 yajl_status
parse(const unsigned char * jsonText,size_t jsonTextLen)729 yajlpp_parse_context::parse(const unsigned char *jsonText, size_t jsonTextLen)
730 {
731 this->ypc_json_text = jsonText;
732
733 yajl_status retval = yajl_parse(this->ypc_handle, jsonText, jsonTextLen);
734
735 size_t consumed = yajl_get_bytes_consumed(this->ypc_handle);
736
737 this->ypc_line_number += std::count(&jsonText[0], &jsonText[consumed], '\n');
738
739 this->ypc_json_text = nullptr;
740
741 if (retval != yajl_status_ok && this->ypc_error_reporter) {
742 auto msg = yajl_get_error(this->ypc_handle, 1, jsonText, jsonTextLen);
743
744 this->ypc_error_reporter(
745 *this, lnav_log_level_t::ERROR,
746 fmt::format("error:{}:{}:invalid json -- {}",
747 this->ypc_source,
748 this->get_line_number(),
749 msg).c_str());
750 yajl_free_error(this->ypc_handle, msg);
751 }
752
753 return retval;
754 }
755
complete_parse()756 yajl_status yajlpp_parse_context::complete_parse()
757 {
758 yajl_status retval = yajl_complete_parse(this->ypc_handle);
759
760 if (retval != yajl_status_ok && this->ypc_error_reporter) {
761 auto msg = yajl_get_error(this->ypc_handle, 0, nullptr, 0);
762
763 this->ypc_error_reporter(
764 *this, lnav_log_level_t::ERROR,
765 fmt::format("error:{}:invalid json -- {}",
766 this->ypc_source,
767 msg).c_str());
768 yajl_free_error(this->ypc_handle, msg);
769 }
770
771 return retval;
772 }
773
get_path() const774 const intern_string_t yajlpp_parse_context::get_path() const
775 {
776 if (this->ypc_path.size() <= 1) {
777 return intern_string_t();
778 }
779 return intern_string::lookup(&this->ypc_path[1],
780 this->ypc_path.size() - 2);
781 }
782
get_full_path() const783 const intern_string_t yajlpp_parse_context::get_full_path() const
784 {
785 if (this->ypc_path.size() <= 1) {
786 static intern_string_t SLASH = intern_string::lookup("/");
787
788 return SLASH;
789 }
790 return intern_string::lookup(&this->ypc_path[0],
791 this->ypc_path.size() - 1);
792 }
793
reset(const struct json_path_container * handlers)794 void yajlpp_parse_context::reset(const struct json_path_container *handlers)
795 {
796 this->ypc_handlers = handlers;
797 this->ypc_path.clear();
798 this->ypc_path.push_back('/');
799 this->ypc_path.push_back('\0');
800 this->ypc_path_index_stack.clear();
801 this->ypc_array_index.clear();
802 this->ypc_callbacks = DEFAULT_CALLBACKS;
803 memset(&this->ypc_alt_callbacks, 0, sizeof(this->ypc_alt_callbacks));
804 this->ypc_sibling_handlers = nullptr;
805 this->ypc_current_handler = nullptr;
806 while (!this->ypc_obj_stack.empty()) {
807 this->ypc_obj_stack.pop();
808 }
809 }
810
set_static_handler(json_path_handler_base & jph)811 void yajlpp_parse_context::set_static_handler(json_path_handler_base &jph)
812 {
813 this->ypc_path.clear();
814 this->ypc_path.push_back('/');
815 this->ypc_path.push_back('\0');
816 this->ypc_path_index_stack.clear();
817 this->ypc_array_index.clear();
818 if (jph.jph_callbacks.yajl_null != nullptr)
819 this->ypc_callbacks.yajl_null = jph.jph_callbacks.yajl_null;
820 if (jph.jph_callbacks.yajl_boolean != nullptr)
821 this->ypc_callbacks.yajl_boolean = jph.jph_callbacks.yajl_boolean;
822 if (jph.jph_callbacks.yajl_integer != nullptr)
823 this->ypc_callbacks.yajl_integer = jph.jph_callbacks.yajl_integer;
824 if (jph.jph_callbacks.yajl_double != nullptr)
825 this->ypc_callbacks.yajl_double = jph.jph_callbacks.yajl_double;
826 if (jph.jph_callbacks.yajl_string != nullptr)
827 this->ypc_callbacks.yajl_string = jph.jph_callbacks.yajl_string;
828 }
829
set_path(const string & path)830 yajlpp_parse_context &yajlpp_parse_context::set_path(const string &path)
831 {
832 this->ypc_path.resize(path.size() + 1);
833 std::copy(path.begin(), path.end(), this->ypc_path.begin());
834 this->ypc_path[path.size()] = '\0';
835 for (size_t lpc = 0; lpc < path.size(); lpc++) {
836 switch (path[lpc]) {
837 case '/':
838 this->ypc_path_index_stack.push_back(
839 this->ypc_path_index_stack.empty() ? 1 : 0 + lpc);
840 break;
841 }
842 }
843 return *this;
844 }
845
get_path_fragment(int offset,char * frag_in,size_t & len_out) const846 const char *yajlpp_parse_context::get_path_fragment(int offset, char *frag_in,
847 size_t &len_out) const
848 {
849 const char *retval;
850 size_t start, end;
851
852 if (offset < 0) {
853 offset = ((int) this->ypc_path_index_stack.size()) + offset;
854 }
855 start = this->ypc_path_index_stack[offset] + ((offset == 0) ? 0 : 1);
856 if ((offset + 1) < (int)this->ypc_path_index_stack.size()) {
857 end = this->ypc_path_index_stack[offset + 1];
858 }
859 else {
860 end = this->ypc_path.size() - 1;
861 }
862 if (this->ypc_handlers) {
863 len_out = json_ptr::decode(frag_in, &this->ypc_path[start], end - start);
864 retval = frag_in;
865 }
866 else {
867 retval = &this->ypc_path[start];
868 len_out = end - start;
869 }
870
871 return retval;
872 }
873
get_line_number() const874 int yajlpp_parse_context::get_line_number() const
875 {
876 if (this->ypc_handle != NULL && this->ypc_json_text) {
877 size_t consumed = yajl_get_bytes_consumed(this->ypc_handle);
878 long current_count = std::count(&this->ypc_json_text[0],
879 &this->ypc_json_text[consumed],
880 '\n');
881
882 return this->ypc_line_number + current_count;
883 }
884 else {
885 return this->ypc_line_number;
886 }
887 }
888
gen()889 void yajlpp_gen_context::gen()
890 {
891 yajlpp_map root(this->ygc_handle);
892
893 for (auto &jph : this->ygc_handlers->jpc_children) {
894 jph.gen(*this, this->ygc_handle);
895 }
896 }
897
gen_schema(const json_path_container * handlers)898 void yajlpp_gen_context::gen_schema(const json_path_container *handlers)
899 {
900 if (handlers == nullptr) {
901 handlers = this->ygc_handlers;
902 }
903
904 {
905 yajlpp_map schema(this->ygc_handle);
906
907 if (!handlers->jpc_schema_id.empty()) {
908 schema.gen("$id");
909 schema.gen(handlers->jpc_schema_id);
910 }
911 schema.gen("$schema");
912 schema.gen("http://json-schema.org/draft-07/schema#");
913 handlers->gen_schema(*this);
914
915 if (!this->ygc_schema_definitions.empty()) {
916 schema.gen("definitions");
917
918 yajlpp_map defs(this->ygc_handle);
919 for (auto &container : this->ygc_schema_definitions) {
920 defs.gen(container.first);
921
922 yajlpp_map def(this->ygc_handle);
923
924 def.gen("title");
925 def.gen(container.first);
926 if (!container.second->jpc_description.empty()) {
927 def.gen("description");
928 def.gen(container.second->jpc_description);
929 }
930 def.gen("type");
931 def.gen("object");
932 def.gen("$$target");
933 def.gen(fmt::format("#/definitions/{}", container.first));
934 container.second->gen_properties(*this);
935 }
936 }
937 }
938 }
939
with_context(yajlpp_parse_context & ypc)940 yajlpp_gen_context &yajlpp_gen_context::with_context(yajlpp_parse_context &ypc)
941 {
942 this->ygc_obj_stack = ypc.ypc_obj_stack;
943 if (ypc.ypc_current_handler == nullptr &&
944 !ypc.ypc_handler_stack.empty() &&
945 ypc.ypc_handler_stack.back() != nullptr) {
946 this->ygc_handlers = ypc.ypc_handler_stack.back()->jph_children;
947 this->ygc_depth += 1;
948 }
949 return *this;
950 }
951
with_children(const json_path_container & container)952 json_path_handler &json_path_handler::with_children(const json_path_container &container) {
953 this->jph_children = &container;
954 return *this;
955 }
956
gen_schema(yajlpp_gen_context & ygc) const957 void json_path_container::gen_schema(yajlpp_gen_context &ygc) const
958 {
959 if (!this->jpc_definition_id.empty()) {
960 ygc.ygc_schema_definitions[this->jpc_definition_id] = this;
961
962 yajl_gen_string(ygc.ygc_handle, "$ref");
963 yajl_gen_string(ygc.ygc_handle, fmt::format("#/definitions/{}", this->jpc_definition_id));
964 return;
965 }
966
967 this->gen_properties(ygc);
968 }
969
gen_properties(yajlpp_gen_context & ygc) const970 void json_path_container::gen_properties(yajlpp_gen_context &ygc) const
971 {
972 auto pattern_count = count_if(this->jpc_children.begin(),
973 this->jpc_children.end(),
974 [](auto &jph) {
975 return jph.jph_is_pattern_property;
976 });
977 auto plain_count = this->jpc_children.size() - pattern_count;
978
979 if (plain_count > 0) {
980 yajl_gen_string(ygc.ygc_handle, "properties");
981 {
982 yajlpp_map properties(ygc.ygc_handle);
983
984 for (auto &child_handler : this->jpc_children) {
985 if (child_handler.jph_is_pattern_property) {
986 continue;
987 }
988 properties.gen(child_handler.jph_property);
989 child_handler.gen_schema(ygc);
990 }
991 }
992 }
993 if (pattern_count > 0) {
994 yajl_gen_string(ygc.ygc_handle, "patternProperties");
995 {
996 yajlpp_map properties(ygc.ygc_handle);
997
998 for (auto &child_handler : this->jpc_children) {
999 if (!child_handler.jph_is_pattern_property) {
1000 continue;
1001 }
1002 properties.gen(child_handler.jph_property);
1003 child_handler.gen_schema(ygc);
1004 }
1005 }
1006 }
1007
1008 yajl_gen_string(ygc.ygc_handle, "additionalProperties");
1009 yajl_gen_bool(ygc.ygc_handle, false);
1010 }
1011
schema_printer(FILE * file,const char * str,size_t len)1012 static void schema_printer(FILE *file, const char *str, size_t len)
1013 {
1014 fwrite(str, len, 1, file);
1015 }
1016
dump_schema_to(const json_path_container & jpc,const char * internals_dir,const char * name)1017 void dump_schema_to(const json_path_container &jpc, const char *internals_dir, const char *name)
1018 {
1019 yajlpp_gen genner;
1020 yajlpp_gen_context ygc(genner, jpc);
1021 auto schema_path = fmt::format("{}/{}", internals_dir, name);
1022 auto file = unique_ptr<FILE, decltype(&fclose)>(fopen(schema_path.c_str(), "w+"), fclose);
1023
1024 if (!file.get()) {
1025 return;
1026 }
1027
1028 yajl_gen_config(genner, yajl_gen_beautify, true);
1029 yajl_gen_config(genner, yajl_gen_print_callback, schema_printer, file.get());
1030
1031 ygc.gen_schema();
1032 }
1033