Merge pull request #4773 from mslourens/openapi3-import

Support Openapi 3 import
This commit is contained in:
Rory Powell 2022-03-18 07:16:13 +00:00 committed by GitHub
commit 8b17493e16
9 changed files with 2933 additions and 3 deletions

View File

@ -2,6 +2,7 @@ import { queryValidation } from "../validation"
import { generateQueryID } from "../../../../db/utils" import { generateQueryID } from "../../../../db/utils"
import { ImportInfo, ImportSource } from "./sources/base" import { ImportInfo, ImportSource } from "./sources/base"
import { OpenAPI2 } from "./sources/openapi2" import { OpenAPI2 } from "./sources/openapi2"
import { OpenAPI3 } from "./sources/openapi3"
import { Query } from "./../../../../definitions/common" import { Query } from "./../../../../definitions/common"
import { Curl } from "./sources/curl" import { Curl } from "./sources/curl"
// @ts-ignore // @ts-ignore
@ -18,7 +19,7 @@ export class RestImporter {
constructor(data: string) { constructor(data: string) {
this.data = data this.data = data
this.sources = [new OpenAPI2(), new Curl()] this.sources = [new OpenAPI2(), new OpenAPI3(), new Curl()]
} }
init = async () => { init = async () => {

View File

@ -23,7 +23,7 @@ export abstract class ImportSource {
name: string, name: string,
method: string, method: string,
path: string, path: string,
url: URL, url: URL | string | undefined,
queryString: string, queryString: string,
headers: object = {}, headers: object = {},
parameters: QueryParameter[] = [], parameters: QueryParameter[] = [],
@ -34,7 +34,13 @@ export abstract class ImportSource {
const transformer = "return data" const transformer = "return data"
const schema = {} const schema = {}
path = this.processPath(path) path = this.processPath(path)
path = `${url.origin}/${path}` if (url) {
if (typeof url === "string") {
path = `${url}/${path}`
} else {
path = `${url.origin}/${path}`
}
}
queryString = this.processQuery(queryString) queryString = this.processQuery(queryString)
const requestBody = JSON.stringify(body, null, 2) const requestBody = JSON.stringify(body, null, 2)

View File

@ -0,0 +1,205 @@
import { ImportInfo } from "./base"
import { Query, QueryParameter } from "../../../../../definitions/datasource"
import { OpenAPIV3 } from "openapi-types"
import { OpenAPISource } from "./base/openapi"
import { URL } from "url"
const parameterNotRef = (
param: OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject
): param is OpenAPIV3.ParameterObject => {
// all refs are deferenced by parser library
return true
}
const requestBodyNotRef = (
param: OpenAPIV3.RequestBodyObject | OpenAPIV3.ReferenceObject | undefined
): param is OpenAPIV3.RequestBodyObject => {
// all refs are deferenced by parser library
return param !== undefined
}
const schemaNotRef = (
param: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined
): param is OpenAPIV3.SchemaObject => {
// all refs are deferenced by parser library
return param !== undefined
}
const isOpenAPI3 = (document: any): document is OpenAPIV3.Document => {
return document.openapi.includes("3.0")
}
const methods: string[] = Object.values(OpenAPIV3.HttpMethods)
const isOperation = (
key: string,
pathItem: any
): pathItem is OpenAPIV3.OperationObject => {
return methods.includes(key)
}
const isParameter = (
key: string,
pathItem: any
): pathItem is OpenAPIV3.ParameterObject => {
return !isOperation(key, pathItem)
}
const getRequestBody = (operation: OpenAPIV3.OperationObject) => {
if (requestBodyNotRef(operation.requestBody)) {
const request: OpenAPIV3.RequestBodyObject = operation.requestBody
const supportedMimeTypes = getMimeTypes(operation)
if (supportedMimeTypes.length > 0) {
const mimeType = supportedMimeTypes[0]
// try get example from request
const content = request.content[mimeType]
if (content.example) {
return content.example
}
// try get example from schema
if (schemaNotRef(content.schema)) {
const schema = content.schema
if (schema.example) {
return schema.example
}
}
}
}
return undefined
}
const getMimeTypes = (operation: OpenAPIV3.OperationObject): string[] => {
if (requestBodyNotRef(operation.requestBody)) {
const request: OpenAPIV3.RequestBodyObject = operation.requestBody
return Object.keys(request.content)
}
return []
}
/**
* OpenAPI Version 3.0
* https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md
*/
export class OpenAPI3 extends OpenAPISource {
document!: OpenAPIV3.Document
isSupported = async (data: string): Promise<boolean> => {
try {
const document: any = await this.parseData(data)
if (isOpenAPI3(document)) {
this.document = document
return true
} else {
return false
}
} catch (err) {
return false
}
}
getInfo = async (): Promise<ImportInfo> => {
const name = this.document.info.title || "OpenAPI Import"
return {
name,
}
}
getQueries = async (datasourceId: string): Promise<Query[]> => {
let url: string | URL | undefined
if (this.document.servers?.length) {
url = this.document.servers[0].url
try {
url = new URL(url)
} catch (err) {
// unable to construct url, e.g. with variables
// proceed with string form of url
}
}
const queries: Query[] = []
for (let [path, pathItemObject] of Object.entries(this.document.paths)) {
// parameters that apply to every operation in the path
let pathParams: OpenAPIV3.ParameterObject[] = []
// pathItemObject can be undefined
if (!pathItemObject) {
continue
}
for (let [key, opOrParams] of Object.entries(pathItemObject)) {
if (isParameter(key, opOrParams)) {
const pathParameters = opOrParams as OpenAPIV3.ParameterObject[]
pathParams.push(...pathParameters)
continue
}
// can not be a parameter, must be an operation
const operation = opOrParams as OpenAPIV3.OperationObject
const methodName = key
const name = operation.operationId || path
let queryString = ""
const headers: any = {}
let requestBody = getRequestBody(operation)
const parameters: QueryParameter[] = []
const mimeTypes = getMimeTypes(operation)
if (mimeTypes.length > 0) {
headers["Content-Type"] = mimeTypes[0]
}
// combine the path parameters with the operation parameters
const operationParams = operation.parameters || []
const allParams = [...pathParams, ...operationParams]
for (let param of allParams) {
if (parameterNotRef(param)) {
switch (param.in) {
case "query":
let prefix = ""
if (queryString) {
prefix = "&"
}
queryString = `${queryString}${prefix}${param.name}={{${param.name}}}`
break
case "header":
headers[param.name] = `{{${param.name}}}`
break
case "path":
// do nothing: param is already in the path
break
case "formData":
// future enhancement
break
}
// add the parameter if it can be bound in our config
if (["query", "header", "path"].includes(param.in)) {
parameters.push({
name: param.name,
default: "",
})
}
}
}
const query = this.constructQuery(
datasourceId,
name,
methodName,
path,
url,
queryString,
headers,
parameters,
requestBody
)
queries.push(query)
}
}
return queries
}
}

View File

@ -0,0 +1,253 @@
{
"openapi": "3.0.2",
"info": {
"description": "A basic swagger file",
"version": "1.0.0",
"title": "CRUD"
},
"servers": [
{
"url": "http://example.com"
}
],
"tags": [
{
"name": "entity"
}
],
"paths": {
"/entities": {
"post": {
"tags": [
"entity"
],
"operationId": "createEntity",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateEntity"
}
}
}
},
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Entity"
}
}
}
}
}
},
"get": {
"tags": [
"entity"
],
"operationId": "getEntities",
"parameters": [
{
"$ref": "#/components/parameters/PageParameter"
},
{
"$ref": "#/components/parameters/SizeParameter"
}
],
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Entities"
}
}
}
}
}
}
},
"/entities/{entityId}": {
"parameters": [
{
"$ref": "#/components/parameters/EntityIdParameter"
}
],
"get": {
"tags": [
"entity"
],
"operationId": "getEntity",
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Entity"
}
}
}
}
}
},
"put": {
"tags": [
"entity"
],
"operationId": "updateEntity",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Entity"
}
}
}
},
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Entity"
}
}
}
}
}
},
"patch": {
"tags": [
"entity"
],
"operationId": "patchEntity",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Entity"
}
}
}
},
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Entity"
}
}
}
}
}
},
"delete": {
"tags": [
"entity"
],
"parameters": [
{
"$ref": "#/components/parameters/APIKeyParameter"
}
],
"operationId": "deleteEntity",
"responses": {
"204": {
"description": "successful operation"
}
}
}
}
},
"components": {
"parameters": {
"EntityIdParameter": {
"schema": {
"type": "integer",
"format": "int64"
},
"name": "entityId",
"in": "path",
"required": true
},
"PageParameter": {
"schema": {
"type": "integer",
"format": "int32"
},
"name": "page",
"in": "query",
"required": false
},
"SizeParameter": {
"schema": {
"type": "integer",
"format": "int32"
},
"name": "size",
"in": "query",
"required": false
},
"APIKeyParameter": {
"schema": {
"type": "string"
},
"name": "x-api-key",
"in": "header",
"required": false
}
},
"schemas": {
"CreateEntity": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string"
}
},
"example": {
"name": "name",
"type": "type"
}
},
"Entity": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
}
}
},
{
"$ref": "#/components/schemas/CreateEntity"
}
],
"example": {
"id": 1,
"name": "name",
"type": "type"
}
},
"Entities" : {
"type": "array",
"items": {
"$ref": "#/components/schemas/Entity"
}
}
}
}
}

