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