]> git.proxmox.com Git - ceph.git/blob - ceph/src/seastar/scripts/seastar-json2code.py
import 15.2.0 Octopus source
[ceph.git] / ceph / src / seastar / scripts / seastar-json2code.py
1 #!/usr/bin/env python3
2
3 # C++ Code generation utility from Swagger definitions.
4 # This utility support Both the swagger 1.2 format
5 # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/1.2.md
6 # And the 2.0 format
7 # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md
8 #
9 # Swagger 2.0 is not only different in its structure (apis have moved, and
10 # models are now under definitions) It also moved from multiple file structure
11 # to a single file.
12 # To keep the multiple file support, each group of APIs will be placed in a single file
13 # Each group can have a .def.json file with its definitions (What used to be models)
14 # Because the APIs in definitions are snippets, they are not legal json objects
15 # and need to be formated as such so that a json parser would work.
16
17 import json
18 import sys
19 import re
20 import glob
21 import argparse
22 import os
23 from string import Template
24
25 parser = argparse.ArgumentParser(description="""Generate C++ class for json
26 handling from swagger definition""")
27
28 parser.add_argument('--outdir', help='the output directory', default='autogen')
29 parser.add_argument('-o', help='Output file', default='')
30 parser.add_argument('-f', help='input file', default='api-java.json')
31 parser.add_argument('-ns', help="""namespace when set struct will be created
32 under the namespace""", default='')
33 parser.add_argument('-jsoninc', help='relative path to the jsaon include',
34 default='json/')
35 parser.add_argument('-jsonns', help='set the json namespace', default='json')
36 parser.add_argument('-indir', help="""when set all json file in the given
37 directory will be parsed, do not use with -f""", default='')
38 parser.add_argument('-debug', help='debug level 0 -quite,1-error,2-verbose',
39 default='1', type=int)
40 parser.add_argument('-combined', help='set the name of the combined file',
41 default='autogen/pathautogen.ee')
42 config = parser.parse_args()
43
44
45 valid_vars = {'string': 'sstring', 'int': 'int', 'double': 'double',
46 'float': 'float', 'long': 'long', 'boolean': 'bool', 'char': 'char',
47 'datetime': 'json::date_time'}
48
49 current_file = ''
50
51 spacing = " "
52 def getitem(d, key, name):
53 if key in d:
54 return d[key]
55 else:
56 raise Exception("'" + key + "' not found in " + name)
57
58 def fprint(f, *args):
59 for arg in args:
60 f.write(arg)
61
62 def fprintln(f, *args):
63 for arg in args:
64 f.write(arg)
65 f.write('\n')
66
67
68 def open_namespace(f, ns=config.ns):
69 fprintln(f, "namespace ", ns , ' {\n')
70
71
72 def close_namespace(f):
73 fprintln(f, '}')
74
75
76 def add_include(f, includes):
77 for include in includes:
78 fprintln(f, '#include ', include)
79 fprintln(f, "")
80
81 def trace_verbose(*params):
82 if config.debug > 1:
83 print(''.join(params))
84
85
86 def trace_err(*params):
87 if config.debug > 0:
88 print(current_file + ':' + ''.join(params))
89
90
91 def valid_type(param):
92 if param in valid_vars:
93 return valid_vars[param]
94 trace_err("Type [", param, "] not defined")
95 return param
96
97
98 def type_change(param, member):
99 if param == "array":
100 if "items" not in member:
101 trace_err("array without item declaration in ", param)
102 return ""
103 item = member["items"]
104 if "type" in item:
105 t = item["type"]
106 elif "$ref" in item:
107 t = item["$ref"]
108 else:
109 trace_err("array items with no type or ref declaration ", param)
110 return ""
111 return "json_list< " + valid_type(t) + " >"
112 return "json_element< " + valid_type(param) + " >"
113
114
115
116 def print_ind_comment(f, ind, *params):
117 fprintln(f, ind, "/**")
118 for s in params:
119 fprintln(f, ind, " * ", s)
120 fprintln(f, ind, " */")
121
122 def print_comment(f, *params):
123 print_ind_comment(f, spacing, *params)
124
125 def print_copyrights(f):
126 fprintln(f, "/*")
127 fprintln(f, "* Copyright (C) 2014 Cloudius Systems, Ltd.")
128 fprintln(f, "*")
129 fprintln(f, "* This work is open source software, licensed under the",
130 " terms of the")
131 fprintln(f, "* BSD license as described in the LICENSE f in the top-",
132 "level directory.")
133 fprintln(f, "*")
134 fprintln(f, "* This is an Auto-Generated-code ")
135 fprintln(f, "* Changes you do in this file will be erased on next",
136 " code generation")
137 fprintln(f, "*/\n")
138
139
140 def print_h_file_headers(f, name):
141 print_copyrights(f)
142 fprintln(f, "#ifndef __JSON_AUTO_GENERATED_" + name)
143 fprintln(f, "#define __JSON_AUTO_GENERATED_" + name + "\n")
144
145
146 def clean_param(param):
147 match = re.match(r"^\{\s*([^\}]+)\s*}", param)
148 if match:
149 return [match.group(1), False]
150 return [param, True]
151
152
153 def get_parameter_by_name(obj, name):
154 for p in obj["parameters"]:
155 if p["name"] == name:
156 return p
157 trace_err ("No Parameter declaration found for ", name)
158
159
160 def clear_path_ending(path):
161 if not path or path[-1] != '/':
162 return path
163 return path[0:-1]
164
165 # check if a parameter is query required.
166 # It will return true if the required flag is set
167 # and if it is a query parameter, both swagger 1.2 'paramType' and swagger 2.0 'in' attributes
168 # are supported
169 def is_required_query_param(param):
170 return "required" in param and param["required"] and ("paramType" in param and param["paramType"] == "query" or "in" in param and param["in"] == "query")
171
172 def add_path(f, path, details):
173 if "summary" in details:
174 print_comment(f, details["summary"])
175 param_starts = path.find("{")
176 if param_starts >= 0:
177 path_reminder = path[param_starts:]
178 vals = path.split("/")
179 vals.reverse()
180 fprintln(f, spacing, 'path_description::add_path("', clear_path_ending(vals.pop()),
181 '",', details["method"], ',"', details["nickname"], '")')
182 while vals:
183 param, is_url = clean_param(vals.pop())
184 if is_url:
185 fprintln(f, spacing, ' ->pushurl("', param, '")')
186 else:
187 param_type = get_parameter_by_name(details, param)
188 if ("allowMultiple" in param_type and
189 param_type["allowMultiple"] == True):
190 fprintln(f, spacing, ' ->pushparam("', param, '",true)')
191 else:
192 fprintln(f, spacing, ' ->pushparam("', param, '")')
193 else:
194 fprintln(f, spacing, 'path_description::add_path("', clear_path_ending(path), '",',
195 details["method"], ',"', details["nickname"], '")')
196 if "parameters" in details:
197 for param in details["parameters"]:
198 if is_required_query_param(param):
199 fprintln(f, spacing, ' ->pushmandatory_param("', param["name"], '")')
200 fprintln(f, spacing, ";")
201
202
203 def get_base_name(param):
204 return os.path.basename(param)
205
206
207 def is_model_valid(name, model):
208 if name in valid_vars:
209 return ""
210 properties = getitem(model[name], "properties", name)
211 for var in properties:
212 type = getitem(properties[var], "type", name + ":" + var)
213 if type == "array":
214 items = getitem(properties[var], "items", name + ":" + var);
215 try :
216 type = getitem(items, "type", name + ":" + var + ":items")
217 except Exception as e:
218 try:
219 type = getitem(items, "$ref", name + ":" + var + ":items")
220 except:
221 raise e;
222 if type not in valid_vars:
223 if type not in model:
224 raise Exception("Unknown type '" + type + "' in Model '" + name + "'")
225 return type
226 valid_vars[name] = name
227 return ""
228
229 def resolve_model_order(data):
230 res = []
231 models = set()
232 for model_name in data:
233 visited = set(model_name)
234 missing = is_model_valid(model_name, data)
235 resolved = missing == ''
236 if not resolved:
237 stack = [model_name]
238 while not resolved:
239 if missing in visited:
240 raise Exception("Cyclic dependency found: " + missing)
241 missing_depends = is_model_valid(missing, data)
242 if missing_depends == '':
243 if missing not in models:
244 res.append(missing)
245 models.add(missing)
246 resolved = len(stack) == 0
247 if not resolved:
248 missing = stack.pop()
249 else:
250 stack.append(missing)
251 missing = missing_depends
252 elif model_name not in models:
253 res.append(model_name)
254 models.add(model_name)
255 return res
256
257 def create_enum_wrapper(model_name, name, values):
258 enum_name = model_name + "_" + name
259 res = " enum class " + enum_name + " {"
260 for enum_entry in values:
261 res = res + " " + enum_entry + ", "
262 res = res + "NUM_ITEMS};\n"
263 wrapper = name + "_wrapper"
264 res = res + Template(""" struct $wrapper : public json::jsonable {
265 $wrapper() = default;
266 virtual std::string to_json() const {
267 switch(v) {
268 """).substitute({'wrapper' : wrapper})
269 for enum_entry in values:
270 res = res + " case " + enum_name + "::" + enum_entry + ": return \"\\\"" + enum_entry + "\\\"\";\n"
271 res = res + Template(""" default: return \"\\\"Unknown\\\"\";
272 }
273 }
274 template<class T>
275 $wrapper (const T& _v) {
276 switch(_v) {
277 """).substitute({'wrapper' : wrapper})
278 for enum_entry in values:
279 res = res + " case T::" + enum_entry + ": v = " + enum_name + "::" + enum_entry + "; break;\n"
280 res = res + Template(""" default: v = $enum_name::NUM_ITEMS;
281 }
282 }
283 template<class T>
284 operator T() const {
285 switch(v) {
286 """).substitute({'enum_name': enum_name})
287 for enum_entry in values:
288 res = res + " case " + enum_name + "::" + enum_entry + ": return T::" + enum_entry + ";\n"
289 return res + Template(""" default: return T::$value;
290 }
291 }
292 typedef typename std::underlying_type<$enum_name>::type pos_type;
293 $wrapper& operator++() {
294 v = static_cast<$enum_name>(static_cast<pos_type>(v) + 1);
295 return *this;
296 }
297 $wrapper & operator++(int) {
298 return ++(*this);
299 }
300 bool operator==(const $wrapper& c) const {
301 return v == c.v;
302 }
303 bool operator!=(const $wrapper& c) const {
304 return v != c.v;
305 }
306 bool operator<=(const $wrapper& c) const {
307 return static_cast<pos_type>(v) <= static_cast<pos_type>(c.v);
308 }
309 static $wrapper begin() {
310 return $wrapper ($enum_name::$value);
311 }
312 static $wrapper end() {
313 return $wrapper ($enum_name::NUM_ITEMS);
314 }
315 static boost::integer_range<$wrapper> all_items() {
316 return boost::irange(begin(), end());
317 }
318 $enum_name v;
319 };
320 """).substitute({'enum_name': enum_name, 'wrapper' : wrapper, 'value':values[0]})
321
322 def to_operation(opr, data):
323 data["method"] = opr.upper()
324 data["nickname"] = data["operationId"]
325 return data
326
327 def to_path(path, data):
328 data["operations"] = [to_operation(k, data[k]) for k in data]
329 data["path"] = path
330
331 return data
332
333 def create_h_file(data, hfile_name, api_name, init_method, base_api):
334 if config.o != '':
335 hfile = open(config.o, "w")
336 else:
337 hfile = open(config.outdir + "/" + hfile_name, "w")
338 print_h_file_headers(hfile, api_name)
339 add_include(hfile, ['<seastar/core/sstring.hh>',
340 '<seastar/json/json_elements.hh>',
341 '<seastar/http/json_path.hh>'])
342
343 add_include(hfile, ['<iostream>', '<boost/range/irange.hpp>'])
344 open_namespace(hfile, "seastar")
345 open_namespace(hfile, "httpd")
346 open_namespace(hfile, api_name)
347
348 if "models" in data:
349 models_order = resolve_model_order(data["models"])
350 for model_name in models_order:
351 model = data["models"][model_name]
352 if 'description' in model:
353 print_ind_comment(hfile, "", model["description"])
354 fprintln(hfile, "struct ", model_name, " : public json::json_base {")
355 member_init = ''
356 member_assignment = ''
357 member_copy = ''
358 for member_name in model["properties"]:
359 member = model["properties"][member_name]
360 if "description" in member:
361 print_comment(hfile, member["description"])
362 if "enum" in member:
363 enum_name = model_name + "_" + member_name
364 fprintln(hfile, create_enum_wrapper(model_name, member_name, member["enum"]))
365 fprintln(hfile, " ", config.jsonns, "::json_element<",
366 member_name, "_wrapper> ",
367 member_name, ";\n")
368 else:
369 fprintln(hfile, " ", config.jsonns, "::",
370 type_change(member["type"], member), " ",
371 member_name, ";\n")
372 member_init += " add(&" + member_name + ',"'
373 member_init += member_name + '");\n'
374 member_assignment += " " + member_name + " = " + "e." + member_name + ";\n"
375 member_copy += " e." + member_name + " = " + member_name + ";\n"
376 fprintln(hfile, "void register_params() {")
377 fprintln(hfile, member_init)
378 fprintln(hfile, '}')
379
380 fprintln(hfile, model_name, '() {')
381 fprintln(hfile, ' register_params();')
382 fprintln(hfile, '}')
383 fprintln(hfile, model_name, '(const ' + model_name + ' & e) {')
384 fprintln(hfile, ' register_params();')
385 fprintln(hfile, member_assignment)
386 fprintln(hfile, '}')
387 fprintln(hfile, "template<class T>")
388 fprintln(hfile, model_name, "& operator=(const ", "T& e) {")
389 fprintln(hfile, member_assignment)
390 fprintln(hfile, " return *this;")
391 fprintln(hfile, "}")
392 fprintln(hfile, model_name, "& operator=(const ", model_name, "& e) {")
393 fprintln(hfile, member_assignment)
394 fprintln(hfile, " return *this;")
395 fprintln(hfile, "}")
396 fprintln(hfile, "template<class T>")
397 fprintln(hfile, model_name, "& update(T& e) {")
398 fprintln(hfile, member_copy)
399 fprintln(hfile, " return *this;")
400 fprintln(hfile, "}")
401 fprintln(hfile, "};\n\n")
402
403 # print_ind_comment(hfile, "", "Initialize the path")
404 # fprintln(hfile, init_method + "(const std::string& description);")
405 fprintln(hfile, 'static const sstring name = "', base_api, '";')
406 for item in data["apis"]:
407 path = item["path"]
408 if "operations" in item:
409 for oper in item["operations"]:
410 if "summary" in oper:
411 print_comment(hfile, oper["summary"])
412
413 param_starts = path.find("{")
414 base_url = path
415 vals = []
416 if param_starts >= 0:
417 vals = path[param_starts:].split("/")
418 vals.reverse()
419 base_url = path[:param_starts]
420
421 fprintln(hfile, 'static const path_description ', getitem(oper, "nickname", oper), '("', clear_path_ending(base_url),
422 '",', oper["method"], ',"', oper["nickname"], '",')
423 fprint(hfile, '{')
424 first = True
425 while vals:
426 path_param, is_url = clean_param(vals.pop())
427 if path_param == "":
428 continue
429 if first == True:
430 first = False
431 else:
432 fprint(hfile, "\n,")
433 if is_url:
434 fprint(hfile, '{', '"/', path_param , '", path_description::url_component_type::FIXED_STRING', '}')
435 else:
436 path_param_type = get_parameter_by_name(oper, path_param)
437 if ("allowMultiple" in path_param_type and
438 path_param_type["allowMultiple"] == True):
439 fprint(hfile, '{', '"', path_param , '", path_description::url_component_type::PARAM_UNTIL_END_OF_PATH', '}')
440 else:
441 fprint(hfile, '{', '"', path_param , '", path_description::url_component_type::PARAM', '}')
442 fprint(hfile, '}')
443 fprint(hfile, ',{')
444 first = True
445 enum_definitions = ""
446 if "enum" in oper:
447 enum_definitions = ("namespace ns_" + oper["nickname"] + " {\n" +
448 create_enum_wrapper(oper["nickname"], "return_type", oper["enum"]) +
449 "}\n")
450 if "parameters" in oper:
451 for param in oper["parameters"]:
452 if is_required_query_param(param):
453 if first == True:
454 first = False
455 else:
456 fprint(hfile, "\n,")
457 fprint(hfile, '"', param["name"], '"')
458 if "enum" in param:
459 enum_definitions = enum_definitions + 'namespace ns_' + oper["nickname"] + '{\n'
460 enm = param["name"]
461 enum_definitions = enum_definitions + 'enum class ' + enm + ' {'
462 for val in param["enum"]:
463 enum_definitions = enum_definitions + val + ", "
464 enum_definitions = enum_definitions + 'NUM_ITEMS};\n'
465 enum_definitions = enum_definitions + enm + ' str2' + enm + '(const sstring& str) {\n'
466 enum_definitions = enum_definitions + ' static const sstring arr[] = {"' + '","'.join(param["enum"]) + '"};\n'
467 enum_definitions = enum_definitions + ' int i;\n'
468 enum_definitions = enum_definitions + ' for (i=0; i < ' + str(len(param["enum"])) + '; i++) {\n'
469 enum_definitions = enum_definitions + ' if (arr[i] == str) {return (' + enm + ')i;}\n}\n'
470 enum_definitions = enum_definitions + ' return (' + enm + ')i;\n'
471 enum_definitions = enum_definitions + '}\n}\n'
472
473 fprintln(hfile, '});')
474 fprintln(hfile, enum_definitions)
475
476 close_namespace(hfile)
477 close_namespace(hfile)
478 close_namespace(hfile)
479 hfile.write("#endif //__JSON_AUTO_GENERATED_HEADERS\n")
480 hfile.close()
481
482 def remove_leading_comma(data):
483 return re.sub(r'^\s*,','', data)
484
485 def format_as_json_object(data):
486 return "{" + remove_leading_comma(data) + "}"
487
488 def check_for_models(data, param):
489 model_name = param.replace(".json", ".def.json")
490 if not os.path.isfile(model_name):
491 return
492 try:
493 with open(model_name) as myfile:
494 json_data = myfile.read()
495 def_data = json.loads(format_as_json_object(json_data))
496 data["models"] = def_data
497 except Exception as e:
498 type, value, tb = sys.exc_info()
499 print("Bad formatted JSON definition file '" + model_name + "' error ", value.message)
500 sys.exit(-1)
501
502 def set_apis(data):
503 return {"apis": [to_path(p, data[p]) for p in data]}
504
505 def parse_file(param, combined):
506 global current_file
507 trace_verbose("parsing ", param, " file")
508 with open(param) as myfile:
509 json_data = myfile.read()
510 try:
511 data = json.loads(json_data)
512 except Exception as e:
513 try:
514 # the passed data is not a valid json, so maybe its a swagger 2.0
515 # snippet, format it as json and try again
516 # set_apis and check_for_models will create an object with a similiar format
517 # to a swagger 1.2 so the code generation would work
518 data = set_apis(json.loads(format_as_json_object(json_data)))
519 check_for_models(data, param)
520 except:
521 # The problem is with the file,
522 # just report the error and exit.
523 type, value, tb = sys.exc_info()
524 print("Bad formatted JSON file '" + param + "' error ", value.message)
525 sys.exit(-1)
526 try:
527 base_file_name = get_base_name(param)
528 current_file = base_file_name
529 hfile_name = base_file_name + ".hh"
530 api_name = base_file_name.replace('.', '_')
531 base_api = base_file_name.replace('.json', '')
532 init_method = "void " + api_name + "_init_path"
533 trace_verbose("creating ", hfile_name)
534 if (combined):
535 fprintln(combined, '#include "', base_file_name, ".cc", '"')
536 create_h_file(data, hfile_name, api_name, init_method, base_api)
537 except:
538 type, value, tb = sys.exc_info()
539 print("Error while parsing JSON file '" + param + "' error ", value.message)
540 sys.exit(-1)
541
542 if "indir" in config and config.indir != '':
543 combined = open(config.combined, "w")
544 for f in glob.glob(os.path.join(config.indir, "*.json")):
545 parse_file(f, combined)
546 else:
547 parse_file(config.f, None)