View File

@ -0,0 +1,153 @@
---
openapi: 3.0.2
info:
description: A basic swagger file
version: 1.0.0
title: CRUD
servers:
- url: http://example.com
tags:
- name: entity
paths:
"/entities":
post:
tags:
- entity
operationId: createEntity
requestBody:
content:
application/json:
schema:
"$ref": "#/components/schemas/CreateEntity"
responses:
'200':
description: successful operation
content:
application/json:
schema:
"$ref": "#/components/schemas/Entity"
get:
tags:
- entity
operationId: getEntities
parameters:
- "$ref": "#/components/parameters/PageParameter"
- "$ref": "#/components/parameters/SizeParameter"
responses:
'200':
description: successful operation
content:
application/json:
schema:
"$ref": "#/components/schemas/Entities"
"/entities/{entityId}":
parameters:
- "$ref": "#/components/parameters/EntityIdParameter"
get:
tags:
- entity
operationId: getEntity
responses:
'200':
description: successful operation
content:
application/json:
schema:
"$ref": "#/components/schemas/Entity"
put:
tags:
- entity
operationId: updateEntity
requestBody:
content:
application/json:
schema:
"$ref": "#/components/schemas/Entity"
responses:
'200':
description: successful operation
content:
application/json:
schema:
"$ref": "#/components/schemas/Entity"
patch:
tags:
- entity
operationId: patchEntity
requestBody:
content:
application/json:
schema:
"$ref": "#/components/schemas/Entity"
responses:
'200':
description: successful operation
content:
application/json:
schema:
"$ref": "#/components/schemas/Entity"
delete:
tags:
- entity
parameters:
- "$ref": "#/components/parameters/APIKeyParameter"
operationId: deleteEntity
responses:
'204':
description: successful operation
components:
parameters:
EntityIdParameter:
schema:
type: integer
format: int64
name: entityId
in: path
required: true
PageParameter:
schema:
type: integer
format: int32
name: page
in: query
required: false
SizeParameter:
schema:
type: integer
format: int32
name: size
in: query
required: false
APIKeyParameter:
schema:
type: string
name: x-api-key
in: header
required: false
schemas:
CreateEntity:
type: object
properties:
name:
type: string
type:
type: string
example:
name: name
type: type
Entity:
allOf:
- type: object
properties:
id:
type: integer
format: int64
- "$ref": "#/components/schemas/CreateEntity"
example:
id: 1
name: name
type: type
Entities:
type: array
items:
"$ref": "#/components/schemas/Entity"

