├── .gitignore ├── LICENSE ├── README.md ├── bin └── swagger_to_uml ├── petstore_example ├── swagger.json ├── swagger.png ├── swagger.puml └── swagger.svg ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | .static_storage/ 58 | .media/ 59 | local_settings.py 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Environments 87 | .env 88 | .venv 89 | env/ 90 | venv/ 91 | ENV/ 92 | env.bak/ 93 | venv.bak/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | 108 | .idea/ 109 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Niels Lohmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swagger to UML 2 | 3 | A small pure Python script that converts [OpenAPI specifications](https://www.openapis.org) (a.k.a. [Swagger](https://swagger.io)) into [Plant UML](http://plantuml.com) diagrams. The goal is not to replace existing documentation generators, but to complement them with a visual representation of the routes, models, and their relationships. 4 | 5 | ## Example 6 | 7 | ![excerpt of the petstore example](petstore_example/swagger.png) 8 | 9 | To create a diagram from the [petstore example](http://petstore.swagger.io), call the script with: 10 | 11 | ``` 12 | python swagger_to_uml.py petstore_example/swagger.json >petstore_example/swagger.puml 13 | ``` 14 | 15 | It will create the file `petstore_example/swagger.puml` which can then be translated into a PNG image with PlantUML with: 16 | 17 | ``` 18 | plantuml petstore_example/swagger.puml -tpng 19 | ``` 20 | 21 | Note you need to install [Plant UML](http://plantuml.com) and [Graphviz](http://www.graphviz.org) for this. 22 | 23 | ## Installation 24 | 25 | The script runs with Python 3 without any additional packages. Transforming PUML into vector graphics or other requires external tools however. 26 | 27 | On macOS, the installation of the required tools with [Homebrew](https://brew.sh) is simple: 28 | 29 | ``` 30 | brew install plantuml graphviz 31 | ``` 32 | 33 | ## Contribute 34 | 35 | The script is just a first proof-of-concept version. Issues and pull requests welcome! 36 | 37 | ## Copyright 38 | 39 | MIT License 40 | 41 | Copyright (c) 2017 Niels Lohmann 42 | 43 | Permission is hereby granted, free of charge, to any person obtaining a copy 44 | of this software and associated documentation files (the "Software"), to deal 45 | in the Software without restriction, including without limitation the rights 46 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 47 | copies of the Software, and to permit persons to whom the Software is 48 | furnished to do so, subject to the following conditions: 49 | 50 | The above copyright notice and this permission notice shall be included in all 51 | copies or substantial portions of the Software. 52 | 53 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 54 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 55 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 56 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 57 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 58 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 59 | SOFTWARE. 60 | -------------------------------------------------------------------------------- /bin/swagger_to_uml: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding=utf-8 3 | 4 | # MIT License 5 | # 6 | # Copyright 2017 Niels Lohmann 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 9 | # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 11 | # permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of 14 | # the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 18 | # OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 19 | # OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | 21 | import json 22 | import sys 23 | from typing import List, Optional, Any, Set 24 | 25 | 26 | def resolve_ref(ref): 27 | return ref.split('/')[-1] 28 | 29 | 30 | class Property: 31 | def __init__(self, name, type, required, example=None, description=None, default=None, enum=None, format=None, 32 | items=None, maximum=None, exclusive_maximum=False, minimum=None, exclusive_minimum=False, 33 | multiple_of=None, max_length=None, min_length=0, pattern=None, max_items=None, min_items=0, 34 | unique_items=False, ref_type=None): 35 | # type 36 | self.type = type # type: str 37 | self.format = format # type: Optional[str] 38 | self.ref_type = ref_type # type: Optional[str] 39 | 40 | # constraints 41 | self.required = required # type: bool 42 | self.enum = enum # type: Optional[List[Any]] 43 | 44 | # documentation 45 | self.name = name # type: str 46 | self.example = example # type: Optional[Any] 47 | self.description = description # type: Optional[str] 48 | self.default = default # type: Optional[Any] 49 | 50 | # numbers 51 | self.maximum = maximum # type: Optional[float,int] 52 | self.exclusive_maximum = exclusive_maximum # type: bool 53 | self.minimum = minimum # type: Optional[float,int] 54 | self.exclusive_minimum = exclusive_minimum # type: bool 55 | self.multiple_of = multiple_of # type: Optional[float,int] 56 | 57 | # strings 58 | self.max_length = max_length # type: Optional[int] 59 | self.min_length = min_length # type: int 60 | self.pattern = pattern # type: Optional[str] 61 | 62 | # arrays 63 | self.max_items = max_items # type: Optional[int] 64 | self.min_items = min_items # type: int 65 | self.unique_items = unique_items # type: bool 66 | self.items = items # type: Optional[str] 67 | 68 | @staticmethod 69 | def from_dict(property_name, d, required): 70 | # whether the type was resolved 71 | ref_type = None 72 | 73 | # We use the Parameter class for parameters inside the swagger specification, but also for parameters. There, 74 | # type information is given in a "schema" property. 75 | if 'type' in d or '$ref' in d: 76 | type_dict = d 77 | elif 'schema' in d: 78 | type_dict = d['schema'] 79 | elif 'allOf' in d and len(d['allOf']) > 0: 80 | type_dict = d['allOf'][0] 81 | else: 82 | type_dict = {} 83 | 84 | # type is given or must be resolved from $ref 85 | if 'type' in type_dict: 86 | type_str = type_dict['type'] 87 | elif '$ref' in type_dict: 88 | type_str = resolve_ref(type_dict['$ref']) 89 | ref_type = type_str 90 | else: 91 | type_str = 'not specified' 92 | 93 | # join multiple types to string 94 | if isinstance(type_str, list): 95 | type_str = '/'.join(type_str) 96 | 97 | # items type is given or must be resolved from $ref 98 | if 'items' in type_dict: 99 | if 'type' in type_dict['items']: 100 | items = type_dict['items']['type'] 101 | else: 102 | items = resolve_ref(type_dict['items']['$ref']) 103 | ref_type = items 104 | else: 105 | items = None 106 | 107 | return Property( 108 | name=property_name, 109 | type=type_str, 110 | required=required, 111 | example=d.get('example'), 112 | description=d.get('description'), 113 | default=d.get('default'), 114 | enum=d.get('enum'), 115 | format=d.get('format'), 116 | items=items, 117 | maximum=d.get('maximum'), 118 | exclusive_maximum=d.get('exclusiveMaximum', False), 119 | minimum=d.get('minimum'), 120 | exclusive_minimum=d.get('exclusiveMinimum', False), 121 | multiple_of=d.get('multipleOf'), 122 | max_length=d.get('maxLength'), 123 | min_length=d.get('minLength', 0), 124 | pattern=d.get('pattern'), 125 | max_items=d.get('maxItems', 0), 126 | min_items=d.get('minItems'), 127 | unique_items=d.get('uniqueItems', False), 128 | ref_type=ref_type 129 | ) 130 | 131 | @property 132 | def uml(self): 133 | # type or array 134 | if self.type == 'array': 135 | # determine lower and upper bound 136 | lower = '' 137 | upper = '' 138 | if self.min_items: 139 | lower = self.min_items if not self.exclusive_minimum else self.min_items + 1 140 | if self.max_items: 141 | upper = self.max_items if not self.exclusive_minimum else self.max_items - 1 142 | 143 | # combine lower and upper bound to bounds string 144 | bounds = '' 145 | if lower or upper: 146 | bounds = '{lower}:{upper}'.format(lower=lower, upper=upper) 147 | 148 | type_str = '{items}[{bounds}]'.format(items=self.items, bounds=bounds) 149 | else: 150 | type_str = self.type 151 | 152 | # format (e.g., date-time) 153 | if self.format: 154 | type_str += ' ({format})'.format(format=self.format) 155 | 156 | # name string (bold if property is required) 157 | if self.required: 158 | name_str = '{name}'.format(name=self.name) 159 | else: 160 | name_str = self.name 161 | 162 | # simple type definition ({field} is a keyword for PlantUML) 163 | result = '{{field}} {type_str} {name_str}'.format(type_str=type_str, name_str=name_str) 164 | 165 | # enum 166 | if self.enum is not None: 167 | result += ' {{{enum_str}}}'.format(enum_str=', '.join([json.dumps(x) for x in self.enum])) 168 | 169 | # min/max 170 | if self.minimum or self.maximum: 171 | minimum = self.minimum if self.minimum is not None else '' 172 | maximum = self.maximum if self.maximum is not None else '' 173 | result += ' {{{minimum}..{maximum}}}'.format(minimum=minimum, maximum=maximum) 174 | 175 | # default value 176 | if self.default is not None: 177 | result += ' = {default}'.format(default=json.dumps(self.default)) 178 | 179 | return result 180 | 181 | 182 | class Definition: 183 | def __init__(self, name, type, properties, relationships): 184 | self.name = name # type: str 185 | self.type = type # type: str 186 | self.properties = properties # type: List[Property] 187 | self.relationships = relationships # type: Set[str] 188 | 189 | @staticmethod 190 | def from_dict(name, d): 191 | properties = [] # type: List[Property] 192 | for property_name, property in d.get('properties', {}).items(): 193 | properties.append(Property.from_dict( 194 | property_name=property_name, 195 | d=property, 196 | required=property_name in d.get('required', []) 197 | )) 198 | 199 | if not 'type' in d: 200 | print('required key "type" not found in dictionary ' + json.dumps(d), file=sys.stderr) 201 | 202 | return Definition(name=name, 203 | type=d['type'], 204 | properties=properties, 205 | relationships={property.ref_type for property in properties if property.ref_type}) 206 | 207 | @property 208 | def uml(self): 209 | result = 'class {name} {{\n'.format(name=self.name) 210 | 211 | # required properties first 212 | for property in sorted(self.properties, key=lambda x: x.required, reverse=True): 213 | result += ' {property_str}\n'.format(property_str=property.uml) 214 | 215 | result += '}\n\n' 216 | 217 | # add relationships 218 | for relationship in sorted(self.relationships): 219 | result += '{name} ..> {relationship}\n'.format(name=self.name, relationship=relationship) 220 | 221 | return result 222 | 223 | 224 | class Parameter: 225 | def __init__(self, name, location, description, required, property): 226 | self.name = name # type: str 227 | self.location = location # type: str 228 | self.description = description # type: Optional[str] 229 | self.required = required # type: bool 230 | self.property = property # type: Property 231 | 232 | @staticmethod 233 | def from_dict(whole, d): 234 | ref = d.get('$ref') 235 | if ref != None: 236 | d = whole['parameters'][resolve_ref(ref)] 237 | return Parameter( 238 | name=d['name'], 239 | location=d['in'], 240 | description=d.get('description'), 241 | required=d.get('required', False), 242 | property=Property.from_dict(d['name'], d, d.get('required', False)) 243 | ) 244 | 245 | 246 | class Response: 247 | def __init__(self, status, description, property): 248 | self.status = status # type: str 249 | self.description = description # type: Optional[str] 250 | self.property = property # type: Property 251 | 252 | @staticmethod 253 | def from_dict(whole, status, d): 254 | return Response( 255 | status=status, 256 | description=d.get('description'), 257 | property=Property.from_dict('', d, False) 258 | ) 259 | 260 | @property 261 | def uml(self): 262 | return '{status}: {type}'.format( 263 | status=self.status, 264 | type=self.property.uml 265 | ) 266 | 267 | 268 | class Operation: 269 | def __init__(self, path, type, summary, description, responses, tags, parameters): 270 | self.path = path # type: str 271 | self.type = type # type: str 272 | self.summary = summary # type: Optional[str] 273 | self.description = description # type: Optional[str] 274 | self.responses = responses # type: List[Response] 275 | self.tags = tags # type: List[str] 276 | self.parameters = parameters # type: List[Parameter] 277 | 278 | def __lt__(self, other): 279 | return self.type < other.type 280 | 281 | @staticmethod 282 | def from_dict(whole, path, type, d, path_parameters): 283 | return Operation( 284 | path=path, 285 | type=type, 286 | summary=d.get('summary'), 287 | description=d.get('description'), 288 | tags=d.get('tags'), 289 | responses=[Response.from_dict(whole, status, response) for status, response in d['responses'].items()], 290 | parameters=path_parameters + [Parameter.from_dict(whole, param) for param in d.get('parameters', [])] 291 | ) 292 | 293 | @property 294 | def uml(self): 295 | # collect used parameter locations 296 | possible_types = ['header', 'path', 'query', 'body', 'formData'] 297 | parameter_types = {x.location for x in self.parameters} 298 | 299 | parameter_strings = [] 300 | for parameter_type in [x for x in possible_types if x in parameter_types]: 301 | # add heading 302 | parameter_strings.append('.. {parameter_type} ..'.format(parameter_type=parameter_type)) 303 | # add parameters 304 | for parameter in [x for x in self.parameters if x.location == parameter_type]: 305 | parameter_strings.append('{parameter_uml}'.format(parameter_uml=parameter.property.uml)) 306 | 307 | # collect references from responses and parameters 308 | references = [x.property.ref_type for x in self.responses if x.property.ref_type] + \ 309 | [x.property.ref_type for x in self.parameters if x.property.ref_type] 310 | 311 | return """class "{name}" {{\n{parameter_str}\n.. responses ..\n{response_str}\n}}\n\n{associations}\n""".format( 312 | name=self.name, 313 | response_str='\n'.join([x.uml for x in self.responses]), 314 | parameter_str='\n'.join(parameter_strings), 315 | associations='\n'.join({'"{name}" ..> {type}'.format(name=self.name, type=type) for type in references}) 316 | ) 317 | 318 | @property 319 | def name(self): 320 | return '{type} {path}'.format( 321 | type=self.type.upper(), 322 | path=self.path 323 | ) 324 | 325 | 326 | class Path: 327 | def __init__(self, path, operations): 328 | self.path = path # type: str 329 | self.operations = operations # type: List[Operation] 330 | 331 | @staticmethod 332 | def from_dict(whole, path_name, d): 333 | parameters = [Parameter.from_dict(whole, param) for param in d.get('parameters', [])] 334 | return Path( 335 | path=path_name, 336 | operations=[Operation.from_dict(whole, path_name, t, op, parameters) for t, op in d.items() if t not in ['parameters', 'summary', 'description']] 337 | ) 338 | 339 | @property 340 | def uml(self): 341 | return 'interface "{path}" {{\n}}\n\n{operation_str}\n{association_str}\n\n'.format( 342 | path=self.path, 343 | operation_str='\n'.join([op.uml for op in self.operations]), 344 | association_str='\n'.join(['"{path}" ..> "{operation_name}"'.format( 345 | path=self.path, operation_name=op.name) for op in sorted(self.operations)]) 346 | ) 347 | 348 | 349 | class Swagger: 350 | def __init__(self, definitions, paths): 351 | self.definitions = definitions # type: List[Definition] 352 | self.paths = paths # type: List[Path] 353 | 354 | @staticmethod 355 | def from_dict(d): 356 | definitions = [Definition.from_dict(name, definition) for name, definition in d.get('definitions',{}).items()] 357 | paths = [Path.from_dict(d, path_name, path) for path_name, path in d['paths'].items()] 358 | return Swagger(definitions=definitions, paths=paths) 359 | 360 | @staticmethod 361 | def from_file(filename): 362 | loader = json.load 363 | if filename.endswith('.yml') or filename.endswith('.yaml'): 364 | import yaml 365 | loader = yaml.load 366 | with open(filename, 'r') as fd: 367 | return Swagger.from_dict(loader(fd)) 368 | 369 | @property 370 | def uml(self): 371 | uml_str = '@startuml\nhide empty members\nset namespaceSeparator none\n\n{paths}\n{definitions}\n@enduml\n' 372 | return uml_str.format( 373 | paths='\n\n'.join([d.uml for d in self.paths]), 374 | definitions='\n\n'.join([d.uml for d in self.definitions]) 375 | ) 376 | 377 | 378 | if __name__ == '__main__': 379 | input_file_name = sys.argv[1] 380 | sw = Swagger.from_file(input_file_name) 381 | print(sw.uml, end='', flush=True) 382 | -------------------------------------------------------------------------------- /petstore_example/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", 5 | "version": "1.0.0", 6 | "title": "Swagger Petstore", 7 | "termsOfService": "http://swagger.io/terms/", 8 | "contact": { 9 | "email": "apiteam@swagger.io" 10 | }, 11 | "license": { 12 | "name": "Apache 2.0", 13 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 14 | } 15 | }, 16 | "host": "petstore.swagger.io", 17 | "basePath": "/v2", 18 | "tags": [ 19 | { 20 | "name": "pet", 21 | "description": "Everything about your Pets", 22 | "externalDocs": { 23 | "description": "Find out more", 24 | "url": "http://swagger.io" 25 | } 26 | }, 27 | { 28 | "name": "store", 29 | "description": "Access to Petstore orders" 30 | }, 31 | { 32 | "name": "user", 33 | "description": "Operations about user", 34 | "externalDocs": { 35 | "description": "Find out more about our store", 36 | "url": "http://swagger.io" 37 | } 38 | } 39 | ], 40 | "schemes": [ 41 | "http" 42 | ], 43 | "paths": { 44 | "/pet": { 45 | "post": { 46 | "tags": [ 47 | "pet" 48 | ], 49 | "summary": "Add a new pet to the store", 50 | "description": "", 51 | "operationId": "addPet", 52 | "consumes": [ 53 | "application/json", 54 | "application/xml" 55 | ], 56 | "produces": [ 57 | "application/xml", 58 | "application/json" 59 | ], 60 | "parameters": [ 61 | { 62 | "in": "body", 63 | "name": "body", 64 | "description": "Pet object that needs to be added to the store", 65 | "required": true, 66 | "schema": { 67 | "$ref": "#/definitions/Pet" 68 | } 69 | } 70 | ], 71 | "responses": { 72 | "405": { 73 | "description": "Invalid input" 74 | } 75 | }, 76 | "security": [ 77 | { 78 | "petstore_auth": [ 79 | "write:pets", 80 | "read:pets" 81 | ] 82 | } 83 | ] 84 | }, 85 | "put": { 86 | "tags": [ 87 | "pet" 88 | ], 89 | "summary": "Update an existing pet", 90 | "description": "", 91 | "operationId": "updatePet", 92 | "consumes": [ 93 | "application/json", 94 | "application/xml" 95 | ], 96 | "produces": [ 97 | "application/xml", 98 | "application/json" 99 | ], 100 | "parameters": [ 101 | { 102 | "in": "body", 103 | "name": "body", 104 | "description": "Pet object that needs to be added to the store", 105 | "required": true, 106 | "schema": { 107 | "$ref": "#/definitions/Pet" 108 | } 109 | } 110 | ], 111 | "responses": { 112 | "400": { 113 | "description": "Invalid ID supplied" 114 | }, 115 | "404": { 116 | "description": "Pet not found" 117 | }, 118 | "405": { 119 | "description": "Validation exception" 120 | } 121 | }, 122 | "security": [ 123 | { 124 | "petstore_auth": [ 125 | "write:pets", 126 | "read:pets" 127 | ] 128 | } 129 | ] 130 | } 131 | }, 132 | "/pet/findByStatus": { 133 | "get": { 134 | "tags": [ 135 | "pet" 136 | ], 137 | "summary": "Finds Pets by status", 138 | "description": "Multiple status values can be provided with comma separated strings", 139 | "operationId": "findPetsByStatus", 140 | "produces": [ 141 | "application/xml", 142 | "application/json" 143 | ], 144 | "parameters": [ 145 | { 146 | "name": "status", 147 | "in": "query", 148 | "description": "Status values that need to be considered for filter", 149 | "required": true, 150 | "type": "array", 151 | "items": { 152 | "type": "string", 153 | "enum": [ 154 | "available", 155 | "pending", 156 | "sold" 157 | ], 158 | "default": "available" 159 | }, 160 | "collectionFormat": "multi" 161 | } 162 | ], 163 | "responses": { 164 | "200": { 165 | "description": "successful operation", 166 | "schema": { 167 | "type": "array", 168 | "items": { 169 | "$ref": "#/definitions/Pet" 170 | } 171 | } 172 | }, 173 | "400": { 174 | "description": "Invalid status value" 175 | } 176 | }, 177 | "security": [ 178 | { 179 | "petstore_auth": [ 180 | "write:pets", 181 | "read:pets" 182 | ] 183 | } 184 | ] 185 | } 186 | }, 187 | "/pet/findByTags": { 188 | "get": { 189 | "tags": [ 190 | "pet" 191 | ], 192 | "summary": "Finds Pets by tags", 193 | "description": "Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", 194 | "operationId": "findPetsByTags", 195 | "produces": [ 196 | "application/xml", 197 | "application/json" 198 | ], 199 | "parameters": [ 200 | { 201 | "name": "tags", 202 | "in": "query", 203 | "description": "Tags to filter by", 204 | "required": true, 205 | "type": "array", 206 | "items": { 207 | "type": "string" 208 | }, 209 | "collectionFormat": "multi" 210 | } 211 | ], 212 | "responses": { 213 | "200": { 214 | "description": "successful operation", 215 | "schema": { 216 | "type": "array", 217 | "items": { 218 | "$ref": "#/definitions/Pet" 219 | } 220 | } 221 | }, 222 | "400": { 223 | "description": "Invalid tag value" 224 | } 225 | }, 226 | "security": [ 227 | { 228 | "petstore_auth": [ 229 | "write:pets", 230 | "read:pets" 231 | ] 232 | } 233 | ], 234 | "deprecated": true 235 | } 236 | }, 237 | "/pet/{petId}": { 238 | "get": { 239 | "tags": [ 240 | "pet" 241 | ], 242 | "summary": "Find pet by ID", 243 | "description": "Returns a single pet", 244 | "operationId": "getPetById", 245 | "produces": [ 246 | "application/xml", 247 | "application/json" 248 | ], 249 | "parameters": [ 250 | { 251 | "name": "petId", 252 | "in": "path", 253 | "description": "ID of pet to return", 254 | "required": true, 255 | "type": "integer", 256 | "format": "int64" 257 | } 258 | ], 259 | "responses": { 260 | "200": { 261 | "description": "successful operation", 262 | "schema": { 263 | "$ref": "#/definitions/Pet" 264 | } 265 | }, 266 | "400": { 267 | "description": "Invalid ID supplied" 268 | }, 269 | "404": { 270 | "description": "Pet not found" 271 | } 272 | }, 273 | "security": [ 274 | { 275 | "api_key": [] 276 | } 277 | ] 278 | }, 279 | "post": { 280 | "tags": [ 281 | "pet" 282 | ], 283 | "summary": "Updates a pet in the store with form data", 284 | "description": "", 285 | "operationId": "updatePetWithForm", 286 | "consumes": [ 287 | "application/x-www-form-urlencoded" 288 | ], 289 | "produces": [ 290 | "application/xml", 291 | "application/json" 292 | ], 293 | "parameters": [ 294 | { 295 | "name": "petId", 296 | "in": "path", 297 | "description": "ID of pet that needs to be updated", 298 | "required": true, 299 | "type": "integer", 300 | "format": "int64" 301 | }, 302 | { 303 | "name": "name", 304 | "in": "formData", 305 | "description": "Updated name of the pet", 306 | "required": false, 307 | "type": "string" 308 | }, 309 | { 310 | "name": "status", 311 | "in": "formData", 312 | "description": "Updated status of the pet", 313 | "required": false, 314 | "type": "string" 315 | } 316 | ], 317 | "responses": { 318 | "405": { 319 | "description": "Invalid input" 320 | } 321 | }, 322 | "security": [ 323 | { 324 | "petstore_auth": [ 325 | "write:pets", 326 | "read:pets" 327 | ] 328 | } 329 | ] 330 | }, 331 | "delete": { 332 | "tags": [ 333 | "pet" 334 | ], 335 | "summary": "Deletes a pet", 336 | "description": "", 337 | "operationId": "deletePet", 338 | "produces": [ 339 | "application/xml", 340 | "application/json" 341 | ], 342 | "parameters": [ 343 | { 344 | "name": "api_key", 345 | "in": "header", 346 | "required": false, 347 | "type": "string" 348 | }, 349 | { 350 | "name": "petId", 351 | "in": "path", 352 | "description": "Pet id to delete", 353 | "required": true, 354 | "type": "integer", 355 | "format": "int64" 356 | } 357 | ], 358 | "responses": { 359 | "400": { 360 | "description": "Invalid ID supplied" 361 | }, 362 | "404": { 363 | "description": "Pet not found" 364 | } 365 | }, 366 | "security": [ 367 | { 368 | "petstore_auth": [ 369 | "write:pets", 370 | "read:pets" 371 | ] 372 | } 373 | ] 374 | } 375 | }, 376 | "/pet/{petId}/uploadImage": { 377 | "post": { 378 | "tags": [ 379 | "pet" 380 | ], 381 | "summary": "uploads an image", 382 | "description": "", 383 | "operationId": "uploadFile", 384 | "consumes": [ 385 | "multipart/form-data" 386 | ], 387 | "produces": [ 388 | "application/json" 389 | ], 390 | "parameters": [ 391 | { 392 | "name": "petId", 393 | "in": "path", 394 | "description": "ID of pet to update", 395 | "required": true, 396 | "type": "integer", 397 | "format": "int64" 398 | }, 399 | { 400 | "name": "additionalMetadata", 401 | "in": "formData", 402 | "description": "Additional data to pass to server", 403 | "required": false, 404 | "type": "string" 405 | }, 406 | { 407 | "name": "file", 408 | "in": "formData", 409 | "description": "file to upload", 410 | "required": false, 411 | "type": "file" 412 | } 413 | ], 414 | "responses": { 415 | "200": { 416 | "description": "successful operation", 417 | "schema": { 418 | "$ref": "#/definitions/ApiResponse" 419 | } 420 | } 421 | }, 422 | "security": [ 423 | { 424 | "petstore_auth": [ 425 | "write:pets", 426 | "read:pets" 427 | ] 428 | } 429 | ] 430 | } 431 | }, 432 | "/store/inventory": { 433 | "get": { 434 | "tags": [ 435 | "store" 436 | ], 437 | "summary": "Returns pet inventories by status", 438 | "description": "Returns a map of status codes to quantities", 439 | "operationId": "getInventory", 440 | "produces": [ 441 | "application/json" 442 | ], 443 | "parameters": [], 444 | "responses": { 445 | "200": { 446 | "description": "successful operation", 447 | "schema": { 448 | "type": "object", 449 | "additionalProperties": { 450 | "type": "integer", 451 | "format": "int32" 452 | } 453 | } 454 | } 455 | }, 456 | "security": [ 457 | { 458 | "api_key": [] 459 | } 460 | ] 461 | } 462 | }, 463 | "/store/order": { 464 | "post": { 465 | "tags": [ 466 | "store" 467 | ], 468 | "summary": "Place an order for a pet", 469 | "description": "", 470 | "operationId": "placeOrder", 471 | "produces": [ 472 | "application/xml", 473 | "application/json" 474 | ], 475 | "parameters": [ 476 | { 477 | "in": "body", 478 | "name": "body", 479 | "description": "order placed for purchasing the pet", 480 | "required": true, 481 | "schema": { 482 | "$ref": "#/definitions/Order" 483 | } 484 | } 485 | ], 486 | "responses": { 487 | "200": { 488 | "description": "successful operation", 489 | "schema": { 490 | "$ref": "#/definitions/Order" 491 | } 492 | }, 493 | "400": { 494 | "description": "Invalid Order" 495 | } 496 | } 497 | } 498 | }, 499 | "/store/order/{orderId}": { 500 | "get": { 501 | "tags": [ 502 | "store" 503 | ], 504 | "summary": "Find purchase order by ID", 505 | "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", 506 | "operationId": "getOrderById", 507 | "produces": [ 508 | "application/xml", 509 | "application/json" 510 | ], 511 | "parameters": [ 512 | { 513 | "name": "orderId", 514 | "in": "path", 515 | "description": "ID of pet that needs to be fetched", 516 | "required": true, 517 | "type": "integer", 518 | "maximum": 10.0, 519 | "minimum": 1.0, 520 | "format": "int64" 521 | } 522 | ], 523 | "responses": { 524 | "200": { 525 | "description": "successful operation", 526 | "schema": { 527 | "$ref": "#/definitions/Order" 528 | } 529 | }, 530 | "400": { 531 | "description": "Invalid ID supplied" 532 | }, 533 | "404": { 534 | "description": "Order not found" 535 | } 536 | } 537 | }, 538 | "delete": { 539 | "tags": [ 540 | "store" 541 | ], 542 | "summary": "Delete purchase order by ID", 543 | "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", 544 | "operationId": "deleteOrder", 545 | "produces": [ 546 | "application/xml", 547 | "application/json" 548 | ], 549 | "parameters": [ 550 | { 551 | "name": "orderId", 552 | "in": "path", 553 | "description": "ID of the order that needs to be deleted", 554 | "required": true, 555 | "type": "integer", 556 | "minimum": 1.0, 557 | "format": "int64" 558 | } 559 | ], 560 | "responses": { 561 | "400": { 562 | "description": "Invalid ID supplied" 563 | }, 564 | "404": { 565 | "description": "Order not found" 566 | } 567 | } 568 | } 569 | }, 570 | "/user": { 571 | "post": { 572 | "tags": [ 573 | "user" 574 | ], 575 | "summary": "Create user", 576 | "description": "This can only be done by the logged in user.", 577 | "operationId": "createUser", 578 | "produces": [ 579 | "application/xml", 580 | "application/json" 581 | ], 582 | "parameters": [ 583 | { 584 | "in": "body", 585 | "name": "body", 586 | "description": "Created user object", 587 | "required": true, 588 | "schema": { 589 | "$ref": "#/definitions/User" 590 | } 591 | } 592 | ], 593 | "responses": { 594 | "default": { 595 | "description": "successful operation" 596 | } 597 | } 598 | } 599 | }, 600 | "/user/createWithArray": { 601 | "post": { 602 | "tags": [ 603 | "user" 604 | ], 605 | "summary": "Creates list of users with given input array", 606 | "description": "", 607 | "operationId": "createUsersWithArrayInput", 608 | "produces": [ 609 | "application/xml", 610 | "application/json" 611 | ], 612 | "parameters": [ 613 | { 614 | "in": "body", 615 | "name": "body", 616 | "description": "List of user object", 617 | "required": true, 618 | "schema": { 619 | "type": "array", 620 | "items": { 621 | "$ref": "#/definitions/User" 622 | } 623 | } 624 | } 625 | ], 626 | "responses": { 627 | "default": { 628 | "description": "successful operation" 629 | } 630 | } 631 | } 632 | }, 633 | "/user/createWithList": { 634 | "post": { 635 | "tags": [ 636 | "user" 637 | ], 638 | "summary": "Creates list of users with given input array", 639 | "description": "", 640 | "operationId": "createUsersWithListInput", 641 | "produces": [ 642 | "application/xml", 643 | "application/json" 644 | ], 645 | "parameters": [ 646 | { 647 | "in": "body", 648 | "name": "body", 649 | "description": "List of user object", 650 | "required": true, 651 | "schema": { 652 | "type": "array", 653 | "items": { 654 | "$ref": "#/definitions/User" 655 | } 656 | } 657 | } 658 | ], 659 | "responses": { 660 | "default": { 661 | "description": "successful operation" 662 | } 663 | } 664 | } 665 | }, 666 | "/user/login": { 667 | "get": { 668 | "tags": [ 669 | "user" 670 | ], 671 | "summary": "Logs user into the system", 672 | "description": "", 673 | "operationId": "loginUser", 674 | "produces": [ 675 | "application/xml", 676 | "application/json" 677 | ], 678 | "parameters": [ 679 | { 680 | "name": "username", 681 | "in": "query", 682 | "description": "The user name for login", 683 | "required": true, 684 | "type": "string" 685 | }, 686 | { 687 | "name": "password", 688 | "in": "query", 689 | "description": "The password for login in clear text", 690 | "required": true, 691 | "type": "string" 692 | } 693 | ], 694 | "responses": { 695 | "200": { 696 | "description": "successful operation", 697 | "schema": { 698 | "type": "string" 699 | }, 700 | "headers": { 701 | "X-Rate-Limit": { 702 | "type": "integer", 703 | "format": "int32", 704 | "description": "calls per hour allowed by the user" 705 | }, 706 | "X-Expires-After": { 707 | "type": "string", 708 | "format": "date-time", 709 | "description": "date in UTC when token expires" 710 | } 711 | } 712 | }, 713 | "400": { 714 | "description": "Invalid username/password supplied" 715 | } 716 | } 717 | } 718 | }, 719 | "/user/logout": { 720 | "get": { 721 | "tags": [ 722 | "user" 723 | ], 724 | "summary": "Logs out current logged in user session", 725 | "description": "", 726 | "operationId": "logoutUser", 727 | "produces": [ 728 | "application/xml", 729 | "application/json" 730 | ], 731 | "parameters": [], 732 | "responses": { 733 | "default": { 734 | "description": "successful operation" 735 | } 736 | } 737 | } 738 | }, 739 | "/user/{username}": { 740 | "get": { 741 | "tags": [ 742 | "user" 743 | ], 744 | "summary": "Get user by user name", 745 | "description": "", 746 | "operationId": "getUserByName", 747 | "produces": [ 748 | "application/xml", 749 | "application/json" 750 | ], 751 | "parameters": [ 752 | { 753 | "name": "username", 754 | "in": "path", 755 | "description": "The name that needs to be fetched. Use user1 for testing. ", 756 | "required": true, 757 | "type": "string" 758 | } 759 | ], 760 | "responses": { 761 | "200": { 762 | "description": "successful operation", 763 | "schema": { 764 | "$ref": "#/definitions/User" 765 | } 766 | }, 767 | "400": { 768 | "description": "Invalid username supplied" 769 | }, 770 | "404": { 771 | "description": "User not found" 772 | } 773 | } 774 | }, 775 | "put": { 776 | "tags": [ 777 | "user" 778 | ], 779 | "summary": "Updated user", 780 | "description": "This can only be done by the logged in user.", 781 | "operationId": "updateUser", 782 | "produces": [ 783 | "application/xml", 784 | "application/json" 785 | ], 786 | "parameters": [ 787 | { 788 | "name": "username", 789 | "in": "path", 790 | "description": "name that need to be updated", 791 | "required": true, 792 | "type": "string" 793 | }, 794 | { 795 | "in": "body", 796 | "name": "body", 797 | "description": "Updated user object", 798 | "required": true, 799 | "schema": { 800 | "$ref": "#/definitions/User" 801 | } 802 | } 803 | ], 804 | "responses": { 805 | "400": { 806 | "description": "Invalid user supplied" 807 | }, 808 | "404": { 809 | "description": "User not found" 810 | } 811 | } 812 | }, 813 | "delete": { 814 | "tags": [ 815 | "user" 816 | ], 817 | "summary": "Delete user", 818 | "description": "This can only be done by the logged in user.", 819 | "operationId": "deleteUser", 820 | "produces": [ 821 | "application/xml", 822 | "application/json" 823 | ], 824 | "parameters": [ 825 | { 826 | "name": "username", 827 | "in": "path", 828 | "description": "The name that needs to be deleted", 829 | "required": true, 830 | "type": "string" 831 | } 832 | ], 833 | "responses": { 834 | "400": { 835 | "description": "Invalid username supplied" 836 | }, 837 | "404": { 838 | "description": "User not found" 839 | } 840 | } 841 | } 842 | } 843 | }, 844 | "securityDefinitions": { 845 | "petstore_auth": { 846 | "type": "oauth2", 847 | "authorizationUrl": "http://petstore.swagger.io/oauth/dialog", 848 | "flow": "implicit", 849 | "scopes": { 850 | "write:pets": "modify pets in your account", 851 | "read:pets": "read your pets" 852 | } 853 | }, 854 | "api_key": { 855 | "type": "apiKey", 856 | "name": "api_key", 857 | "in": "header" 858 | } 859 | }, 860 | "definitions": { 861 | "Order": { 862 | "type": "object", 863 | "properties": { 864 | "id": { 865 | "type": "integer", 866 | "format": "int64" 867 | }, 868 | "petId": { 869 | "type": "integer", 870 | "format": "int64" 871 | }, 872 | "quantity": { 873 | "type": "integer", 874 | "format": "int32" 875 | }, 876 | "shipDate": { 877 | "type": "string", 878 | "format": "date-time" 879 | }, 880 | "status": { 881 | "type": "string", 882 | "description": "Order Status", 883 | "enum": [ 884 | "placed", 885 | "approved", 886 | "delivered" 887 | ] 888 | }, 889 | "complete": { 890 | "type": "boolean", 891 | "default": false 892 | } 893 | }, 894 | "xml": { 895 | "name": "Order" 896 | } 897 | }, 898 | "Category": { 899 | "type": "object", 900 | "properties": { 901 | "id": { 902 | "type": "integer", 903 | "format": "int64" 904 | }, 905 | "name": { 906 | "type": "string" 907 | } 908 | }, 909 | "xml": { 910 | "name": "Category" 911 | } 912 | }, 913 | "User": { 914 | "type": "object", 915 | "properties": { 916 | "id": { 917 | "type": "integer", 918 | "format": "int64" 919 | }, 920 | "username": { 921 | "type": "string" 922 | }, 923 | "firstName": { 924 | "type": "string" 925 | }, 926 | "lastName": { 927 | "type": "string" 928 | }, 929 | "email": { 930 | "type": "string" 931 | }, 932 | "password": { 933 | "type": "string" 934 | }, 935 | "phone": { 936 | "type": "string" 937 | }, 938 | "userStatus": { 939 | "type": "integer", 940 | "format": "int32", 941 | "description": "User Status" 942 | } 943 | }, 944 | "xml": { 945 | "name": "User" 946 | } 947 | }, 948 | "Tag": { 949 | "type": "object", 950 | "properties": { 951 | "id": { 952 | "type": "integer", 953 | "format": "int64" 954 | }, 955 | "name": { 956 | "type": "string" 957 | } 958 | }, 959 | "xml": { 960 | "name": "Tag" 961 | } 962 | }, 963 | "Pet": { 964 | "type": "object", 965 | "required": [ 966 | "name", 967 | "photoUrls" 968 | ], 969 | "properties": { 970 | "id": { 971 | "type": "integer", 972 | "format": "int64" 973 | }, 974 | "category": { 975 | "$ref": "#/definitions/Category" 976 | }, 977 | "name": { 978 | "type": "string", 979 | "example": "doggie" 980 | }, 981 | "photoUrls": { 982 | "type": "array", 983 | "xml": { 984 | "name": "photoUrl", 985 | "wrapped": true 986 | }, 987 | "items": { 988 | "type": "string" 989 | } 990 | }, 991 | "tags": { 992 | "type": "array", 993 | "xml": { 994 | "name": "tag", 995 | "wrapped": true 996 | }, 997 | "items": { 998 | "$ref": "#/definitions/Tag" 999 | } 1000 | }, 1001 | "status": { 1002 | "type": "string", 1003 | "description": "pet status in the store", 1004 | "enum": [ 1005 | "available", 1006 | "pending", 1007 | "sold" 1008 | ] 1009 | } 1010 | }, 1011 | "xml": { 1012 | "name": "Pet" 1013 | } 1014 | }, 1015 | "ApiResponse": { 1016 | "type": "object", 1017 | "properties": { 1018 | "code": { 1019 | "type": "integer", 1020 | "format": "int32" 1021 | }, 1022 | "type": { 1023 | "type": "string" 1024 | }, 1025 | "message": { 1026 | "type": "string" 1027 | } 1028 | } 1029 | } 1030 | }, 1031 | "externalDocs": { 1032 | "description": "Find out more about Swagger", 1033 | "url": "http://swagger.io" 1034 | } 1035 | } -------------------------------------------------------------------------------- /petstore_example/swagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nlohmann/swagger_to_uml/079ce3337058d66bf594ecfacd8fc21c2750fe9f/petstore_example/swagger.png -------------------------------------------------------------------------------- /petstore_example/swagger.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | hide empty members 3 | set namespaceSeparator none 4 | 5 | class "/pet" { 6 | } 7 | 8 | class "POST /pet" { 9 | .. body .. 10 | {field} Pet body 11 | .. responses .. 12 | 405: {field} not specified 13 | } 14 | 15 | "POST /pet" ..> Pet 16 | 17 | class "PUT /pet" { 18 | .. body .. 19 | {field} Pet body 20 | .. responses .. 21 | 400: {field} not specified 22 | 404: {field} not specified 23 | 405: {field} not specified 24 | } 25 | 26 | "PUT /pet" ..> Pet 27 | 28 | "/pet" ..> "POST /pet" 29 | "/pet" ..> "PUT /pet" 30 | 31 | 32 | 33 | class "/pet/findByStatus" { 34 | } 35 | 36 | class "GET /pet/findByStatus" { 37 | .. query .. 38 | {field} string[] status 39 | .. responses .. 40 | 200: {field} Pet[] 41 | 400: {field} not specified 42 | } 43 | 44 | "GET /pet/findByStatus" ..> Pet 45 | 46 | "/pet/findByStatus" ..> "GET /pet/findByStatus" 47 | 48 | 49 | 50 | class "/pet/findByTags" { 51 | } 52 | 53 | class "GET /pet/findByTags" { 54 | .. query .. 55 | {field} string[] tags 56 | .. responses .. 57 | 200: {field} Pet[] 58 | 400: {field} not specified 59 | } 60 | 61 | "GET /pet/findByTags" ..> Pet 62 | 63 | "/pet/findByTags" ..> "GET /pet/findByTags" 64 | 65 | 66 | 67 | class "/pet/{petId}" { 68 | } 69 | 70 | class "GET /pet/{petId}" { 71 | .. path .. 72 | {field} integer (int64) petId 73 | .. responses .. 74 | 200: {field} Pet 75 | 400: {field} not specified 76 | 404: {field} not specified 77 | } 78 | 79 | "GET /pet/{petId}" ..> Pet 80 | 81 | class "POST /pet/{petId}" { 82 | .. path .. 83 | {field} integer (int64) petId 84 | .. formData .. 85 | {field} string name 86 | {field} string status 87 | .. responses .. 88 | 405: {field} not specified 89 | } 90 | 91 | 92 | 93 | class "DELETE /pet/{petId}" { 94 | .. header .. 95 | {field} string api_key 96 | .. path .. 97 | {field} integer (int64) petId 98 | .. responses .. 99 | 400: {field} not specified 100 | 404: {field} not specified 101 | } 102 | 103 | 104 | 105 | "/pet/{petId}" ..> "DELETE /pet/{petId}" 106 | "/pet/{petId}" ..> "GET /pet/{petId}" 107 | "/pet/{petId}" ..> "POST /pet/{petId}" 108 | 109 | 110 | 111 | class "/pet/{petId}/uploadImage" { 112 | } 113 | 114 | class "POST /pet/{petId}/uploadImage" { 115 | .. path .. 116 | {field} integer (int64) petId 117 | .. formData .. 118 | {field} string additionalMetadata 119 | {field} file file 120 | .. responses .. 121 | 200: {field} ApiResponse 122 | } 123 | 124 | "POST /pet/{petId}/uploadImage" ..> ApiResponse 125 | 126 | "/pet/{petId}/uploadImage" ..> "POST /pet/{petId}/uploadImage" 127 | 128 | 129 | 130 | class "/store/inventory" { 131 | } 132 | 133 | class "GET /store/inventory" { 134 | 135 | .. responses .. 136 | 200: {field} object 137 | } 138 | 139 | 140 | 141 | "/store/inventory" ..> "GET /store/inventory" 142 | 143 | 144 | 145 | class "/store/order" { 146 | } 147 | 148 | class "POST /store/order" { 149 | .. body .. 150 | {field} Order body 151 | .. responses .. 152 | 200: {field} Order 153 | 400: {field} not specified 154 | } 155 | 156 | "POST /store/order" ..> Order 157 | 158 | "/store/order" ..> "POST /store/order" 159 | 160 | 161 | 162 | class "/store/order/{orderId}" { 163 | } 164 | 165 | class "GET /store/order/{orderId}" { 166 | .. path .. 167 | {field} integer (int64) orderId {1.0..10.0} 168 | .. responses .. 169 | 200: {field} Order 170 | 400: {field} not specified 171 | 404: {field} not specified 172 | } 173 | 174 | "GET /store/order/{orderId}" ..> Order 175 | 176 | class "DELETE /store/order/{orderId}" { 177 | .. path .. 178 | {field} integer (int64) orderId {1.0..} 179 | .. responses .. 180 | 400: {field} not specified 181 | 404: {field} not specified 182 | } 183 | 184 | 185 | 186 | "/store/order/{orderId}" ..> "DELETE /store/order/{orderId}" 187 | "/store/order/{orderId}" ..> "GET /store/order/{orderId}" 188 | 189 | 190 | 191 | class "/user" { 192 | } 193 | 194 | class "POST /user" { 195 | .. body .. 196 | {field} User body 197 | .. responses .. 198 | default: {field} not specified 199 | } 200 | 201 | "POST /user" ..> User 202 | 203 | "/user" ..> "POST /user" 204 | 205 | 206 | 207 | class "/user/createWithArray" { 208 | } 209 | 210 | class "POST /user/createWithArray" { 211 | .. body .. 212 | {field} User[] body 213 | .. responses .. 214 | default: {field} not specified 215 | } 216 | 217 | "POST /user/createWithArray" ..> User 218 | 219 | "/user/createWithArray" ..> "POST /user/createWithArray" 220 | 221 | 222 | 223 | class "/user/createWithList" { 224 | } 225 | 226 | class "POST /user/createWithList" { 227 | .. body .. 228 | {field} User[] body 229 | .. responses .. 230 | default: {field} not specified 231 | } 232 | 233 | "POST /user/createWithList" ..> User 234 | 235 | "/user/createWithList" ..> "POST /user/createWithList" 236 | 237 | 238 | 239 | class "/user/login" { 240 | } 241 | 242 | class "GET /user/login" { 243 | .. query .. 244 | {field} string username 245 | {field} string password 246 | .. responses .. 247 | 200: {field} string 248 | 400: {field} not specified 249 | } 250 | 251 | 252 | 253 | "/user/login" ..> "GET /user/login" 254 | 255 | 256 | 257 | class "/user/logout" { 258 | } 259 | 260 | class "GET /user/logout" { 261 | 262 | .. responses .. 263 | default: {field} not specified 264 | } 265 | 266 | 267 | 268 | "/user/logout" ..> "GET /user/logout" 269 | 270 | 271 | 272 | class "/user/{username}" { 273 | } 274 | 275 | class "GET /user/{username}" { 276 | .. path .. 277 | {field} string username 278 | .. responses .. 279 | 200: {field} User 280 | 400: {field} not specified 281 | 404: {field} not specified 282 | } 283 | 284 | "GET /user/{username}" ..> User 285 | 286 | class "PUT /user/{username}" { 287 | .. path .. 288 | {field} string username 289 | .. body .. 290 | {field} User body 291 | .. responses .. 292 | 400: {field} not specified 293 | 404: {field} not specified 294 | } 295 | 296 | "PUT /user/{username}" ..> User 297 | 298 | class "DELETE /user/{username}" { 299 | .. path .. 300 | {field} string username 301 | .. responses .. 302 | 400: {field} not specified 303 | 404: {field} not specified 304 | } 305 | 306 | 307 | 308 | "/user/{username}" ..> "DELETE /user/{username}" 309 | "/user/{username}" ..> "GET /user/{username}" 310 | "/user/{username}" ..> "PUT /user/{username}" 311 | 312 | 313 | class Order { 314 | {field} integer (int64) id 315 | {field} integer (int64) petId 316 | {field} integer (int32) quantity 317 | {field} string (date-time) shipDate 318 | {field} string status {"placed", "approved", "delivered"} 319 | {field} boolean complete = false 320 | } 321 | 322 | 323 | 324 | class Category { 325 | {field} integer (int64) id 326 | {field} string name 327 | } 328 | 329 | 330 | 331 | class User { 332 | {field} integer (int64) id 333 | {field} string username 334 | {field} string firstName 335 | {field} string lastName 336 | {field} string email 337 | {field} string password 338 | {field} string phone 339 | {field} integer (int32) userStatus 340 | } 341 | 342 | 343 | 344 | class Tag { 345 | {field} integer (int64) id 346 | {field} string name 347 | } 348 | 349 | 350 | 351 | class Pet { 352 | {field} string name 353 | {field} string[] photoUrls 354 | {field} integer (int64) id 355 | {field} Category category 356 | {field} Tag[] tags 357 | {field} string status {"available", "pending", "sold"} 358 | } 359 | 360 | Pet ..> Category 361 | Pet ..> Tag 362 | 363 | 364 | class ApiResponse { 365 | {field} integer (int32) code 366 | {field} string type 367 | {field} string message 368 | } 369 | 370 | 371 | @enduml 372 | -------------------------------------------------------------------------------- /petstore_example/swagger.svg: -------------------------------------------------------------------------------- 1 | /petPOST /petPetbodybody405:not specifiedresponsesPetstringnamestring[]photoUrlsinteger (int64) idCategory categoryTag[] tagsstring status {"available", "pending", "sold"}PUT /petPetbodybody400:not specified404:not specified405:not specifiedresponses/pet/findByStatusGET /pet/findByStatusstring[]statusquery200: Pet[]400:not specifiedresponses/pet/findByTagsGET /pet/findByTagsstring[]tagsquery200: Pet[]400:not specifiedresponses/pet/{petId}GET /pet/{petId}integer (int64)petIdpath200: Pet400:not specified404:not specifiedresponsesPOST /pet/{petId}integer (int64)petIdpathstring namestring statusformData405:not specifiedresponsesDELETE /pet/{petId}integer (int64)petIdpathstring api_keyheader400:not specified404:not specifiedresponses/pet/{petId}/uploadImagePOST /pet/{petId}/uploadImageinteger (int64)petIdpathstring additionalMetadatafile fileformData200: ApiResponseresponsesApiResponseinteger (int32) codestring typestring message/store/inventoryGET /store/inventory200: objectresponses/store/orderPOST /store/orderOrderbodybody200: Order400:not specifiedresponsesOrderinteger (int64) idinteger (int64) petIdinteger (int32) quantitystring (date-time) shipDatestring status {"placed", "approved", "delivered"}boolean complete = false/store/order/{orderId}GET /store/order/{orderId}integer (int64)orderId{1.0..10.0}path200: Order400:not specified404:not specifiedresponsesDELETE /store/order/{orderId}integer (int64)orderId{1.0..}path400:not specified404:not specifiedresponses/userPOST /userUserbodybodydefault:not specifiedresponsesUserinteger (int64) idstring usernamestring firstNamestring lastNamestring emailstring passwordstring phoneinteger (int32) userStatus/user/createWithArrayPOST /user/createWithArrayUser[]bodybodydefault:not specifiedresponses/user/createWithListPOST /user/createWithListUser[]bodybodydefault:not specifiedresponses/user/loginGET /user/loginstringusernamestringpasswordquery200: string400:not specifiedresponses/user/logoutGET /user/logoutdefault:not specifiedresponses/user/{username}GET /user/{username}stringusernamepath200: User400:not specified404:not specifiedresponsesPUT /user/{username}stringusernamepathUserbodybody400:not specified404:not specifiedresponsesDELETE /user/{username}stringusernamepath400:not specified404:not specifiedresponsesCategoryinteger (int64) idstring nameTaginteger (int64) idstring name -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML==5.1 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | requires = [ 4 | 'PyYAML==5.1' 5 | ] 6 | 7 | setup( 8 | name='swagger_to_uml', 9 | version='0.1', 10 | description='swagger_to_uml', 11 | classifiers=[ 12 | "Programming Language :: Python" 13 | ], 14 | author='Niels Lohmann', 15 | author_email='mail@nlohmann.me', 16 | license='MIT', 17 | url='http://nlohmann.me', 18 | keywords='swagger uml plantuml', 19 | packages=find_packages(), 20 | include_package_data=True, 21 | zip_safe=False, 22 | install_requires=requires, 23 | tests_require=requires, 24 | scripts=['bin/swagger_to_uml'] 25 | ) 26 | --------------------------------------------------------------------------------