Skip to content
76 changes: 56 additions & 20 deletions openapi2jsonschema/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
allow_null_optional_fields,
change_dict_values,
append_no_duplicates,
get_request_and_response_body_components_from_paths,
)
from openapi2jsonschema.errors import UnsupportedError

Expand Down Expand Up @@ -43,13 +44,31 @@
@click.option(
"--kubernetes", is_flag=True, help="Enable Kubernetes specific processors"
)
@click.option(
"--no-all", is_flag=True, help="Do not generate all.json file"
)
@click.option(
"--strict",
is_flag=True,
help="Prohibits properties not in the schema (additionalProperties: false)",
)
@click.option(
"--include-bodies",
is_flag=True,
help="Include request and response bodies as if they are components",
)
@click.argument("schema", metavar="SCHEMA_URL")
def default(output, schema, prefix, stand_alone, expanded, kubernetes, strict):
def default(
output,
schema,
prefix,
stand_alone,
expanded,
kubernetes,
strict,
no_all,
include_bodies,
):
"""
Converts a valid OpenAPI specification into a set of JSON Schema files
"""
Expand Down Expand Up @@ -126,8 +145,15 @@ def default(output, schema, prefix, stand_alone, expanded, kubernetes, strict):
else:
components = data["components"]["schemas"]

generated_files = []

if include_bodies:
components.update(
get_request_and_response_body_components_from_paths(data["paths"]),
)

for title in components:
kind = title.split(".")[-1].lower()
kind = title.split(".")[-1]
if kubernetes:
group = title.split(".")[-3].lower()
api_version = title.split(".")[-2].lower()
Expand Down Expand Up @@ -164,8 +190,7 @@ def default(output, schema, prefix, stand_alone, expanded, kubernetes, strict):
if (
kubernetes
and stand_alone
and kind
in [
and kind.lower() in [
"jsonschemaprops",
"jsonschemapropsorarray",
"customresourcevalidation",
Expand All @@ -183,9 +208,9 @@ def default(output, schema, prefix, stand_alone, expanded, kubernetes, strict):
specification = updated

if stand_alone:
base = "file://%s/%s/" % (os.getcwd(), output)
specification = JsonRef.replace_refs(
specification, base_uri=base)
# Put generated file on list for dereferencig $ref elements
# after all files will be generated
generated_files.append(full_name)

if "additionalProperties" in specification:
if specification["additionalProperties"]:
Expand All @@ -209,19 +234,30 @@ def default(output, schema, prefix, stand_alone, expanded, kubernetes, strict):
except Exception as e:
error("An error occured processing %s: %s" % (kind, e))

with open("%s/all.json" % output, "w") as all_file:
info("Generating schema for all types")
contents = {"oneOf": []}
for title in types:
if version < "3":
contents["oneOf"].append(
{"$ref": "%s#/definitions/%s" % (prefix, title)}
)
else:
contents["oneOf"].append(
{"$ref": (title.replace("#/components/schemas/", "") + ".json")}
)
all_file.write(json.dumps(contents, indent=2))
if stand_alone:
base = "file://%s/%s/" % (os.getcwd(), output)
for file_name in generated_files:
full_path = "%s/%s.json" % (output, file_name)
specification = json.load(open(full_path))
specification = JsonRef.replace_refs(
specification, base_uri=base)
with open(full_path, "w") as schema_file:
schema_file.write(json.dumps(specification, indent=2))

if not no_all:
with open("%s/all.json" % output, "w") as all_file:
info("Generating schema for all types")
contents = {"oneOf": []}
for title in types:
if version < "3":
contents["oneOf"].append(
{"$ref": "%s#/definitions/%s" % (prefix, title)}
)
else:
contents["oneOf"].append(
{"$ref": (title.replace("#/components/schemas/", "") + ".json")}
)
all_file.write(json.dumps(contents, indent=2))


if __name__ == "__main__":
Expand Down
57 changes: 56 additions & 1 deletion openapi2jsonschema/util.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env python

import re


def iteritems(d):
if hasattr(dict, "iteritems"):
Expand Down Expand Up @@ -77,7 +79,10 @@ def allow_null_optional_fields(data, parent=None, grand_parent=None, key=None):
def change_dict_values(d, prefix, version):
new = {}
try:
is_nullable = False
for k, v in iteritems(d):
if k == 'nullable':
is_nullable = True
new_v = v
if isinstance(v, dict):
new_v = change_dict_values(v, prefix, version)
Expand All @@ -90,10 +95,14 @@ def change_dict_values(d, prefix, version):
if version < "3":
new_v = "%s%s" % (prefix, v)
else:
new_v = v.replace("#/components/schemas/", "") + ".json"
new_v = v.replace("#/components/schemas/", "").lower() + ".json"
else:
new_v = v
new[k] = new_v
if is_nullable and 'type' in new:
if not isinstance(new['type'], list):
new['type'] = [new['type']]
new['type'].append('null')
return new
except AttributeError:
return d
Expand All @@ -108,3 +117,49 @@ def append_no_duplicates(obj, key, value):
obj[key] = []
if value not in obj[key]:
obj[key].append(value)


def get_components_from_body_definition(body_definition, prefix=""):
MIMETYPE_TO_TYPENAME_MAP = {
"application/json": "json",
"application/vnd.api+json": "jsonapi",
}
result = {}
for mimetype, definition in body_definition.get("content", {}).items():
type_name = MIMETYPE_TO_TYPENAME_MAP.get(
mimetype,
mimetype.replace("/", "_"),
)
if "schema" in definition:
result["{:s}{:s}".format(prefix, type_name)] = definition["schema"]
return result


def get_request_and_response_body_components_from_paths(paths):
components = {}
for path, path_definition in paths.items():
for http_method, http_method_definition in path_definition.items():
name_prefix_fmt = "paths_{:s}_{:s}_{{:s}}_".format(
# Paths "/" and "/root" will conflict,
# no idea how to solve this elegantly.
path.lstrip("/").replace("/", "_") or "root",
http_method.upper(),
)
name_prefix_fmt = re.sub(
r"\{([^:\}]+)\}",
r"_\1_",
name_prefix_fmt,
)
if "requestBody" in http_method_definition:
components.update(get_components_from_body_definition(
http_method_definition["requestBody"],
prefix=name_prefix_fmt.format("request")
))
responses = http_method_definition["responses"]
for response_code, response in responses.items():
response_name_part = "response_{}".format(response_code)
components.update(get_components_from_body_definition(
response,
prefix=name_prefix_fmt.format(response_name_part),
))
return components