diff options
Diffstat (limited to 'src/seastar/scripts/seastar-json2code.py')
-rwxr-xr-x | src/seastar/scripts/seastar-json2code.py | 578 |
1 files changed, 578 insertions, 0 deletions
diff --git a/src/seastar/scripts/seastar-json2code.py b/src/seastar/scripts/seastar-json2code.py new file mode 100755 index 000000000..b19df4fa0 --- /dev/null +++ b/src/seastar/scripts/seastar-json2code.py @@ -0,0 +1,578 @@ +#!/usr/bin/env python3 + +# C++ Code generation utility from Swagger definitions. +# This utility support Both the swagger 1.2 format +# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/1.2.md +# And the 2.0 format +# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md +# +# Swagger 2.0 is not only different in its structure (apis have moved, and +# models are now under definitions) It also moved from multiple file structure +# to a single file. +# To keep the multiple file support, each group of APIs will be placed in a single file +# Each group can have a .def.json file with its definitions (What used to be models) +# Because the APIs in definitions are snippets, they are not legal json objects +# and need to be formated as such so that a json parser would work. + +import json +import sys +import re +import glob +import argparse +import os +from string import Template + +parser = argparse.ArgumentParser(description="""Generate C++ class for json +handling from swagger definition""") + +parser.add_argument('--outdir', help='the output directory', default='autogen') +parser.add_argument('-o', help='Output file', default='') +parser.add_argument('-f', help='input file', default='api-java.json') +parser.add_argument('-ns', help="""namespace when set struct will be created +under the namespace""", default='') +parser.add_argument('-jsoninc', help='relative path to the jsaon include', + default='json/') +parser.add_argument('-jsonns', help='set the json namespace', default='json') +parser.add_argument('-indir', help="""when set all json file in the given +directory will be parsed, do not use with -f""", default='') +parser.add_argument('-debug', help='debug level 0 -quite,1-error,2-verbose', + default='1', type=int) +parser.add_argument('-combined', help='set the name of the combined file', + default='autogen/pathautogen.ee') +parser.add_argument('--create-cc', dest='create_cc', action='store_true', default=False, + help='Put global variables in a .cc file') +config = parser.parse_args() + + +valid_vars = {'string': 'sstring', 'int': 'int', 'double': 'double', + 'float': 'float', 'long': 'long', 'boolean': 'bool', 'char': 'char', + 'datetime': 'json::date_time'} + +current_file = '' + +spacing = " " +def getitem(d, key, name): + if key in d: + return d[key] + else: + raise Exception("'" + key + "' not found in " + name) + +def fprint(f, *args): + for arg in args: + f.write(arg) + +def fprintln(f, *args): + for arg in args: + f.write(arg) + f.write('\n') + + +def open_namespace(f, ns=config.ns): + fprintln(f, "namespace ", ns , ' {\n') + + +def close_namespace(f): + fprintln(f, '}') + + +def add_include(f, includes): + for include in includes: + fprintln(f, '#include ', include) + fprintln(f, "") + +def trace_verbose(*params): + if config.debug > 1: + print(''.join(params)) + + +def trace_err(*params): + if config.debug > 0: + print(current_file + ':' + ''.join(params)) + + +def valid_type(param): + if param in valid_vars: + return valid_vars[param] + trace_err("Type [", param, "] not defined") + return param + + +def type_change(param, member): + if param == "array": + if "items" not in member: + trace_err("array without item declaration in ", param) + return "" + item = member["items"] + if "type" in item: + t = item["type"] + elif "$ref" in item: + t = item["$ref"] + else: + trace_err("array items with no type or ref declaration ", param) + return "" + return "json_list< " + valid_type(t) + " >" + return "json_element< " + valid_type(param) + " >" + + + +def print_ind_comment(f, ind, *params): + fprintln(f, ind, "/**") + for s in params: + fprintln(f, ind, " * ", s) + fprintln(f, ind, " */") + +def print_comment(f, *params): + print_ind_comment(f, spacing, *params) + +def print_copyrights(f): + fprintln(f, "/*") + fprintln(f, "* Copyright (C) 2014 Cloudius Systems, Ltd.") + fprintln(f, "*") + fprintln(f, "* This work is open source software, licensed under the", + " terms of the") + fprintln(f, "* BSD license as described in the LICENSE f in the top-", + "level directory.") + fprintln(f, "*") + fprintln(f, "* This is an Auto-Generated-code ") + fprintln(f, "* Changes you do in this file will be erased on next", + " code generation") + fprintln(f, "*/\n") + + +def print_h_file_headers(f, name): + print_copyrights(f) + fprintln(f, "#ifndef __JSON_AUTO_GENERATED_" + name) + fprintln(f, "#define __JSON_AUTO_GENERATED_" + name + "\n") + + +def clean_param(param): + match = re.match(r"^\{\s*([^\}]+)\s*}", param) + if match: + return [match.group(1), False] + return [param, True] + + +def get_parameter_by_name(obj, name): + for p in obj["parameters"]: + if p["name"] == name: + return p + trace_err ("No Parameter declaration found for ", name) + + +def clear_path_ending(path): + if not path or path[-1] != '/': + return path + return path[0:-1] + +# check if a parameter is query required. +# It will return true if the required flag is set +# and if it is a query parameter, both swagger 1.2 'paramType' and swagger 2.0 'in' attributes +# are supported +def is_required_query_param(param): + return "required" in param and param["required"] and ("paramType" in param and param["paramType"] == "query" or "in" in param and param["in"] == "query") + +def add_path(f, path, details): + if "summary" in details: + print_comment(f, details["summary"]) + param_starts = path.find("{") + if param_starts >= 0: + path_reminder = path[param_starts:] + vals = path.split("/") + vals.reverse() + fprintln(f, spacing, 'path_description::add_path("', clear_path_ending(vals.pop()), + '",', details["method"], ',"', details["nickname"], '")') + while vals: + param, is_url = clean_param(vals.pop()) + if is_url: + fprintln(f, spacing, ' ->pushurl("', param, '")') + else: + param_type = get_parameter_by_name(details, param) + if ("allowMultiple" in param_type and + param_type["allowMultiple"] == True): + fprintln(f, spacing, ' ->pushparam("', param, '",true)') + else: + fprintln(f, spacing, ' ->pushparam("', param, '")') + else: + fprintln(f, spacing, 'path_description::add_path("', clear_path_ending(path), '",', + details["method"], ',"', details["nickname"], '")') + if "parameters" in details: + for param in details["parameters"]: + if is_required_query_param(param): + fprintln(f, spacing, ' ->pushmandatory_param("', param["name"], '")') + fprintln(f, spacing, ";") + + +def get_base_name(param): + return os.path.basename(param) + + +def is_model_valid(name, model): + if name in valid_vars: + return "" + properties = getitem(model[name], "properties", name) + for var in properties: + type = getitem(properties[var], "type", name + ":" + var) + if type == "array": + items = getitem(properties[var], "items", name + ":" + var); + try : + type = getitem(items, "type", name + ":" + var + ":items") + except Exception as e: + try: + type = getitem(items, "$ref", name + ":" + var + ":items") + except: + raise e; + if type not in valid_vars: + if type not in model: + raise Exception("Unknown type '" + type + "' in Model '" + name + "'") + return type + valid_vars[name] = name + return "" + +def resolve_model_order(data): + res = [] + models = set() + for model_name in data: + visited = set(model_name) + missing = is_model_valid(model_name, data) + resolved = missing == '' + if not resolved: + stack = [model_name] + while not resolved: + if missing in visited: + raise Exception("Cyclic dependency found: " + missing) + missing_depends = is_model_valid(missing, data) + if missing_depends == '': + if missing not in models: + res.append(missing) + models.add(missing) + resolved = len(stack) == 0 + if not resolved: + missing = stack.pop() + else: + stack.append(missing) + missing = missing_depends + elif model_name not in models: + res.append(model_name) + models.add(model_name) + return res + +def create_enum_wrapper(model_name, name, values): + enum_name = model_name + "_" + name + res = " enum class " + enum_name + " {" + for enum_entry in values: + res = res + " " + enum_entry + ", " + res = res + "NUM_ITEMS};\n" + wrapper = name + "_wrapper" + res = res + Template(""" struct $wrapper : public json::jsonable { + $wrapper() = default; + virtual std::string to_json() const { + switch(v) { + """).substitute({'wrapper' : wrapper}) + for enum_entry in values: + res = res + " case " + enum_name + "::" + enum_entry + ": return \"\\\"" + enum_entry + "\\\"\";\n" + res = res + Template(""" default: return \"\\\"Unknown\\\"\"; + } + } + template<class T> + $wrapper (const T& _v) { + switch(_v) { + """).substitute({'wrapper' : wrapper}) + for enum_entry in values: + res = res + " case T::" + enum_entry + ": v = " + enum_name + "::" + enum_entry + "; break;\n" + res = res + Template(""" default: v = $enum_name::NUM_ITEMS; + } + } + template<class T> + operator T() const { + switch(v) { + """).substitute({'enum_name': enum_name}) + for enum_entry in values: + res = res + " case " + enum_name + "::" + enum_entry + ": return T::" + enum_entry + ";\n" + return res + Template(""" default: return T::$value; + } + } + typedef typename std::underlying_type<$enum_name>::type pos_type; + $wrapper& operator++() { + v = static_cast<$enum_name>(static_cast<pos_type>(v) + 1); + return *this; + } + $wrapper & operator++(int) { + return ++(*this); + } + bool operator==(const $wrapper& c) const { + return v == c.v; + } + bool operator!=(const $wrapper& c) const { + return v != c.v; + } + bool operator<=(const $wrapper& c) const { + return static_cast<pos_type>(v) <= static_cast<pos_type>(c.v); + } + static $wrapper begin() { + return $wrapper ($enum_name::$value); + } + static $wrapper end() { + return $wrapper ($enum_name::NUM_ITEMS); + } + static boost::integer_range<$wrapper> all_items() { + return boost::irange(begin(), end()); + } + $enum_name v; + }; + """).substitute({'enum_name': enum_name, 'wrapper' : wrapper, 'value':values[0]}) + +def to_operation(opr, data): + data["method"] = opr.upper() + data["nickname"] = data["operationId"] + return data + +def to_path(path, data): + data["operations"] = [to_operation(k, data[k]) for k in data] + data["path"] = path + + return data + +def create_h_file(data, hfile_name, api_name, init_method, base_api): + if config.o != '': + final_hfile_name = config.o + else: + final_hfile_name = config.outdir + "/" + hfile_name + hfile = open(final_hfile_name, "w") + + if config.create_cc: + ccfile = open(final_hfile_name.rstrip('.hh') + ".cc", "w") + add_include(ccfile, ['"{}"'.format(final_hfile_name)]) + open_namespace(ccfile, "seastar") + open_namespace(ccfile, "httpd") + open_namespace(ccfile, api_name) + else: + ccfile = hfile + print_h_file_headers(hfile, api_name) + add_include(hfile, ['<seastar/core/sstring.hh>', + '<seastar/json/json_elements.hh>', + '<seastar/http/json_path.hh>']) + + add_include(hfile, ['<iostream>', '<boost/range/irange.hpp>']) + open_namespace(hfile, "seastar") + open_namespace(hfile, "httpd") + open_namespace(hfile, api_name) + + if "models" in data: + models_order = resolve_model_order(data["models"]) + for model_name in models_order: + model = data["models"][model_name] + if 'description' in model: + print_ind_comment(hfile, "", model["description"]) + fprintln(hfile, "struct ", model_name, " : public json::json_base {") + member_init = '' + member_assignment = '' + member_copy = '' + for member_name in model["properties"]: + member = model["properties"][member_name] + if "description" in member: + print_comment(hfile, member["description"]) + if "enum" in member: + enum_name = model_name + "_" + member_name + fprintln(hfile, create_enum_wrapper(model_name, member_name, member["enum"])) + fprintln(hfile, " ", config.jsonns, "::json_element<", + member_name, "_wrapper> ", + member_name, ";\n") + else: + fprintln(hfile, " ", config.jsonns, "::", + type_change(member["type"], member), " ", + member_name, ";\n") + member_init += " add(&" + member_name + ',"' + member_init += member_name + '");\n' + member_assignment += " " + member_name + " = " + "e." + member_name + ";\n" + member_copy += " e." + member_name + " = " + member_name + ";\n" + fprintln(hfile, "void register_params() {") + fprintln(hfile, member_init) + fprintln(hfile, '}') + + fprintln(hfile, model_name, '() {') + fprintln(hfile, ' register_params();') + fprintln(hfile, '}') + fprintln(hfile, model_name, '(const ' + model_name + ' & e) {') + fprintln(hfile, ' register_params();') + fprintln(hfile, member_assignment) + fprintln(hfile, '}') + fprintln(hfile, "template<class T>") + fprintln(hfile, model_name, "& operator=(const ", "T& e) {") + fprintln(hfile, member_assignment) + fprintln(hfile, " return *this;") + fprintln(hfile, "}") + fprintln(hfile, model_name, "& operator=(const ", model_name, "& e) {") + fprintln(hfile, member_assignment) + fprintln(hfile, " return *this;") + fprintln(hfile, "}") + fprintln(hfile, "template<class T>") + fprintln(hfile, model_name, "& update(T& e) {") + fprintln(hfile, member_copy) + fprintln(hfile, " return *this;") + fprintln(hfile, "}") + fprintln(hfile, "};\n\n") + + # print_ind_comment(hfile, "", "Initialize the path") +# fprintln(hfile, init_method + "(const std::string& description);") + fprintln(hfile, 'static const sstring name = "', base_api, '";') + for item in data["apis"]: + path = item["path"] + if "operations" in item: + for oper in item["operations"]: + if "summary" in oper: + print_comment(hfile, oper["summary"]) + + param_starts = path.find("{") + base_url = path + vals = [] + if param_starts >= 0: + vals = path[param_starts:].split("/") + vals.reverse() + base_url = path[:param_starts] + + varname = getitem(oper, "nickname", oper) + if config.create_cc: + fprintln(hfile, 'extern const path_description ', varname, ';') + maybe_static = '' + else: + maybe_static = 'static ' + fprintln(ccfile, maybe_static, 'const path_description ', varname, '("', clear_path_ending(base_url), + '",', oper["method"], ',"', oper["nickname"], '",') + fprint(ccfile, '{') + first = True + while vals: + path_param, is_url = clean_param(vals.pop()) + if path_param == "": + continue + if first == True: + first = False + else: + fprint(ccfile, "\n,") + if is_url: + fprint(ccfile, '{', '"/', path_param , '", path_description::url_component_type::FIXED_STRING', '}') + else: + path_param_type = get_parameter_by_name(oper, path_param) + if ("allowMultiple" in path_param_type and + path_param_type["allowMultiple"] == True): + fprint(ccfile, '{', '"', path_param , '", path_description::url_component_type::PARAM_UNTIL_END_OF_PATH', '}') + else: + fprint(ccfile, '{', '"', path_param , '", path_description::url_component_type::PARAM', '}') + fprint(ccfile, '}') + fprint(ccfile, ',{') + first = True + enum_definitions = "" + if "enum" in oper: + enum_definitions = ("namespace ns_" + oper["nickname"] + " {\n" + + create_enum_wrapper(oper["nickname"], "return_type", oper["enum"]) + + "}\n") + funcs = "" + if "parameters" in oper: + for param in oper["parameters"]: + if is_required_query_param(param): + if first == True: + first = False + else: + fprint(ccfile, "\n,") + fprint(ccfile, '"', param["name"], '"') + if "enum" in param: + enum_definitions = enum_definitions + 'namespace ns_' + oper["nickname"] + '{\n' + enm = param["name"] + enum_definitions = enum_definitions + 'enum class ' + enm + ' {' + for val in param["enum"]: + enum_definitions = enum_definitions + val + ", " + enum_definitions = enum_definitions + 'NUM_ITEMS};\n' + enum_definitions = enum_definitions + enm + ' str2' + enm + '(const sstring& str);' + + funcs = funcs + enm + ' str2' + enm + '(const sstring& str) {\n' + funcs = funcs + ' static const sstring arr[] = {"' + '","'.join(param["enum"]) + '"};\n' + funcs = funcs + ' int i;\n' + funcs = funcs + ' for (i=0; i < ' + str(len(param["enum"])) + '; i++) {\n' + funcs = funcs + ' if (arr[i] == str) {return (' + enm + ')i;}\n}\n' + funcs = funcs + ' return (' + enm + ')i;\n' + funcs = funcs + '}\n' + + enum_definitions = enum_definitions + '}\n' + + fprintln(ccfile, '});') + fprintln(hfile, enum_definitions) + open_namespace(ccfile, 'ns_' + oper["nickname"]) + fprintln(ccfile, funcs) + close_namespace(ccfile) + + close_namespace(hfile) + close_namespace(hfile) + close_namespace(hfile) + if config.create_cc: + close_namespace(ccfile) + close_namespace(ccfile) + close_namespace(ccfile) + + hfile.write("#endif //__JSON_AUTO_GENERATED_HEADERS\n") + hfile.close() + +def remove_leading_comma(data): + return re.sub(r'^\s*,','', data) + +def format_as_json_object(data): + return "{" + remove_leading_comma(data) + "}" + +def check_for_models(data, param): + model_name = param.replace(".json", ".def.json") + if not os.path.isfile(model_name): + return + try: + with open(model_name) as myfile: + json_data = myfile.read() + def_data = json.loads(format_as_json_object(json_data)) + data["models"] = def_data + except Exception as e: + type, value, tb = sys.exc_info() + print("Bad formatted JSON definition file '" + model_name + "' error ", value.message) + sys.exit(-1) + +def set_apis(data): + return {"apis": [to_path(p, data[p]) for p in data]} + +def parse_file(param, combined): + global current_file + trace_verbose("parsing ", param, " file") + with open(param) as myfile: + json_data = myfile.read() + try: + data = json.loads(json_data) + except Exception as e: + try: + # the passed data is not a valid json, so maybe its a swagger 2.0 + # snippet, format it as json and try again + # set_apis and check_for_models will create an object with a similiar format + # to a swagger 1.2 so the code generation would work + data = set_apis(json.loads(format_as_json_object(json_data))) + check_for_models(data, param) + except: + # The problem is with the file, + # just report the error and exit. + type, value, tb = sys.exc_info() + print("Bad formatted JSON file '" + param + "' error ", value.message) + sys.exit(-1) + try: + base_file_name = get_base_name(param) + current_file = base_file_name + hfile_name = base_file_name + ".hh" + api_name = base_file_name.replace('.', '_') + base_api = base_file_name.replace('.json', '') + init_method = "void " + api_name + "_init_path" + trace_verbose("creating ", hfile_name) + if (combined): + fprintln(combined, '#include "', base_file_name, ".cc", '"') + create_h_file(data, hfile_name, api_name, init_method, base_api) + except: + type, value, tb = sys.exc_info() + print("Error while parsing JSON file '" + param + "' error ", value.message) + sys.exit(-1) + +if "indir" in config and config.indir != '': + combined = open(config.combined, "w") + for f in glob.glob(os.path.join(config.indir, "*.json")): + parse_file(f, combined) +else: + parse_file(config.f, None) |