changeset 1355:70b6067f4552

Add tool to generate dict_json_dict_schema.cc from dict_json_dict_schema.json.
author Thomas Klausner <tk@giga.or.at>
date Sat, 18 May 2019 11:06:01 +0200
parents 0dff6a604b0a
children b68fb21c2f72
files extensions/dict_json/CMakeLists.txt extensions/dict_json/dict_json_dict_schema.cc extensions/dict_json/json-schema-to-c.cc
diffstat 3 files changed, 359 insertions(+), 168 deletions(-) [+]
line wrap: on
line diff
--- a/extensions/dict_json/CMakeLists.txt	Fri May 17 12:59:19 2019 +0200
+++ b/extensions/dict_json/CMakeLists.txt	Sat May 18 11:06:01 2019 +0200
@@ -6,11 +6,12 @@
 # We use JSONCPP and JSON-Schema to parse and validate JSON files
 PKG_CHECK_MODULES(JSONCPP REQUIRED jsoncpp)
 PKG_CHECK_MODULES(JSON_SCHEMA REQUIRED json-schema)
+PKG_CHECK_MODULES(PCRECPP REQUIRED libpcrecpp)
 
 # List of source files
 SET(DICT_JSON_SRC
 	dict_json.cc
-	dict_json_dict_schema.cc
+	${CMAKE_CURRENT_BINARY_DIR}/dict_json_dict_schema.cc
 )
 
 INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR})
@@ -28,6 +29,14 @@
 ADD_EXECUTABLE(dict-json-diff dict-json-diff.cc)
 TARGET_LINK_LIBRARIES(dict-json-diff ${JSONCPP_LIBRARIES} ${JSON_SCHEMA_STATIC_LIBRARIES})
 
+ADD_EXECUTABLE(json-schema-to-c json-schema-to-c.cc)
+TARGET_LINK_LIBRARIES(json-schema-to-c ${JSONCPP_LIBRARIES} ${JSON_SCHEMA_STATIC_LIBRARIES} ${PCRECPP_LIBRARIES})
+
+ADD_CUSTOM_COMMAND(
+	OUTPUT dict_json_dict_schema.cc
+	COMMAND json-schema-to-c ${CMAKE_CURRENT_SOURCE_DIR}/dict_json_dict_schema.json ${CMAKE_CURRENT_BINARY_DIR}/dict_json_dict_schema.cc
+	DEPENDS dict_json_dict_schema.json
+)
 
 ####
 ## INSTALL section ##
@@ -38,6 +47,3 @@
 INSTALL(TARGETS dict-json-diff
 	RUNTIME DESTINATION ${INSTALL_DAEMON_SUFFIX}
 	COMPONENT freeDiameter-dictionary-json)