View File

@ -0,0 +1,804 @@
---
openapi: 3.0.2
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 [http://swagger.io](http://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: "/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/xml:
schema:
"$ref": "#/components/schemas/Pet"
application/json:
schema:
"$ref": "#/components/schemas/Pet"
'400':
description: Invalid ID supplied
'404':
description: Pet not found
'405':
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/xml:
schema:
"$ref": "#/components/schemas/Pet"
application/json:
schema:
"$ref": "#/components/schemas/Pet"
'405':
description: Invalid input
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/xml:
schema:
type: array
items:
"$ref": "#/components/schemas/Pet"
application/json:
schema:
type: array
items:
"$ref": "#/components/schemas/Pet"
'400':
description: Invalid status value
security:
- petstore_auth:
- write:pets
- read:pets
"/pet/findByTags":
get:
tags:
- pet
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/xml:
schema:
type: array
items:
"$ref": "#/components/schemas/Pet"
application/json:
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/xml:
schema:
"$ref": "#/components/schemas/Pet"
application/json:
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:
'405':
description: Invalid input
security:
- petstore_auth:
- write:pets
- read:pets
delete:
tags:
- pet
summary: Deletes a pet
description: ''
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"
'405':
description: Invalid input
"/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/xml:
schema:
"$ref": "#/components/schemas/Order"
application/json:
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/xml:
schema:
"$ref": "#/components/schemas/User"
application/json:
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/xml:
schema:
"$ref": "#/components/schemas/User"
application/json:
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

View File

@ -0,0 +1,240 @@
const fs = require("fs")
const path = require("path")
const { OpenAPI3 } = require("../../openapi3")
const getData = (file, extension) => {
return fs.readFileSync(
path.join(__dirname, `./data/${file}/${file}.${extension}`),
"utf8"
)
}
describe("OpenAPI3 Import", () => {
let openapi3
beforeEach(() => {
openapi3 = new OpenAPI3()
})
it("validates unsupported data", async () => {
let data
let supported
// non json / yaml
data = "curl http://example.com"
supported = await openapi3.isSupported(data)
expect(supported).toBe(false)
// Empty
data = ""
supported = await openapi3.isSupported(data)
expect(supported).toBe(false)
})
const runTests = async (filename, test, assertions) => {
for (let extension of ["json", "yaml"]) {
await test(filename, extension, assertions)
}
}
const testImportInfo = async (file, extension) => {
await openapi3.isSupported(getData(file, extension))
const info = await openapi3.getInfo()
expect(info.name).toBe("Swagger Petstore - OpenAPI 3.0")
}
it("returns import info", async () => {
await runTests("petstore", testImportInfo)
})
describe("Returns queries", () => {
const indexQueries = queries => {
return queries.reduce((acc, query) => {
acc[query.name] = query
return acc
}, {})
}
const getQueries = async (file, extension) => {
await openapi3.isSupported(getData(file, extension))
const queries = await openapi3.getQueries()
expect(queries.length).toBe(6)
return indexQueries(queries)
}
const testVerb = async (file, extension, assertions) => {
const queries = await getQueries(file, extension)
for (let [operationId, method] of Object.entries(assertions)) {
expect(queries[operationId].queryVerb).toBe(method)
}
}
it("populates verb", async () => {
const assertions = {
createEntity: "create",
getEntities: "read",
getEntity: "read",
updateEntity: "update",
patchEntity: "patch",
deleteEntity: "delete",
}
await runTests("crud", testVerb, assertions)
})
const testPath = async (file, extension, assertions) => {
const queries = await getQueries(file, extension)
for (let [operationId, urlPath] of Object.entries(assertions)) {
expect(queries[operationId].fields.path).toBe(urlPath)
}
}
it("populates path", async () => {
const assertions = {
createEntity: "http://example.com/entities",
getEntities: "http://example.com/entities",
getEntity: "http://example.com/entities/{{entityId}}",
updateEntity: "http://example.com/entities/{{entityId}}",
patchEntity: "http://example.com/entities/{{entityId}}",
deleteEntity: "http://example.com/entities/{{entityId}}",
}
await runTests("crud", testPath, assertions)
})
const testHeaders = async (file, extension, assertions) => {
const queries = await getQueries(file, extension)
for (let [operationId, headers] of Object.entries(assertions)) {
expect(queries[operationId].fields.headers).toStrictEqual(headers)
}
}
const contentTypeHeader = {
"Content-Type": "application/json",
}
it("populates headers", async () => {
const assertions = {
createEntity: {
...contentTypeHeader,
},
getEntities: {},
getEntity: {},
updateEntity: {
...contentTypeHeader,
},
patchEntity: {
...contentTypeHeader,
},
deleteEntity: {
"x-api-key": "{{x-api-key}}",
},
}
await runTests("crud", testHeaders, assertions)
})
const testQuery = async (file, extension, assertions) => {
const queries = await getQueries(file, extension)
for (let [operationId, queryString] of Object.entries(assertions)) {
expect(queries[operationId].fields.queryString).toStrictEqual(
queryString
)
}
}
it("populates query", async () => {
const assertions = {
createEntity: "",
getEntities: "page={{page}}&size={{size}}",
getEntity: "",
updateEntity: "",
patchEntity: "",
deleteEntity: "",
}
await runTests("crud", testQuery, assertions)
})
const testParameters = async (file, extension, assertions) => {
const queries = await getQueries(file, extension)
for (let [operationId, parameters] of Object.entries(assertions)) {
expect(queries[operationId].parameters).toStrictEqual(parameters)
}
}
it("populates parameters", async () => {
const assertions = {
createEntity: [],
getEntities: [
{
name: "page",
default: "",
},
{
name: "size",
default: "",
},
],
getEntity: [
{
name: "entityId",
default: "",
},
],
updateEntity: [
{
name: "entityId",
default: "",
},
],
patchEntity: [
{
name: "entityId",
default: "",
},
],
deleteEntity: [
{
name: "entityId",
default: "",
},
{
name: "x-api-key",
default: "",
},
],
}
await runTests("crud", testParameters, assertions)
})
const testBody = async (file, extension, assertions) => {
const queries = await getQueries(file, extension)
for (let [operationId, body] of Object.entries(assertions)) {
expect(queries[operationId].fields.requestBody).toStrictEqual(
JSON.stringify(body, null, 2)
)
}
}
it("populates body", async () => {
const assertions = {
createEntity: {
name: "name",
type: "type",
},
getEntities: undefined,
getEntity: undefined,
updateEntity: {
id: 1,
name: "name",
type: "type",
},
patchEntity: {
id: 1,
name: "name",
type: "type",
},
deleteEntity: undefined,
}
await runTests("crud", testBody, assertions)
})
})
})

View File

@ -23,14 +23,27 @@ const oapi2CrudYaml = getData("openapi2/data/crud/crud.json")
const oapi2PetstoreJson = getData("openapi2/data/petstore/petstore.json") const oapi2PetstoreJson = getData("openapi2/data/petstore/petstore.json")
const oapi2PetstoreYaml = getData("openapi2/data/petstore/petstore.json") const oapi2PetstoreYaml = getData("openapi2/data/petstore/petstore.json")
// openapi3
const oapi3CrudJson = getData("openapi3/data/crud/crud.json")
const oapi3CrudYaml = getData("openapi3/data/crud/crud.json")
const oapi3PetstoreJson = getData("openapi3/data/petstore/petstore.json")
const oapi3PetstoreYaml = getData("openapi3/data/petstore/petstore.json")
// curl // curl
const curl = getData("curl/data/post.txt") const curl = getData("curl/data/post.txt")
const datasets = { const datasets = {
// openapi2 (swagger)
oapi2CrudJson, oapi2CrudJson,
oapi2CrudYaml, oapi2CrudYaml,
oapi2PetstoreJson, oapi2PetstoreJson,
oapi2PetstoreYaml, oapi2PetstoreYaml,
// openapi3
oapi3CrudJson,
oapi3CrudYaml,
oapi3PetstoreJson,
oapi3PetstoreYaml,
// curl
curl curl
} }
@ -56,6 +69,7 @@ describe("Rest Importer", () => {
it("gets info", async () => { it("gets info", async () => {
const assertions = { const assertions = {
// openapi2 (swagger)
"oapi2CrudJson" : { "oapi2CrudJson" : {
name: "CRUD", name: "CRUD",
}, },
@ -68,6 +82,20 @@ describe("Rest Importer", () => {
"oapi2PetstoreYaml" :{ "oapi2PetstoreYaml" :{
name: "Swagger Petstore", name: "Swagger Petstore",
}, },
// openapi3
"oapi3CrudJson" : {
name: "CRUD",
},
"oapi3CrudYaml" : {
name: "CRUD",
},
"oapi3PetstoreJson" : {
name: "Swagger Petstore - OpenAPI 3.0",
},
"oapi3PetstoreYaml" :{
name: "Swagger Petstore - OpenAPI 3.0",
},
// curl
"curl": { "curl": {
name: "example.com", name: "example.com",
} }
@ -89,6 +117,7 @@ describe("Rest Importer", () => {
// simple sanity assertions that the whole dataset // simple sanity assertions that the whole dataset
// makes it through the importer // makes it through the importer
const assertions = { const assertions = {
// openapi2 (swagger)
"oapi2CrudJson" : { "oapi2CrudJson" : {
count: 6, count: 6,
}, },
@ -101,6 +130,20 @@ describe("Rest Importer", () => {
"oapi2PetstoreYaml" :{ "oapi2PetstoreYaml" :{
count: 20, count: 20,
}, },
// openapi3
"oapi3CrudJson" : {
count: 6,
},
"oapi3CrudYaml" :{
count: 6,
},
"oapi3PetstoreJson" : {
count: 19,
},
"oapi3PetstoreYaml" :{
count: 19,
},
// curl
"curl": { "curl": {
count: 1 count: 1
} }