diff --git a/examples/basic-auth.md b/examples/v2/basic-auth.md
similarity index 100%
rename from examples/basic-auth.md
rename to examples/v2/basic-auth.md
diff --git a/examples/basic-auth.yaml b/examples/v2/basic-auth.yaml
similarity index 100%
rename from examples/basic-auth.yaml
rename to examples/v2/basic-auth.yaml
diff --git a/examples/default.md b/examples/v2/default.md
similarity index 100%
rename from examples/default.md
rename to examples/v2/default.md
diff --git a/examples/default.yaml b/examples/v2/default.yaml
similarity index 100%
rename from examples/default.yaml
rename to examples/v2/default.yaml
diff --git a/examples/echo.md b/examples/v2/echo.md
similarity index 100%
rename from examples/echo.md
rename to examples/v2/echo.md
diff --git a/examples/echo.yaml b/examples/v2/echo.yaml
similarity index 100%
rename from examples/echo.yaml
rename to examples/v2/echo.yaml
diff --git a/examples/heroku-pets.md b/examples/v2/heroku-pets.md
similarity index 100%
rename from examples/heroku-pets.md
rename to examples/v2/heroku-pets.md
diff --git a/examples/heroku-pets.yaml b/examples/v2/heroku-pets.yaml
similarity index 100%
rename from examples/heroku-pets.yaml
rename to examples/v2/heroku-pets.yaml
diff --git a/examples/instagram.md b/examples/v2/instagram.md
similarity index 100%
rename from examples/instagram.md
rename to examples/v2/instagram.md
diff --git a/examples/instagram.yaml b/examples/v2/instagram.yaml
similarity index 100%
rename from examples/instagram.yaml
rename to examples/v2/instagram.yaml
diff --git a/examples/minimal.md b/examples/v2/minimal.md
similarity index 100%
rename from examples/minimal.md
rename to examples/v2/minimal.md
diff --git a/examples/minimal.yaml b/examples/v2/minimal.yaml
similarity index 100%
rename from examples/minimal.yaml
rename to examples/v2/minimal.yaml
diff --git a/examples/petstore_full.md b/examples/v2/petstore_full.md
similarity index 100%
rename from examples/petstore_full.md
rename to examples/v2/petstore_full.md
diff --git a/examples/petstore_full.yaml b/examples/v2/petstore_full.yaml
similarity index 100%
rename from examples/petstore_full.yaml
rename to examples/v2/petstore_full.yaml
diff --git a/examples/petstore_simple.md b/examples/v2/petstore_simple.md
similarity index 100%
rename from examples/petstore_simple.md
rename to examples/v2/petstore_simple.md
diff --git a/examples/petstore_simple.yaml b/examples/v2/petstore_simple.yaml
similarity index 100%
rename from examples/petstore_simple.yaml
rename to examples/v2/petstore_simple.yaml
diff --git a/examples/security.md b/examples/v2/security.md
similarity index 100%
rename from examples/security.md
rename to examples/v2/security.md
diff --git a/examples/security.yaml b/examples/v2/security.yaml
similarity index 100%
rename from examples/security.yaml
rename to examples/v2/security.yaml
diff --git a/examples/v3/petstore.md b/examples/v3/petstore.md
new file mode 100644
index 0000000..fcdd62f
--- /dev/null
+++ b/examples/v3/petstore.md
@@ -0,0 +1,385 @@
+# Swagger Petstore - OpenAPI 3.0
+This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about
+Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!
+You can now help us improve the API whether it's by making changes to the definition itself or to the code.
+That way, with time, we can improve the API in general, and expose some of the new features in OAS3.
+
+Some useful links:
+
+- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)
+- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)
+
+## Version: 1.0.11
+
+### Terms of service
+http://swagger.io/terms/
+
+**Contact information:**
+apiteam@swagger.io
+
+**License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
+
+[Find out more about Swagger](http://swagger.io)
+
+---
+## pet
+Everything about your Pets
+[Find out more](http://swagger.io)
+
+### [PUT] /pet
+**Update an existing pet**
+
+Update an existing pet by Id
+
+#### Responses
+
+| Code | Description | Schema |
+| ---- | ----------- | ------ |
+| 200 | Successful operation | **application/json**: [Pet](#pet)
**application/xml**: [Pet](#pet)
|
+| 400 | Invalid ID supplied | |
+| 404 | Pet not found | |
+| 422 | Validation exception | |
+
+##### Security
+
+| Security Schema | Scopes | |
+| --------------- | ------ | --- |
+| petstore_auth | write:pets | read:pets |
+
+### [POST] /pet
+**Add a new pet to the store**
+
+#### Responses
+
+| Code | Description | Schema |
+| ---- | ----------- | ------ |
+| 200 | Successful operation | **application/json**: [Pet](#pet)
**application/xml**: [Pet](#pet)
|
+| 400 | Invalid input | |
+| 422 | Validation exception | |
+
+##### Security
+
+| Security Schema | Scopes | |
+| --------------- | ------ | --- |
+| petstore_auth | write:pets | read:pets |
+
+### [GET] /pet/findByStatus
+**Finds Pets by status**
+
+Multiple status values can be provided with comma separated strings
+
+#### Responses
+
+| Code | Description | Schema | Links |
+| ---- | ----------- | ------ | ----- |
+| 200 | successful operation | **application/json**: [ [Pet](#pet) ]
**application/xml**: [ [Pet](#pet) ]
| **address**
Address link description
Parameters {
"userId": "$request.path.id",
"entityId": "$request.entity.id"
}
|
+| 400 | Invalid status value | | |
+
+##### Security
+
+| Security Schema | Scopes | |
+| --------------- | ------ | --- |
+| petstore_auth | write:pets | read:pets |
+
+### ~~[GET] /pet/findByTags~~
+
+***DEPRECATED***
+
+**Finds Pets by tags**
+
+Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
+
+#### Responses
+
+| Code | Description | Schema |
+| ---- | ----------- | ------ |
+| 200 | successful operation | **application/json**: [ [Pet](#pet) ]
**application/xml**: [ [Pet](#pet) ]
|
+| 400 | Invalid tag value | |
+
+##### Security
+
+| Security Schema | Scopes | |
+| --------------- | ------ | --- |
+| petstore_auth | write:pets | read:pets |
+
+### [GET] /pet/{petId}
+**Find pet by ID**
+
+Returns a single pet
+
+#### Responses
+
+| Code | Description | Schema |
+| ---- | ----------- | ------ |
+| 200 | successful operation | **application/json**: [Pet](#pet)
**application/xml**: [Pet](#pet)
|
+| 400 | Invalid ID supplied | |
+| 404 | Pet not found | |
+
+##### Security
+
+| Security Schema | Scopes | |
+| --------------- | ------ | --- |
+| api_key | | |
+| petstore_auth | write:pets | read:pets |
+
+### [POST] /pet/{petId}
+**Updates a pet in the store with form data**
+
+#### Responses
+
+| Code | Description |
+| ---- | ----------- |
+| 400 | Invalid input |
+
+##### Security
+
+| Security Schema | Scopes | |
+| --------------- | ------ | --- |
+| petstore_auth | write:pets | read:pets |
+
+### [DELETE] /pet/{petId}
+**Deletes a pet**
+
+delete a pet
+
+#### Responses
+
+| Code | Description |
+| ---- | ----------- |
+| 400 | Invalid pet value |
+
+##### Security
+
+| Security Schema | Scopes | |
+| --------------- | ------ | --- |
+| petstore_auth | write:pets | read:pets |
+
+### [POST] /pet/{petId}/uploadImage
+**uploads an image**
+
+#### Responses
+
+| Code | Description | Schema |
+| ---- | ----------- | ------ |
+| 200 | successful operation | **application/json**: [ApiResponse](#apiresponse)
|
+
+##### Security
+
+| Security Schema | Scopes | |
+| --------------- | ------ | --- |
+| petstore_auth | write:pets | read:pets |
+
+---
+## store
+Access to Petstore orders
+[Find out more about our store](http://swagger.io)
+
+### [GET] /store/inventory
+**Returns pet inventories by status**
+
+Returns a map of status codes to quantities
+
+#### Responses
+
+| Code | Description | Schema |
+| ---- | ----------- | ------ |
+| 200 | successful operation | **application/json**: object
|
+
+##### Security
+
+| Security Schema | Scopes |
+| --------------- | ------ |
+| api_key | |
+
+### [POST] /store/order
+**Place an order for a pet**
+
+Place a new order in the store
+
+#### Responses
+
+| Code | Description | Schema |
+| ---- | ----------- | ------ |
+| 200 | successful operation | **application/json**: [Order](#order)
|
+| 400 | Invalid input | |
+| 422 | Validation exception | |
+
+### [GET] /store/order/{orderId}
+**Find purchase order by ID**
+
+For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
+
+#### Responses
+
+| Code | Description | Schema |
+| ---- | ----------- | ------ |
+| 200 | successful operation | **application/json**: [Order](#order)
**application/xml**: [Order](#order)
|
+| 400 | Invalid ID supplied | |
+| 404 | Order not found | |
+
+### [DELETE] /store/order/{orderId}
+**Delete purchase order by ID**
+
+For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
+
+#### Responses
+
+| Code | Description |
+| ---- | ----------- |
+| 400 | Invalid ID supplied |
+| 404 | Order not found |
+
+---
+## user
+Operations about user
+
+### [POST] /user
+**Create user**
+
+This can only be done by the logged in user.
+
+#### Responses
+
+| Code | Description | Schema |
+| ---- | ----------- | ------ |
+| default | successful operation | **application/json**: [User](#user)
**application/xml**: [User](#user)
|
+
+### [POST] /user/createWithList
+**Creates list of users with given input array**
+
+#### Responses
+
+| Code | Description | Schema |
+| ---- | ----------- | ------ |
+| 200 | Successful operation | **application/json**: [User](#user)
**application/xml**: [User](#user)
|
+| default | successful operation | |
+
+### [GET] /user/login
+**Logs user into the system**
+
+#### Responses
+
+| Code | Description | Schema |
+| ---- | ----------- | ------ |
+| 200 | successful operation
**Headers:**
**X-Rate-Limit**: calls per hour allowed by the user
**X-Expires-After**: date in UTC when token expires
| **application/xml**: string
**application/json**: string
|
+| 400 | Invalid username/password supplied | |
+
+### [GET] /user/logout
+**Logs out current logged in user session**
+
+#### Responses
+
+| Code | Description |
+| ---- | ----------- |
+| default | successful operation |
+
+### [GET] /user/{username}
+**Get user by user name**
+
+#### Responses
+
+| Code | Description | Schema |
+| ---- | ----------- | ------ |
+| 200 | successful operation | **application/json**: [User](#user)
**application/xml**: [User](#user)
|
+| 400 | Invalid username supplied | |
+| 404 | User not found | |
+
+### [PUT] /user/{username}
+**Update user**
+
+This can only be done by the logged in user.
+
+#### Responses
+
+| Code | Description |
+| ---- | ----------- |
+| default | successful operation |
+
+### [DELETE] /user/{username}
+**Delete user**
+
+This can only be done by the logged in user.
+
+#### Responses
+
+| Code | Description |
+| ---- | ----------- |
+| 400 | Invalid username supplied |
+| 404 | User not found |
+
+---
+### Models
+
+#### Order
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| id | long | *Example:* `10` | No |
+| petId | long | *Example:* `198772` | No |
+| quantity | integer | *Example:* `7` | No |
+| shipDate | dateTime | | No |
+| status | string | Order Status
*Enum:* `"placed"`, `"approved"`, `"delivered"`
*Example:* `"approved"` | No |
+| complete | boolean | | No |
+
+#### Customer
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| id | long | *Example:* `100000` | No |
+| username | string | *Example:* `"fehguy"` | No |
+| address | [ [Address](#address) ] | | No |
+
+#### Address
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| street | string | *Example:* `"437 Lytton"` | No |
+| city | string | *Example:* `"Palo Alto"` | No |
+| state | string | *Example:* `"CA"` | No |
+| zip | string | *Example:* `"94301"` | No |
+
+#### Category
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| id | long | *Example:* `1` | No |
+| name | string | *Example:* `"Dogs"` | No |
+
+#### User
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| id | long | *Example:* `10` | No |
+| username | string | *Example:* `"theUser"` | No |
+| firstName | string | *Example:* `"John"` | No |
+| lastName | string | *Example:* `"James"` | No |
+| email | string | *Example:* `"john@email.com"` | No |
+| password | string | *Example:* `"12345"` | No |
+| phone | string | *Example:* `"12345"` | No |
+| userStatus | integer | User Status
*Example:* `1` | No |
+
+#### Tag
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| id | long | | No |
+| name | string | | No |
+
+#### Pet
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| id | long | *Example:* `10` | No |
+| name | string | *Example:* `"doggie"` | Yes |
+| category | [Category](#category) | | No |
+| photoUrls | [ string ] | | Yes |
+| tags | [ [Tag](#tag) ] | | No |
+| status | string | pet status in the store
*Enum:* `"available"`, `"pending"`, `"sold"` | No |
+
+#### ApiResponse
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| code | integer | | No |
+| type | string | | No |
+| message | string | | No |
diff --git a/examples/v3/petstore.yaml b/examples/v3/petstore.yaml
new file mode 100644
index 0000000..6d7d1b4
--- /dev/null
+++ b/examples/v3/petstore.yaml
@@ -0,0 +1,819 @@
+openapi: 3.0.3
+info:
+ title: Swagger Petstore - OpenAPI 3.0
+ description: |-
+ This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about
+ Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!
+ You can now help us improve the API whether it's by making changes to the definition itself or to the code.
+ That way, with time, we can improve the API in general, and expose some of the new features in OAS3.
+
+ Some useful links:
+ - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)
+ - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)
+
+ termsOfService: http://swagger.io/terms/
+ contact:
+ email: apiteam@swagger.io
+ license:
+ name: Apache 2.0
+ url: http://www.apache.org/licenses/LICENSE-2.0.html
+ version: 1.0.11
+externalDocs:
+ description: Find out more about Swagger
+ url: http://swagger.io
+servers:
+ - url: https://petstore3.swagger.io/api/v3
+tags:
+ - name: pet
+ description: Everything about your Pets
+ externalDocs:
+ description: Find out more
+ url: http://swagger.io
+ - name: store
+ description: Access to Petstore orders
+ externalDocs:
+ description: Find out more about our store
+ url: http://swagger.io
+ - name: user
+ description: Operations about user
+paths:
+ /pet:
+ put:
+ tags:
+ - pet
+ summary: Update an existing pet
+ description: Update an existing pet by Id
+ operationId: updatePet
+ requestBody:
+ description: Update an existent pet in the store
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Pet'
+ application/xml:
+ schema:
+ $ref: '#/components/schemas/Pet'
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/Pet'
+ required: true
+ responses:
+ '200':
+ description: Successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Pet'
+ application/xml:
+ schema:
+ $ref: '#/components/schemas/Pet'
+ '400':
+ description: Invalid ID supplied
+ '404':
+ description: Pet not found
+ '422':
+ description: Validation exception
+ security:
+ - petstore_auth:
+ - write:pets
+ - read:pets
+ post:
+ tags:
+ - pet
+ summary: Add a new pet to the store
+ description: Add a new pet to the store
+ operationId: addPet
+ requestBody:
+ description: Create a new pet in the store
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Pet'
+ application/xml:
+ schema:
+ $ref: '#/components/schemas/Pet'
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/Pet'
+ required: true
+ responses:
+ '200':
+ description: Successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Pet'
+ application/xml:
+ schema:
+ $ref: '#/components/schemas/Pet'
+ '400':
+ description: Invalid input
+ '422':
+ description: Validation exception
+ security:
+ - petstore_auth:
+ - write:pets
+ - read:pets
+ /pet/findByStatus:
+ get:
+ tags:
+ - pet
+ summary: Finds Pets by status
+ description: Multiple status values can be provided with comma separated strings
+ operationId: findPetsByStatus
+ parameters:
+ - name: status
+ in: query
+ description: Status values that need to be considered for filter
+ required: false
+ explode: true
+ schema:
+ type: string
+ default: available
+ enum:
+ - available
+ - pending
+ - sold
+ responses:
+ '200':
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Pet'
+ application/xml:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Pet'
+ links:
+ address:
+ # the target link operationId
+ operationId: getUserAddress
+ description: Address link description
+ parameters:
+ # get the `id` field from the request path parameter named `id`
+ userId: $request.path.id
+ entityId: $request.entity.id
+ requestBody: RequestBody
+ server:
+ url: https://development.gigantic-server.com/v1
+ description: Development server
+ '400':
+ description: Invalid status value
+ security:
+ - petstore_auth:
+ - write:pets
+ - read:pets
+ /pet/findByTags:
+ get:
+ tags:
+ - pet
+ deprecated: true
+ summary: Finds Pets by tags
+ description: Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
+ operationId: findPetsByTags
+ parameters:
+ - name: tags
+ in: query
+ description: Tags to filter by
+ required: false
+ explode: true
+ schema:
+ type: array
+ items:
+ type: string
+ responses:
+ '200':
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Pet'
+ application/xml:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Pet'
+ '400':
+ description: Invalid tag value
+ security:
+ - petstore_auth:
+ - write:pets
+ - read:pets
+ /pet/{petId}:
+ get:
+ tags:
+ - pet
+ summary: Find pet by ID
+ description: Returns a single pet
+ operationId: getPetById
+ parameters:
+ - name: petId
+ in: path
+ description: ID of pet to return
+ required: true
+ schema:
+ type: integer
+ format: int64
+ responses:
+ '200':
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Pet'
+ application/xml:
+ schema:
+ $ref: '#/components/schemas/Pet'
+ '400':
+ description: Invalid ID supplied
+ '404':
+ description: Pet not found
+ security:
+ - api_key: []
+ - petstore_auth:
+ - write:pets
+ - read:pets
+ post:
+ tags:
+ - pet
+ summary: Updates a pet in the store with form data
+ description: ''
+ operationId: updatePetWithForm
+ parameters:
+ - name: petId
+ in: path
+ description: ID of pet that needs to be updated
+ required: true
+ schema:
+ type: integer
+ format: int64
+ - name: name
+ in: query
+ description: Name of pet that needs to be updated
+ schema:
+ type: string
+ - name: status
+ in: query
+ description: Status of pet that needs to be updated
+ schema:
+ type: string
+ responses:
+ '400':
+ description: Invalid input
+ security:
+ - petstore_auth:
+ - write:pets
+ - read:pets
+ delete:
+ tags:
+ - pet
+ summary: Deletes a pet
+ description: delete a pet
+ operationId: deletePet
+ parameters:
+ - name: api_key
+ in: header
+ description: ''
+ required: false
+ schema:
+ type: string
+ - name: petId
+ in: path
+ description: Pet id to delete
+ required: true
+ schema:
+ type: integer
+ format: int64
+ responses:
+ '400':
+ description: Invalid pet value
+ security:
+ - petstore_auth:
+ - write:pets
+ - read:pets
+ /pet/{petId}/uploadImage:
+ post:
+ tags:
+ - pet
+ summary: uploads an image
+ description: ''
+ operationId: uploadFile
+ parameters:
+ - name: petId
+ in: path
+ description: ID of pet to update
+ required: true
+ schema:
+ type: integer
+ format: int64
+ - name: additionalMetadata
+ in: query
+ description: Additional Metadata
+ required: false
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/octet-stream:
+ schema:
+ type: string
+ format: binary
+ responses:
+ '200':
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ApiResponse'
+ security:
+ - petstore_auth:
+ - write:pets
+ - read:pets
+ /store/inventory:
+ get:
+ tags:
+ - store
+ summary: Returns pet inventories by status
+ description: Returns a map of status codes to quantities
+ operationId: getInventory
+ responses:
+ '200':
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: object
+ additionalProperties:
+ type: integer
+ format: int32
+ security:
+ - api_key: []
+ /store/order:
+ post:
+ tags:
+ - store
+ summary: Place an order for a pet
+ description: Place a new order in the store
+ operationId: placeOrder
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Order'
+ application/xml:
+ schema:
+ $ref: '#/components/schemas/Order'
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/Order'
+ responses:
+ '200':
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Order'
+ '400':
+ description: Invalid input
+ '422':
+ description: Validation exception
+ /store/order/{orderId}:
+ get:
+ tags:
+ - store
+ summary: Find purchase order by ID
+ description: For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
+ operationId: getOrderById
+ parameters:
+ - name: orderId
+ in: path
+ description: ID of order that needs to be fetched
+ required: true
+ schema:
+ type: integer
+ format: int64
+ responses:
+ '200':
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Order'
+ application/xml:
+ schema:
+ $ref: '#/components/schemas/Order'
+ '400':
+ description: Invalid ID supplied
+ '404':
+ description: Order not found
+ delete:
+ tags:
+ - store
+ summary: Delete purchase order by ID
+ description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
+ operationId: deleteOrder
+ parameters:
+ - name: orderId
+ in: path
+ description: ID of the order that needs to be deleted
+ required: true
+ schema:
+ type: integer
+ format: int64
+ responses:
+ '400':
+ description: Invalid ID supplied
+ '404':
+ description: Order not found
+ /user:
+ post:
+ tags:
+ - user
+ summary: Create user
+ description: This can only be done by the logged in user.
+ operationId: createUser
+ requestBody:
+ description: Created user object
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/User'
+ application/xml:
+ schema:
+ $ref: '#/components/schemas/User'
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/User'
+ responses:
+ default:
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/User'
+ application/xml:
+ schema:
+ $ref: '#/components/schemas/User'
+ /user/createWithList:
+ post:
+ tags:
+ - user
+ summary: Creates list of users with given input array
+ description: Creates list of users with given input array
+ operationId: createUsersWithListInput
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/User'
+ responses:
+ '200':
+ description: Successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/User'
+ application/xml:
+ schema:
+ $ref: '#/components/schemas/User'
+ default:
+ description: successful operation
+ /user/login:
+ get:
+ tags:
+ - user
+ summary: Logs user into the system
+ description: ''
+ operationId: loginUser
+ parameters:
+ - name: username
+ in: query
+ description: The user name for login
+ required: false
+ schema:
+ type: string
+ - name: password
+ in: query
+ description: The password for login in clear text
+ required: false
+ schema:
+ type: string
+ responses:
+ '200':
+ description: successful operation
+ headers:
+ X-Rate-Limit:
+ description: calls per hour allowed by the user
+ schema:
+ type: integer
+ format: int32
+ X-Expires-After:
+ description: date in UTC when token expires
+ schema:
+ type: string
+ format: date-time
+ content:
+ application/xml:
+ schema:
+ type: string
+ application/json:
+ schema:
+ type: string
+ '400':
+ description: Invalid username/password supplied
+ /user/logout:
+ get:
+ tags:
+ - user
+ summary: Logs out current logged in user session
+ description: ''
+ operationId: logoutUser
+ parameters: []
+ responses:
+ default:
+ description: successful operation
+ /user/{username}:
+ get:
+ tags:
+ - user
+ summary: Get user by user name
+ description: ''
+ operationId: getUserByName
+ parameters:
+ - name: username
+ in: path
+ description: 'The name that needs to be fetched. Use user1 for testing. '
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/User'
+ application/xml:
+ schema:
+ $ref: '#/components/schemas/User'
+ '400':
+ description: Invalid username supplied
+ '404':
+ description: User not found
+ put:
+ tags:
+ - user
+ summary: Update user
+ description: This can only be done by the logged in user.
+ operationId: updateUser
+ parameters:
+ - name: username
+ in: path
+ description: name that need to be deleted
+ required: true
+ schema:
+ type: string
+ requestBody:
+ description: Update an existent user in the store
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/User'
+ application/xml:
+ schema:
+ $ref: '#/components/schemas/User'
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/User'
+ responses:
+ default:
+ description: successful operation
+ delete:
+ tags:
+ - user
+ summary: Delete user
+ description: This can only be done by the logged in user.
+ operationId: deleteUser
+ parameters:
+ - name: username
+ in: path
+ description: The name that needs to be deleted
+ required: true
+ schema:
+ type: string
+ responses:
+ '400':
+ description: Invalid username supplied
+ '404':
+ description: User not found
+components:
+ schemas:
+ Order:
+ type: object
+ properties:
+ id:
+ type: integer
+ format: int64
+ example: 10
+ petId:
+ type: integer
+ format: int64
+ example: 198772
+ quantity:
+ type: integer
+ format: int32
+ example: 7
+ shipDate:
+ type: string
+ format: date-time
+ status:
+ type: string
+ description: Order Status
+ example: approved
+ enum:
+ - placed
+ - approved
+ - delivered
+ complete:
+ type: boolean
+ xml:
+ name: order
+ Customer:
+ type: object
+ properties:
+ id:
+ type: integer
+ format: int64
+ example: 100000
+ username:
+ type: string
+ example: fehguy
+ address:
+ type: array
+ xml:
+ name: addresses
+ wrapped: true
+ items:
+ $ref: '#/components/schemas/Address'
+ xml:
+ name: customer
+ Address:
+ type: object
+ properties:
+ street:
+ type: string
+ example: 437 Lytton
+ city:
+ type: string
+ example: Palo Alto
+ state:
+ type: string
+ example: CA
+ zip:
+ type: string
+ example: '94301'
+ xml:
+ name: address
+ Category:
+ type: object
+ properties:
+ id:
+ type: integer
+ format: int64
+ example: 1
+ name:
+ type: string
+ example: Dogs
+ xml:
+ name: category
+ User:
+ type: object
+ properties:
+ id:
+ type: integer
+ format: int64
+ example: 10
+ username:
+ type: string
+ example: theUser
+ firstName:
+ type: string
+ example: John
+ lastName:
+ type: string
+ example: James
+ email:
+ type: string
+ example: john@email.com
+ password:
+ type: string
+ example: '12345'
+ phone:
+ type: string
+ example: '12345'
+ userStatus:
+ type: integer
+ description: User Status
+ format: int32
+ example: 1
+ xml:
+ name: user
+ Tag:
+ type: object
+ properties:
+ id:
+ type: integer
+ format: int64
+ name:
+ type: string
+ xml:
+ name: tag
+ Pet:
+ required:
+ - name
+ - photoUrls
+ type: object
+ properties:
+ id:
+ type: integer
+ format: int64
+ example: 10
+ name:
+ type: string
+ example: doggie
+ category:
+ $ref: '#/components/schemas/Category'
+ photoUrls:
+ type: array
+ xml:
+ wrapped: true
+ items:
+ type: string
+ xml:
+ name: photoUrl
+ tags:
+ type: array
+ xml:
+ wrapped: true
+ items:
+ $ref: '#/components/schemas/Tag'
+ status:
+ type: string
+ description: pet status in the store
+ enum:
+ - available
+ - pending
+ - sold
+ xml:
+ name: pet
+ ApiResponse:
+ type: object
+ properties:
+ code:
+ type: integer
+ format: int32
+ type:
+ type: string
+ message:
+ type: string
+ xml:
+ name: '##default'
+ requestBodies:
+ Pet:
+ description: Pet object that needs to be added to the store
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Pet'
+ application/xml:
+ schema:
+ $ref: '#/components/schemas/Pet'
+ UserArray:
+ description: List of user object
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/User'
+ securitySchemes:
+ petstore_auth:
+ type: oauth2
+ flows:
+ implicit:
+ authorizationUrl: https://petstore3.swagger.io/oauth/authorize
+ scopes:
+ write:pets: modify pets in your account
+ read:pets: read your pets
+ api_key:
+ type: apiKey
+ name: api_key
+ in: header
diff --git a/scripts/make-examples.ts b/scripts/make-examples.ts
index 9fa521a..eced9e7 100644
--- a/scripts/make-examples.ts
+++ b/scripts/make-examples.ts
@@ -1,19 +1,25 @@
import path from 'path';
import fs from 'fs';
-import { execSync } from 'child_process';
+import { execFileSync } from 'child_process';
-const directory = path.join(__dirname, '..', 'examples');
+const examplesDirectory = path.join(__dirname, '..', 'examples');
-fs.readdir(directory, (err, files) => {
- if (err) {
- console.error(`Unable to read directory: ${err}`);
- return;
- }
+// Read all subdirectories
+const subdirectories = fs.readdirSync(examplesDirectory, { withFileTypes: true })
+ .filter((dirent) => dirent.isDirectory())
+ .map((dirent) => dirent.name);
+
+subdirectories.forEach((subdirectory) => {
+ const subdirectoryPath = path.join(examplesDirectory, subdirectory);
+
+ // Read all files in the subdirectory
+ const files = fs.readdirSync(subdirectoryPath);
files.forEach((filename) => {
if (!filename.match(/\.yaml$/)) return;
- console.log(`Processing ${filename}`);
+
+ console.log(`Processing ${filename} in ${subdirectory}`);
try {
- execSync(`node dist/swagger-markdown.js -i examples/${filename}`);
+ execFileSync('node', ['dist/swagger-markdown.js', '-i', `examples/${subdirectory}/${filename}`]);
console.log('Done\n');
} catch (e) {
console.log(e);
diff --git a/src/index.ts b/src/index.ts
index b8101c8..c6c1e2f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -2,10 +2,11 @@ import SwaggerParser from '@apidevtools/swagger-parser';
import fs from 'fs';
import markdownlint from 'markdownlint';
import markdownlintRuleHelpers from 'markdownlint-rule-helpers';
-import { OpenAPIV2 } from 'openapi-types';
+import { OpenAPIV2, OpenAPIV3 } from 'openapi-types';
import { AllSwaggerDocumentVersions, Options } from './types';
import { isV2Document, isV31Document, isV3Document } from './lib/detectDocumentVersion';
import { transformSwaggerV2 } from './transformers/documentV2';
+import { transformSwaggerV3 } from './transformers/documentV3';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const markdownlintConfig = require('../.markdownlint.json');
@@ -30,7 +31,11 @@ export function partiallyDereference(
const [key, value] = entries[i];
if (Array.isArray(value)) {
obj[key] = value.map((item) => partiallyDereference(item, $refs));
- } else if (key === '$ref' && !value.startsWith('#/definitions/')) {
+ } else if (
+ key === '$ref'
+ && !value.startsWith('#/definitions/') // V2
+ && !value.startsWith('#/components/') // V3
+ ) {
return partiallyDereference($refs.get(value), $refs);
} else {
obj[key] = partiallyDereference(value, $refs);
@@ -53,10 +58,10 @@ export function transfromSwagger(inputDoc: AllSwaggerDocumentVersions, options:
if (isV2Document(inputDoc) || options.forceVersion === '2') {
// Quick hack to allow version 3 to be processed as it version 2
- // Will be removed as soon as support of version 3 will be in place
+ // Will be removed as soon as support of version 3 will be in place`
plainDocument = transformSwaggerV2(inputDoc as OpenAPIV2.Document, options);
- } else if (isV3Document(inputDoc)) {
- throw new Error('OpenAPI V3 is not yet supported');
+ } else if (isV3Document(inputDoc) || options.forceVersion === '3') {
+ plainDocument = transformSwaggerV3(inputDoc as OpenAPIV3.Document, options);
} else if (isV31Document(inputDoc)) {
throw new Error('OpenAPI V3.1 is not yet supported');
} else {
diff --git a/src/lib/markdown/mdstring.spec.ts b/src/lib/markdown/mdstring.spec.ts
index efd1a77..732f59d 100644
--- a/src/lib/markdown/mdstring.spec.ts
+++ b/src/lib/markdown/mdstring.spec.ts
@@ -140,4 +140,37 @@ describe('MDstring', () => {
const s2 = MDstring.string('');
expect(s2.concat(b).concat(s).get()).to.be.equal(`${b}${a}${b}`);
});
+ describe('codeBlock', () => {
+ it('should wrap the existing string in a code block without language', () => {
+ const mdString = new MDstring('console.log("Hello, World!");');
+ mdString.codeBlock();
+ expect(mdString.get()).to.be.equal('```\nconsole.log("Hello, World!");\n```');
+ });
+
+ it('should wrap the existing string in a code block with specified language', () => {
+ const mdString = new MDstring('console.log("Hello, World!");');
+ mdString.codeBlock('javascript');
+ expect(mdString.get()).to.be.equal('```javascript\nconsole.log("Hello, World!");\n```');
+ });
+
+ it('should not modify an empty string', () => {
+ const mdString = new MDstring();
+ mdString.codeBlock('python');
+ expect(mdString.get()).to.be.equal('');
+ });
+
+ it('should handle multi-line code blocks', () => {
+ const mdString = new MDstring('const x = 1;\nconst y = 2;\nconsole.log(x + y);');
+ mdString.codeBlock('javascript');
+ expect(mdString.get()).to.be.equal(
+ '```javascript\nconst x = 1;\nconst y = 2;\nconsole.log(x + y);\n```',
+ );
+ });
+
+ it('should return the MDstring instance for method chaining', () => {
+ const mdString = new MDstring('Hello, World!');
+ const result = mdString.codeBlock();
+ expect(result).to.be.deep.equal(mdString);
+ });
+ });
});
diff --git a/src/lib/markdown/mdstring.ts b/src/lib/markdown/mdstring.ts
index 99512ff..9e73b6f 100644
--- a/src/lib/markdown/mdstring.ts
+++ b/src/lib/markdown/mdstring.ts
@@ -131,6 +131,17 @@ export class MDstring {
return this;
}
+ public codeBlock(language = ''): MDstring {
+ if (this._string !== '') {
+ this.set(
+ `\`\`\`${language}
+${this._string}
+\`\`\``,
+ );
+ }
+ return this;
+ }
+
public escape(): MDstring {
this.set(`${textEscape(this._string)}`);
return this;
diff --git a/src/transformers/v2/models/Tags.spec.ts b/src/transformers/common/Tags.spec.ts
similarity index 89%
rename from src/transformers/v2/models/Tags.spec.ts
rename to src/transformers/common/Tags.spec.ts
index 09b370a..ea80694 100644
--- a/src/transformers/v2/models/Tags.spec.ts
+++ b/src/transformers/common/Tags.spec.ts
@@ -9,6 +9,10 @@ const tags = [{
name: 'Tag1',
description: 'Some description1',
externalDocs: 'http://127.0.0.1',
+}, {
+ name: 'Tag@#!',
+ description: 'Some special tag',
+ externalDocs: 'http://127.0.0.1',
}];
describe('Tags collection', () => {
diff --git a/src/transformers/common/Tags.ts b/src/transformers/common/Tags.ts
new file mode 100644
index 0000000..3faa0ce
--- /dev/null
+++ b/src/transformers/common/Tags.ts
@@ -0,0 +1,26 @@
+import { OpenAPIV2, OpenAPIV3 } from 'openapi-types';
+
+type AnyTagObject = OpenAPIV2.TagObject | OpenAPIV3.TagObject;
+
+export class TagsCollection {
+ private tags: { [key: string]: T } = {};
+
+ public get length(): number {
+ return Object.keys(this.tags).length;
+ }
+
+ public tag(tag: T): TagsCollection {
+ if (!('name' in tag)) {
+ throw new Error('Tag must have name property');
+ }
+ this.tags[tag.name] = tag;
+ return this;
+ }
+
+ public getTag(name: string): T | null {
+ if (name in this.tags) {
+ return this.tags[name];
+ }
+ return null;
+ }
+}
diff --git a/src/transformers/v2/contact.spec.ts b/src/transformers/common/v2-3/contact.spec.ts
similarity index 100%
rename from src/transformers/v2/contact.spec.ts
rename to src/transformers/common/v2-3/contact.spec.ts
diff --git a/src/transformers/v2/contact.ts b/src/transformers/common/v2-3/contact.ts
similarity index 67%
rename from src/transformers/v2/contact.ts
rename to src/transformers/common/v2-3/contact.ts
index c78b787..154dac6 100644
--- a/src/transformers/v2/contact.ts
+++ b/src/transformers/common/v2-3/contact.ts
@@ -1,11 +1,12 @@
-import { OpenAPIV2 } from 'openapi-types';
-import { Markdown } from '../../lib/markdown';
+import { OpenAPIV2, OpenAPIV3 } from 'openapi-types';
+import { Markdown } from '../../../lib/markdown';
/**
- * http://swagger.io/specification/#contactObject
- * Contact info transformer
+ * @todo: add extensions, e.g. ^x-
*/
-export function transformContact(contact: OpenAPIV2.ContactObject) {
+export function transformContact(
+ contact: OpenAPIV2.ContactObject | OpenAPIV3.ContactObject,
+) {
const md = Markdown.md();
if (Object.keys(contact).some(
diff --git a/src/transformers/v2/externalDocs.spec.ts b/src/transformers/common/v2-3/externalDocs.spec.ts
similarity index 100%
rename from src/transformers/v2/externalDocs.spec.ts
rename to src/transformers/common/v2-3/externalDocs.spec.ts
diff --git a/src/transformers/common/v2-3/externalDocs.ts b/src/transformers/common/v2-3/externalDocs.ts
new file mode 100644
index 0000000..12fc23d
--- /dev/null
+++ b/src/transformers/common/v2-3/externalDocs.ts
@@ -0,0 +1,20 @@
+import { OpenAPIV2, OpenAPIV3 } from 'openapi-types';
+import { Markdown } from '../../../lib/markdown';
+
+const DEFAULT_TEXT = 'Find more info here';
+
+/**
+ * @todo: add extensions, e.g. ^x-
+ */
+export function transformExternalDocs(
+ externalDocs: OpenAPIV2.ExternalDocumentationObject | OpenAPIV3.ExternalDocumentationObject,
+) {
+ const md = Markdown.md();
+ if ('url' in externalDocs) {
+ md.line(md.string().link(
+ externalDocs.description || DEFAULT_TEXT,
+ externalDocs.url,
+ ));
+ }
+ return md.export();
+}
diff --git a/src/transformers/v2/groupPathsByTags.spec.ts b/src/transformers/common/v2-3/groupPathsByTags.spec.ts
similarity index 100%
rename from src/transformers/v2/groupPathsByTags.spec.ts
rename to src/transformers/common/v2-3/groupPathsByTags.spec.ts
diff --git a/src/transformers/v2/groupPathsByTags.ts b/src/transformers/common/v2-3/groupPathsByTags.ts
similarity index 53%
rename from src/transformers/v2/groupPathsByTags.ts
rename to src/transformers/common/v2-3/groupPathsByTags.ts
index 3c509e0..c51e62f 100644
--- a/src/transformers/v2/groupPathsByTags.ts
+++ b/src/transformers/common/v2-3/groupPathsByTags.ts
@@ -1,21 +1,26 @@
-import { OpenAPIV2 } from 'openapi-types';
-import { ALLOWED_METHODS } from '../../types';
+import { OpenAPIV2, OpenAPIV3 } from 'openapi-types';
+import { ALLOWED_METHODS_V2, ALLOWED_METHODS_V3 } from '../../../types';
-type Tagged = { [tag: string]: OpenAPIV2.PathsObject };
+type AnyPathObject = OpenAPIV2.PathsObject | OpenAPIV3.PathsObject;
+
+type Tagged = { [tag: string]: T };
/**
* Group path and methods by tags they have
*/
-export function groupPathsByTags(
- inputDoc: OpenAPIV2.PathsObject,
-): Tagged {
- const tagged: Tagged = {};
+export function groupPathsByTags(
+ inputDoc: T,
+ allowedMethods: typeof ALLOWED_METHODS_V2 | typeof ALLOWED_METHODS_V3,
+): Tagged {
+ const tagged: Tagged = {};
Object.keys(inputDoc).forEach((path: string) => {
const data = inputDoc[path];
Object.keys(data).forEach((method) => {
- if (ALLOWED_METHODS.includes(method)) {
- const pathMethod: OpenAPIV2.OperationObject = data[method];
+ if (allowedMethods.includes(method)) {
+ const pathMethod: T extends OpenAPIV2.PathsObject
+ ? OpenAPIV2.OperationObject
+ : OpenAPIV3.OperationObject = data[method];
const tags = pathMethod.tags || [''];
tags.forEach((tagName) => {
if (!tagged[tagName]) {
diff --git a/src/transformers/v2/headers.spec.ts b/src/transformers/common/v2-3/headers.spec.ts
similarity index 100%
rename from src/transformers/v2/headers.spec.ts
rename to src/transformers/common/v2-3/headers.spec.ts
diff --git a/src/transformers/v2/headers.ts b/src/transformers/common/v2-3/headers.ts
similarity index 76%
rename from src/transformers/v2/headers.ts
rename to src/transformers/common/v2-3/headers.ts
index 798f081..13a2887 100644
--- a/src/transformers/v2/headers.ts
+++ b/src/transformers/common/v2-3/headers.ts
@@ -1,6 +1,6 @@
-import { OpenAPIV2 } from 'openapi-types';
-import { Markdown } from '../../lib/markdown';
-import { MDstring } from '../../lib/markdown/mdstring';
+import { OpenAPIV2, OpenAPIV3 } from 'openapi-types';
+import { Markdown } from '../../../lib/markdown';
+import { MDstring } from '../../../lib/markdown/mdstring';
/**
* Make it a little bit simpler
@@ -10,7 +10,9 @@ import { MDstring } from '../../lib/markdown/mdstring';
* @param {OpenAPIV2.HeadersObject} headers
* @return {*} {MDstring}
*/
-export function transformHeaders(headers: OpenAPIV2.HeadersObject): MDstring {
+export function transformHeaders(
+ headers: OpenAPIV2.HeadersObject | { [index: string]: OpenAPIV3.HeaderObject },
+): MDstring {
const md = Markdown.md();
let string: MDstring;
const subString = md.string();
diff --git a/src/transformers/v2/info.spec.ts b/src/transformers/common/v2-3/info.spec.ts
similarity index 100%
rename from src/transformers/v2/info.spec.ts
rename to src/transformers/common/v2-3/info.spec.ts
diff --git a/src/transformers/v2/info.ts b/src/transformers/common/v2-3/info.ts
similarity index 85%
rename from src/transformers/v2/info.ts
rename to src/transformers/common/v2-3/info.ts
index 40b0d6d..ea56b16 100644
--- a/src/transformers/v2/info.ts
+++ b/src/transformers/common/v2-3/info.ts
@@ -1,7 +1,7 @@
-import { OpenAPIV2 } from 'openapi-types';
+import { OpenAPIV2, OpenAPIV3 } from 'openapi-types';
import { transformContact } from './contact';
import { transformLicense } from './license';
-import { Markdown } from '../../lib/markdown';
+import { Markdown } from '../../../lib/markdown';
/**
* http://swagger.io/specification/#infoObject
@@ -10,7 +10,7 @@ import { Markdown } from '../../lib/markdown';
* @param {Object} info
* @returns {String}
*/
-export function transformInfo(info: OpenAPIV2.InfoObject) {
+export function transformInfo(info: OpenAPIV2.InfoObject | OpenAPIV3.InfoObject) {
const md = Markdown.md();
if (info !== null && typeof info === 'object') {
if ('title' in info) {
diff --git a/src/transformers/v2/license.spec.ts b/src/transformers/common/v2-3/license.spec.ts
similarity index 100%
rename from src/transformers/v2/license.spec.ts
rename to src/transformers/common/v2-3/license.spec.ts
diff --git a/src/transformers/v2/license.ts b/src/transformers/common/v2-3/license.ts
similarity index 54%
rename from src/transformers/v2/license.ts
rename to src/transformers/common/v2-3/license.ts
index c7039e9..56fc55c 100644
--- a/src/transformers/v2/license.ts
+++ b/src/transformers/common/v2-3/license.ts
@@ -1,16 +1,18 @@
-import { OpenAPIV2 } from 'openapi-types';
-import { Markdown } from '../../lib/markdown';
+import { OpenAPIV2, OpenAPIV3 } from 'openapi-types';
+import { Markdown } from '../../../lib/markdown';
+import { MDstring } from '../../../lib/markdown/mdstring';
/**
- * http://swagger.io/specification/#licenseObject
- * License object transformer
+ * @todo: add extensions, e.g. ^x-
*/
-export function transformLicense(license: OpenAPIV2.LicenseObject): string {
+export function transformLicense(
+ license: OpenAPIV2.LicenseObject | OpenAPIV3.LicenseObject,
+): string {
const md = Markdown.md();
if ('url' in license || 'name' in license) {
const licenseDocument = md.string('License:').bold();
- let url;
+ let url: MDstring;
if ('url' in license && 'name' in license) {
url = md.string().link(license.name, license.url);
} else {
diff --git a/src/transformers/v2/security.spec.ts b/src/transformers/common/v2-3/security.spec.ts
similarity index 100%
rename from src/transformers/v2/security.spec.ts
rename to src/transformers/common/v2-3/security.spec.ts
diff --git a/src/transformers/common/v2-3/security.ts b/src/transformers/common/v2-3/security.ts
new file mode 100644
index 0000000..4cddca5
--- /dev/null
+++ b/src/transformers/common/v2-3/security.ts
@@ -0,0 +1,51 @@
+import { OpenAPIV2, OpenAPIV3 } from 'openapi-types';
+import { Markdown } from '../../../lib/markdown';
+
+export function transformSecurity(
+ security: OpenAPIV2.SecurityRequirementObject[] | OpenAPIV3.SecurityRequirementObject[],
+) {
+ const md = Markdown.md();
+ md.line(md.string('Security').h5()).line();
+ let hasRules = false;
+
+ let maxLength = 0;
+ security.forEach((rules) => {
+ Object.keys(rules).forEach((key) => {
+ maxLength = rules[key].length > maxLength ? rules[key].length : maxLength;
+ });
+ });
+ maxLength++;
+ if (maxLength < 2) {
+ maxLength = 2;
+ }
+
+ const table = md.table();
+
+ table.th('Security Schema')
+ .th('Scopes');
+
+ for (let i = 0; i < maxLength - 2; i++) {
+ table.th('');
+ }
+
+ security.forEach(
+ (rules: OpenAPIV2.SecurityRequirementObject | OpenAPIV3.SecurityRequirementObject) => {
+ const tr = table.tr();
+ Object.keys(rules).forEach((key) => {
+ hasRules = true;
+ const line = [key].concat(rules[key]);
+ while (line.length < maxLength) {
+ line.push('');
+ }
+ line.forEach((l) => tr.td(l));
+ });
+ },
+ );
+
+ md.line(table);
+
+ if (hasRules) {
+ return md.export();
+ }
+ return '';
+}
diff --git a/src/transformers/v2/tag.spec.ts b/src/transformers/common/v2-3/tag.spec.ts
similarity index 100%
rename from src/transformers/v2/tag.spec.ts
rename to src/transformers/common/v2-3/tag.spec.ts
diff --git a/src/transformers/v2/tag.ts b/src/transformers/common/v2-3/tag.ts
similarity index 81%
rename from src/transformers/v2/tag.ts
rename to src/transformers/common/v2-3/tag.ts
index 73eedd1..49c4209 100644
--- a/src/transformers/v2/tag.ts
+++ b/src/transformers/common/v2-3/tag.ts
@@ -1,7 +1,10 @@
-import { OpenAPIV2 } from 'openapi-types';
-import { Markdown } from '../../lib/markdown';
+import { OpenAPIV2, OpenAPIV3 } from 'openapi-types';
+import { Markdown } from '../../../lib/markdown';
-export function transformTag(tag: OpenAPIV2.TagObject | '') {
+/**
+ * @todo: add extensions, e.g. ^x-
+ */
+export function transformTag(tag: OpenAPIV2.TagObject | OpenAPIV3.TagObject | '') {
const md = Markdown.md();
// If tag is just en empty line, then this means
diff --git a/src/transformers/documentV2.ts b/src/transformers/documentV2.ts
index 5c1c003..c5c5ffc 100644
--- a/src/transformers/documentV2.ts
+++ b/src/transformers/documentV2.ts
@@ -1,14 +1,14 @@
import { OpenAPIV2 } from 'openapi-types';
-import { Options } from '../types';
-import { transformInfo } from './v2/info';
+import { ALLOWED_METHODS_V2, Options } from '../types';
+import { transformInfo } from './common/v2-3/info';
import { transformPath } from './v2/path';
import { transformSecurityDefinitions } from './v2/securityDefinitions';
-import { transformExternalDocs } from './v2/externalDocs';
+import { transformExternalDocs } from './common/v2-3/externalDocs';
import { transformDefinition } from './v2/definitions';
-import { TagsCollection } from './v2/models/Tags';
+import { TagsCollection } from './common/Tags';
import { Markdown } from '../lib/markdown';
-import { groupPathsByTags } from './v2/groupPathsByTags';
-import { transformTag } from './v2/tag';
+import { groupPathsByTags } from './common/v2-3/groupPathsByTags';
+import { transformTag } from './common/v2-3/tag';
import { transformSchemes } from './v2/schemes';
export function transformSwaggerV2(
@@ -58,7 +58,10 @@ export function transformSwaggerV2(
// Process Paths
if ('paths' in inputDoc) {
// Group paths by tag name
- const tagged = groupPathsByTags(inputDoc.paths);
+ const tagged = groupPathsByTags(
+ inputDoc.paths,
+ ALLOWED_METHODS_V2,
+ );
Object.keys(tagged).forEach((tagName) => {
md.line(md.string().horizontalRule());
diff --git a/src/transformers/documentV3.ts b/src/transformers/documentV3.ts
new file mode 100644
index 0000000..c9a4082
--- /dev/null
+++ b/src/transformers/documentV3.ts
@@ -0,0 +1,80 @@
+import { OpenAPIV3 } from 'openapi-types';
+import { ALLOWED_METHODS_V3, Options } from '../types';
+import { transformInfo } from './common/v2-3/info';
+import { transformPath } from './v3/path';
+import { Markdown } from '../lib/markdown';
+import { TagsCollection } from './common/Tags';
+import { transformExternalDocs } from './common/v2-3/externalDocs';
+import { transformTag } from './common/v2-3/tag';
+import { groupPathsByTags } from './common/v2-3/groupPathsByTags';
+import { transformDefinition } from './v2/definitions';
+
+export function transformSwaggerV3(
+ inputDoc: OpenAPIV3.Document,
+ options: Options,
+): string {
+ const md = Markdown.md();
+
+ // Skip servers
+ // those are used for the mock server and won't be rendered
+
+ // Security and Responses are supposed to be dereferenced (?)
+ // and shall not be present in the root namespace
+
+ // Process info
+ if (!options.skipInfo && 'info' in inputDoc) {
+ md.line(transformInfo(inputDoc.info));
+ }
+
+ // Collect tags
+ const tagsCollection = new TagsCollection();
+ if ('tags' in inputDoc) {
+ inputDoc.tags.forEach((tag) => {
+ tagsCollection.tag(tag);
+ });
+ }
+
+ if ('externalDocs' in inputDoc) {
+ md.line(transformExternalDocs(inputDoc.externalDocs));
+ }
+
+ // Security definitions aren't presented in v3
+
+ // All components must be dereferenced
+
+ // Process Paths
+ if ('paths' in inputDoc) {
+ // Group paths by tag name
+ const tagged = groupPathsByTags(inputDoc.paths, ALLOWED_METHODS_V3);
+
+ Object.keys(tagged).forEach((tagName) => {
+ md.line(md.string().horizontalRule());
+ if (tagsCollection.length) {
+ // Display Tag
+ const tagObject = tagsCollection.getTag(tagName) || '';
+ md.line(transformTag(tagObject));
+ }
+ const pathsUnderTag = tagged[tagName];
+ Object.keys(pathsUnderTag).forEach((path: string) => md.line(
+ transformPath(
+ path,
+ inputDoc.paths[path] as unknown,
+ ),
+ ));
+ });
+ }
+
+ // Models (components)
+ if ('components' in inputDoc) {
+ md.line(md.string().horizontalRule());
+ md.line(
+ // @todo: move transform definition to the v3 folder and refactor to respect v3 only
+ transformDefinition(
+ inputDoc.components.schemas as never,
+ ),
+ );
+ }
+
+ // Glue all pieces down
+ return md.export();
+}
diff --git a/src/transformers/v2/externalDocs.ts b/src/transformers/v2/externalDocs.ts
deleted file mode 100644
index 8703e09..0000000
--- a/src/transformers/v2/externalDocs.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { OpenAPIV2 } from 'openapi-types';
-import { Markdown } from '../../lib/markdown';
-
-const DEFAULT_TEXT = 'Find more info here';
-
-/**
- * https://swagger.io/specification/v2/#externalDocumentationObject
- */
-export function transformExternalDocs(externalDocs: OpenAPIV2.ExternalDocumentationObject) {
- const md = Markdown.md();
- if ('url' in externalDocs) {
- md.line(md.string().link(
- externalDocs.description || DEFAULT_TEXT,
- externalDocs.url,
- ));
- }
- return md.export();
-}
diff --git a/src/transformers/v2/models/Tags.ts b/src/transformers/v2/models/Tags.ts
deleted file mode 100644
index 6fa10bc..0000000
--- a/src/transformers/v2/models/Tags.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { OpenAPIV2 } from 'openapi-types';
-
-export class TagsCollection {
- private tags: { [key: string]: OpenAPIV2.TagObject } = {};
-
- public get length(): number {
- return Object.keys(this.tags).length;
- }
-
- public tag(tag: OpenAPIV2.TagObject): TagsCollection {
- if (!('name' in tag)) {
- throw new Error('Tag must have name property');
- }
- this.tags[tag.name] = tag;
- return this;
- }
-
- public getTag(name: string): OpenAPIV2.TagObject | null {
- if (name in this.tags) {
- return this.tags[name];
- }
- return null;
- }
-}
diff --git a/src/transformers/v2/path.ts b/src/transformers/v2/path.ts
index e9d050e..444c050 100644
--- a/src/transformers/v2/path.ts
+++ b/src/transformers/v2/path.ts
@@ -1,10 +1,10 @@
import { OpenAPIV2 } from 'openapi-types';
import { transformResponses } from './pathResponses';
import { transformParameters } from './pathParameters';
-import { transformSecurity } from './security';
+import { transformSecurity } from '../common/v2-3/security';
import { Markdown } from '../../lib/markdown';
-import { ALLOWED_METHODS } from '../../types';
-import { transformExternalDocs } from './externalDocs';
+import { ALLOWED_METHODS_V2 } from '../../types';
+import { transformExternalDocs } from '../common/v2-3/externalDocs';
import { transformSchemes } from './schemes';
/**
@@ -35,7 +35,7 @@ export function transformPath(
// Go further method by methods
Object.keys(data).forEach((method) => {
- if (ALLOWED_METHODS.includes(method)) {
+ if (ALLOWED_METHODS_V2.includes(method)) {
md.line('');
// Set method as a subheader
md.line(md.string(method.toUpperCase()).h4());
diff --git a/src/transformers/v2/pathResponses.ts b/src/transformers/v2/pathResponses.ts
index c96fb77..bc2a62b 100644
--- a/src/transformers/v2/pathResponses.ts
+++ b/src/transformers/v2/pathResponses.ts
@@ -2,7 +2,7 @@ import { OpenAPIV2 } from 'openapi-types';
import { Schema } from './models/Schema';
import { dataTypeResolver } from './dataTypes';
import { Markdown } from '../../lib/markdown';
-import { transformHeaders } from './headers';
+import { transformHeaders } from '../common/v2-3/headers';
/**
* Build responses table
@@ -15,9 +15,8 @@ export function transformResponses(responses: OpenAPIV2.ResponsesObject) {
.line();
// Check if schema somewhere
- const schemas = Object.keys(responses).reduce(
- (acc, response) => acc || 'schema' in responses[response],
- false,
+ const schemas = Object.values(responses).some(
+ (response) => 'schema' in response,
);
const table = md.table();
diff --git a/src/transformers/v2/security.ts b/src/transformers/v2/security.ts
deleted file mode 100644
index d8ea95d..0000000
--- a/src/transformers/v2/security.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { OpenAPIV2 } from 'openapi-types';
-import { Markdown } from '../../lib/markdown';
-
-export function transformSecurity(security: OpenAPIV2.SecurityRequirementObject[]) {
- const md = Markdown.md();
- md.line(md.string('Security').h5()).line();
- let hasRules = false;
-
- let maxLength = 0;
- security.forEach((rules) => {
- Object.keys(rules).forEach((key) => {
- maxLength = rules[key].length > maxLength ? rules[key].length : maxLength;
- });
- });
- maxLength++;
- if (maxLength < 2) {
- maxLength = 2;
- }
-
- const table = md.table();
-
- table.th('Security Schema')
- .th('Scopes');
-
- for (let i = 0; i < maxLength - 2; i++) {
- table.th('');
- }
-
- security.forEach((rules: OpenAPIV2.SecurityRequirementObject) => {
- const tr = table.tr();
- Object.keys(rules).forEach((key) => {
- hasRules = true;
- const line = [key].concat(rules[key]);
- while (line.length < maxLength) {
- line.push('');
- }
- line.forEach((l) => tr.td(l));
- });
- });
-
- md.line(table);
-
- if (hasRules) {
- return md.export();
- }
- return '';
-}
diff --git a/src/transformers/v3/dataTypes.spec.ts b/src/transformers/v3/dataTypes.spec.ts
new file mode 100644
index 0000000..0de426a
--- /dev/null
+++ b/src/transformers/v3/dataTypes.spec.ts
@@ -0,0 +1,73 @@
+import { expect } from 'chai';
+import { dataTypeResolver } from './dataTypes';
+import { Schema } from './models/Schema';
+import { anchor } from '../../lib/anchor';
+
+const fixture = [
+ // References
+ [
+ new Schema({ $ref: '#/definitions/ErrorModel' }),
+ `[ErrorModel](#${anchor('ErrorModel')})`,
+ ],
+ // Standard usecases
+ [new Schema({ type: 'integer', format: 'int32' }), 'integer'],
+ [new Schema({ type: 'integer' }), 'integer'],
+ [new Schema({ type: 'integer', format: 'int64' }), 'long'],
+ [new Schema({ type: 'number', format: 'float' }), 'float'],
+ [new Schema({ type: 'number', format: 'double' }), 'double'],
+ [new Schema({ type: 'number' }), 'number'],
+ [new Schema({ type: 'string' }), 'string'],
+ [new Schema({ type: 'string', format: 'byte' }), 'byte'],
+ [new Schema({ type: 'string', format: 'binary' }), 'binary'],
+ [new Schema({ type: 'boolean' }), 'boolean'],
+ [new Schema({ type: 'string', format: 'date' }), 'date'],
+ [new Schema({ type: 'string', format: 'date-time' }), 'dateTime'],
+ [new Schema({ type: 'string', format: 'password' }), 'password'],
+ [new Schema({ type: ['string', 'boolean'] }), 'string, boolean'],
+ [new Schema({ type: ['string', 'boolean'], format: 'truefalsy' }), 'string (truefalsy), boolean (truefalsy)'],
+ // Arrays
+ [
+ new Schema({
+ type: 'array',
+ items: { type: 'string' },
+ }), '[ string ]',
+ ], [
+ new Schema({
+ type: 'array',
+ items: { $ref: '#/definitions/ErrorModel' },
+ }), `[ [ErrorModel](#${anchor('ErrorModel')}) ]`],
+ // Weird usecases
+ [new Schema({ type: 'random', format: 'number' }), 'random (number)'],
+ [new Schema({ type: 'integer', format: 'int128' }), 'integer (int128)'],
+ [new Schema({ type: 'a', format: 'b' }), 'a (b)'],
+ // Empty schema
+ [new Schema(), ''],
+ [(new Schema()).setType(null as any).setFormat(null as any), ''],
+ // Objects
+ [new Schema({
+ type: 'object',
+ properties: {
+ data: {
+ type: 'array',
+ items: {
+ type: 'object',
+ properties: {
+ notificationId: {
+ type: 'integer',
+ description: 'ID of the notification',
+ },
+ },
+ },
+ },
+ },
+ // @todo: the schema according to the types is incorrect, although it is been seen in many places
+ } as any), '{ **"data"**: [ { **"notificationId"**: integer } ] }'],
+];
+
+describe('Data Types', () => {
+ it('should convert type and format to the common names', () => {
+ fixture.forEach((usecase) => {
+ expect(dataTypeResolver(usecase[0] as Schema)).to.be.equal(usecase[1]);
+ });
+ });
+});
diff --git a/src/transformers/v3/dataTypes.ts b/src/transformers/v3/dataTypes.ts
new file mode 100644
index 0000000..be4b635
--- /dev/null
+++ b/src/transformers/v3/dataTypes.ts
@@ -0,0 +1,106 @@
+import { anchor } from '../../lib/anchor';
+import { Markdown } from '../../lib/markdown';
+import { SchemaInterface } from './models/Schema';
+
+const resolver = {
+ integer: {
+ int32: 'integer',
+ int64: 'long',
+ },
+ number: {
+ float: 'float',
+ double: 'double',
+ },
+ string: {
+ byte: 'byte',
+ binary: 'binary',
+ date: 'date',
+ 'date-time': 'dateTime',
+ password: 'password',
+ },
+};
+
+export function resolveType(type: string, format: string): string | undefined {
+ if (type in resolver) {
+ if (format) {
+ return format in resolver[type]
+ ? resolver[type][format]
+ : `${type} (${format})`;
+ }
+ return type;
+ }
+ return undefined;
+}
+
+/**
+ * Transform data types into common names
+ * @param {Schema} schema
+ * @return {String}
+ */
+export const dataTypeResolver = (schema: SchemaInterface): string => {
+ const md = Markdown.md();
+
+ const all = schema.getAllOf();
+ if (all) {
+ return all.map((subSchema: SchemaInterface) => dataTypeResolver(subSchema))
+ .filter((type) => type !== '')
+ .join(' & ');
+ }
+
+ const reference = schema.getReference();
+ if (reference) {
+ const name = reference.match(/\/([^/]*)$/i)[1];
+ const link = anchor(name);
+ return md.string().link(name, `#${link}`).get();
+ }
+
+ // Cast it to the array
+ const schemaType = schema.getType();
+ const types: string[] = Array.isArray(schemaType) ? schemaType : [schemaType];
+ const format = schema.getFormat();
+
+ const resolveResults = types.map((type: string) => {
+ if (type in resolver) {
+ if (format) {
+ return format in resolver[type]
+ ? resolver[type][format]
+ : `${type} (${format})`;
+ }
+ return type;
+ }
+
+ if (format) {
+ return `${type} (${format})`;
+ }
+
+ if (type === 'array') {
+ const subType = dataTypeResolver(schema.getItems());
+ return `[ ${subType} ]`;
+ }
+
+ // If schema has properties, it means that type is object
+ // we can simply skip this check, furthemore some yaml files may just miss it
+ if (Object.keys(schema.properties).length > 0) {
+ const { properties } = schema;
+ const values = Object.values(properties).map((p) => dataTypeResolver(p));
+ const keys = Object.keys(properties);
+ const pairs = [];
+ for (let i = 0; i < keys.length; i++) {
+ pairs.push(
+ md.string(`"${keys[i]}"`)
+ .bold()
+ .concat(`: ${values[i]}`).get(),
+ );
+ }
+ return `{ ${pairs.join(', ')} }`;
+ }
+
+ if (type) {
+ return md.string(type).get();
+ }
+
+ return md.string('').get();
+ }).filter((s) => s.length > 0); // and filter out empty strings
+
+ return md.string(resolveResults.join(', ')).get();
+};
diff --git a/src/transformers/v3/link.spec.ts b/src/transformers/v3/link.spec.ts
new file mode 100644
index 0000000..fe84825
--- /dev/null
+++ b/src/transformers/v3/link.spec.ts
@@ -0,0 +1,66 @@
+import { expect } from 'chai';
+import { processLink } from './link';
+
+describe('processLink', () => {
+ it('Should correctly format a simple string link', () => {
+ const linkName = 'TestLink';
+ const linkValue = 'https://example.com';
+ const result = processLink(linkName, linkValue);
+ expect(result.toString()).to.be.equal('**TestLink** https://example.com');
+ });
+
+ it('Should handle a link object with only a description', () => {
+ const linkName = 'TestLink';
+ const link = {
+ description: 'This is a test description',
+ };
+ const result = processLink(linkName, link);
+ const expected = '**TestLink**
This is a test description';
+ expect(result.toString()).to.be.equal(expected);
+ });
+
+ it('Should process a link object with parameters but no description', () => {
+ const linkName = 'TestLink';
+ const link = {
+ parameters: {
+ param1: 'value1',
+ param2: 'value2',
+ },
+ };
+ const result = processLink(linkName, link);
+ const expected = '**TestLink**
Parameters {
"param1": "value1",
"param2": "value2"
}
';
+ expect(result.toString()).to.be.equal(expected);
+ });
+
+ it('Should format a link object with both description and parameters', () => {
+ const linkName = 'ComplexLink';
+ const link = {
+ description: 'A complex link with description and parameters',
+ parameters: {
+ param1: 'value1',
+ param2: 'value2',
+ },
+ };
+ const result = processLink(linkName, link);
+ const expected = '**ComplexLink**
'
+ + 'A complex link with description and parameters
'
+ + 'Parameters {
'
+ + '"param1": "value1",
"param2": "value2"
'
+ + '}
';
+ expect(result.toString()).to.be.equal(expected);
+ });
+
+ it('Should handle a link object with an empty parameters object', () => {
+ const linkName = 'EmptyParamsLink';
+ const link = {
+ description: 'A link with empty parameters',
+ parameters: {},
+ };
+ const result = processLink(linkName, link);
+ const expected = '**EmptyParamsLink**
'
+ + 'A link with empty parameters
'
+ + 'Parameters {
'
+ + '}
';
+ expect(result.toString()).to.be.equal(expected);
+ });
+});
diff --git a/src/transformers/v3/link.ts b/src/transformers/v3/link.ts
new file mode 100644
index 0000000..ba27174
--- /dev/null
+++ b/src/transformers/v3/link.ts
@@ -0,0 +1,34 @@
+import { OpenAPIV3 } from 'openapi-types';
+import { Markdown } from '../../lib/markdown';
+import { MDstring } from '../../lib/markdown/mdstring';
+
+export function processLink(
+ linkName: string,
+ link: OpenAPIV3.LinkObject | string,
+): MDstring {
+ const md = Markdown.md();
+
+ if (typeof link === 'string') {
+ return md.string(linkName).bold().concat(' ').concat(link);
+ }
+
+ const linkString = md.string(linkName).bold();
+
+ if ('description' in link) {
+ linkString.br(true).concat(link.description);
+ }
+
+ if ('parameters' in link) {
+ linkString.br(true).concat('Parameters {').br(true);
+ const links = [];
+ Object.entries(link.parameters).forEach((parameter) => {
+ links.push(`"${parameter[0]}": "${parameter[1]}"`);
+ });
+ if (links.length > 0) {
+ linkString.concat(links.join(',
')).br(true);
+ }
+ linkString.concat('}').br(true);
+ }
+
+ return linkString;
+}
diff --git a/src/transformers/v3/models/Schema.ts b/src/transformers/v3/models/Schema.ts
new file mode 100644
index 0000000..8e9acc3
--- /dev/null
+++ b/src/transformers/v3/models/Schema.ts
@@ -0,0 +1,154 @@
+import { OpenAPIV3 } from 'openapi-types';
+
+export interface SchemaInterface {
+ type?: string;
+ format?: string;
+ ref?: string;
+ allOf?: SchemaInterface[];
+ items?: SchemaInterface;
+ properties?: { [name: string]: SchemaInterface };
+ getType?(): string | undefined;
+ getFormat?(): string | undefined;
+ getItems?(): SchemaInterface;
+ getReference(): string | undefined;
+ getAllOf?(): SchemaInterface[];
+}
+
+export class Schema implements SchemaInterface {
+ public type?: string;
+
+ public format?: string;
+
+ public ref?: string;
+
+ public allOf?: SchemaInterface[];
+
+ public items?: SchemaInterface;
+
+ public properties?: { [name: string]: SchemaInterface } = {};
+
+ /**
+ * constructor
+ *
+ * @param {Object} [schema=null]
+ */
+ constructor(schema?: Partial) {
+ if (schema) {
+ if ('type' in schema) {
+ this.setType(schema.type);
+ }
+ if ('format' in schema) {
+ this.setFormat(schema.format);
+ }
+ if ('$ref' in schema) {
+ this.setReference(schema.$ref as string);
+ }
+ if ('items' in schema) {
+ this.setItems(schema.items);
+ }
+ if ('allOf' in schema) {
+ this.setAllOf(schema.allOf);
+ }
+ if ('properties' in schema) {
+ // At this point the document is dereferenced
+ // So we can avoid the reference here
+ this.setProperties(schema.properties);
+ }
+ }
+ }
+
+ /**
+ * @param {String} ref
+ */
+ public setReference(ref: string): Schema {
+ this.ref = ref;
+ return this;
+ }
+
+ /**
+ * @return {String}
+ */
+ public getReference(): string | undefined {
+ return this.ref;
+ }
+
+ public setProperties(properties: {
+ [name: string]: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject
+ }): Schema {
+ Object.keys(properties).forEach(
+ (name) => {
+ this.properties[name] = new Schema(properties[name] as OpenAPIV3.SchemaObject);
+ },
+ );
+ return this;
+ }
+
+ /**
+ * @param {String} type
+ */
+ public setType(type: string | string[]): Schema {
+ // @todo: wtf
+ this.type = type as string;
+ return this;
+ }
+
+ /**
+ * @param {Array