From b51261cf227182123a643162c9f1d347449b5175 Mon Sep 17 00:00:00 2001 From: Vishal Shingala Date: Fri, 5 May 2023 11:00:48 +0530 Subject: [PATCH 01/14] Added support for validation of specifications in case of errors to report User input errors correctly --- assets/openapi3Schema.json | 1665 +++++++++++++++++++++++++ assets/swagger2Schema.json | 1618 ++++++++++++++++++++++++ lib/common/UserError.js | 15 + lib/common/generateValidationError.js | 75 ++ lib/schemapack.js | 237 ++-- 5 files changed, 3499 insertions(+), 111 deletions(-) create mode 100644 assets/openapi3Schema.json create mode 100644 assets/swagger2Schema.json create mode 100644 lib/common/UserError.js create mode 100644 lib/common/generateValidationError.js diff --git a/assets/openapi3Schema.json b/assets/openapi3Schema.json new file mode 100644 index 000000000..387e2f905 --- /dev/null +++ b/assets/openapi3Schema.json @@ -0,0 +1,1665 @@ +{ + "id": "https://spec.openapis.org/oas/3.0/schema/2021-09-28", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Validation schema for OpenAPI Specification 3.0.X.", + "type": "object", + "required": [ + "openapi", + "info", + "paths" + ], + "properties": { + "openapi": { + "type": "string", + "pattern": "^3\\.0\\.\\d(-.+)?$" + }, + "info": { + "$ref": "#/definitions/Info" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + }, + "security": { + "type": "array", + "items": { + "$ref": "#/definitions/SecurityRequirement" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/definitions/Tag" + }, + "uniqueItems": true + }, + "paths": { + "$ref": "#/definitions/Paths" + }, + "components": { + "$ref": "#/definitions/Components" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true, + "definitions": { + "Reference": { + "type": "object", + "required": [ + "$ref" + ], + "patternProperties": { + "^\\$ref$": { + "type": "string", + "format": "uri-reference" + } + } + }, + "Info": { + "type": "object", + "required": [ + "title", + "version" + ], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "termsOfService": { + "type": "string", + "format": "uri-reference" + }, + "contact": { + "$ref": "#/definitions/Contact" + }, + "license": { + "$ref": "#/definitions/License" + }, + "version": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "Contact": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + }, + "email": { + "type": "string", + "format": "email" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "License": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "Server": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ServerVariable" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "ServerVariable": { + "type": "object", + "required": [ + "default" + ], + "properties": { + "enum": { + "type": "array", + "items": { + "type": "string" + } + }, + "default": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "Components": { + "type": "object", + "properties": { + "schemas": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "responses": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Response" + } + ] + } + } + }, + "parameters": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Parameter" + } + ] + } + } + }, + "examples": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Example" + } + ] + } + } + }, + "requestBodies": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/RequestBody" + } + ] + } + } + }, + "headers": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Header" + } + ] + } + } + }, + "securitySchemes": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/SecurityScheme" + } + ] + } + } + }, + "links": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Link" + } + ] + } + } + }, + "callbacks": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Callback" + } + ] + } + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "Schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "minLength": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + }, + "minProperties": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "required": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "enum": { + "type": "array", + "items": { + }, + "minItems": 1, + "uniqueItems": false + }, + "type": { + "type": "string", + "enum": [ + "array", + "boolean", + "integer", + "number", + "object", + "string" + ] + }, + "not": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "allOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "oneOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "anyOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "properties": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + }, + { + "type": "boolean" + } + ], + "default": true + }, + "description": { + "type": "string" + }, + "format": { + "type": "string" + }, + "default": { + }, + "nullable": { + "type": "boolean", + "default": false + }, + "discriminator": { + "$ref": "#/definitions/Discriminator" + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "example": { + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "xml": { + "$ref": "#/definitions/XML" + }, + "reference": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Discriminator": { + "type": "object", + "required": [ + "propertyName" + ], + "properties": { + "propertyName": { + "type": "string" + }, + "mapping": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "XML": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string", + "format": "uri" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean", + "default": false + }, + "wrapped": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "Response": { + "type": "object", + "required": [ + "description" + ], + "properties": { + "description": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Header" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + } + }, + "links": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Link" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "MediaType": { + "type": "object", + "properties": { + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "example": { + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "encoding": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Encoding" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true, + "allOf": [ + { + "$ref": "#/definitions/ExampleXORExamples" + } + ] + }, + "Example": { + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value": { + }, + "externalValue": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "Header": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string", + "enum": [ + "simple" + ], + "default": "simple" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + }, + "minProperties": 1, + "maxProperties": 1 + }, + "example": { + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true, + "allOf": [ + { + "$ref": "#/definitions/ExampleXORExamples" + }, + { + "$ref": "#/definitions/SchemaXORContent" + } + ] + }, + "Paths": { + "type": "object", + "patternProperties": { + "^\\/": { + "$ref": "#/definitions/PathItem" + }, + "^x-": { + } + }, + "additionalProperties": true + }, + "PathItem": { + "type": "object", + "properties": { + "$ref": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + }, + "parameters": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Parameter" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "uniqueItems": false + } + }, + "patternProperties": { + "^(get|put|post|delete|options|head|patch|trace)$": { + "$ref": "#/definitions/Operation" + }, + "^x-": { + } + }, + "additionalProperties": true + }, + "Operation": { + "type": "object", + "required": [ + "responses" + ], + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Parameter" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "uniqueItems": false + }, + "requestBody": { + "oneOf": [ + { + "$ref": "#/definitions/RequestBody" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "responses": { + "$ref": "#/definitions/Responses" + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Callback" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "security": { + "type": "array", + "items": { + "$ref": "#/definitions/SecurityRequirement" + } + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "Responses": { + "type": "object", + "properties": { + "default": { + "oneOf": [ + { + "$ref": "#/definitions/Response" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "patternProperties": { + "^[1-5](?:\\d{2}|XX)$": { + "oneOf": [ + { + "$ref": "#/definitions/Response" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "^x-": { + } + }, + "minProperties": 1, + "additionalProperties": true + }, + "SecurityRequirement": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "Tag": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "ExternalDocumentation": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "ExampleXORExamples": { + "description": "Example and examples are mutually exclusive", + "not": { + "required": [ + "example", + "examples" + ] + } + }, + "SchemaXORContent": { + "description": "Schema and content are mutually exclusive, at least one is required", + "not": { + "required": [ + "schema", + "content" + ] + }, + "oneOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "content" + ], + "description": "Some properties are not allowed if content is present", + "allOf": [ + { + "not": { + "required": [ + "style" + ] + } + }, + { + "not": { + "required": [ + "explode" + ] + } + }, + { + "not": { + "required": [ + "allowReserved" + ] + } + }, + { + "not": { + "required": [ + "example" + ] + } + }, + { + "not": { + "required": [ + "examples" + ] + } + } + ] + } + ] + }, + "Parameter": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "in": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + }, + "minProperties": 1, + "maxProperties": 1 + }, + "example": { + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true, + "required": [ + "name", + "in" + ], + "allOf": [ + { + "$ref": "#/definitions/ExampleXORExamples" + }, + { + "$ref": "#/definitions/SchemaXORContent" + }, + { + "$ref": "#/definitions/ParameterLocation" + } + ] + }, + "ParameterLocation": { + "description": "Parameter location", + "oneOf": [ + { + "description": "Parameter in path", + "required": [ + "required" + ], + "properties": { + "in": { + "enum": [ + "path" + ] + }, + "style": { + "enum": [ + "matrix", + "label", + "simple" + ], + "default": "simple" + }, + "required": { + "enum": [ + true + ] + } + } + }, + { + "description": "Parameter in query", + "properties": { + "in": { + "enum": [ + "query" + ] + }, + "style": { + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ], + "default": "form" + } + } + }, + { + "description": "Parameter in header", + "properties": { + "in": { + "enum": [ + "header" + ] + }, + "style": { + "enum": [ + "simple" + ], + "default": "simple" + } + } + }, + { + "description": "Parameter in cookie", + "properties": { + "in": { + "enum": [ + "cookie" + ] + }, + "style": { + "enum": [ + "form" + ], + "default": "form" + } + } + } + ] + }, + "RequestBody": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "description": { + "type": "string" + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + } + }, + "required": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "SecurityScheme": { + "oneOf": [ + { + "$ref": "#/definitions/APIKeySecurityScheme" + }, + { + "$ref": "#/definitions/HTTPSecurityScheme" + }, + { + "$ref": "#/definitions/OAuth2SecurityScheme" + }, + { + "$ref": "#/definitions/OpenIdConnectSecurityScheme" + } + ] + }, + "APIKeySecurityScheme": { + "type": "object", + "required": [ + "type", + "name", + "in" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "apiKey" + ] + }, + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "header", + "query", + "cookie" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "HTTPSecurityScheme": { + "type": "object", + "required": [ + "scheme", + "type" + ], + "properties": { + "scheme": { + "type": "string" + }, + "bearerFormat": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "http" + ] + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true, + "oneOf": [ + { + "description": "Bearer", + "properties": { + "scheme": { + "type": "string", + "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$" + } + } + }, + { + "description": "Non Bearer", + "not": { + "required": [ + "bearerFormat" + ] + }, + "properties": { + "scheme": { + "not": { + "type": "string", + "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$" + } + } + } + } + ] + }, + "OAuth2SecurityScheme": { + "type": "object", + "required": [ + "type", + "flows" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flows": { + "$ref": "#/definitions/OAuthFlows" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "OpenIdConnectSecurityScheme": { + "type": "object", + "required": [ + "type", + "openIdConnectUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "openIdConnect" + ] + }, + "openIdConnectUrl": { + "type": "string", + "format": "uri-reference" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "OAuthFlows": { + "type": "object", + "properties": { + "implicit": { + "$ref": "#/definitions/ImplicitOAuthFlow" + }, + "password": { + "$ref": "#/definitions/PasswordOAuthFlow" + }, + "clientCredentials": { + "$ref": "#/definitions/ClientCredentialsFlow" + }, + "authorizationCode": { + "$ref": "#/definitions/AuthorizationCodeOAuthFlow" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "ImplicitOAuthFlow": { + "type": "object", + "required": [ + "authorizationUrl", + "scopes" + ], + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "PasswordOAuthFlow": { + "type": "object", + "required": [ + "tokenUrl", + "scopes" + ], + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "ClientCredentialsFlow": { + "type": "object", + "required": [ + "tokenUrl", + "scopes" + ], + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "AuthorizationCodeOAuthFlow": { + "type": "object", + "required": [ + "authorizationUrl", + "tokenUrl", + "scopes" + ], + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri-reference" + }, + "tokenUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true + }, + "Link": { + "type": "object", + "properties": { + "operationId": { + "type": "string" + }, + "operationRef": { + "type": "string", + "format": "uri-reference" + }, + "parameters": { + "type": "object", + "additionalProperties": { + } + }, + "requestBody": { + }, + "description": { + "type": "string" + }, + "server": { + "$ref": "#/definitions/Server" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": true, + "not": { + "description": "Operation Id and Operation Ref are mutually exclusive", + "required": [ + "operationId", + "operationRef" + ] + } + }, + "Callback": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PathItem" + }, + "patternProperties": { + "^x-": { + } + } + }, + "Encoding": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Header" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "style": { + "type": "string", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ] + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": true + } + } + } \ No newline at end of file diff --git a/assets/swagger2Schema.json b/assets/swagger2Schema.json new file mode 100644 index 000000000..92f0a4ed0 --- /dev/null +++ b/assets/swagger2Schema.json @@ -0,0 +1,1618 @@ +{ + "title": "A JSON Schema for Swagger 2.0 API.", + "id": "http://swagger.io/v2/schema.json#", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "required": [ + "swagger", + "info", + "paths" + ], + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "swagger": { + "type": "string", + "enum": [ + "2.0" + ], + "description": "The Swagger version of this document." + }, + "info": { + "$ref": "#/definitions/info" + }, + "host": { + "type": "string", + "pattern": "^[^{}/ :\\\\]+(?::\\d+)?$", + "description": "The host (name or ip) of the API. Example: 'swagger.io'" + }, + "basePath": { + "type": "string", + "pattern": "^/", + "description": "The base path to the API. Example: '/api'." + }, + "schemes": { + "$ref": "#/definitions/schemesList" + }, + "consumes": { + "description": "A list of MIME types accepted by the API.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "produces": { + "description": "A list of MIME types the API can produce.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "paths": { + "$ref": "#/definitions/paths" + }, + "definitions": { + "$ref": "#/definitions/definitions" + }, + "parameters": { + "$ref": "#/definitions/parameterDefinitions" + }, + "responses": { + "$ref": "#/definitions/responseDefinitions" + }, + "security": { + "$ref": "#/definitions/security" + }, + "securityDefinitions": { + "$ref": "#/definitions/securityDefinitions" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/definitions/tag" + }, + "uniqueItems": true + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + } + }, + "definitions": { + "info": { + "type": "object", + "description": "General information about the API.", + "required": [ + "version", + "title" + ], + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "title": { + "type": "string", + "description": "A unique and precise title of the API." + }, + "version": { + "type": "string", + "description": "A semantic version number of the API." + }, + "description": { + "type": "string", + "description": "A longer description of the API. Should be different from the title. GitHub Flavored Markdown is allowed." + }, + "termsOfService": { + "type": "string", + "description": "The terms of service for the API." + }, + "contact": { + "$ref": "#/definitions/contact" + }, + "license": { + "$ref": "#/definitions/license" + } + } + }, + "contact": { + "type": "object", + "description": "Contact information for the owners of the API.", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The identifying name of the contact person/organization." + }, + "url": { + "type": "string", + "description": "The URL pointing to the contact information.", + "format": "uri" + }, + "email": { + "type": "string", + "description": "The email address of the contact person/organization.", + "format": "email" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "license": { + "type": "object", + "required": [ + "name" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The name of the license type. It's encouraged to use an OSI compatible license." + }, + "url": { + "type": "string", + "description": "The URL pointing to the license.", + "format": "uri" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "paths": { + "type": "object", + "description": "Relative paths to the individual endpoints. They must be relative to the 'basePath'.", + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + }, + "^/": { + "$ref": "#/definitions/pathItem" + } + }, + "additionalProperties": false + }, + "definitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/schema" + }, + "description": "One or more JSON objects describing the schemas being consumed and produced by the API." + }, + "parameterDefinitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/parameter" + }, + "description": "One or more JSON representations for parameters" + }, + "responseDefinitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/response" + }, + "description": "One or more JSON representations for responses" + }, + "externalDocs": { + "type": "object", + "additionalProperties": false, + "description": "information about external documentation", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "examples": { + "type": "object", + "additionalProperties": true + }, + "mimeType": { + "type": "string", + "description": "The MIME type of the HTTP message." + }, + "operation": { + "type": "object", + "required": [ + "responses" + ], + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "summary": { + "type": "string", + "description": "A brief summary of the operation." + }, + "description": { + "type": "string", + "description": "A longer description of the operation, GitHub Flavored Markdown is allowed." + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + }, + "operationId": { + "type": "string", + "description": "A unique identifier of the operation." + }, + "produces": { + "description": "A list of MIME types the API can produce.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "consumes": { + "description": "A list of MIME types the API can consume.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "parameters": { + "$ref": "#/definitions/parametersList" + }, + "responses": { + "$ref": "#/definitions/responses" + }, + "schemes": { + "$ref": "#/definitions/schemesList" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "security": { + "$ref": "#/definitions/security" + } + } + }, + "pathItem": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "$ref": { + "type": "string" + }, + "get": { + "$ref": "#/definitions/operation" + }, + "put": { + "$ref": "#/definitions/operation" + }, + "post": { + "$ref": "#/definitions/operation" + }, + "delete": { + "$ref": "#/definitions/operation" + }, + "options": { + "$ref": "#/definitions/operation" + }, + "head": { + "$ref": "#/definitions/operation" + }, + "patch": { + "$ref": "#/definitions/operation" + }, + "parameters": { + "$ref": "#/definitions/parametersList" + } + } + }, + "responses": { + "type": "object", + "description": "Response objects names can either be any valid HTTP status code or 'default'.", + "minProperties": 1, + "additionalProperties": false, + "patternProperties": { + "^([0-9]{3})$|^(default)$": { + "$ref": "#/definitions/responseValue" + }, + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "not": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + } + }, + "responseValue": { + "oneOf": [ + { + "$ref": "#/definitions/response" + }, + { + "$ref": "#/definitions/jsonReference" + } + ] + }, + "response": { + "type": "object", + "required": [ + "description" + ], + "properties": { + "description": { + "type": "string" + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "$ref": "#/definitions/fileSchema" + } + ] + }, + "headers": { + "$ref": "#/definitions/headers" + }, + "examples": { + "$ref": "#/definitions/examples" + } + }, + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/header" + } + }, + "header": { + "type": "object", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "string", + "number", + "integer", + "boolean", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "vendorExtension": { + "description": "Any property starting with x- is valid.", + "additionalProperties": true, + "additionalItems": true + }, + "bodyParameter": { + "type": "object", + "required": [ + "name", + "in", + "schema" + ], + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "body" + ] + }, + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "schema": { + "$ref": "#/definitions/schema" + } + }, + "additionalProperties": false + }, + "headerParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "header" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "queryParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "query" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "allowEmptyValue": { + "type": "boolean", + "default": false, + "description": "allows sending a parameter by name only or with an empty value." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormatWithMulti" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "formDataParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "formData" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "allowEmptyValue": { + "type": "boolean", + "default": false, + "description": "allows sending a parameter by name only or with an empty value." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array", + "file" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormatWithMulti" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "pathParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "required": [ + "required" + ], + "properties": { + "required": { + "type": "boolean", + "enum": [ + true + ], + "description": "Determines whether or not this parameter is required or optional." + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "path" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "nonBodyParameter": { + "type": "object", + "required": [ + "name", + "in", + "type" + ], + "oneOf": [ + { + "$ref": "#/definitions/headerParameterSubSchema" + }, + { + "$ref": "#/definitions/formDataParameterSubSchema" + }, + { + "$ref": "#/definitions/queryParameterSubSchema" + }, + { + "$ref": "#/definitions/pathParameterSubSchema" + } + ] + }, + "parameter": { + "oneOf": [ + { + "$ref": "#/definitions/bodyParameter" + }, + { + "$ref": "#/definitions/nonBodyParameter" + } + ] + }, + "schema": { + "type": "object", + "description": "A deterministic version of a JSON Schema object.", + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "$ref": { + "type": "string" + }, + "format": { + "type": "string" + }, + "title": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/title" + }, + "description": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/description" + }, + "default": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/default" + }, + "multipleOf": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf" + }, + "maximum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/maximum" + }, + "exclusiveMaximum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum" + }, + "minimum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/minimum" + }, + "exclusiveMinimum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum" + }, + "maxLength": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minLength": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "pattern": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/pattern" + }, + "maxItems": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minItems": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "uniqueItems": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems" + }, + "maxProperties": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minProperties": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "required": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray" + }, + "enum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/enum" + }, + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "type": "boolean" + } + ], + "default": { + + } + }, + "type": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/type" + }, + "items": { + "anyOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/schema" + } + } + ], + "default": { + + } + }, + "allOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/schema" + } + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/schema" + }, + "default": { + + } + }, + "discriminator": { + "type": "string" + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "xml": { + "$ref": "#/definitions/xml" + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + }, + "example": { + + } + }, + "additionalProperties": false + }, + "fileSchema": { + "type": "object", + "description": "A deterministic version of a JSON Schema object.", + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "required": [ + "type" + ], + "properties": { + "format": { + "type": "string" + }, + "title": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/title" + }, + "description": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/description" + }, + "default": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/default" + }, + "required": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray" + }, + "type": { + "type": "string", + "enum": [ + "file" + ] + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + }, + "example": { + + } + }, + "additionalProperties": false + }, + "primitivesItems": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "string", + "number", + "integer", + "boolean", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "security": { + "type": "array", + "items": { + "$ref": "#/definitions/securityRequirement" + }, + "uniqueItems": true + }, + "securityRequirement": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + "xml": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean", + "default": false + }, + "wrapped": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "tag": { + "type": "object", + "additionalProperties": false, + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "securityDefinitions": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/basicAuthenticationSecurity" + }, + { + "$ref": "#/definitions/apiKeySecurity" + }, + { + "$ref": "#/definitions/oauth2ImplicitSecurity" + }, + { + "$ref": "#/definitions/oauth2PasswordSecurity" + }, + { + "$ref": "#/definitions/oauth2ApplicationSecurity" + }, + { + "$ref": "#/definitions/oauth2AccessCodeSecurity" + } + ] + } + }, + "basicAuthenticationSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "basic" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "apiKeySecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "name", + "in" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "apiKey" + ] + }, + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "header", + "query" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2ImplicitSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "authorizationUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "implicit" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2PasswordSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "tokenUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "password" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2ApplicationSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "tokenUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "application" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2AccessCodeSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "authorizationUrl", + "tokenUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "accessCode" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2Scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "mediaTypeList": { + "type": "array", + "items": { + "$ref": "#/definitions/mimeType" + }, + "uniqueItems": true + }, + "parametersList": { + "type": "array", + "description": "The parameters needed to send a valid API call.", + "additionalItems": false, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/parameter" + }, + { + "$ref": "#/definitions/jsonReference" + } + ] + }, + "uniqueItems": true + }, + "schemesList": { + "type": "array", + "description": "The transfer protocol of the API.", + "items": { + "type": "string", + "enum": [ + "http", + "https", + "ws", + "wss" + ] + }, + "uniqueItems": true + }, + "collectionFormat": { + "type": "string", + "enum": [ + "csv", + "ssv", + "tsv", + "pipes" + ], + "default": "csv" + }, + "collectionFormatWithMulti": { + "type": "string", + "enum": [ + "csv", + "ssv", + "tsv", + "pipes", + "multi" + ], + "default": "csv" + }, + "title": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/title" + }, + "description": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/description" + }, + "default": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/default" + }, + "multipleOf": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf" + }, + "maximum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/maximum" + }, + "exclusiveMaximum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum" + }, + "minimum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/minimum" + }, + "exclusiveMinimum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum" + }, + "maxLength": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minLength": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "pattern": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/pattern" + }, + "maxItems": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minItems": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "uniqueItems": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems" + }, + "enum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/enum" + }, + "jsonReference": { + "type": "object", + "required": [ + "$ref" + ], + "additionalProperties": false, + "properties": { + "$ref": { + "type": "string" + } + } + } + } + } + \ No newline at end of file diff --git a/lib/common/UserError.js b/lib/common/UserError.js new file mode 100644 index 000000000..f420f8908 --- /dev/null +++ b/lib/common/UserError.js @@ -0,0 +1,15 @@ +/** + * constructor userError + * @constructor + * @param {*} message errorMessage + * @param {*} data additional data to be reported + */ +class UserError extends Error { + constructor(message, data) { + super(message); + this.name = 'UserError'; + this.data = data || {}; + } +} + +module.exports = UserError; diff --git a/lib/common/generateValidationError.js b/lib/common/generateValidationError.js new file mode 100644 index 000000000..815e59c94 --- /dev/null +++ b/lib/common/generateValidationError.js @@ -0,0 +1,75 @@ +const Ajv = require('ajv-draft-04'), + UserError = require('./UserError'), + addFormats = require('ajv-formats'), + openapi3Schema = require('../../assets/openapi3Schema.json'), + swagger2Schema = require('../../assets/swagger2Schema.json'), + openapi3Utils = require('../30XUtils/schemaUtils30X'), + swagger2Utils = require('../swaggerUtils/schemaUtilsSwagger'); + +/** + * Constructs Error message from given AJV Validation Error object. + * + * @param {Object} ajvError - AJV Validation Error object (reference: https://ajv.js.org/#validation-errors) + * @returns {String} - Error message constructed (To be displayed as error message for invalid specs) + */ +function constructErrorMessage (ajvError) { + let message = 'Provided API Specification is invalid, '; + + message += ajvError.message; + + if (ajvError.params) { + message += ' ' + JSON.stringify(ajvError.params); + } + + if (ajvError.instancePath) { + message += ` at "${ajvError.instancePath}"`; + } + + return message; +} + +/** + * Validates given OpenAPI specification against JSON schema of OpenAPI spec using AJV and + * generates UserError or Unhandled error depending upon type of error. + * + * @param {Object} definition - Parsed OAS definition + * @param {string} definitionVersion - Corresponding Definition version + * @param {*} error - Original Error object + * @returns {*} - Generated Error object + */ +function generateError (definition, definitionVersion, error) { + let ajv = new Ajv({ + schemaId: 'auto', + strict: false + }), + validate, + valid = true; + + addFormats(ajv); + + if (definitionVersion === openapi3Utils.version) { + validate = ajv.compile(openapi3Schema); + } + else if (definitionVersion === swagger2Utils.version) { + validate = ajv.compile(swagger2Schema); + } + + validate && (valid = validate(definition)); + + if (valid) { + if (error instanceof Error) { + return error; + } + + const errorMessage = typeof error === 'string' ? error : _.get(error, 'message', 'Failed to generate collection.'); + + return new Error(errorMessage); + } + else { + return new UserError(constructErrorMessage(validate.errors[0]), error); + } +} + +module.exports = { + generateError +}; diff --git a/lib/schemapack.js b/lib/schemapack.js index 8ebb7c130..692ccd524 100644 --- a/lib/schemapack.js +++ b/lib/schemapack.js @@ -28,6 +28,7 @@ const COLLECTION_NAME = 'Imported from OpenAPI 3.0', schemaUtils = require('./schemaUtils'), v2 = require('../libV2/index'), { getServersPathVars } = require('./common/schemaUtilsCommon.js'), + { generateError } = require('./common/generateValidationError.js'), MODULE_VERSION = { V1: 'v1', V2: 'v2' @@ -84,19 +85,26 @@ class SchemaPack { return callback(new OpenApiErr('The schema must be validated before attempting conversion')); } - // We only convert if swagger is found otherwise this.openapi remains the same - return convertToOAS30IfSwagger(getConcreteSchemaUtils(this.input), this.openapi, (error, convertedOpenAPI) => { - if (error) { - return callback(error); - } + try { + // We only convert if swagger is found otherwise this.openapi remains the same + return convertToOAS30IfSwagger(getConcreteSchemaUtils(this.input), this.openapi, (error, convertedOpenAPI) => { + if (error) { + return callback(error); + } - this.openapi = convertedOpenAPI; + this.openapi = convertedOpenAPI; - this.concreteUtils = concreteUtils; - this.specComponents = concreteUtils.getRequiredData(this.openapi); + this.concreteUtils = concreteUtils; + this.specComponents = concreteUtils.getRequiredData(this.openapi); - v2.convertV2(this, callback); - }); + v2.convertV2(this, callback); + }); + } + catch (err) { + const error = generateError(this.openapi, _.get(this.validationResult, 'specificationVersion'), err); + + return callback(error); + } } // need to store the schema here @@ -300,124 +308,131 @@ class SchemaPack { return callback(new OpenApiErr('The schema must be validated before attempting conversion')); } - // We only convert if swagger is found otherwise this.openapi remains the same - convertToOAS30IfSwagger(concreteUtils, this.openapi, (error, newOpenapi) => { - if (error) { - return callback(error); - } + try { + // We only convert if swagger is found otherwise this.openapi remains the same + convertToOAS30IfSwagger(concreteUtils, this.openapi, (error, newOpenapi) => { + if (error) { + return callback(error); + } - this.openapi = newOpenapi; - // this cannot be attempted before validation - specComponentsAndUtils = { concreteUtils }; - Object.assign(specComponentsAndUtils, concreteUtils.getRequiredData(this.openapi)); + this.openapi = newOpenapi; + // this cannot be attempted before validation + specComponentsAndUtils = { concreteUtils }; + Object.assign(specComponentsAndUtils, concreteUtils.getRequiredData(this.openapi)); - // create and sanitize basic spec - openapi = this.openapi; - openapi.servers = _.isEmpty(openapi.servers) ? [{ url: '/' }] : openapi.servers; - openapi.securityDefs = _.get(openapi, 'components.securitySchemes', {}); - openapi.baseUrl = _.get(openapi, 'servers.0.url', '{{baseURL}}'); + // create and sanitize basic spec + openapi = this.openapi; + openapi.servers = _.isEmpty(openapi.servers) ? [{ url: '/' }] : openapi.servers; + openapi.securityDefs = _.get(openapi, 'components.securitySchemes', {}); + openapi.baseUrl = _.get(openapi, 'servers.0.url', '{{baseURL}}'); - // TODO: Multiple server variables need to be saved as environments - openapi.baseUrlVariables = _.get(openapi, 'servers.0.variables'); + // TODO: Multiple server variables need to be saved as environments + openapi.baseUrlVariables = _.get(openapi, 'servers.0.variables'); - // Fix {scheme} and {path} vars in the URL to :scheme and :path - openapi.baseUrl = schemaUtils.fixPathVariablesInUrl(openapi.baseUrl); + // Fix {scheme} and {path} vars in the URL to :scheme and :path + openapi.baseUrl = schemaUtils.fixPathVariablesInUrl(openapi.baseUrl); - // Creating a new instance of a Postman collection - // All generated folders and requests will go inside this - generatedStore.collection = new sdk.Collection({ - info: { - name: _.isEmpty(_.get(openapi, 'info.title')) ? COLLECTION_NAME : _.get(openapi, 'info.title') - } - }); + // Creating a new instance of a Postman collection + // All generated folders and requests will go inside this + generatedStore.collection = new sdk.Collection({ + info: { + name: _.isEmpty(_.get(openapi, 'info.title')) ? COLLECTION_NAME : _.get(openapi, 'info.title') + } + }); - if (openapi.security) { - authHelper = schemaUtils.getAuthHelper(openapi, openapi.security); - if (authHelper) { - generatedStore.collection.auth = authHelper; + if (openapi.security) { + authHelper = schemaUtils.getAuthHelper(openapi, openapi.security); + if (authHelper) { + generatedStore.collection.auth = authHelper; + } } - } - // ---- Collection Variables ---- - // adding the collection variables for all the necessary root level variables - // and adding them to the collection variables - schemaUtils.convertToPmCollectionVariables( - openapi.baseUrlVariables, - 'baseUrl', - openapi.baseUrl - ).forEach((element) => { - generatedStore.collection.variables.add(element); - }); + // ---- Collection Variables ---- + // adding the collection variables for all the necessary root level variables + // and adding them to the collection variables + schemaUtils.convertToPmCollectionVariables( + openapi.baseUrlVariables, + 'baseUrl', + openapi.baseUrl + ).forEach((element) => { + generatedStore.collection.variables.add(element); + }); - generatedStore.collection.describe(schemaUtils.getCollectionDescription(openapi)); + generatedStore.collection.describe(schemaUtils.getCollectionDescription(openapi)); - // Only change the stack limit if the optimizeConversion option is true - if (options.optimizeConversion) { - // Deciding stack limit based on size of the schema, number of refs and number of paths. - analysis = schemaUtils.analyzeSpec(openapi); + // Only change the stack limit if the optimizeConversion option is true + if (options.optimizeConversion) { + // Deciding stack limit based on size of the schema, number of refs and number of paths. + analysis = schemaUtils.analyzeSpec(openapi); - // Update options on the basis of analysis. - options = schemaUtils.determineOptions(analysis, options); - } - Object.assign(this.analytics, analysis); - Object.assign(this.analytics, { - assignedStack: options.stackLimit, - complexityScore: options.complexityScore - }); - - // ---- Collection Items ---- - // Adding the collection items from openapi spec based on folderStrategy option - // For tags, All operations are grouped based on respective tags object - // For paths, All operations are grouped based on corresponding paths - try { - if (options.folderStrategy === 'tags') { - schemaUtils.addCollectionItemsUsingTags( - openapi, - generatedStore, - specComponentsAndUtils, - options, - schemaCache - ); - } - else { - schemaUtils.addCollectionItemsUsingPaths( - openapi, - generatedStore, - specComponentsAndUtils, - options, - schemaCache - ); + // Update options on the basis of analysis. + options = schemaUtils.determineOptions(analysis, options); } + Object.assign(this.analytics, analysis); + Object.assign(this.analytics, { + assignedStack: options.stackLimit, + complexityScore: options.complexityScore + }); + + // ---- Collection Items ---- + // Adding the collection items from openapi spec based on folderStrategy option + // For tags, All operations are grouped based on respective tags object + // For paths, All operations are grouped based on corresponding paths + try { + if (options.folderStrategy === 'tags') { + schemaUtils.addCollectionItemsUsingTags( + openapi, + generatedStore, + specComponentsAndUtils, + options, + schemaCache + ); + } + else { + schemaUtils.addCollectionItemsUsingPaths( + openapi, + generatedStore, + specComponentsAndUtils, + options, + schemaCache + ); + } - if (options.includeWebhooks) { - schemaUtils.addCollectionItemsFromWebhooks( - openapi, - generatedStore, - specComponentsAndUtils, - options, - schemaCache - ); + if (options.includeWebhooks) { + schemaUtils.addCollectionItemsFromWebhooks( + openapi, + generatedStore, + specComponentsAndUtils, + options, + schemaCache + ); + } + } + catch (e) { + return callback(e); } - } - catch (e) { - return callback(e); - } - collectionJSON = generatedStore.collection.toJSON(); + collectionJSON = generatedStore.collection.toJSON(); - // this needs to be deleted as even if version is not specified to sdk, - // it returns a version property with value set as undefined - // this fails validation against v2.1 collection schema definition. - delete collectionJSON.info.version; + // this needs to be deleted as even if version is not specified to sdk, + // it returns a version property with value set as undefined + // this fails validation against v2.1 collection schema definition. + delete collectionJSON.info.version; - return callback(null, { - result: true, - output: [{ - type: 'collection', - data: collectionJSON - }], - analytics: this.analytics + return callback(null, { + result: true, + output: [{ + type: 'collection', + data: collectionJSON + }], + analytics: this.analytics + }); }); - }); + } + catch (err) { + const error = generateError(this.openapi, _.get(this.validationResult, 'specificationVersion'), err); + + return callback(error); + } } /** From 54b02fadffc9c307cd03fd0e98680815602c3831 Mon Sep 17 00:00:00 2001 From: Vishal Shingala Date: Fri, 5 May 2023 11:04:29 +0530 Subject: [PATCH 02/14] Added required ajv packages --- package-lock.json | 34 +++++++++++++++++++++++++++------- package.json | 3 ++- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a38dfd2f..1200168c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "4.12.0", "license": "Apache-2.0", "dependencies": { - "ajv": "8.1.0", + "ajv": "8.5.0", + "ajv-draft-04": "1.0.0", "ajv-formats": "2.1.1", "async": "3.2.4", "commander": "2.20.3", @@ -559,9 +560,9 @@ } }, "node_modules/ajv": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.1.0.tgz", - "integrity": "sha512-B/Sk2Ix7A36fs/ZkuGLIR86EdjbgR6fsAcbx9lOP/QBSXujDNbVmIS/U4Itz5k8fPFDeVZl/zQ/gJW4Jrq6XjQ==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.5.0.tgz", + "integrity": "sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ==", "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -573,6 +574,19 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ajv-formats": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", @@ -6004,9 +6018,9 @@ } }, "ajv": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.1.0.tgz", - "integrity": "sha512-B/Sk2Ix7A36fs/ZkuGLIR86EdjbgR6fsAcbx9lOP/QBSXujDNbVmIS/U4Itz5k8fPFDeVZl/zQ/gJW4Jrq6XjQ==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.5.0.tgz", + "integrity": "sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ==", "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -6014,6 +6028,12 @@ "uri-js": "^4.2.2" } }, + "ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "requires": {} + }, "ajv-formats": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", diff --git a/package.json b/package.json index 46090c0f2..66f78b77d 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,8 @@ "openapi2postmanv2": "./bin/openapi2postmanv2.js" }, "dependencies": { - "ajv": "8.1.0", + "ajv": "8.5.0", + "ajv-draft-04": "1.0.0", "ajv-formats": "2.1.1", "async": "3.2.4", "commander": "2.20.3", From e4b5d96fd7f4d854776cf1a9357c56aee228771c Mon Sep 17 00:00:00 2001 From: Aman Singh <121886615+aman-v-singh@users.noreply.github.com> Date: Mon, 8 May 2023 15:13:58 +0530 Subject: [PATCH 03/14] Checking for typeof bodyContent in getXmlVersionContent, expecting to be string --- lib/schemaUtils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/schemaUtils.js b/lib/schemaUtils.js index 920428277..644c8f0ae 100644 --- a/lib/schemaUtils.js +++ b/lib/schemaUtils.js @@ -2099,6 +2099,7 @@ module.exports = { } else { let getXmlVersionContent = (bodyContent) => { + bodyContent = (typeof bodyContent === 'string') ? bodyContent : ''; const regExp = new RegExp('([<\\?xml]+[\\s{1,}]+[version="\\d.\\d"]+[\\sencoding="]+.{1,15}"\\?>)'); let xmlBody = bodyContent; From 4428f772ec584aa69b58fe2a013c4d45a7603684 Mon Sep 17 00:00:00 2001 From: Vishal Shingala Date: Tue, 9 May 2023 18:28:10 +0530 Subject: [PATCH 04/14] Added support for reporting user errors with validation --- lib/common/generateValidationError.js | 9 +++++---- lib/schemapack.js | 16 +++++++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/common/generateValidationError.js b/lib/common/generateValidationError.js index 815e59c94..17a974094 100644 --- a/lib/common/generateValidationError.js +++ b/lib/common/generateValidationError.js @@ -4,7 +4,8 @@ const Ajv = require('ajv-draft-04'), openapi3Schema = require('../../assets/openapi3Schema.json'), swagger2Schema = require('../../assets/swagger2Schema.json'), openapi3Utils = require('../30XUtils/schemaUtils30X'), - swagger2Utils = require('../swaggerUtils/schemaUtilsSwagger'); + swagger2Utils = require('../swaggerUtils/schemaUtilsSwagger'), + { jsonPointerDecodeAndReplace } = require('../jsonPointer'); /** * Constructs Error message from given AJV Validation Error object. @@ -13,7 +14,7 @@ const Ajv = require('ajv-draft-04'), * @returns {String} - Error message constructed (To be displayed as error message for invalid specs) */ function constructErrorMessage (ajvError) { - let message = 'Provided API Specification is invalid, '; + let message = 'Provided API Specification is invalid. '; message += ajvError.message; @@ -21,8 +22,8 @@ function constructErrorMessage (ajvError) { message += ' ' + JSON.stringify(ajvError.params); } - if (ajvError.instancePath) { - message += ` at "${ajvError.instancePath}"`; + if (typeof ajvError.instancePath === 'string') { + message += ` at "${jsonPointerDecodeAndReplace(ajvError.instancePath)}"`; } return message; diff --git a/lib/schemapack.js b/lib/schemapack.js index 692ccd524..bfb0743e7 100644 --- a/lib/schemapack.js +++ b/lib/schemapack.js @@ -87,8 +87,10 @@ class SchemaPack { try { // We only convert if swagger is found otherwise this.openapi remains the same - return convertToOAS30IfSwagger(getConcreteSchemaUtils(this.input), this.openapi, (error, convertedOpenAPI) => { - if (error) { + return convertToOAS30IfSwagger(getConcreteSchemaUtils(this.input), this.openapi, (err, convertedOpenAPI) => { + if (err) { + const error = generateError(this.openapi, _.get(this.validationResult, 'specificationVersion'), err); + return callback(error); } @@ -310,8 +312,10 @@ class SchemaPack { try { // We only convert if swagger is found otherwise this.openapi remains the same - convertToOAS30IfSwagger(concreteUtils, this.openapi, (error, newOpenapi) => { - if (error) { + convertToOAS30IfSwagger(concreteUtils, this.openapi, (err, newOpenapi) => { + if (err) { + const error = generateError(this.openapi, _.get(this.validationResult, 'specificationVersion'), err); + return callback(error); } @@ -408,7 +412,9 @@ class SchemaPack { } } catch (e) { - return callback(e); + const error = generateError(this.openapi, _.get(this.validationResult, 'specificationVersion'), e); + + return callback(error); } collectionJSON = generatedStore.collection.toJSON(); From 2e656c7b30500a2b658ad2789253429d1840692b Mon Sep 17 00:00:00 2001 From: Vishal Shingala Date: Tue, 9 May 2023 18:48:32 +0530 Subject: [PATCH 05/14] Added support for reporting user errors with validation --- lib/common/generateValidationError.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/common/generateValidationError.js b/lib/common/generateValidationError.js index 17a974094..683cb2d83 100644 --- a/lib/common/generateValidationError.js +++ b/lib/common/generateValidationError.js @@ -46,6 +46,9 @@ function generateError (definition, definitionVersion, error) { validate, valid = true; + // Log original error for better obseravibility in case of User errors + console.error(error); + addFormats(ajv); if (definitionVersion === openapi3Utils.version) { From 9efb8172593210157f2cb36b01c5080eaacf9d04 Mon Sep 17 00:00:00 2001 From: Vishal Shingala Date: Thu, 11 May 2023 19:30:59 +0530 Subject: [PATCH 06/14] Fixed issue where conversion was stuck for certain schema type of string containg patterns --- assets/json-schema-faker.js | 4 +++- test/unit/faker.test.js | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/assets/json-schema-faker.js b/assets/json-schema-faker.js index 14ab0bfbe..5de28e540 100644 --- a/assets/json-schema-faker.js +++ b/assets/json-schema-faker.js @@ -23846,8 +23846,10 @@ function extend() { /** * CHANGE: This Makes sure that we're not adding extra spaces in generated value, * As such behaviour generates invalid data when pattern is mentioned. + * + * To avoid infinite loop, make sure we keep adding spaces in cases where value is empty string */ - value += (schema.pattern ? '' : ' ') + value; + value += ((schema.pattern && value.length !== 0) ? '' : ' ') + value; } if (value.length > max) { value = value.substr(0, max); diff --git a/test/unit/faker.test.js b/test/unit/faker.test.js index e2fc486e7..0c329dc99 100644 --- a/test/unit/faker.test.js +++ b/test/unit/faker.test.js @@ -121,4 +121,19 @@ describe('JSON SCHEMA FAKER TESTS', function () { expect(value.name).to.be.a('string'); }); }); + + it('Should successsfully generate data for certain patterns that can generate empty string', function () { + const schema = { + 'maxLength': 63, + 'minLength': 2, + 'pattern': '^[A-Za-z !#$%&0-9,\'*+\\-.()/:;=@\\\\_\\[\\]`{}]*$', + 'type': 'string', + 'description': 'The exact name on the credit card.' + }; + + var fakedData = schemaFaker(schema); + expect(fakedData).to.be.an('string'); + expect(fakedData.length >= 2).to.be.true; + expect(fakedData.length <= 63).to.be.true; + }); }); From cbe5002913107db5443fd4af149352dfef6c43f9 Mon Sep 17 00:00:00 2001 From: Vishal Shingala Date: Thu, 11 May 2023 20:01:02 +0530 Subject: [PATCH 07/14] Fixed an issue where definition validation was not considering multiple white space characters --- lib/common/versionUtils.js | 2 +- test/unit/versionUtils.test.js | 171 +++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 1 deletion(-) diff --git a/lib/common/versionUtils.js b/lib/common/versionUtils.js index 152e7fcdb..7ece46ace 100644 --- a/lib/common/versionUtils.js +++ b/lib/common/versionUtils.js @@ -21,7 +21,7 @@ const _ = require('lodash'), * @returns {object} the resultant regular expresion using the provided data */ function getVersionRegexp({ key, version }) { - return new RegExp(`${key}['|"]?:\\s?[\\\]?['|"]?${version}`); + return new RegExp(`${key}['|"]?\\s*:\\s*[\\\]?['|"]?${version}`); } /** diff --git a/test/unit/versionUtils.test.js b/test/unit/versionUtils.test.js index 90b956c81..feff4b214 100644 --- a/test/unit/versionUtils.test.js +++ b/test/unit/versionUtils.test.js @@ -25,6 +25,23 @@ describe('getSpecVersion', function() { expect(specVersion).to.be.equal('3.0'); }); + it('Should resolve as 3.0 even if the provided spec contain spaces before version from a YAML input', function() { + const inputData = 'openapi : 3.0.0' + + 'info:' + + ' version: 1.0.0' + + ' title: Sample API' + + ' description: A sample API to illustrate OpenAPI concepts' + + 'paths:' + + ' /list:' + + ' get:' + + ' description: Returns a list of stuff' + + ' responses:' + + ' \'200\':' + + ' description: Successful response', + specVersion = getSpecVersion({ type: stringType, data: inputData }); + expect(specVersion).to.be.equal('3.0'); + }); + it('Should resolve as 3.1 the provided spec version from a YAML input', function() { const inputData = 'openapi: 3.1.0' + 'info:' + @@ -48,6 +65,30 @@ describe('getSpecVersion', function() { expect(specVersion).to.be.equal('3.1'); }); + it('Should resolve as 3.1 even if the provided spec contain spaces before version from a YAML input', function() { + // Below data contains tabs and spaces before version field which is considered a valid YAML + const inputData = 'openapi : \t"3.1.0"' + + 'info:' + + ' title: Non-oAuth Scopes example' + + ' version: 1.0.0' + + 'paths:' + + ' /users:' + + ' get:' + + ' security:' + + ' - bearerAuth:' + + ' - \'read:users\'' + + ' - \'public\'' + + 'components:' + + ' securitySchemes:' + + ' bearerAuth:' + + ' type: http' + + ' scheme: bearer' + + ' bearerFormat: jwt' + + ' description: \'note: non-oauth scopes are not defined at the securityScheme level\'', + specVersion = getSpecVersion({ type: stringType, data: inputData }); + expect(specVersion).to.be.equal('3.1'); + }); + it('Should resolve as 2.0 the provided spec version from a YAML input', function() { const inputData = 'swagger: "2.0"' + 'info:' + @@ -74,6 +115,33 @@ describe('getSpecVersion', function() { expect(specVersion).to.be.equal('2.0'); }); + it('Should resolve as 2.0 even if the provided spec contain spaces before version from a YAML input', function() { + // Below data contains newline before version field which is considered a valid YAML + const inputData = 'swagger :\n "2.0"\n' + + 'info:' + + ' version: 1.0.0' + + ' title: Swagger Petstore' + + ' license:' + + ' name: MIT' + + 'host: petstore.swagger.io' + + 'basePath: /v1' + + 'schemes:' + + ' - http' + + 'consumes:' + + ' - application/json' + + 'produces:' + + ' - application/json' + + 'paths:' + + ' /pets:' + + ' get:' + + ' summary: List all pets' + + ' operationId: listPets' + + ' tags:' + + ' - pets', + specVersion = getSpecVersion({ type: stringType, data: inputData }); + expect(specVersion).to.be.equal('2.0'); + }); + it('Should resolve as 3.0 the provided spec version from a JSON input', function() { const inputData = { 'openapi': '3.0.0', @@ -101,6 +169,33 @@ describe('getSpecVersion', function() { expect(specVersion).to.be.equal('3.0'); }); + it('Should resolve as 3.0 even if the provided spec contain spaces before version from a JSON input', function() { + const inputData = `{ + 'openapi' : '3.0.0', + 'info': { + 'version': '1.0.0', + 'title': 'Sample API', + 'description': 'A sample API to illustrate OpenAPI concepts' + }, + 'paths': { + '/users': { + 'get': { + 'security': [ + { + 'bearerAuth': [ + 'read:users', + 'public' + ] + } + ] + } + } + } + }`, + specVersion = getSpecVersion({ type: stringType, data: inputData }); + expect(specVersion).to.be.equal('3.0'); + }); + it('Should resolve as 3.1 the provided spec version from a JSON input', function() { const inputData = { 'openapi': '3.1.0', @@ -137,6 +232,43 @@ describe('getSpecVersion', function() { expect(specVersion).to.be.equal('3.1'); }); + it('Should resolve as 3.1 even if the provided spec contain spaces before version from a JSON input', function() { + // Below data contains both tab and spaces after openapi field + const inputData = `{ + 'openapi' : '3.1.0', + 'info': { + 'title': 'Non-oAuth Scopes example', + 'version': '1.0.0' + }, + 'paths': { + '/users': { + 'get': { + 'security': [ + { + 'bearerAuth': [ + 'read:users', + 'public' + ] + } + ] + } + } + }, + 'components': { + 'securitySchemes': { + 'bearerAuth': { + 'type': 'http', + 'scheme': 'bearer', + 'bearerFormat': 'jwt', + 'description': 'note: non-oauth scopes are not defined at the securityScheme level' + } + } + } + }`, + specVersion = getSpecVersion({ type: stringType, data: inputData }); + expect(specVersion).to.be.equal('3.1'); + }); + it('Should resolve as 2.0 the provided spec version from a JSON input', function() { const inputData = { 'swagger': '2.0', @@ -173,6 +305,45 @@ describe('getSpecVersion', function() { specVersion = getSpecVersion({ type: jsonType, data: inputData }); expect(specVersion).to.be.equal('2.0'); }); + + it('Should resolve as 2.0 even if the provided spec contain spaces before version from a JSON input', function() { + // Below data contains new line before version field which is a valid json + const inputData = `{ + 'swagger': + '2.0', + 'info': { + 'version': '1.0.0', + 'title': 'Swagger Petstore', + 'license': { + 'name': 'MIT' + } + }, + 'host': 'petstore.swagger.io', + 'basePath': '/v1', + 'schemes': [ + 'http' + ], + 'consumes': [ + 'application/json' + ], + 'produces': [ + 'application/json' + ], + 'paths': { + '/pets': { + 'get': { + 'summary': 'List all pets', + 'operationId': 'listPets', + 'tags': [ + 'pets' + ] + } + } + } + }`, + specVersion = getSpecVersion({ type: stringType, data: inputData }); + expect(specVersion).to.be.equal('2.0'); + }); }); describe('filterOptionsByVersion method', function() { From 2d0842b1745957b8bac15a6aaa7867f140efe0e1 Mon Sep 17 00:00:00 2001 From: Vishal Shingala Date: Thu, 11 May 2023 20:44:17 +0530 Subject: [PATCH 08/14] Fixed an issue where definition validation was not considering multiple white space characters --- test/unit/versionUtils.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit/versionUtils.test.js b/test/unit/versionUtils.test.js index feff4b214..48576dd76 100644 --- a/test/unit/versionUtils.test.js +++ b/test/unit/versionUtils.test.js @@ -485,23 +485,23 @@ describe('compareVersion method', function () { describe('getVersionRegexBySpecificationVersion method', function () { it('should return regex for 3.0', function () { const result = getVersionRegexBySpecificationVersion('3.0'); - expect(result.toString()).to.equal('/openapi[\'|\"]?:\\s?[\\]?[\'|\"]?3.0/'); + expect(result.toString()).to.equal('/openapi[\'|\"]?\\s*:\\s*[\\]?[\'|\"]?3.0/'); }); it('should return regex for 3.0.0', function () { const result = getVersionRegexBySpecificationVersion('3.0.0'); - expect(result.toString()).to.equal('/openapi[\'|\"]?:\\s?[\\]?[\'|\"]?3.0/'); + expect(result.toString()).to.equal('/openapi[\'|\"]?\\s*:\\s*[\\]?[\'|\"]?3.0/'); }); it('should return regex for 3.1', function () { const result = getVersionRegexBySpecificationVersion('3.1'); - expect(result.toString()).to.equal('/openapi[\'|\"]?:\\s?[\\]?[\'|\"]?3.1/'); + expect(result.toString()).to.equal('/openapi[\'|\"]?\\s*:\\s*[\\]?[\'|\"]?3.1/'); }); it('should return regex for 2.0', function () { const result = getVersionRegexBySpecificationVersion('2.0'); - expect(result.toString()).to.equal('/swagger[\'|\"]?:\\s?[\\]?[\'|\"]?2.0/'); + expect(result.toString()).to.equal('/swagger[\'|\"]?\\s*:\\s*[\\]?[\'|\"]?2.0/'); }); it('should return regex for 3.0 as default', function () { const result = getVersionRegexBySpecificationVersion('invalid'); - expect(result.toString()).to.equal('/openapi[\'|\"]?:\\s?[\\]?[\'|\"]?3.0/'); + expect(result.toString()).to.equal('/openapi[\'|\"]?\\s*:\\s*[\\]?[\'|\"]?3.0/'); }); }); From afe9a58cc2ac992dd276f6a62d652761071b5519 Mon Sep 17 00:00:00 2001 From: Vishal Shingala Date: Fri, 12 May 2023 12:54:42 +0530 Subject: [PATCH 09/14] #708 Fixes issue where if string is defined for required field, conversion was failing --- assets/json-schema-faker.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/json-schema-faker.js b/assets/json-schema-faker.js index 5de28e540..ddb2aa9f7 100644 --- a/assets/json-schema-faker.js +++ b/assets/json-schema-faker.js @@ -24258,7 +24258,7 @@ function extend() { const properties = value.properties || {}; const patternProperties = value.patternProperties || {}; - const requiredProperties = typeof value.required === 'boolean' ? [] : (value.required || []).slice(); + const requiredProperties = (!Array.isArray(value.required)) ? [] : (value.required || []).slice(); const allowsAdditional = value.additionalProperties !== false; const propertyKeys = Object.keys(properties); @@ -24352,6 +24352,10 @@ function extend() { const skipped = []; const missing = []; + if (typeof _props !== 'object') { + console.log('here'); + } + _props.forEach(key => { for (let i = 0; i < ignoreProperties.length; i += 1) { if ((ignoreProperties[i] instanceof RegExp && ignoreProperties[i].test(key)) From ccda74f0c949b1110cf33dd446902aef68b23335 Mon Sep 17 00:00:00 2001 From: Vishal Shingala Date: Fri, 12 May 2023 13:01:53 +0530 Subject: [PATCH 10/14] #708 Fixes issue where if string is defined for required field, conversion was failing --- assets/json-schema-faker.js | 4 ---- test/unit/faker.test.js | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/assets/json-schema-faker.js b/assets/json-schema-faker.js index ddb2aa9f7..9650a59f6 100644 --- a/assets/json-schema-faker.js +++ b/assets/json-schema-faker.js @@ -24352,10 +24352,6 @@ function extend() { const skipped = []; const missing = []; - if (typeof _props !== 'object') { - console.log('here'); - } - _props.forEach(key => { for (let i = 0; i < ignoreProperties.length; i += 1) { if ((ignoreProperties[i] instanceof RegExp && ignoreProperties[i].test(key)) diff --git a/test/unit/faker.test.js b/test/unit/faker.test.js index 0c329dc99..465e4ae62 100644 --- a/test/unit/faker.test.js +++ b/test/unit/faker.test.js @@ -136,4 +136,22 @@ describe('JSON SCHEMA FAKER TESTS', function () { expect(fakedData.length >= 2).to.be.true; expect(fakedData.length <= 63).to.be.true; }); + + it('Should successsfully generate data iff required is defined as string', function () { + const schema = { + type: 'object', + required: 'timebase', + properties: { + timebase: { type: 'string' }, + linkid: { type: 'string' }, + chartRef: { type: 'string' } + } + }; + + var fakedData = schemaFaker(schema); + expect(fakedData).to.be.an('object'); + expect(fakedData.timebase).to.be.a('string'); + expect(fakedData.linkid).to.be.a('string'); + expect(fakedData.chartRef).to.be.a('string'); + }); }); From ce52c9f11e488e2df5d515e12e5a71897fc098a0 Mon Sep 17 00:00:00 2001 From: Vishal Shingala Date: Tue, 16 May 2023 19:07:51 +0530 Subject: [PATCH 11/14] Fixed issue where for certain path segments, collection generation failed --- lib/schemaUtils.js | 6 +++++- test/unit/util.test.js | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/lib/schemaUtils.js b/lib/schemaUtils.js index fcb5b452a..fedf49a7c 100644 --- a/lib/schemaUtils.js +++ b/lib/schemaUtils.js @@ -651,7 +651,11 @@ module.exports = { // adding children for the nodes in the trie // start at the top-level and do a DFS for (i = 0; i < pathLength; i++) { - if (!currentNode.children[currentPath[i]]) { + /** + * Use hasOwnProperty to determine if property exists as certain JS fuction are present + * as part of each object. e.g. `constructor`. + */ + if (!(typeof currentNode.children === 'object' && currentNode.children.hasOwnProperty(currentPath[i]))) { // if the currentPath doesn't already exist at this node, // add it as a folder currentNode.addChildren(currentPath[i], new Node({ diff --git a/test/unit/util.test.js b/test/unit/util.test.js index 54d1878e5..64478a7b8 100644 --- a/test/unit/util.test.js +++ b/test/unit/util.test.js @@ -686,6 +686,45 @@ describe('SCHEMA UTILITY FUNCTION TESTS ', function () { done(); }); + + it('should generate trie for definition with certain path segment same as JS object function names correctly', + function (done) { + var openapi = { + 'openapi': '3.0.0', + 'info': { + 'version': '1.0.0', + 'title': 'Swagger Petstore', + 'license': { + 'name': 'MIT' + } + }, + 'servers': [ + { + 'url': 'http://petstore.swagger.io/{v1}' + } + ], + 'paths': { + '/constructor/v3/update-constructor': { + 'get': { + 'summary': 'List all pets', + 'operationId': 'listPets', + 'responses': { + '200': { + 'description': 'An paged array of pets' + } + } + } + } + } + }, + output = SchemaUtils.generateTrieFromPaths(openapi), + root = output.tree.root; + + expect(root.children).to.be.an('object').that.has.all.keys('constructor'); + expect(root.children.constructor.requestCount).to.equal(1); + + done(); + }); }); describe('convertPathVariables', function() { From 19bfe522322d874afb1b2273a53d555f80581193 Mon Sep 17 00:00:00 2001 From: Vishal Shingala Date: Tue, 23 May 2023 20:13:05 +0530 Subject: [PATCH 12/14] Added support for generating body examples as XML if mentioned --- lib/common/js2xml.js | 472 ++++++++++++++++++ lib/schemaUtils.js | 76 ++- libV2/schemaUtils.js | 16 +- test/data/valid_swagger/yaml/xml_example.yaml | 233 +++++++++ test/unit/base.test.js | 100 +++- test/unit/convertV2.test.js | 85 +++- test/unit/util.test.js | 4 +- 7 files changed, 956 insertions(+), 30 deletions(-) create mode 100644 lib/common/js2xml.js create mode 100644 test/data/valid_swagger/yaml/xml_example.yaml diff --git a/lib/common/js2xml.js b/lib/common/js2xml.js new file mode 100644 index 000000000..95e4ba6e5 --- /dev/null +++ b/lib/common/js2xml.js @@ -0,0 +1,472 @@ +/* eslint-disable */ + +/** + * This code is stripped down version of https://github.com/ibm-maximo-dev/xml-js + * Only essential part to convert JSON to XML is used here. + * + * Ideally there should be no updates needed to this code as it already handles all types of data for JSON object. + * But in case this need updates, take a look at below file specifically from which this logic was inspired from. + * + * https://github.com/ibm-maximo-dev/xml-js/blob/master/lib/js2xml.js + */ + +var currentElement, currentElementName; + +const DEFAULT_OPTIONS = { + spaces: ' ', + compact: true +} + +function isArray (value) { + if (Array.isArray) { + return Array.isArray(value); + } + // fallback for older browsers like IE 8 + return Object.prototype.toString.call( value ) === '[object Array]'; +} + +// Options helper +const helper = { + copyOptions: function (options) { + var key, copy = {}; + for (key in options) { + if (options.hasOwnProperty(key)) { + copy[key] = options[key]; + } + } + return copy; + }, + + ensureFlagExists: function (item, options) { + if (!(item in options) || typeof options[item] !== 'boolean') { + options[item] = false; + } + }, + + ensureSpacesExists: function (options) { + if (!('spaces' in options) || (typeof options.spaces !== 'number' && typeof options.spaces !== 'string')) { + options.spaces = 0; + } + }, + + ensureAlwaysArrayExists: function (options) { + if (!('alwaysArray' in options) || (typeof options.alwaysArray !== 'boolean' && !isArray(options.alwaysArray))) { + options.alwaysArray = false; + } + }, + + ensureKeyExists: function (key, options) { + if (!(key + 'Key' in options) || typeof options[key + 'Key'] !== 'string') { + options[key + 'Key'] = options.compact ? '_' + key : key; + } + }, + + checkFnExists: function (key, options) { + return key + 'Fn' in options; + } +}; + +function validateOptions(userOptions) { + var options = helper.copyOptions(userOptions); + helper.ensureFlagExists('ignoreDeclaration', options); + helper.ensureFlagExists('ignoreInstruction', options); + helper.ensureFlagExists('ignoreAttributes', options); + helper.ensureFlagExists('ignoreText', options); + helper.ensureFlagExists('ignoreComment', options); + helper.ensureFlagExists('ignoreCdata', options); + helper.ensureFlagExists('ignoreDoctype', options); + helper.ensureFlagExists('compact', options); + helper.ensureFlagExists('indentText', options); + helper.ensureFlagExists('indentCdata', options); + helper.ensureFlagExists('indentAttributes', options); + helper.ensureFlagExists('indentInstruction', options); + helper.ensureFlagExists('fullTagEmptyElement', options); + helper.ensureFlagExists('noQuotesForNativeAttributes', options); + helper.ensureSpacesExists(options); + if (typeof options.spaces === 'number') { + options.spaces = Array(options.spaces + 1).join(' '); + } + helper.ensureKeyExists('declaration', options); + helper.ensureKeyExists('instruction', options); + helper.ensureKeyExists('attributes', options); + helper.ensureKeyExists('text', options); + helper.ensureKeyExists('comment', options); + helper.ensureKeyExists('cdata', options); + helper.ensureKeyExists('doctype', options); + helper.ensureKeyExists('type', options); + helper.ensureKeyExists('name', options); + helper.ensureKeyExists('elements', options); + helper.checkFnExists('doctype', options); + helper.checkFnExists('instruction', options); + helper.checkFnExists('cdata', options); + helper.checkFnExists('comment', options); + helper.checkFnExists('text', options); + helper.checkFnExists('instructionName', options); + helper.checkFnExists('elementName', options); + helper.checkFnExists('attributeName', options); + helper.checkFnExists('attributeValue', options); + helper.checkFnExists('attributes', options); + helper.checkFnExists('fullTagEmptyElement', options); + return options; +} + +function writeIndentation(options, depth, firstLine) { + return (!firstLine && options.spaces ? '\n' : '') + Array(depth + 1).join(options.spaces); +} + +function writeAttributes(attributes, options, depth) { + if (options.ignoreAttributes) { + return ''; + } + if ('attributesFn' in options) { + attributes = options.attributesFn(attributes, currentElementName, currentElement); + } + var key, + attr, + attrName, + attrPlusValue, + quote, + result = []; + // NOTE: we don't the length of the element name, so this is the best we can do + var attrLineWidth = (depth + 1) * options.spaces; + var wrapped = false; + for (key in attributes) { + if ( + attributes.hasOwnProperty(key) && + attributes[key] !== null && + attributes[key] !== undefined + ) { + quote = + options.noQuotesForNativeAttributes && + typeof attributes[key] !== "string" + ? "" + : '"'; + attr = "" + attributes[key]; // ensure number and boolean are converted to String + attr = attr.replace(/"/g, """); + attr = attr.replace(/ options.indentAttributesWidth + ) { + result.push(writeIndentation(options, depth + 1, false)); + attrLineWidth = + (depth + 1) * options.spaces + attrPlusValue.length + 1; + wrapped = true; + } else { + result.push(" "); + } + } else { + result.push( + options.spaces && options.indentAttributes + ? writeIndentation(options, depth + 1, false) + : " " + ); + } + result.push(attrPlusValue); + } + } + if ( + attributes && + Object.keys(attributes).length && + options.spaces && + options.indentAttributes + ) { + if ( + options.indentAttributesWidth && + !wrapped + ) { + result.push(" "); + } else { + result.push(writeIndentation(options, depth, false)); + } + } + return result.join(''); +} + +function writeDeclaration(declaration, options, depth) { + currentElement = declaration; + currentElementName = 'xml'; + return options.ignoreDeclaration ? '' : ''; +} + +function writeInstruction(instruction, options, depth) { + if (options.ignoreInstruction) { + return ''; + } + var key; + for (key in instruction) { + if (instruction.hasOwnProperty(key)) { + break; + } + } + var instructionName = 'instructionNameFn' in options ? options.instructionNameFn(key, instruction[key], currentElementName, currentElement) : key; + if (typeof instruction[key] === 'object') { + currentElement = instruction; + currentElementName = instructionName; + return ''; + } else { + var instructionValue = instruction[key] ? instruction[key] : ''; + if ('instructionFn' in options) instructionValue = options.instructionFn(instructionValue, key, currentElementName, currentElement); + return ''; + } +} + +function writeComment(comment, options) { + return options.ignoreComment ? '' : ''; +} + +function writeCdata(cdata, options) { + return options.ignoreCdata ? '' : '', ']]]]>')) + ']]>'; +} + +function writeDoctype(doctype, options) { + return options.ignoreDoctype ? '' : ''; +} + +function writeText(text, options) { + if (options.ignoreText) return ''; + text = '' + text; // ensure Number and Boolean are converted to String + text = text.replace(/&/g, '&'); // desanitize to avoid double sanitization + text = text.replace(/&/g, '&').replace(//g, '>'); + return 'textFn' in options ? options.textFn(text, currentElementName, currentElement) : text; +} + +function hasContent(element, options) { + var i; + if (element.elements && element.elements.length) { + for (i = 0; i < element.elements.length; ++i) { + switch (element.elements[i][options.typeKey]) { + case 'text': + if (options.indentText) { + return true; + } + break; // skip to next key + case 'cdata': + if (options.indentCdata) { + return true; + } + break; // skip to next key + case 'instruction': + if (options.indentInstruction) { + return true; + } + break; // skip to next key + case 'doctype': + case 'comment': + case 'element': + return true; + default: + return true; + } + } + } + return false; +} + +function writeElement(element, options, depth) { + currentElement = element; + currentElementName = element.name; + var xml = [], elementName = 'elementNameFn' in options ? options.elementNameFn(element.name, element) : element.name; + xml.push('<' + elementName); + if (element[options.attributesKey]) { + xml.push(writeAttributes(element[options.attributesKey], options, depth)); + } + var withClosingTag = element[options.elementsKey] && element[options.elementsKey].length || element[options.attributesKey] && element[options.attributesKey]['xml:space'] === 'preserve'; + if (!withClosingTag) { + if ('fullTagEmptyElementFn' in options) { + withClosingTag = options.fullTagEmptyElementFn(element.name, element); + } else { + withClosingTag = options.fullTagEmptyElement; + } + } + if (withClosingTag) { + xml.push('>'); + if (element[options.elementsKey] && element[options.elementsKey].length) { + xml.push(writeElements(element[options.elementsKey], options, depth + 1)); + currentElement = element; + currentElementName = element.name; + } + xml.push(options.spaces && hasContent(element, options) ? '\n' + Array(depth + 1).join(options.spaces) : ''); + xml.push(''); + } else { + xml.push('/>'); + } + return xml.join(''); +} + +function writeElements(elements, options, depth, firstLine) { + return elements.reduce(function (xml, element) { + var indent = writeIndentation(options, depth, firstLine && !xml); + switch (element.type) { + case 'element': return xml + indent + writeElement(element, options, depth); + case 'comment': return xml + indent + writeComment(element[options.commentKey], options); + case 'doctype': return xml + indent + writeDoctype(element[options.doctypeKey], options); + case 'cdata': return xml + (options.indentCdata ? indent : '') + writeCdata(element[options.cdataKey], options); + case 'text': return xml + (options.indentText ? indent : '') + writeText(element[options.textKey], options); + case 'instruction': + var instruction = {}; + instruction[element[options.nameKey]] = element[options.attributesKey] ? element : element[options.instructionKey]; + return xml + (options.indentInstruction ? indent : '') + writeInstruction(instruction, options, depth); + } + }, ''); +} + +function hasContentCompact(element, options, anyContent) { + var key; + for (key in element) { + if (element.hasOwnProperty(key)) { + switch (key) { + case options.parentKey: + case options.attributesKey: + break; // skip to next key + case options.textKey: + if (options.indentText || anyContent) { + return true; + } + break; // skip to next key + case options.cdataKey: + if (options.indentCdata || anyContent) { + return true; + } + break; // skip to next key + case options.instructionKey: + if (options.indentInstruction || anyContent) { + return true; + } + break; // skip to next key + case options.doctypeKey: + case options.commentKey: + return true; + default: + return true; + } + } + } + return false; +} + +function writeElementCompact(element, name, options, depth, indent) { + currentElement = element; + currentElementName = name; + var elementName = 'elementNameFn' in options ? options.elementNameFn(name, element) : name; + if (typeof element === 'undefined' || element === null || element === '') { + return 'fullTagEmptyElementFn' in options && options.fullTagEmptyElementFn(name, element) || options.fullTagEmptyElement ? '<' + elementName + '>' : '<' + elementName + '/>'; + } + var xml = []; + if (name) { + xml.push('<' + elementName); + if (typeof element !== 'object') { + xml.push('>' + writeText(element, options) + ''); + return xml.join(''); + } + if (element[options.attributesKey]) { + xml.push(writeAttributes(element[options.attributesKey], options, depth)); + } + var withClosingTag = hasContentCompact(element, options, true) || element[options.attributesKey] && element[options.attributesKey]['xml:space'] === 'preserve'; + if (!withClosingTag) { + if ('fullTagEmptyElementFn' in options) { + withClosingTag = options.fullTagEmptyElementFn(name, element); + } else { + withClosingTag = options.fullTagEmptyElement; + } + } + if (withClosingTag) { + xml.push('>'); + } else { + xml.push('/>'); + return xml.join(''); + } + } + xml.push(writeElementsCompact(element, options, depth + 1, false)); + currentElement = element; + currentElementName = name; + if (name) { + xml.push((indent ? writeIndentation(options, depth, false) : '') + ''); + } + return xml.join(''); +} + +function writeElementsCompact(element, options, depth, firstLine) { + var i, key, nodes, xml = []; + for (key in element) { + if (element.hasOwnProperty(key)) { + nodes = isArray(element[key]) ? element[key] : [element[key]]; + for (i = 0; i < nodes.length; ++i) { + switch (key) { + case options.declarationKey: xml.push(writeDeclaration(nodes[i], options, depth)); break; + case options.instructionKey: xml.push((options.indentInstruction ? writeIndentation(options, depth, firstLine) : '') + writeInstruction(nodes[i], options, depth)); break; + case options.attributesKey: case options.parentKey: break; // skip + case options.textKey: xml.push((options.indentText ? writeIndentation(options, depth, firstLine) : '') + writeText(nodes[i], options)); break; + case options.cdataKey: xml.push((options.indentCdata ? writeIndentation(options, depth, firstLine) : '') + writeCdata(nodes[i], options)); break; + case options.doctypeKey: xml.push(writeIndentation(options, depth, firstLine) + writeDoctype(nodes[i], options)); break; + case options.commentKey: xml.push(writeIndentation(options, depth, firstLine) + writeComment(nodes[i], options)); break; + default: xml.push(writeIndentation(options, depth, firstLine) + writeElementCompact(nodes[i], key, options, depth, hasContentCompact(nodes[i], options))); + } + firstLine = firstLine && !xml.length; + } + } + } + return xml.join(''); +} + +module.exports = function (js, indentChar) { + // If provided data is not JSON, return it as is. XML string could be already present as example. + if (typeof js !== 'object') { + return js; + } + + try { + let options = DEFAULT_OPTIONS; + + // Override indent character if defined. + indentChar && (options.spaces = indentChar); + options = validateOptions(options); + var xml = []; + currentElement = js; + currentElementName = '_root_'; + if (options.compact) { + xml.push(writeElementsCompact(js, options, 0, true)); + } else { + if (js[options.declarationKey]) { + xml.push(writeDeclaration(js[options.declarationKey], options, 0)); + } + if (js[options.elementsKey] && js[options.elementsKey].length) { + xml.push(writeElements(js[options.elementsKey], options, 0, !xml.length)); + } + } + return xml.join(''); + } catch (err) { + console.error(err); + + // Handle failures gracefully via returning the message inside body rather than failing entire collection + return 'Failed to generate XML data from provided Example.' + } +}; diff --git a/lib/schemaUtils.js b/lib/schemaUtils.js index fedf49a7c..0b45efcc5 100644 --- a/lib/schemaUtils.js +++ b/lib/schemaUtils.js @@ -13,6 +13,7 @@ const { formatDataPath, checkIsCorrectType, isKnownType } = require('./common/sc deref = require('./deref.js'), _ = require('lodash'), xmlFaker = require('./xmlSchemaFaker.js'), + js2xml = require('./common/js2xml.js'), openApiErr = require('./error.js'), ajvValidationError = require('./ajValidation/ajvValidationError'), utils = require('./utils.js'), @@ -141,6 +142,24 @@ function verifyDeprecatedProperties(resolvedSchema, includeDeprecated) { }); } +/** + * Adds XML version content for XML specific bodies. + * + * @param {*} bodyContent - XML Body content + * @returns {*} - Body with correct XML version content + */ +function getXmlVersionContent (bodyContent) { + bodyContent = (typeof bodyContent === 'string') ? bodyContent : ''; + const regExp = new RegExp('([<\\?xml]+[\\s{1,}]+[version="\\d.\\d"]+[\\sencoding="]+.{1,15}"\\?>)'); + let xmlBody = bodyContent; + + if (!bodyContent.match(regExp)) { + const versionContent = '\n'; + xmlBody = versionContent + xmlBody; + } + return xmlBody; +} + /** * Safe wrapper for schemaFaker that resolves references and * removes things that might make schemaFaker crash @@ -1396,7 +1415,7 @@ module.exports = { * @return {object} responseBody, contentType header needed */ convertToPmResponseBody: function(contentObj, components, options, schemaCache) { - var responseBody, cTypeHeader, hasComputedType, cTypes, + var responseBody, cTypeHeader, hasComputedType, cTypes, headerFamily, isJsonLike = false; if (!contentObj) { return { @@ -1432,11 +1451,17 @@ module.exports = { }; } } + + headerFamily = this.getHeaderFamily(cTypeHeader); responseBody = this.convertToPmBodyData(contentObj[cTypeHeader], REQUEST_TYPE.EXAMPLE, cTypeHeader, PARAMETER_SOURCE.RESPONSE, options.indentCharacter, components, options, schemaCache); - if (this.getHeaderFamily(cTypeHeader) === HEADER_TYPE.JSON) { + + if (headerFamily === HEADER_TYPE.JSON) { responseBody = JSON.stringify(responseBody, null, options.indentCharacter); } + else if (cTypeHeader === TEXT_XML || cTypeHeader === APP_XML || headerFamily === HEADER_TYPE.XML) { + responseBody = getXmlVersionContent(responseBody); + } else if (typeof responseBody !== 'string') { if (cTypeHeader === MEDIA_TYPE_ALL_RANGES) { if (!_.isObject(responseBody) && _.isFunction(_.get(responseBody, 'toString'))) { @@ -1556,8 +1581,9 @@ module.exports = { resolveTo = this.resolveToExampleOrSchema(requestType, options.requestParametersResolution, options.exampleParametersResolution); let concreteUtils = components && components.hasOwnProperty('concreteUtils') ? - components.concreteUtils : - DEFAULT_SCHEMA_UTILS; + components.concreteUtils : + DEFAULT_SCHEMA_UTILS, + headerFamily = this.getHeaderFamily(contentType); if (_.isEmpty(bodyObj)) { return bodyData; @@ -1566,7 +1592,7 @@ module.exports = { if (bodyObj.example && (resolveTo === 'example' || !bodyObj.schema)) { if (bodyObj.example.hasOwnProperty('$ref')) { bodyObj.example = this.getRefObject(bodyObj.example.$ref, components, options); - if (this.getHeaderFamily(contentType) === HEADER_TYPE.JSON) { + if (headerFamily === HEADER_TYPE.JSON) { // try to parse the example as JSON. OK if this fails // eslint-disable-next-line max-depth @@ -1578,10 +1604,19 @@ module.exports = { } } bodyData = bodyObj.example; + + // Convert example into XML if present as JSON data + if (contentType === TEXT_XML || contentType === APP_XML || headerFamily === HEADER_TYPE.XML) { + bodyData = js2xml(bodyData, indentCharacter); + } } else if (!_.isEmpty(bodyObj.examples) && (resolveTo === 'example' || !bodyObj.schema)) { // take one of the examples as the body and not all bodyData = this.getExampleData(bodyObj.examples, components, options); + + if (contentType === TEXT_XML || contentType === APP_XML || headerFamily === HEADER_TYPE.XML) { + bodyData = js2xml(bodyData, indentCharacter); + } } else if (bodyObj.schema) { if (bodyObj.schema.hasOwnProperty('$ref')) { @@ -1593,6 +1628,9 @@ module.exports = { resolvedSchema = this.getRefObject(bodyObj.schema.$ref, components, options); bodyObj.schema = concreteUtils.addOuterPropsToRefSchemaIfIsSupported(resolvedSchema, outerProps); } + else { + bodyObj.schema = this.getRefObject(bodyObj.schema.$ref, components, options); + } } if (options.schemaFaker) { if (this.getHeaderFamily(contentType) === HEADER_TYPE.XML) { @@ -1613,9 +1651,18 @@ module.exports = { } return ''; } - // Do not fake the bodyData if the complexity is 10. - bodyData = safeSchemaFaker(bodyObj.schema || {}, resolveTo, PROCESSING_TYPE.CONVERSION, parameterSourceOption, - components, schemaFormat, schemaCache, options); + + if ( + resolveTo === 'example' && + _.get(bodyObj.schema, 'example') !== undefined && + (contentType === TEXT_XML || contentType === APP_XML || headerFamily === HEADER_TYPE.XML) + ) { + bodyData = js2xml(bodyObj.schema.example, indentCharacter); + } + else { + bodyData = safeSchemaFaker(bodyObj.schema || {}, resolveTo, PROCESSING_TYPE.CONVERSION, parameterSourceOption, + components, schemaFormat, schemaCache, options); + } } else { // do not fake if the option is false @@ -2100,18 +2147,7 @@ module.exports = { }; } else { - let getXmlVersionContent = (bodyContent) => { - bodyContent = (typeof bodyContent === 'string') ? bodyContent : ''; - const regExp = new RegExp('([<\\?xml]+[\\s{1,}]+[version="\\d.\\d"]+[\\sencoding="]+.{1,15}"\\?>)'); - let xmlBody = bodyContent; - - if (!bodyContent.match(regExp)) { - const versionContent = '\n'; - xmlBody = versionContent + xmlBody; - } - return xmlBody; - }, - headerFamily; + let headerFamily; bodyData = this.convertToPmBodyData(contentObj[bodyType], requestType, bodyType, PARAMETER_SOURCE.REQUEST, options.indentCharacter, components, options, schemaCache); diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index 44a355240..ed66390fb 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -5,6 +5,7 @@ const schemaFaker = require('../assets/json-schema-faker'), _ = require('lodash'), mergeAllOf = require('json-schema-merge-allof'), xmlFaker = require('./xmlSchemaFaker.js'), + js2xml = require('../lib/common/js2xml'), URLENCODED = 'application/x-www-form-urlencoded', APP_JSON = 'application/json', APP_JS = 'application/javascript', @@ -1067,6 +1068,7 @@ let QUERYPARAM = 'query', resolveRequestBodyData = (context, requestBodySchema, bodyType) => { let { parametersResolution, indentCharacter } = context.computedOptions, + headerFamily = getHeaderFamily(bodyType), bodyData = '', shouldGenerateFromExample = parametersResolution === 'example', example, @@ -1128,6 +1130,11 @@ let QUERYPARAM = 'query', requestBodySchema = requestBodySchema.schema || requestBodySchema; requestBodySchema = resolveSchema(context, requestBodySchema); + // If schema object has example defined, try to use that if no example is defiend at request body level + if (example === undefined && _.get(requestBodySchema, 'example') !== undefined) { + example = requestBodySchema.example; + } + if (shouldGenerateFromExample && (example !== undefined || examples)) { /** * Here it could be example or examples (plural) @@ -1135,7 +1142,12 @@ let QUERYPARAM = 'query', */ const exampleData = example || getExampleData(context, examples); - bodyData = exampleData; + if (bodyType === APP_XML || bodyType === TEXT_XML || headerFamily === HEADER_TYPE.XML) { + bodyData = js2xml(exampleData, indentCharacter); + } + else { + bodyData = exampleData; + } } else if (requestBodySchema) { requestBodySchema = requestBodySchema.schema || requestBodySchema; @@ -1144,7 +1156,7 @@ let QUERYPARAM = 'query', requestBodySchema = resolveSchema(context, requestBodySchema); } - if (bodyType === APP_XML || bodyType === TEXT_XML) { + if (bodyType === APP_XML || bodyType === TEXT_XML || headerFamily === HEADER_TYPE.XML) { return xmlFaker(null, requestBodySchema, indentCharacter); } diff --git a/test/data/valid_swagger/yaml/xml_example.yaml b/test/data/valid_swagger/yaml/xml_example.yaml new file mode 100644 index 000000000..35a1589a7 --- /dev/null +++ b/test/data/valid_swagger/yaml/xml_example.yaml @@ -0,0 +1,233 @@ +swagger: '2.0' +info: + title: YAML data + version: '1.0' +schemes: + - https +securityDefinitions: + Bearer: + description: Authorization 'Bearer' token + in: header + name: Authorization + type: apiKey +tags: + - name: client + description: Client resources +paths: + /client: + get: + deprecated: false + summary: Query Client + produces: + - application/xml + responses: + '200': + description: OK + schema: + $ref: '#/definitions/Data' + post: + deprecated: false + summary: Query Client + produces: + - application/xml + responses: + '200': + description: OK + schema: + type: object + properties: + CstmrPmtStsRpt: + type: object + properties: + GrpHdr: + type: object + properties: + MsgId: + type: string + CreDtTm: + type: string + InitgPty: + type: object + properties: + Id: + type: object + properties: + OrgId: + type: object + properties: + BICOrBEI: + type: string + required: + - BICOrBEI + required: + - OrgId + required: + - Id + required: + - MsgId + - CreDtTm + - InitgPty + OrgnlGrpInfAndSts: + type: object + properties: + OrgnlMsgId: + type: string + Orgn1MsgNmId: + type: string + OrgnlCreDtTm: + type: string + Orgn1NbOfTxs: + type: string + required: + - OrgnlMsgId + - Orgn1MsgNmId + - OrgnlCreDtTm + - Orgn1NbOfTxs + OrgnlPmtInfAndSts: + type: array + items: + type: object + properties: + OrgnlPmtInfId: + type: string + TxInfAndSts: + type: object + properties: + OrgnlEndToEndId: + type: string + TxSts: + type: string + required: + - OrgnlEndToEndId + - TxSts + required: + - OrgnlPmtInfId + - TxInfAndSts + required: + - GrpHdr + - OrgnlGrpInfAndSts + - OrgnlPmtInfAndSts + required: + - CstmrPmtStsRpt + examples: + application/xml: + CstmrPmtStsRpt: + GrpHdr: + MsgId: 20201213-PSR/1798570726 + InitgPty: + Id: + OrgId: + BICOrBEI: US33 + OrgnlGrpInfAndSts: + OrgnlMsgId: '100060058' + Orgn1MsgNmId: pain.001.02 + OrgnlCreDtTm: '2023-05-16T14:35:23-05:00' + Orgn1NbOfTxs: '1' + OrgnlPmtInfAndSts: + - OrgnlPmtInfId: ASIA + TxInfAndSts: + OrgnlEndToEndId: ASIADD + TxSts: ACTC + - OrgnlPmtInfId: EU + TxInfAndSts: + OrgnlEndToEndId: EUDD + TxSts: EUTC +definitions: + Data: + type: object + properties: + CstmrPmtStsRpt: + type: object + properties: + GrpHdr: + type: object + properties: + MsgId: + type: string + CreDtTm: + type: string + InitgPty: + type: object + properties: + Id: + type: object + properties: + OrgId: + type: object + properties: + BICOrBEI: + type: string + required: + - BICOrBEI + required: + - OrgId + required: + - Id + required: + - MsgId + - CreDtTm + - InitgPty + OrgnlGrpInfAndSts: + type: object + properties: + OrgnlMsgId: + type: string + Orgn1MsgNmId: + type: string + OrgnlCreDtTm: + type: string + Orgn1NbOfTxs: + type: string + required: + - OrgnlMsgId + - Orgn1MsgNmId + - OrgnlCreDtTm + - Orgn1NbOfTxs + OrgnlPmtInfAndSts: + type: array + items: + type: object + properties: + OrgnlPmtInfId: + type: string + TxInfAndSts: + type: object + properties: + OrgnlEndToEndId: + type: string + TxSts: + type: string + required: + - OrgnlEndToEndId + - TxSts + required: + - OrgnlPmtInfId + - TxInfAndSts + required: + - GrpHdr + - OrgnlGrpInfAndSts + - OrgnlPmtInfAndSts + required: + - CstmrPmtStsRpt + example: + CstmrPmtStsRpt: + GrpHdr: + MsgId: 20231213-PSR/1798570726 + InitgPty: + Id: + OrgId: + BICOrBEI: US33 + OrgnlGrpInfAndSts: + OrgnlMsgId: '100060058' + Orgn1MsgNmId: pain.001.02 + OrgnlCreDtTm: '2023-05-16T14:35:23-05:00' + Orgn1NbOfTxs: '1' + OrgnlPmtInfAndSts: + - OrgnlPmtInfId: ASIA + TxInfAndSts: + OrgnlEndToEndId: ASIADD + TxSts: ACTC + - OrgnlPmtInfId: EU + TxInfAndSts: + OrgnlEndToEndId: EUDD + TxSts: EUTC diff --git a/test/unit/base.test.js b/test/unit/base.test.js index bbe204c75..ffd8b8312 100644 --- a/test/unit/base.test.js +++ b/test/unit/base.test.js @@ -1593,7 +1593,8 @@ describe('CONVERT FUNCTION TESTS ', function() { ' (boolean)\n' + ' (number)\n' + '', - expectedResponseBody = '\n' + + expectedResponseBody = '\n' + + '\n' + ' (integer)\n' + ' (string)\n' + ' (boolean)\n' + @@ -1623,7 +1624,8 @@ describe('CONVERT FUNCTION TESTS ', function() { ' (string)\n' + ' (boolean)\n' + '', - expectedResponseBody = '\n' + + expectedResponseBody = '\n' + + '\n' + ' (integer)\n' + ' (string)\n' + ' (boolean)\n' + @@ -1657,7 +1659,8 @@ describe('CONVERT FUNCTION TESTS ', function() { ' (string)\n' + ' (boolean)\n' + '', - expectedResponseBody = '\n' + + expectedResponseBody = '\n' + + '\n' + ' (integer)\n' + ' (string)\n' + ' (boolean)\n' + @@ -1696,7 +1699,8 @@ describe('CONVERT FUNCTION TESTS ', function() { ' (string)\n' + ' (boolean)\n' + '', - expectedResponseBody = '\n' + + expectedResponseBody = '\n' + + '\n' + ' (integer)\n' + ' (string)\n' + ' (boolean)\n' + @@ -1737,7 +1741,8 @@ describe('CONVERT FUNCTION TESTS ', function() { ' (boolean)\n' + ' \n' + '', - expectedResponseBody = '\n' + + expectedResponseBody = '\n' + + '\n' + ' \n' + ' (integer)\n' + ' (string)\n' + @@ -1961,8 +1966,8 @@ describe('CONVERT FUNCTION TESTS ', function() { expect(result.output[0].type).to.have.equal('collection'); expect(result.output[0].data).to.have.property('info'); expect(result.output[0].data).to.have.property('item'); + done(); }); - done(); }); it('must read values consumes', function (done) { @@ -2059,6 +2064,89 @@ describe('CONVERT FUNCTION TESTS ', function() { .and.to.have.all.keys('content', 'paging'); }); }); + + it('Should convert a swagger document with XML example correctly', function(done) { + const fileData = fs.readFileSync(path.join(__dirname, SWAGGER_20_FOLDER_YAML, 'xml_example.yaml'), 'utf8'), + input = { + type: 'string', + data: fileData + }; + Converter.convert(input, { }, (error, result) => { + expect(error).to.be.null; + expect(result.result).to.equal(true); + expect(result.output.length).to.equal(1); + expect(result.output[0].type).to.have.equal('collection'); + expect(result.output[0].data).to.have.property('info'); + expect(result.output[0].data).to.have.property('item'); + expect(result.output[0].data.item[0].item[0].response[0].body).to.eql(` + + + 20231213-PSR/1798570726 + + + + US33 + + + + + + 100060058 + pain.001.02 + 2023-05-16T14:35:23-05:00 + 1 + + + ASIA + + ASIADD + ACTC + + + + EU + + EUDD + EUTC + + +`); + expect(result.output[0].data.item[0].item[1].response[0].body).to.eql(` + + + 20201213-PSR/1798570726 + + + + US33 + + + + + + 100060058 + pain.001.02 + 2023-05-16T14:35:23-05:00 + 1 + + + ASIA + + ASIADD + ACTC + + + + EU + + EUDD + EUTC + + +`); + done(); + }); + }); }); describe('requestNameSource option', function() { diff --git a/test/unit/convertV2.test.js b/test/unit/convertV2.test.js index e4516f19f..0a3c09cb7 100644 --- a/test/unit/convertV2.test.js +++ b/test/unit/convertV2.test.js @@ -1872,8 +1872,8 @@ describe('The convert v2 Function', function() { expect(result.output[0].type).to.have.equal('collection'); expect(result.output[0].data).to.have.property('info'); expect(result.output[0].data).to.have.property('item'); + done(); }); - done(); }); it('must read values consumes', function (done) { @@ -1973,6 +1973,89 @@ describe('The convert v2 Function', function() { .and.to.have.all.keys('content', 'paging'); }); }); + + it('Should convert a swagger document with XML example correctly', function(done) { + const fileData = fs.readFileSync(path.join(__dirname, SWAGGER_20_FOLDER_YAML, 'xml_example.yaml'), 'utf8'), + input = { + type: 'string', + data: fileData + }; + Converter.convertV2(input, { parametersResolution: 'Example' }, (error, result) => { + expect(error).to.be.null; + expect(result.result).to.equal(true); + expect(result.output.length).to.equal(1); + expect(result.output[0].type).to.have.equal('collection'); + expect(result.output[0].data).to.have.property('info'); + expect(result.output[0].data).to.have.property('item'); + expect(result.output[0].data.item[0].item[0].response[0].body).to.eql(` + + + 20231213-PSR/1798570726 + + + + US33 + + + + + + 100060058 + pain.001.02 + 2023-05-16T14:35:23-05:00 + 1 + + + ASIA + + ASIADD + ACTC + + + + EU + + EUDD + EUTC + + +`); + expect(result.output[0].data.item[0].item[1].response[0].body).to.eql(` + + + 20201213-PSR/1798570726 + + + + US33 + + + + + + 100060058 + pain.001.02 + 2023-05-16T14:35:23-05:00 + 1 + + + ASIA + + ASIADD + ACTC + + + + EU + + EUDD + EUTC + + +`); + done(); + }); + }); }); it('Should honor indent character option', function(done) { diff --git a/test/unit/util.test.js b/test/unit/util.test.js index 64478a7b8..ef2f9befe 100644 --- a/test/unit/util.test.js +++ b/test/unit/util.test.js @@ -2332,6 +2332,7 @@ describe('SCHEMA UTILITY FUNCTION TESTS ', function () { }).responseBody; expect(pmResponseBody).to.equal( [ + '', '', ' (string)', ' ', @@ -2454,7 +2455,8 @@ describe('SCHEMA UTILITY FUNCTION TESTS ', function () { pmResponse = SchemaUtils.convertToPmResponse(response, code, {}, {}, { schemaFaker: true, indentCharacter: ' ' }).toJSON(); - expect(pmResponse.body).to.equal('\n (integer)\n (string)\n'); + expect(pmResponse.body).to.equal('\n' + + '\n (integer)\n (string)\n'); expect(pmResponse.name).to.equal(response.description); expect(pmResponse.code).to.equal(200); expect(pmResponse._postman_previewlanguage).to.equal('xml'); From f4e1c031580355410135bf9a3fb6e9497daec11f Mon Sep 17 00:00:00 2001 From: Vishal Shingala Date: Wed, 24 May 2023 12:19:05 +0530 Subject: [PATCH 13/14] feature/changelog-release-4.13.0 --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bcb638c1..defd7545d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ ## [Unreleased] +### Added + +- Added support for generating request and response bodies in correct XML format from mentioned examples. +- Added support for validation of specifications in case of errors to report User input errors correctly. + +### Fixed + +- Fixed issue where conversion was stuck for certain schemas with pattern. +- Fixed an issue where definition validation was not considering multiple white space characters. +- Fixed issue [#708](https://github.com/postmanlabs/openapi-to-postman/issues/708) where if string is defined for required field, conversion was failing. +- Fixed issue where for certain path segments, collection generation failed. +- Fixed TypeError occurring while checking typeof bodyContent in getXmlVersionContent. + ## [v4.12.0] - 2023-05-04 ### Added From ddcb412acb8b83c1a86c0c3f0e8f33beac9b0b54 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 24 May 2023 06:53:00 +0000 Subject: [PATCH 14/14] Prepare release v4.13.0 --- CHANGELOG.md | 20 ++++++++++++-------- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index defd7545d..2f705f358 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,18 +2,20 @@ ## [Unreleased] +## [v4.13.0] - 2023-05-24 + ### Added -- Added support for generating request and response bodies in correct XML format from mentioned examples. -- Added support for validation of specifications in case of errors to report User input errors correctly. +- Added support for generating request and response bodies in correct XML format from mentioned examples. +- Added support for validation of specifications in case of errors to report User input errors correctly. ### Fixed -- Fixed issue where conversion was stuck for certain schemas with pattern. -- Fixed an issue where definition validation was not considering multiple white space characters. -- Fixed issue [#708](https://github.com/postmanlabs/openapi-to-postman/issues/708) where if string is defined for required field, conversion was failing. -- Fixed issue where for certain path segments, collection generation failed. -- Fixed TypeError occurring while checking typeof bodyContent in getXmlVersionContent. +- Fixed issue where conversion was stuck for certain schemas with pattern. +- Fixed an issue where definition validation was not considering multiple white space characters. +- Fixed issue [#708](https://github.com/postmanlabs/openapi-to-postman/issues/708) where if string is defined for required field, conversion was failing. +- Fixed issue where for certain path segments, collection generation failed. +- Fixed TypeError occurring while checking typeof bodyContent in getXmlVersionContent. ## [v4.12.0] - 2023-05-04 @@ -560,7 +562,9 @@ Newer releases follow the [Keep a Changelog](https://keepachangelog.com/en/1.0.0 - Base release -[Unreleased]: https://github.com/postmanlabs/openapi-to-postman/compare/v4.12.0...HEAD +[Unreleased]: https://github.com/postmanlabs/openapi-to-postman/compare/v4.13.0...HEAD + +[v4.13.0]: https://github.com/postmanlabs/openapi-to-postman/compare/v4.12.0...v4.13.0 [v4.12.0]: https://github.com/postmanlabs/openapi-to-postman/compare/v4.11.0...v4.12.0 diff --git a/package-lock.json b/package-lock.json index 1200168c2..3f3022a58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openapi-to-postmanv2", - "version": "4.12.0", + "version": "4.13.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "openapi-to-postmanv2", - "version": "4.12.0", + "version": "4.13.0", "license": "Apache-2.0", "dependencies": { "ajv": "8.5.0", diff --git a/package.json b/package.json index 66f78b77d..a7544ceee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openapi-to-postmanv2", - "version": "4.12.0", + "version": "4.13.0", "description": "Convert a given OpenAPI specification to Postman Collection v2.0", "homepage": "https://github.com/postmanlabs/openapi-to-postman", "bugs": "https://github.com/postmanlabs/openapi-to-postman/issues",