]>
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 parser
.add_argument('--create-cc', dest
='create_cc', action
='store_true', default
=False,
43 help='Put global variables in a .cc file')
44 config
= parser
.parse_args()
47 valid_vars
= {'string': 'sstring', 'int': 'int', 'double': 'double',
48 'float': 'float', 'long': 'long', 'boolean': 'bool', 'char': 'char',
49 'datetime': 'json::date_time'}
54 def getitem(d
, key
, name
):
58 raise Exception("'" + key
+ "' not found in " + name
)
64 def fprintln(f
, *args
):
70 def open_namespace(f
, ns
=config
.ns
):
71 fprintln(f
, "namespace ", ns
, ' {\n')
74 def close_namespace(f
):
78 def add_include(f
, includes
):
79 for include
in includes
:
80 fprintln(f
, '#include ', include
)
83 def trace_verbose(*params
):
85 print(''.join(params
))
88 def trace_err(*params
):
90 print(current_file
+ ':' + ''.join(params
))
93 def valid_type(param
):
94 if param
in valid_vars
:
95 return valid_vars
[param
]
96 trace_err("Type [", param
, "] not defined")
100 def type_change(param
, member
):
102 if "items" not in member
:
103 trace_err("array without item declaration in ", param
)
105 item
= member
["items"]
111 trace_err("array items with no type or ref declaration ", param
)
113 return "json_list< " + valid_type(t
) + " >"
114 return "json_element< " + valid_type(param
) + " >"
118 def print_ind_comment(f
, ind
, *params
):
119 fprintln(f
, ind
, "/**")
121 fprintln(f
, ind
, " * ", s
)
122 fprintln(f
, ind
, " */")
124 def print_comment(f
, *params
):
125 print_ind_comment(f
, spacing
, *params
)
127 def print_copyrights(f
):
129 fprintln(f
, "* Copyright (C) 2014 Cloudius Systems, Ltd.")
131 fprintln(f
, "* This work is open source software, licensed under the",
133 fprintln(f
, "* BSD license as described in the LICENSE f in the top-",
136 fprintln(f
, "* This is an Auto-Generated-code ")
137 fprintln(f
, "* Changes you do in this file will be erased on next",
142 def print_h_file_headers(f
, name
):
144 fprintln(f
, "#ifndef __JSON_AUTO_GENERATED_" + name
)
145 fprintln(f
, "#define __JSON_AUTO_GENERATED_" + name
+ "\n")
148 def clean_param(param
):
149 match
= re
.match(r
"^\{\s*([^\}]+)\s*}", param
)
151 return [match
.group(1), False]
155 def get_parameter_by_name(obj
, name
):
156 for p
in obj
["parameters"]:
157 if p
["name"] == name
:
159 trace_err ("No Parameter declaration found for ", name
)
162 def clear_path_ending(path
):
163 if not path
or path
[-1] != '/':
167 # check if a parameter is query required.
168 # It will return true if the required flag is set
169 # and if it is a query parameter, both swagger 1.2 'paramType' and swagger 2.0 'in' attributes
171 def is_required_query_param(param
):
172 return "required" in param
and param
["required"] and ("paramType" in param
and param
["paramType"] == "query" or "in" in param
and param
["in"] == "query")
174 def add_path(f
, path
, details
):
175 if "summary" in details
:
176 print_comment(f
, details
["summary"])
177 param_starts
= path
.find("{")
178 if param_starts
>= 0:
179 path_reminder
= path
[param_starts
:]
180 vals
= path
.split("/")
182 fprintln(f
, spacing
, 'path_description::add_path("', clear_path_ending(vals
.pop()),
183 '",', details
["method"], ',"', details
["nickname"], '")')
185 param
, is_url
= clean_param(vals
.pop())
187 fprintln(f
, spacing
, ' ->pushurl("', param
, '")')
189 param_type
= get_parameter_by_name(details
, param
)
190 if ("allowMultiple" in param_type
and
191 param_type
["allowMultiple"] == True):
192 fprintln(f
, spacing
, ' ->pushparam("', param
, '",true)')
194 fprintln(f
, spacing
, ' ->pushparam("', param
, '")')
196 fprintln(f
, spacing
, 'path_description::add_path("', clear_path_ending(path
), '",',
197 details
["method"], ',"', details
["nickname"], '")')
198 if "parameters" in details
:
199 for param
in details
["parameters"]:
200 if is_required_query_param(param
):
201 fprintln(f
, spacing
, ' ->pushmandatory_param("', param
["name"], '")')
202 fprintln(f
, spacing
, ";")
205 def get_base_name(param
):
206 return os
.path
.basename(param
)
209 def is_model_valid(name
, model
):
210 if name
in valid_vars
:
212 properties
= getitem(model
[name
], "properties", name
)
213 for var
in properties
:
214 type = getitem(properties
[var
], "type", name
+ ":" + var
)
216 items
= getitem(properties
[var
], "items", name
+ ":" + var
);
218 type = getitem(items
, "type", name
+ ":" + var
+ ":items")
219 except Exception as e
:
221 type = getitem(items
, "$ref", name
+ ":" + var
+ ":items")
224 if type not in valid_vars
:
225 if type not in model
:
226 raise Exception("Unknown type '" + type + "' in Model '" + name
+ "'")
228 valid_vars
[name
] = name
231 def resolve_model_order(data
):
234 for model_name
in data
:
235 visited
= set(model_name
)
236 missing
= is_model_valid(model_name
, data
)
237 resolved
= missing
== ''
241 if missing
in visited
:
242 raise Exception("Cyclic dependency found: " + missing
)
243 missing_depends
= is_model_valid(missing
, data
)
244 if missing_depends
== '':
245 if missing
not in models
:
248 resolved
= len(stack
) == 0
250 missing
= stack
.pop()
252 stack
.append(missing
)
253 missing
= missing_depends
254 elif model_name
not in models
:
255 res
.append(model_name
)
256 models
.add(model_name
)
259 def create_enum_wrapper(model_name
, name
, values
):
260 enum_name
= model_name
+ "_" + name
261 res
= " enum class " + enum_name
+ " {"
262 for enum_entry
in values
:
263 res
= res
+ " " + enum_entry
+ ", "
264 res
= res
+ "NUM_ITEMS};\n"
265 wrapper
= name
+ "_wrapper"
266 res
= res
+ Template(""" struct $wrapper : public json::jsonable {
267 $wrapper() = default;
268 virtual std::string to_json() const {
270 """).substitute({'wrapper' : wrapper
})
271 for enum_entry
in values
:
272 res
= res
+ " case " + enum_name
+ "::" + enum_entry
+ ": return \"\\\"" + enum_entry
+ "\\\"\";\n"
273 res
= res
+ Template(""" default: return \"\\\"Unknown\\\"\";
277 $wrapper (const T& _v) {
279 """).substitute({'wrapper' : wrapper
})
280 for enum_entry
in values
:
281 res
= res
+ " case T::" + enum_entry
+ ": v = " + enum_name
+ "::" + enum_entry
+ "; break;\n"
282 res
= res
+ Template(""" default: v = $enum_name::NUM_ITEMS;
288 """).substitute({'enum_name': enum_name
})
289 for enum_entry
in values
:
290 res
= res
+ " case " + enum_name
+ "::" + enum_entry
+ ": return T::" + enum_entry
+ ";\n"
291 return res
+ Template(""" default: return T::$value;
294 typedef typename std::underlying_type<$enum_name>::type pos_type;
295 $wrapper& operator++() {
296 v = static_cast<$enum_name>(static_cast<pos_type>(v) + 1);
299 $wrapper & operator++(int) {
302 bool operator==(const $wrapper& c) const {
305 bool operator!=(const $wrapper& c) const {
308 bool operator<=(const $wrapper& c) const {
309 return static_cast<pos_type>(v) <= static_cast<pos_type>(c.v);
311 static $wrapper begin() {
312 return $wrapper ($enum_name::$value);
314 static $wrapper end() {
315 return $wrapper ($enum_name::NUM_ITEMS);
317 static boost::integer_range<$wrapper> all_items() {
318 return boost::irange(begin(), end());
322 """).substitute({'enum_name': enum_name
, 'wrapper' : wrapper
, 'value':values
[0]})
324 def to_operation(opr
, data
):
325 data
["method"] = opr
.upper()
326 data
["nickname"] = data
["operationId"]
329 def to_path(path
, data
):
330 data
["operations"] = [to_operation(k
, data
[k
]) for k
in data
]
335 def create_h_file(data
, hfile_name
, api_name
, init_method
, base_api
):
337 final_hfile_name
= config
.o
339 final_hfile_name
= config
.outdir
+ "/" + hfile_name
340 hfile
= open(final_hfile_name
, "w")
343 ccfile
= open(final_hfile_name
.rstrip('.hh') + ".cc", "w")
344 add_include(ccfile
, ['"{}"'.format(final_hfile_name
)])
345 open_namespace(ccfile
, "seastar")
346 open_namespace(ccfile
, "httpd")
347 open_namespace(ccfile
, api_name
)
350 print_h_file_headers(hfile
, api_name
)
351 add_include(hfile
, ['<seastar/core/sstring.hh>',
352 '<seastar/json/json_elements.hh>',
353 '<seastar/http/json_path.hh>'])
355 add_include(hfile
, ['<iostream>', '<boost/range/irange.hpp>'])
356 open_namespace(hfile
, "seastar")
357 open_namespace(hfile
, "httpd")
358 open_namespace(hfile
, api_name
)
361 models_order
= resolve_model_order(data
["models"])
362 for model_name
in models_order
:
363 model
= data
["models"][model_name
]
364 if 'description' in model
:
365 print_ind_comment(hfile
, "", model
["description"])
366 fprintln(hfile
, "struct ", model_name
, " : public json::json_base {")
368 member_assignment
= ''
370 for member_name
in model
["properties"]:
371 member
= model
["properties"][member_name
]
372 if "description" in member
:
373 print_comment(hfile
, member
["description"])
375 enum_name
= model_name
+ "_" + member_name
376 fprintln(hfile
, create_enum_wrapper(model_name
, member_name
, member
["enum"]))
377 fprintln(hfile
, " ", config
.jsonns
, "::json_element<",
378 member_name
, "_wrapper> ",
381 fprintln(hfile
, " ", config
.jsonns
, "::",
382 type_change(member
["type"], member
), " ",
384 member_init
+= " add(&" + member_name
+ ',"'
385 member_init
+= member_name
+ '");\n'
386 member_assignment
+= " " + member_name
+ " = " + "e." + member_name
+ ";\n"
387 member_copy
+= " e." + member_name
+ " = " + member_name
+ ";\n"
388 fprintln(hfile
, "void register_params() {")
389 fprintln(hfile
, member_init
)
392 fprintln(hfile
, model_name
, '() {')
393 fprintln(hfile
, ' register_params();')
395 fprintln(hfile
, model_name
, '(const ' + model_name
+ ' & e) {')
396 fprintln(hfile
, ' register_params();')
397 fprintln(hfile
, member_assignment
)
399 fprintln(hfile
, "template<class T>")
400 fprintln(hfile
, model_name
, "& operator=(const ", "T& e) {")
401 fprintln(hfile
, member_assignment
)
402 fprintln(hfile
, " return *this;")
404 fprintln(hfile
, model_name
, "& operator=(const ", model_name
, "& e) {")
405 fprintln(hfile
, member_assignment
)
406 fprintln(hfile
, " return *this;")
408 fprintln(hfile
, "template<class T>")
409 fprintln(hfile
, model_name
, "& update(T& e) {")
410 fprintln(hfile
, member_copy
)
411 fprintln(hfile
, " return *this;")
413 fprintln(hfile
, "};\n\n")
415 # print_ind_comment(hfile, "", "Initialize the path")
416 # fprintln(hfile, init_method + "(const std::string& description);")
417 fprintln(hfile
, 'static const sstring name = "', base_api
, '";')
418 for item
in data
["apis"]:
420 if "operations" in item
:
421 for oper
in item
["operations"]:
422 if "summary" in oper
:
423 print_comment(hfile
, oper
["summary"])
425 param_starts
= path
.find("{")
428 if param_starts
>= 0:
429 vals
= path
[param_starts
:].split("/")
431 base_url
= path
[:param_starts
]
433 varname
= getitem(oper
, "nickname", oper
)
435 fprintln(hfile
, 'extern const path_description ', varname
, ';')
438 maybe_static
= 'static '
439 fprintln(ccfile
, maybe_static
, 'const path_description ', varname
, '("', clear_path_ending(base_url
),
440 '",', oper
["method"], ',"', oper
["nickname"], '",')
444 path_param
, is_url
= clean_param(vals
.pop())
450 fprint(ccfile
, "\n,")
452 fprint(ccfile
, '{', '"/', path_param
, '", path_description::url_component_type::FIXED_STRING', '}')
454 path_param_type
= get_parameter_by_name(oper
, path_param
)
455 if ("allowMultiple" in path_param_type
and
456 path_param_type
["allowMultiple"] == True):
457 fprint(ccfile
, '{', '"', path_param
, '", path_description::url_component_type::PARAM_UNTIL_END_OF_PATH', '}')
459 fprint(ccfile
, '{', '"', path_param
, '", path_description::url_component_type::PARAM', '}')
463 enum_definitions
= ""
465 enum_definitions
= ("namespace ns_" + oper
["nickname"] + " {\n" +
466 create_enum_wrapper(oper
["nickname"], "return_type", oper
["enum"]) +
469 if "parameters" in oper
:
470 for param
in oper
["parameters"]:
471 if is_required_query_param(param
):
475 fprint(ccfile
, "\n,")
476 fprint(ccfile
, '"', param
["name"], '"')
478 enum_definitions
= enum_definitions
+ 'namespace ns_' + oper
["nickname"] + '{\n'
480 enum_definitions
= enum_definitions
+ 'enum class ' + enm
+ ' {'
481 for val
in param
["enum"]:
482 enum_definitions
= enum_definitions
+ val
+ ", "
483 enum_definitions
= enum_definitions
+ 'NUM_ITEMS};\n'
484 enum_definitions
= enum_definitions
+ enm
+ ' str2' + enm
+ '(const sstring& str);'
486 funcs
= funcs
+ enm
+ ' str2' + enm
+ '(const sstring& str) {\n'
487 funcs
= funcs
+ ' static const sstring arr[] = {"' + '","'.join(param
["enum"]) + '"};\n'
488 funcs
= funcs
+ ' int i;\n'
489 funcs
= funcs
+ ' for (i=0; i < ' + str(len(param
["enum"])) + '; i++) {\n'
490 funcs
= funcs
+ ' if (arr[i] == str) {return (' + enm
+ ')i;}\n}\n'
491 funcs
= funcs
+ ' return (' + enm
+ ')i;\n'
492 funcs
= funcs
+ '}\n'
494 enum_definitions
= enum_definitions
+ '}\n'
496 fprintln(ccfile
, '});')
497 fprintln(hfile
, enum_definitions
)
498 open_namespace(ccfile
, 'ns_' + oper
["nickname"])
499 fprintln(ccfile
, funcs
)
500 close_namespace(ccfile
)
502 close_namespace(hfile
)
503 close_namespace(hfile
)
504 close_namespace(hfile
)
506 close_namespace(ccfile
)
507 close_namespace(ccfile
)
508 close_namespace(ccfile
)
510 hfile
.write("#endif //__JSON_AUTO_GENERATED_HEADERS\n")
513 def remove_leading_comma(data
):
514 return re
.sub(r
'^\s*,','', data
)
516 def format_as_json_object(data
):
517 return "{" + remove_leading_comma(data
) + "}"
519 def check_for_models(data
, param
):
520 model_name
= param
.replace(".json", ".def.json")
521 if not os
.path
.isfile(model_name
):
524 with
open(model_name
) as myfile
:
525 json_data
= myfile
.read()
526 def_data
= json
.loads(format_as_json_object(json_data
))
527 data
["models"] = def_data
528 except Exception as e
:
529 type, value
, tb
= sys
.exc_info()
530 print("Bad formatted JSON definition file '" + model_name
+ "' error ", value
.message
)
534 return {"apis": [to_path(p
, data
[p
]) for p
in data
]}
536 def parse_file(param
, combined
):
538 trace_verbose("parsing ", param
, " file")
539 with
open(param
) as myfile
:
540 json_data
= myfile
.read()
542 data
= json
.loads(json_data
)
543 except Exception as e
:
545 # the passed data is not a valid json, so maybe its a swagger 2.0
546 # snippet, format it as json and try again
547 # set_apis and check_for_models will create an object with a similiar format
548 # to a swagger 1.2 so the code generation would work
549 data
= set_apis(json
.loads(format_as_json_object(json_data
)))
550 check_for_models(data
, param
)
552 # The problem is with the file,
553 # just report the error and exit.
554 type, value
, tb
= sys
.exc_info()
555 print("Bad formatted JSON file '" + param
+ "' error ", value
.message
)
558 base_file_name
= get_base_name(param
)
559 current_file
= base_file_name
560 hfile_name
= base_file_name
+ ".hh"
561 api_name
= base_file_name
.replace('.', '_')
562 base_api
= base_file_name
.replace('.json', '')
563 init_method
= "void " + api_name
+ "_init_path"
564 trace_verbose("creating ", hfile_name
)
566 fprintln(combined
, '#include "', base_file_name
, ".cc", '"')
567 create_h_file(data
, hfile_name
, api_name
, init_method
, base_api
)
569 type, value
, tb
= sys
.exc_info()
570 print("Error while parsing JSON file '" + param
+ "' error ", value
.message
)
573 if "indir" in config
and config
.indir
!= '':
574 combined
= open(config
.combined
, "w")
575 for f
in glob
.glob(os
.path
.join(config
.indir
, "*.json")):
576 parse_file(f
, combined
)
578 parse_file(config
.f
, None)