-
-# dict_json_dict_schema.cc is created from dict_json_dict_schema.json
-# the tool for that is not yet open source, but the conversion is straightforward
--- a/extensions/dict_json/dict_json_dict_schema.cc	Fri May 17 12:59:19 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,164 +0,0 @@
-const char * dict_json_dict_schema = "\
-{ \n\
-    \"definitions\": { \n\
-        \"content\": { \n\
-            \"type\": \"array\", \n\
-            \"items\": { \n\
-                \"type\": \"object\", \n\
-                \"additionalProperties\": false, \n\
-                \"required\": [ \"AVP\" ], \n\
-                \"properties\": { \n\
-                    \"AVP\": { \"type\": \"string\" }, \n\
-                    \"Vendor\": { \"$ref\": \"#/definitions/unsigned-integer\" }, \n\
-                    \"First\": { \"type\": \"boolean\" }, \n\
-                    \"Min\": { \"$ref\": \"#/definitions/unsigned-integer\" }, \n\
-                    \"Max\": { \"anyOf\": [ { \"type\": \"integer\" }, { \"enum\": [ \"unbounded\" ] } ] } \n\
-                } \n\
-            } \n\
-        }, \n\
- \n\
-        \"identifier\": { \"type\": \"string\", \"pattern\": \"^[[:print:]]+$\" }, \n\
-        \"type\": { \n\
-            \"enum\": [ \n\
-                \"Address\", \n\
-                \"DiameterIdentity\", \n\
-                \"DiameterURI\", \n\
-                \"Enumerated\", \n\
-                \"Float32\", \n\
-                \"Float64\", \n\
-                \"Grouped\", \n\
-                \"Integer32\", \n\
-                \"Integer64\", \n\
-                \"IPFilterRule\", \n\
-                \"OctetString\", \n\
-                \"Time\", \n\
-                \"Unsigned32\", \n\
-                \"Unsigned64\", \n\
-                \"UTF8String\" \n\
-            ] \n\
-        }, \n\
-        \"unsigned-integer\": { \"type\": \"integer\", \"minimum\": 0 } \n\
-    }, \n\
-     \n\
-    \"type\": \"object\", \n\
-    \"additionalProperties\": false, \n\
-    \"properties\": { \n\
-        \"Vendors\": { \n\
-            \"type\": \"array\", \n\
-            \"items\": { \n\
-                \"type\": \"object\", \n\
-                \"additionalProperties\": false, \n\
-                \"required\": [ \"Code\", \"Name\" ], \n\
-                \"properties\": { \n\
-                    \"Code\": { \"$ref\": \"#/definitions/unsigned-integer\" }, \n\
-                    \"Name\": { \"$ref\": \"#/definitions/identifier\" } \n\
-                } \n\
-            } \n\
-        }, \n\
-        \"Types\": { \n\
-            \"type\": \"array\", \n\
-            \"items\": { \n\
-                \"type\": \"object\", \n\
-                \"additionalProperties\": false, \n\
-                \"required\": [ \"Name\", \"Base\" ], \n\
-                \"properties\": { \n\
-                    \"Name\": { \"type\": \"string\" }, \n\
-                    \"Base\": { \"type\": \"string\" } \n\
-                } \n\
-            } \n\
-        }, \n\
-        \"AVPs\": { \n\
-            \"type\": \"array\", \n\
-            \"items\": { \n\
-                \"type\": \"object\", \n\
-                \"additionalProperties\": false, \n\
-                \"required\": [ \"Code\", \"Name\", \"Type\" ], \n\
-                \"properties\": { \n\
-                    \"Code\": { \"$ref\": \"#/definitions/unsigned-integer\" }, \n\
-                    \"Vendor\": { \"$ref\": \"#/definitions/unsigned-integer\" }, \n\
-                    \"Name\": { \"$ref\": \"#/definitions/identifier\" }, \n\
-                    \"Flags\": { \n\
-                        \"type\": \"object\", \n\
-                        \"additionalProperties\": false, \n\
-                        \"properties\": { \n\
-                            \"Must\": { \"type\": \"string\", \"pattern\": \"^[VMP]*$\" }, \n\
-                            \"MustNot\": { \"type\": \"string\", \"pattern\": \"^[VMP]*$\" } \n\
-                        } \n\
-                    }, \n\
-                    \"Type\": { \"$ref\": \"#/definitions/identifier\" }, \n\
-                    \"EnumValues\": { \n\
-                        \"type\": \"array\", \n\
-                        \"items\": { \n\
-                            \"type\": \"object\", \n\
-                            \"additionalProperties\": false, \n\
-                            \"required\": [ \"Code\", \"Name\" ], \n\
-                            \"properties\": { \n\
-                                \"Code\": { \"anyOf\": [ { \"type\": \"integer\" }, { \"type\": \"number\" }, { \"type\": \"string\" } ] }, \n\
-                                \"Name\": { \"type\": \"string\", \"pattern\": \"^[[:print:]]*$\" } \n\
-                            } \n\
-                        } \n\
-                    } \n\
-                } \n\
-            } \n\
-        }, \n\
-        \"Applications\": { \n\
-            \"type\": \"array\", \n\
-            \"items\": { \n\
-                \"type\": \"object\", \n\
-                \"additionalProperties\": false, \n\
-                \"required\": [ \"Code\", \"Name\" ], \n\
-                \"properties\": { \n\
-                    \"Code\": { \"$ref\": \"#/definitions/unsigned-integer\" }, \n\
-                    \"Name\": { \"$ref\": \"#/definitions/identifier\" } \n\
-                } \n\
-            } \n\
-        }, \n\
-        \"Commands\": { \n\
-            \"type\": \"array\", \n\
-            \"items\": { \n\
-                \"type\": \"object\", \n\
-                \"additionalProperties\": false, \n\
-                \"required\": [ \"Code\", \"Name\" ], \n\
-                \"properties\": { \n\
-                    \"Code\": { \"$ref\": \"#/definitions/unsigned-integer\" }, \n\
-                    \"Name\": { \"$ref\": \"#/definitions/identifier\" }, \n\
-                    \"Application\": { \"$ref\": \"#/definitions/identifier\" }, \n\
-                    \"Flags\": { \n\
-                        \"type\": \"object\", \n\
-                        \"additionalProperties\": false, \n\
-                        \"properties\": { \n\
-                            \"Must\": { \"type\": \"string\", \"pattern\": \"^[RPE]*$\" }, \n\
-                            \"MustNot\": { \"type\": \"string\", \"pattern\": \"^[RPET]*$\" } \n\
-                        } \n\
-                    } \n\
-                } \n\
-            } \n\
-        }, \n\
-        \"CommandRules\": { \n\
-            \"type\": \"array\", \n\
-            \"items\": { \n\
-                \"type\": \"object\", \n\
-                \"additionalProperties\": false, \n\
-                \"required\": [ \"Command\", \"Content\" ], \n\
-                \"properties\": { \n\
-                    \"Command\": { \"type\": \"string\", \"minimum\": 0 }, \n\
-                    \"Content\": { \"$ref\": \"#/definitions/content\" } \n\
-                } \n\
-            } \n\
-        }, \n\
-        \"AVPRules\": { \n\
-            \"type\": \"array\", \n\
-            \"items\": { \n\
-                \"type\": \"object\", \n\
-                \"additionalProperties\": false, \n\
-                \"required\": [ \"AVP\", \"Content\" ], \n\
-                \"properties\": { \n\
-                    \"AVP\": { \"type\": \"string\" }, \n\
-                    \"Vendor\": { \"type\": \"integer\", \"minimum\" : 0 }, \n\
-                    \"Content\": { \"$ref\": \"#/definitions/content\" } \n\
-                } \n\
-            } \n\
-        } \n\
-    } \n\
-} \n\
-";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extensions/dict_json/json-schema-to-c.cc	Sat May 18 11:06:01 2019 +0200
@@ -0,0 +1,349 @@
+/**********************************************************************************************************
+ * Software License Agreement (BSD License)                                                               *
+ * Author: Thomas Klausner <tk@giga.or.at>                                                                *
+ *                                                                                                        *
+ * Copyright (c) 2016, 2017, 2019 Thomas Klausner                                                         *
+ * All rights reserved.                                                                                   *
+ *                                                                                                        *
+ * Written under contract by nfotex IT GmbH, http://nfotex.com/                                           *
+ *                                                                                                        *
+ * Redistribution and use of this software in source and binary forms, with or without modification, are  *
+ * permitted provided that the following conditions are met:                                              *
+ *                                                                                                        *
+ * * Redistributions of source code must retain the above                                                 *
+ *   copyright notice, this list of conditions and the                                                    *
+ *   following disclaimer.                                                                                *
+ *                                                                                                        *
+ * * Redistributions in binary form must reproduce the above                                              *
+ *   copyright notice, this list of conditions and the                                                    *
+ *   following disclaimer in the documentation and/or other                                               *
+ *   materials provided with the distribution.                                                            *
+ *                                                                                                        *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT     *
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS    *
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF   *
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                                                             *
+ **********************************************************************************************************/
+
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <json/SchemaValidator.h>
+
+[[noreturn]]
+void usage(char *prg, int exit_status) {
+    FILE *f = (exit_status ? stderr : stdout);
+
+    fprintf(f, "Usage: %s [-hV] [-i include] [-N variable-name] [-n namespace] [-t type] json [output]\n", prg);
+    fprintf(f, "options:\n\
+  -h, --help              print this usage message and exit\n\
+  -i, --include FILE      include FILE in output C source file\n\
+  -n, --namespace NAME    specify namespace in which to define variable\n\
+  -N, --name NAME         specify name of variable to define\n\
+  --no-validate           don't validate against JSON meta schema (default)\n\
+  --type TYPE             specify type of variable (char * (default) or std::string)\n\
+  --validate              validate against JSON meta schema\n\
+  --validate-only         validate against JSON meta schema and exit (don't create C source)\n\
+");
+
+    exit(exit_status);
+}
+
+const char *OPTIONS = "hi:N:n:t:V";
+
+enum {
+    OPT_NO_VALIDATE = 256,
+    OPT_VALIDATE_ONLY
+};
+struct option options[] = {
+    { "help", no_argument, NULL, 'h' },
+    { "include", required_argument, NULL, 'i' },
+    { "name", required_argument, NULL, 'N' },
+    { "namespace", required_argument, NULL, 'n' },
+    { "no-validate", no_argument, NULL, OPT_NO_VALIDATE },
+    { "type", required_argument, NULL, 't' },
+    { "validate", no_argument, NULL, 'V' },
+    { "validate-only", no_argument, NULL, OPT_VALIDATE_ONLY },
+    { NULL, 0, NULL, 0 }
+};
+
+char *read_full_file (const char *filename, off_t max_size, off_t* size_ret, const char *desc) {
+    FILE *fp;
+    if ((fp = fopen (filename, "rb")) == NULL) {
+        fprintf (stderr, "couldn't open %s file [%s]: %d, %s\n", desc, filename, errno, strerror(errno));
+        return NULL;
+    }
+    struct stat stat_buf;
+    if (fstat (fileno(fp), &stat_buf) < 0) {
+        fprintf (stderr, "couldn't stat %s file [%s]: %d, %s\n", desc, filename, errno, strerror(errno));
+        fclose (fp);
+        return NULL;
+    }
+    off_t n = stat_buf.st_size;
+    if (max_size > 0 && n > max_size) {
+        fprintf (stderr, "%s file [%s] is larger than %" PRIi64 " bytes\n", desc, filename, (int64_t)max_size);
+        fclose (fp);
+        return NULL;
+    }
+    char *buf;
+    if ((buf = (char *) malloc ((size_t)n+1)) == NULL) {
+        fprintf (stderr, "error allocating %" PRIi64 " bytes for read of %s file [%s]\n", (int64_t)n, desc, filename);
+        fclose (fp);
+        return NULL;
+    }
+    if (fread (buf, 1, (size_t)n, fp) < (size_t) n) {
+        fprintf (stderr, "error reading %s file [%s]: %d, %s\n", desc, filename, errno, strerror(errno));
+        fclose (fp);
+        free (buf);
+        return NULL;
+    }
+
+    fclose (fp);
+    buf[n] = '\0';
+    if (size_ret != NULL)
+        *size_ret = n;
+    return buf;
+}
+
+int main (int argc, char **argv) {
+    char *name_space = NULL;
+    char *name = NULL;
+    bool free_name = false;
+    char default_type[] = "char *";
+    char *type = default_type;
+    bool validate = false;
+    bool convert = true;
+    char *include = NULL;
+
+    int c;
+    while ((c=getopt_long(argc, argv, OPTIONS, options, NULL)) != EOF) {
+        switch (c) {
+            case 'i':
+                include = optarg;
+                break;
+
+            case 'N':
+                name = optarg;
+                break;
+
+            case 'n':
+                name_space = optarg;
+                break;
+
+            case 't':
+                type = optarg;
+                break;
+
+            case 'V':
+                validate = true;
+                break;
+
+            case OPT_NO_VALIDATE:
+                validate = false;
+                break;
+
+            case OPT_VALIDATE_ONLY:
+                validate = true;
+                convert = false;
+                break;
+
+            case 'h':
+                usage(argv[0], 0);
+
+            default:
+                usage(argv[0], 1);
+        }
+    }
+
+    if (optind != argc - 2 && optind != argc - 1)
+        usage(argv[0], 1);
+
+    char *input = argv[optind];
+    char *output = NULL;
+    if (optind == argc -2) {
+        output = argv[optind+1];
+    }
+
+    char *str = read_full_file(input, 10*1024*1024, NULL, input);
+
+    Json::Reader reader;
+
+    Json::Value json;
+    if (!reader.parse(str, json)) {
+	fprintf(stderr, "%s: parse error: %s\n", input, reader.getFormattedErrorMessages());
+        exit(1);
+    }
+
+    if (validate) {
+        std::string error_message;
+        Json::SchemaValidator *validator;
+
+        try {
+            validator = Json::SchemaValidator::create_meta_validator();
+        }
+        catch (Json::SchemaValidator::Exception e) {
+            fprintf(stderr, "%s: can't create meta schema validator\n", argv[0]);
+            exit(1);
+        }
+
+        if (!validator->validate(json)) {
+            const std::vector<Json::SchemaValidator::Error> errors = validator->errors();
+
+            for (unsigned int i=0; i<errors.size(); i++)
+                fprintf(stderr, "%s:%s: %s\n", input, errors[i].path.c_str(), errors[i].message.c_str());
+            exit(1);
+        }
+
+        try {
+            Json::SchemaValidator v(json);
+        }
+        catch (Json::SchemaValidator::Exception e) {
+            fprintf(stderr, "%s: can't create schema validator: %s\n", argv[0], e.type_message().c_str());
+            for (auto error : e.errors) {
+                fprintf(stderr, "%s:%s: %s\n", input, error.path.c_str(), error.message.c_str());
+            }
+            exit(1);
+        }
+    }
+
+    if (!convert) {
+        exit(0);
+    }
+
+    FILE *fin = fopen(input, "r");
+    if (fin == NULL) {
+        fprintf(stderr, "%s: can't open schema file [%s]: %s\n", argv[0], input, strerror(errno));
+        exit(1);
+    }
+
+    FILE *fout;
+
+    if (output) {
+        fout = fopen(output, "w");
+        if (fout == NULL) {
+            fprintf(stderr, "%s: can't create output file [%s]: %s\n", argv[0], output, strerror(errno));
+            exit(1);
+        }
+    }
+    else {
+        fout = stdout;
+    }
+
+    if (name == NULL) {
+        char *base = basename(input);
+        char *end = strrchr(base, '.');
+        if (end == NULL) {
+            end = base + strlen(base);
+        }
+
+        name = strndup(base, static_cast<size_t>(end-base));
+
+        for (char *p = name; *p; p++) {
+            if ((*p >= 'A' && *p < 'Z') || (*p >= 'a' && *p < 'z') || *p == '_') {
+                continue;
+            }
+            else if (*p >= '0' && *p <= '9') {
+                if (p > name) {
+                    continue;
+                }
+            }
+            *p = '_';
+        }
+    }
+    else {
+        for (const char *p = name; *p; p++) {
+            if ((*p >= 'A' && *p < 'Z') || (*p >= 'a' && *p < 'z') || *p == '_') {
+                continue;
+            }
+            else if (*p >= '0' && *p <= '9') {
+                if (p > name) {
+                    continue;
+                }
+            }
+            else if (p[0] == ':' && p[1] == ':') {
+                p += 1;
+                continue;
+            }
+
+            fprintf(stderr, "%s: name [%s] is not a valid C identifier\n", argv[0], name);
+            exit(1);
+        }
+    }
+
+    if (include) {
+        fprintf(fout, "#include <%s>\n\n", include);
+    }
+
+    if (strcmp(type, "std::string") == 0) {
+        fputs("#include <string>\n\n", fout);
+    }
+
+    if (name_space) {
+        fprintf(fout, "namespace %s {\n\n", name_space);
+    }
+
+    fprintf(fout, "const %s %s = \"\\\n", type, name);
+
+    if (free_name) {
+        free(name);
+        name = NULL;
+    }
+
+    char line[8192];
+
+    while (fgets(line, sizeof(line), fin)) {
+        if (line[strlen(line)-1] == '\n')
+            line[strlen(line)-1] = '\0';
+
+        char *p = line;
+        char *end = line+strlen(line);
+        char *q;
+        do {
+            q = p + strcspn(p, "\"\\");
+            if (q < end) {
+                fprintf(fout, "%.*s\\%c", (int)(q-p), p, *q);
+                p=q+1;
+            }
+            else
+                fprintf(fout, "%s", p);
+        } while (q < end);
+
+        fputs(" \\n\\\n", fout);
+    }
+
+    fputs("\";\n", fout);
+
+    if (name_space) {
+        fputs("\n}\n", fout);
+    }
+
+    if (ferror(fin)) {
+        fprintf(stderr, "%s: read error on schema file [%s]: %s\n", argv[0], input, strerror(errno));
+        fclose(fout);
+        unlink(output);
+        exit(1);
+    }
+
+    fclose(fin);
+
+    if (ferror(fout)) {
+        fprintf(stderr, "%s: write error on output file [%s]: %s\n", argv[0], output, strerror(errno));
+        fclose(fout);
+        unlink(output);
+        exit(1);
+    }
+
+    fclose(fout);
+
+    exit(0);
+}
"Welcome to our mercurial repository"