]>
git.proxmox.com Git - ceph.git/blob - ceph/src/seastar/scripts/seastar-json2code.py
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
7 # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md
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
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.
23 from string
import Template
25 parser
= argparse
.ArgumentParser(description
="""Generate C++ class for json
26 handling from swagger definition""")
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',
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()
45 valid_vars
= {'string': 'sstring', 'int': 'int', 'double': 'double',
46 'float': 'float', 'long': 'long', 'boolean': 'bool', 'char': 'char',
47 'datetime': 'json::date_time'}
52 def getitem(d
, key
, name
):
56 raise Exception("'" + key
+ "' not found in " + name
)
62 def fprintln(f
, *args
):
68 def open_namespace(f
, ns
=config
.ns
):
69 fprintln(f
, "namespace ", ns
, ' {\n')
72 def close_namespace(f
):
76 def add_include(f
, includes
):
77 for include
in includes
:
78 fprintln(f
, '#include ', include
)
81 def trace_verbose(*params
):
83 print(''.join(params
))
86 def trace_err(*params
):
88 print(current_file
+ ':' + ''.join(params
))
91 def valid_type(param
):
92 if param
in valid_vars
:
93 return valid_vars
[param
]
94 trace_err("Type [", param
, "] not defined")
98 def type_change(param
, member
):
100 if "items" not in member
:
101 trace_err("array without item declaration in ", param
)
103 item
= member
["items"]
109 trace_err("array items with no type or ref declaration ", param
)
111 return "json_list< " + valid_type(t
) + " >"
112 return "json_element< " + valid_type(param
) + " >"
116 def print_ind_comment(f
, ind
, *params
):
117 fprintln(f
, ind
, "/**")
119 fprintln(f
, ind
, " * ", s
)
120 fprintln(f
, ind
, " */")
122 def print_comment(f
, *params
):
123 print_ind_comment(f
, spacing
, *params
)
125 def print_copyrights(f
):
127 fprintln(f
, "* Copyright (C) 2014 Cloudius Systems, Ltd.")
129 fprintln(f
, "* This work is open source software, licensed under the",
131 fprintln(f
, "* BSD license as described in the LICENSE f in the top-",
134 fprintln(f
, "* This is an Auto-Generated-code ")
135 fprintln(f
, "* Changes you do in this file will be erased on next",
140 def print_h_file_headers(f
, name
):
142 fprintln(f
, "#ifndef __JSON_AUTO_GENERATED_" + name
)
143 fprintln(f
, "#define __JSON_AUTO_GENERATED_" + name
+ "\n")
146 def clean_param(param
):
147 match
= re
.match(r
"^\{\s*([^\}]+)\s*}", param
)
149 return [match
.group(1), False]
153 def get_parameter_by_name(obj
, name
):
154 for p
in obj
["parameters"]:
155 if p
["name"] == name
:
157 trace_err ("No Parameter declaration found for ", name
)
160 def clear_path_ending(path
):
161 if not path
or path
[-1] != '/':
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
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")
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("/")
180 fprintln(f
, spacing
, 'path_description::add_path("', clear_path_ending(vals
.pop()),
181 '",', details
["method"], ',"', details
["nickname"], '")')
183 param
, is_url
= clean_param(vals
.pop())
185 fprintln(f
, spacing
, ' ->pushurl("', param
, '")')
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)')
192 fprintln(f
, spacing
, ' ->pushparam("', param
, '")')
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
, ";")
203 def get_base_name(param
):
204 return os
.path
.basename(param
)
207 def is_model_valid(name
, model
):
208 if name
in valid_vars
:
210 properties
= getitem(model
[name
], "properties", name
)
211 for var
in properties
:
212 type = getitem(properties
[var
], "type", name
+ ":" + var
)
214 items
= getitem(properties
[var
], "items", name
+ ":" + var
);
216 type = getitem(items
, "type", name
+ ":" + var
+ ":items")
217 except Exception as e
:
219 type = getitem(items
, "$ref", name
+ ":" + var
+ ":items")
222 if type not in valid_vars
:
223 if type not in model
:
224 raise Exception("Unknown type '" + type + "' in Model '" + name
+ "'")
226 valid_vars
[name
] = name
229 def resolve_model_order(data
):
232 for model_name
in data
:
233 visited
= set(model_name
)
234 missing
= is_model_valid(model_name
, data
)
235 resolved
= missing
== ''
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
:
246 resolved
= len(stack
) == 0
248 missing
= stack
.pop()
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
)
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 {
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\\\"\";
275 $wrapper (const T& _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;
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;
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);
297 $wrapper & operator++(int) {
300 bool operator==(const $wrapper& c) const {
303 bool operator!=(const $wrapper& c) const {
306 bool operator<=(const $wrapper& c) const {
307 return static_cast<pos_type>(v) <= static_cast<pos_type>(c.v);
309 static $wrapper begin() {
310 return $wrapper ($enum_name::$value);
312 static $wrapper end() {
313 return $wrapper ($enum_name::NUM_ITEMS);
315 static boost::integer_range<$wrapper> all_items() {
316 return boost::irange(begin(), end());
320 """).substitute({'enum_name': enum_name
, 'wrapper' : wrapper
, 'value':values
[0]})
322 def to_operation(opr
, data
):
323 data
["method"] = opr
.upper()
324 data
["nickname"] = data
["operationId"]
327 def to_path(path
, data
):
328 data
["operations"] = [to_operation(k
, data
[k
]) for k
in data
]
333 def create_h_file(data
, hfile_name
, api_name
, init_method
, base_api
):
335 hfile
= open(config
.o
, "w")
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>'])
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
)
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 {")
356 member_assignment
= ''
358 for member_name
in model
["properties"]:
359 member
= model
["properties"][member_name
]
360 if "description" in member
:
361 print_comment(hfile
, member
["description"])
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> ",
369 fprintln(hfile
, " ", config
.jsonns
, "::",
370 type_change(member
["type"], member
), " ",
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
)
380 fprintln(hfile
, model_name
, '() {')
381 fprintln(hfile
, ' register_params();')
383 fprintln(hfile
, model_name
, '(const ' + model_name
+ ' & e) {')
384 fprintln(hfile
, ' register_params();')
385 fprintln(hfile
, member_assignment
)
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;")
392 fprintln(hfile
, model_name
, "& operator=(const ", model_name
, "& e) {")
393 fprintln(hfile
, member_assignment
)
394 fprintln(hfile
, " return *this;")
396 fprintln(hfile
, "template<class T>")
397 fprintln(hfile
, model_name
, "& update(T& e) {")
398 fprintln(hfile
, member_copy
)
399 fprintln(hfile
, " return *this;")
401 fprintln(hfile
, "};\n\n")
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"]:
408 if "operations" in item
:
409 for oper
in item
["operations"]:
410 if "summary" in oper
:
411 print_comment(hfile
, oper
["summary"])
413 param_starts
= path
.find("{")
416 if param_starts
>= 0:
417 vals
= path
[param_starts
:].split("/")
419 base_url
= path
[:param_starts
]
421 fprintln(hfile
, 'static const path_description ', getitem(oper
, "nickname", oper
), '("', clear_path_ending(base_url
),
422 '",', oper
["method"], ',"', oper
["nickname"], '",')
426 path_param
, is_url
= clean_param(vals
.pop())
434 fprint(hfile
, '{', '"/', path_param
, '", path_description::url_component_type::FIXED_STRING', '}')
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', '}')
441 fprint(hfile
, '{', '"', path_param
, '", path_description::url_component_type::PARAM', '}')
445 enum_definitions
= ""
447 enum_definitions
= ("namespace ns_" + oper
["nickname"] + " {\n" +
448 create_enum_wrapper(oper
["nickname"], "return_type", oper
["enum"]) +
450 if "parameters" in oper
:
451 for param
in oper
["parameters"]:
452 if is_required_query_param(param
):
457 fprint(hfile
, '"', param
["name"], '"')
459 enum_definitions
= enum_definitions
+ 'namespace ns_' + oper
["nickname"] + '{\n'
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'
473 fprintln(hfile
, '});')
474 fprintln(hfile
, enum_definitions
)
476 close_namespace(hfile
)
477 close_namespace(hfile
)
478 close_namespace(hfile
)
479 hfile
.write("#endif //__JSON_AUTO_GENERATED_HEADERS\n")
482 def remove_leading_comma(data
):
483 return re
.sub(r
'^\s*,','', data
)
485 def format_as_json_object(data
):
486 return "{" + remove_leading_comma(data
) + "}"
488 def check_for_models(data
, param
):
489 model_name
= param
.replace(".json", ".def.json")
490 if not os
.path
.isfile(model_name
):
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
)
503 return {"apis": [to_path(p
, data
[p
]) for p
in data
]}
505 def parse_file(param
, combined
):
507 trace_verbose("parsing ", param
, " file")
508 with
open(param
) as myfile
:
509 json_data
= myfile
.read()
511 data
= json
.loads(json_data
)
512 except Exception as e
:
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
)
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
)
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
)
535 fprintln(combined
, '#include "', base_file_name
, ".cc", '"')
536 create_h_file(data
, hfile_name
, api_name
, init_method
, base_api
)
538 type, value
, tb
= sys
.exc_info()
539 print("Error while parsing JSON file '" + param
+ "' error ", value
.message
)
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
)
547 parse_file(config
.f
, None)