More TypeScript
This commit is contained in:
Родитель
9b86c7b41c
Коммит
09d132ea09
8
cli.ts
8
cli.ts
|
@ -1,5 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/* tslint:disable */
|
||||
|
||||
import * as yargs from "yargs"
|
||||
import * as os from "os"
|
||||
import { log } from "./lib/util/logging"
|
||||
|
@ -7,8 +9,7 @@ import { log } from "./lib/util/logging"
|
|||
const defaultLogDir = log.directory
|
||||
const logFilepath = log.filepath
|
||||
|
||||
/* tslint:disable-next-line:no-var-requires */
|
||||
const packageVersion = require("./package.json").version
|
||||
const packageVersion = require("../package.json").version
|
||||
|
||||
yargs
|
||||
.version(packageVersion)
|
||||
|
@ -28,7 +29,8 @@ yargs
|
|||
})
|
||||
.global(["h", "l", "f"])
|
||||
.help()
|
||||
.argv;
|
||||
|
||||
if (yargs.argv._.length === 0 && yargs.argv.h === false) {
|
||||
yargs.coerce("help", arg => true)
|
||||
yargs.coerce("help", arg => true).argv;
|
||||
}
|
||||
|
|
4
index.ts
4
index.ts
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import * as C from "./lib/util/constants"
|
||||
|
||||
// Easy to use methods from validate.ts
|
||||
export {
|
||||
getDocumentsFromCompositeSwagger,
|
||||
|
@ -22,4 +24,4 @@ export { LiveValidator } from "./lib/validators/liveValidator"
|
|||
export { SpecResolver } from "./lib/validators/specResolver"
|
||||
|
||||
// Constants
|
||||
export { Constants } from "./lib/util/constants"
|
||||
export const Constants = C
|
||||
|
|
|
@ -3,16 +3,20 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// import vscodeJsonRpc = require("vscode-jsonrpc")
|
||||
import * as linq from "linq"
|
||||
import * as jsonPath from "jsonpath"
|
||||
import * as yaml from "js-yaml"
|
||||
import * as utils from "../util/utils"
|
||||
import { log } from "../util/logging"
|
||||
import { SpecValidator } from "../validators/specValidator"
|
||||
import * as specValidator from "../validators/specValidator"
|
||||
import * as extensionBase from "@microsoft.azure/autorest-extension-base"
|
||||
import { IAutoRestPluginInitiator } from
|
||||
"@microsoft.azure/autorest-extension-base/dist/lib/extension-base"
|
||||
import { SourceLocation } from
|
||||
"@microsoft.azure/autorest-extension-base/dist/lib/types"
|
||||
import { Unknown } from "../util/unknown"
|
||||
import { Error } from "../util/error"
|
||||
import { Spec } from "../validators/specResolver"
|
||||
|
||||
const openAPIDocUrl = "https://github.com/Azure/oav"
|
||||
|
||||
|
@ -22,14 +26,24 @@ const modelValidationCategory = "ExampleModelViolation"
|
|||
|
||||
class FormattedOutput {
|
||||
constructor(
|
||||
public readonly channel: string,
|
||||
public readonly details: any,
|
||||
public readonly code: any[],
|
||||
public readonly channel: Channel,
|
||||
public readonly details: {},
|
||||
public readonly code: string[],
|
||||
public readonly text: string,
|
||||
public readonly source: any[]) {
|
||||
public readonly source: SourceLocation[]) {
|
||||
}
|
||||
}
|
||||
|
||||
export type Channel = "information" | "warning" | "error" | "debug" | "verbose"
|
||||
|
||||
export interface Message {
|
||||
readonly channel: Channel
|
||||
readonly text: string
|
||||
readonly details: Unknown
|
||||
readonly code: string[]
|
||||
readonly source: SourceLocation[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise with the examples validation of the swagger.
|
||||
*/
|
||||
|
@ -59,14 +73,18 @@ extension.Add(
|
|||
await Promise.all(promises)
|
||||
})
|
||||
|
||||
export interface Options extends specValidator.Options {
|
||||
consoleLogLevel?: Unknown
|
||||
}
|
||||
|
||||
export async function openApiValidationExample(
|
||||
swagger: yaml.DocumentLoadResult, swaggerFileName: string, options?: any)
|
||||
: Promise<any[]> {
|
||||
swagger: yaml.DocumentLoadResult, swaggerFileName: string, options?: Options)
|
||||
: Promise<Message[]> {
|
||||
const formattedResult: FormattedOutput[] = []
|
||||
if (!options) { options = {} }
|
||||
options.consoleLogLevel = "off"
|
||||
log.consoleLogLevel = options.consoleLogLevel
|
||||
const specVal = new SpecValidator(swaggerFileName, swagger, options)
|
||||
const specVal = new specValidator.SpecValidator(swaggerFileName, swagger as Spec, options)
|
||||
// console.error(JSON.stringify(swagger, null, 2))
|
||||
await specVal.initialize()
|
||||
try {
|
||||
|
@ -74,15 +92,16 @@ export async function openApiValidationExample(
|
|||
const specValidationResult = specVal.specValidationResult
|
||||
for (const op of utils.getKeys(specValidationResult.operations)) {
|
||||
const xmsExamplesNode = specValidationResult.operations[op]["x-ms-examples"];
|
||||
for (const scenario of utils.getKeys(xmsExamplesNode.scenarios)) {
|
||||
const scenarios = xmsExamplesNode.scenarios as specValidator.SpecScenarios
|
||||
for (const scenario of utils.getKeys(scenarios)) {
|
||||
// invalid? meaning that there's an issue found in the validation
|
||||
const scenarioItem = xmsExamplesNode.scenarios[scenario]
|
||||
const scenarioItem = scenarios[scenario]
|
||||
if (scenarioItem.isValid === false) {
|
||||
// get path to x-ms-examples in swagger
|
||||
const xmsexPath = linq
|
||||
.from(jsonPath.nodes(
|
||||
swagger, `$.paths[*][?(@.operationId==='${op}')]["x-ms-examples"]`))
|
||||
.select((x: any) => x.path)
|
||||
.select(x => x.path)
|
||||
.firstOrDefault();
|
||||
if (!xmsexPath) {
|
||||
throw new Error("Model Validator: Path to x-ms-examples not found.")
|
||||
|
@ -99,13 +118,14 @@ export async function openApiValidationExample(
|
|||
// request
|
||||
const request = scenarioItem.request
|
||||
if (request.isValid === false) {
|
||||
const error = request.error
|
||||
const error = request.error as Error
|
||||
const innerErrors = error.innerErrors
|
||||
if (!innerErrors || !innerErrors.length) {
|
||||
throw new Error("Model Validator: Unexpected format.")
|
||||
}
|
||||
for (const innerError of innerErrors) {
|
||||
const path = convertIndicesFromStringToNumbers(innerError.path)
|
||||
const innerErrorPath = innerError.path as string[]
|
||||
const path = convertIndicesFromStringToNumbers(innerErrorPath)
|
||||
// console.error(JSON.stringify(error, null, 2))
|
||||
const resultDetails = {
|
||||
type: "Error",
|
||||
|
@ -140,7 +160,7 @@ export async function openApiValidationExample(
|
|||
for (const responseCode of utils.getKeys(scenarioItem.responses)) {
|
||||
const response = scenarioItem.responses[responseCode]
|
||||
if (response.isValid === false) {
|
||||
const error = response.error
|
||||
const error = response.error as Error
|
||||
const innerErrors = error.innerErrors
|
||||
if (!innerErrors || !innerErrors.length) {
|
||||
throw new Error("Model Validator: Unexpected format.")
|
||||
|
@ -196,10 +216,10 @@ export async function openApiValidationExample(
|
|||
* Path comes with indices as strings in "inner errors", so converting those to actual numbers for
|
||||
* path to work.
|
||||
*/
|
||||
function convertIndicesFromStringToNumbers(path: any) {
|
||||
const result = path.slice()
|
||||
function convertIndicesFromStringToNumbers(path: string[]): Array<string|number> {
|
||||
const result: Array<string|number> = path.slice()
|
||||
for (let i = 1; i < result.length; ++i) {
|
||||
const num = parseInt(result[i])
|
||||
const num = parseInt(result[i] as string)
|
||||
if (!isNaN(num) && result[i - 1] === "parameters") {
|
||||
result[i] = num
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ export const builder: yargs.CommandBuilder = {
|
|||
},
|
||||
}
|
||||
|
||||
export async function handler(argv: yargs.Arguments) {
|
||||
export async function handler(argv: yargs.Arguments): Promise<void> {
|
||||
log.debug(argv.toString())
|
||||
const specPath = argv.specPath
|
||||
const vOptions = {
|
||||
|
|
|
@ -40,7 +40,7 @@ export const builder: yargs.CommandBuilder = {
|
|||
},
|
||||
}
|
||||
|
||||
export async function handler(argv: yargs.Arguments) {
|
||||
export async function handler(argv: yargs.Arguments): Promise<void> {
|
||||
log.debug(argv.toString())
|
||||
const specPath = argv.specPath
|
||||
const operationIds = argv.operationIds
|
||||
|
|
|
@ -68,7 +68,7 @@ export const builder: yargs.CommandBuilder = {
|
|||
},
|
||||
}
|
||||
|
||||
export async function handler(argv: yargs.Arguments) {
|
||||
export async function handler(argv: yargs.Arguments): Promise<void> {
|
||||
log.debug(argv.toString())
|
||||
const specPath = argv.specPath
|
||||
const vOptions = {
|
||||
|
|
|
@ -21,7 +21,7 @@ export const builder: yargs.CommandBuilder = {
|
|||
},
|
||||
}
|
||||
|
||||
export function handler(argv: yargs.Arguments) {
|
||||
export async function handler(argv: yargs.Arguments): Promise<void> {
|
||||
log.debug(argv.toString())
|
||||
const specPath = argv.specPath
|
||||
const operationIds = argv.operationIds
|
||||
|
@ -30,8 +30,8 @@ export function handler(argv: yargs.Arguments) {
|
|||
logFilepath: argv.f,
|
||||
}
|
||||
if (specPath.match(/.*composite.*/ig) !== null) {
|
||||
return validate.validateExamplesInCompositeSpec(specPath, vOptions)
|
||||
await validate.validateExamplesInCompositeSpec(specPath, vOptions)
|
||||
} else {
|
||||
return validate.validateExamples(specPath, operationIds, vOptions)
|
||||
await validate.validateExamples(specPath, operationIds, vOptions)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ export const command = "validate-spec <spec-path>"
|
|||
|
||||
export const describe = "Performs semantic validation of the spec."
|
||||
|
||||
export function handler(argv: yargs.Arguments) {
|
||||
export async function handler(argv: yargs.Arguments): Promise<void> {
|
||||
log.debug(argv.toString())
|
||||
const specPath = argv.specPath
|
||||
const vOptions = {
|
||||
|
@ -18,8 +18,8 @@ export function handler(argv: yargs.Arguments) {
|
|||
}
|
||||
|
||||
if (specPath.match(/.*composite.*/ig) !== null) {
|
||||
return validate.validateCompositeSpec(specPath, vOptions)
|
||||
await validate.validateCompositeSpec(specPath, vOptions)
|
||||
} else {
|
||||
return validate.validateSpec(specPath, vOptions)
|
||||
await validate.validateSpec(specPath, vOptions)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,15 @@
|
|||
* license information.
|
||||
*/
|
||||
|
||||
import { LiveValidationError } from "./liveValidationError"
|
||||
import { Operation } from "sway"
|
||||
|
||||
/**
|
||||
* @class
|
||||
* Initializes a new instance of the PotentialOperationsResult class.
|
||||
*
|
||||
* @constructor
|
||||
* Provides information about the issue that occured while performing
|
||||
* Provides information about the issue that occurred while performing
|
||||
* live request and response validation.
|
||||
*
|
||||
* @member {Array<Operation>} List of potential operations found.
|
||||
|
@ -18,9 +21,9 @@
|
|||
*
|
||||
*/
|
||||
export class PotentialOperationsResult {
|
||||
public readonly operations: any[]
|
||||
public readonly reason: any
|
||||
constructor(operations: any[], reason: any) {
|
||||
public readonly operations: Operation[]
|
||||
public readonly reason?: LiveValidationError
|
||||
constructor(operations: Operation[], reason: undefined|LiveValidationError) {
|
||||
this.operations = operations || []
|
||||
if (reason) {
|
||||
this.reason = reason
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { Unknown } from "../util/unknown"
|
||||
|
||||
/*
|
||||
* @class
|
||||
* A Response wrapper that encapsulates basic info that can be validated for an HTTP(s) response.
|
||||
|
@ -12,7 +14,7 @@ export class ResponseWrapper {
|
|||
*
|
||||
* @param {number|string} statusCode The response statusCode
|
||||
*
|
||||
* @param {any} body The response body
|
||||
* @param {Unknown} body The response body
|
||||
*
|
||||
* @param {object} headers The response headers
|
||||
*
|
||||
|
@ -22,8 +24,8 @@ export class ResponseWrapper {
|
|||
*/
|
||||
constructor(
|
||||
public statusCode: number|string,
|
||||
public body: any,
|
||||
public headers: any,
|
||||
public body: Unknown,
|
||||
public headers: Unknown,
|
||||
public encoding?: string) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,25 +4,27 @@
|
|||
import * as url from "url"
|
||||
import * as utils from "../util/utils"
|
||||
import * as msRest from "ms-rest"
|
||||
import { Unknown } from "../util/unknown"
|
||||
|
||||
export interface Headers {
|
||||
[name: string]: string
|
||||
readonly [name: string]: string
|
||||
}
|
||||
|
||||
export type Request = msRest.WebResource
|
||||
|
||||
export interface Response {
|
||||
body: any
|
||||
headers: Headers
|
||||
readonly body: Unknown
|
||||
readonly headers: Headers
|
||||
readonly statusCode: string
|
||||
}
|
||||
|
||||
export interface Responses {
|
||||
longrunning: {
|
||||
initialResponse: Response
|
||||
finalResponse: Response
|
||||
readonly longrunning: {
|
||||
readonly initialResponse: Response
|
||||
readonly finalResponse: Response
|
||||
}
|
||||
standard: {
|
||||
finalResponse: Response
|
||||
readonly standard: {
|
||||
readonly finalResponse: Response
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,7 +40,7 @@ export class HttpTemplate {
|
|||
: "management.azure.com"
|
||||
}
|
||||
|
||||
protected getCurlRequestHeaders(padding?: any): string {
|
||||
protected getCurlRequestHeaders(padding?: string): string {
|
||||
let result = ``
|
||||
if (!padding) { padding = `` }
|
||||
if (this.request.body) {
|
||||
|
@ -55,31 +57,27 @@ export class HttpTemplate {
|
|||
}
|
||||
|
||||
protected getRequestBody(): string {
|
||||
let body = ``
|
||||
if (this.request && this.request.body !== null && this.request.body !== undefined) {
|
||||
body = JSON.stringify(this.request.body)
|
||||
}
|
||||
return body
|
||||
return this.request && this.request.body !== null && this.request.body !== undefined
|
||||
? JSON.stringify(this.request.body)
|
||||
: ""
|
||||
}
|
||||
|
||||
// The format for request body in Curl has been inspired from the following links:
|
||||
// - https://stackoverflow.com/questions/34847981/curl-with-multiline-of-json
|
||||
// - https://ok-b.org/t/34847981/curl-with-multiline-of-json
|
||||
protected getCurlRequestBody(padding?: string): string {
|
||||
let body = ``
|
||||
if (!padding) { padding = `` }
|
||||
if (this.request && this.request.body !== null && this.request.body !== undefined) {
|
||||
const part = JSON.stringify(this.request.body, null, 2).split(`\n`).join(`\n${padding}`)
|
||||
body = `\n${padding}-d @- << EOF\n${part}\n${padding}EOF`
|
||||
return `\n${padding}-d @- << EOF\n${part}\n${padding}EOF`
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
protected getResponseBody(response: Response): string {
|
||||
let body = ``
|
||||
if (response && response.body !== null && response.body !== undefined) {
|
||||
body = JSON.stringify(response.body)
|
||||
}
|
||||
return body
|
||||
return response && response.body !== null && response.body !== undefined
|
||||
? JSON.stringify(response.body)
|
||||
: ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
// import * as url from 'url'
|
||||
import { HttpTemplate, Request, Responses } from "./httpTemplate"
|
||||
import { HttpTemplate, Request, Responses, Response } from "./httpTemplate"
|
||||
import * as uuid from "uuid"
|
||||
import * as utils from "../util/utils"
|
||||
|
||||
|
@ -53,7 +52,7 @@ export class MarkdownHttpTemplate extends HttpTemplate {
|
|||
return result
|
||||
}
|
||||
|
||||
private getResponseHeaders(response: any): string {
|
||||
private getResponseHeaders(response: Response): string {
|
||||
let result = ``
|
||||
if (response.body) {
|
||||
result += `Content-Length: ${JSON.stringify(response.body).length}\n`
|
||||
|
@ -94,7 +93,7 @@ ${this.getRequestBody()}
|
|||
return requestTemplate
|
||||
}
|
||||
|
||||
private populateResponse(response: any, responseType: any) {
|
||||
private populateResponse(response: Response, responseType: string) {
|
||||
if (!responseType) { responseType = "Response" }
|
||||
const responseGuid = uuid.v4()
|
||||
const responseTemplate = `
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import * as utils from "../util/utils"
|
||||
import { HttpTemplate, Request, Responses, Response } from "./httpTemplate"
|
||||
import * as uuid from "uuid"
|
||||
import { Unknown } from "../util/unknown"
|
||||
|
||||
export class YamlHttpTemplate extends HttpTemplate {
|
||||
|
||||
|
@ -91,7 +92,7 @@ ${this.getRequestHeaders()}
|
|||
return requestTemplate
|
||||
}
|
||||
|
||||
private populateResponse(response: any, responseType: any): string {
|
||||
private populateResponse(response: Response, responseType: Unknown): string {
|
||||
if (!responseType) { responseType = "Response" }
|
||||
const responseGuid = uuid.v4()
|
||||
const date = new Date().toISOString().replace(/(\W)/ig, "")
|
||||
|
|
|
@ -1,14 +1,22 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
// import util = require('util')
|
||||
// import JsonRefs = require('json-refs')
|
||||
import yuml2svg = require("yuml2svg")
|
||||
import * as utils from "./util/utils"
|
||||
import { Constants } from "./util/constants"
|
||||
import * as C from "./util/constants"
|
||||
import { log } from "./util/logging"
|
||||
import { Unknown } from "./util/unknown"
|
||||
import { Spec } from "./validators/specResolver"
|
||||
import { Model } from "./util/utils"
|
||||
|
||||
const ErrorCodes = Constants.ErrorCodes;
|
||||
// const ErrorCodes = C.ErrorCodes;
|
||||
|
||||
export interface Options {
|
||||
readonly direction?: Unknown
|
||||
readonly shouldDisableAllof?: Unknown
|
||||
readonly shouldDisableProperties?: Unknown
|
||||
readonly shouldDisableRefs?: Unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* @class
|
||||
|
@ -16,13 +24,13 @@ const ErrorCodes = Constants.ErrorCodes;
|
|||
*/
|
||||
export class UmlGenerator {
|
||||
|
||||
private specInJson: any
|
||||
private readonly specInJson: Spec
|
||||
|
||||
private graphDefinition: any
|
||||
private graphDefinition: string
|
||||
|
||||
private options: any
|
||||
private readonly options: Options
|
||||
|
||||
private bg: any
|
||||
private readonly bg = "{bg:cornsilk}"
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
|
@ -32,15 +40,13 @@ export class UmlGenerator {
|
|||
*
|
||||
* @return {object} An instance of the UmlGenerator class.
|
||||
*/
|
||||
constructor(specInJson: any, options: any) {
|
||||
constructor(specInJson: null|undefined|Spec, options: null|undefined|Options) {
|
||||
if (specInJson === null || specInJson === undefined || typeof specInJson !== "object") {
|
||||
throw new Error("specInJson is a required property of type object")
|
||||
}
|
||||
this.specInJson = specInJson
|
||||
this.graphDefinition = ""
|
||||
if (!options) { options = {} }
|
||||
this.options = options
|
||||
this.bg = "{bg:cornsilk}"
|
||||
this.options = !options ? {} : options
|
||||
}
|
||||
|
||||
public async generateDiagramFromGraph(): Promise<string> {
|
||||
|
@ -69,9 +75,9 @@ export class UmlGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
private generateAllOfForModel(modelName: any, model: any): void {
|
||||
private generateAllOfForModel(modelName: Unknown, model: Model): void {
|
||||
if (model.allOf) {
|
||||
model.allOf.map((item: any) => {
|
||||
model.allOf.map(item => {
|
||||
const referencedModel = item
|
||||
const ref = item.$ref
|
||||
const segments = ref.split("/")
|
||||
|
@ -84,7 +90,7 @@ export class UmlGenerator {
|
|||
private generateModelPropertiesGraph(): void {
|
||||
const spec = this.specInJson
|
||||
const definitions = spec.definitions
|
||||
const references: any[] = []
|
||||
const references: string[] = []
|
||||
for (const modelName of utils.getKeys(definitions)) {
|
||||
const model = definitions[modelName]
|
||||
const modelProperties = model.properties
|
||||
|
@ -111,7 +117,8 @@ export class UmlGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
private getPropertyType(modelName: any, property: any, references: any) {
|
||||
private getPropertyType(
|
||||
modelName: Unknown, property: Model, references: string[]): string {
|
||||
if (property.type && property.type.match(/^(string|number|boolean)$/i) !== null) {
|
||||
return property.type
|
||||
}
|
||||
|
|
|
@ -1,84 +1,93 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export const Constants = {
|
||||
constraints: [
|
||||
"minLength",
|
||||
"maxLength",
|
||||
"minimum",
|
||||
"maximum",
|
||||
"enum",
|
||||
"maxItems",
|
||||
"minItems",
|
||||
"uniqueItems",
|
||||
"multipleOf",
|
||||
"pattern"
|
||||
],
|
||||
xmsExamples: "x-ms-examples",
|
||||
exampleInSpec: "example-in-spec",
|
||||
BodyParameterValid: "BODY_PARAMETER_VALID",
|
||||
xmsSkipUrlEncoding: "x-ms-skip-url-encoding",
|
||||
xmsParameterizedHost: "x-ms-parameterized-host",
|
||||
Errors: "Errors",
|
||||
Warnings: "Warnings",
|
||||
ErrorCodes: {
|
||||
InternalError: { name: "INTERNAL_ERROR", id: "OAV100" },
|
||||
InitializationError: { name: "INITIALIZATION_ERROR", id: "OAV101" },
|
||||
ResolveSpecError: { name: "RESOLVE_SPEC_ERROR", id: "OAV102" },
|
||||
RefNotFoundError: { name: "REF_NOTFOUND_ERROR", id: "OAV103" },
|
||||
JsonParsingError: { name: "JSON_PARSING_ERROR", id: "OAV104" },
|
||||
RequiredParameterExampleNotFound: {
|
||||
name: "REQUIRED_PARAMETER_EXAMPLE_NOT_FOUND",
|
||||
id: "OAV105"
|
||||
},
|
||||
ErrorInPreparingRequest: { name: "ERROR_IN_PREPARING_REQUEST", id: "OAV106" },
|
||||
XmsExampleNotFoundError: { name: "X-MS-EXAMPLE_NOTFOUND_ERROR", id: "OAV107" },
|
||||
ResponseValidationError: { name: "RESPONSE_VALIDATION_ERROR", id: "OAV108" },
|
||||
RequestValidationError: { name: "REQUEST_VALIDATION_ERROR", id: "OAV109" },
|
||||
ResponseBodyValidationError: { name: "RESPONSE_BODY_VALIDATION_ERROR", id: "OAV110" },
|
||||
ResponseStatusCodeNotInExample: { name: "RESPONSE_STATUS_CODE_NOT_IN_EXAMPLE", id: "OAV111" },
|
||||
ResponseStatusCodeNotInSpec: { name: "RESPONSE_STATUS_CODE_NOT_IN_SPEC", id: "OAV112" },
|
||||
ResponseSchemaNotInSpec: { nam: "RESPONSE_SCHEMA_NOT_IN_SPEC", id: "OAV113" },
|
||||
RequiredParameterNotInExampleError: {
|
||||
name: "REQUIRED_PARAMETER_NOT_IN_EXAMPLE_ERROR",
|
||||
id: "OAV114"
|
||||
},
|
||||
BodyParameterValidationError: { name: "BODY_PARAMETER_VALIDATION_ERROR", id: "OAV115" },
|
||||
TypeValidationError: { name: "TYPE_VALIDATION_ERROR", id: "OAV116" },
|
||||
ConstraintValidationError: { name: "CONSTRAINT_VALIDATION_ERROR", id: "OAV117" },
|
||||
StatuscodeNotInExampleError: { name: "STATUS_CODE_NOT_IN_EXAMPLE_ERROR", id: "OAV118" },
|
||||
SemanticValidationError: { name: "SEMANTIC_VALIDATION_ERROR", id: "OAV119" },
|
||||
MultipleOperationsFound: { name: "MULTIPLE_OPERATIONS_FOUND", id: "OAV120" },
|
||||
NoOperationFound: { name: "NO_OPERATION_FOUND", id: "OAV121" },
|
||||
IncorrectInput: { name: "INCORRECT_INPUT", id: "OAV122" },
|
||||
PotentialOperationSearchError: { name: "POTENTIAL_OPERATION_SEARCH_ERROR", id: "OAV123" },
|
||||
PathNotFoundInRequestUrl: { name: "PATH_NOT_FOUND_IN_REQUEST_URL", id: "OAV124" },
|
||||
OperationNotFoundInCache: { name: "OPERATION_NOT_FOUND_IN_CACHE", id: "OAV125" },
|
||||
OperationNotFoundInCacheWithVerb: {
|
||||
name: "OPERATION_NOT_FOUND_IN_CACHE_WITH_VERB",
|
||||
id: "OAV126"
|
||||
}, // Implies we found correct api-version + provider in cache
|
||||
OperationNotFoundInCacheWithApi: {
|
||||
name: "OPERATION_NOT_FOUND_IN_CACHE_WITH_API",
|
||||
id: "OAV127"
|
||||
}, // Implies we found correct provider in cache
|
||||
OperationNotFoundInCacheWithProvider: {
|
||||
name: "OPERATION_NOT_FOUND_IN_CACHE_WITH_PROVIDER",
|
||||
id: "OAV128"
|
||||
}, // Implies we never found correct provider in cache
|
||||
DoubleForwardSlashesInUrl: { name: "DOUBLE_FORWARD_SLASHES_IN_URL", id: "OAV129" }
|
||||
export const xmsParameterizedHost = "x-ms-parameterized-host"
|
||||
|
||||
export const xmsExamples = "x-ms-examples"
|
||||
|
||||
export const xmsSkipUrlEncoding = "x-ms-skip-url-encoding"
|
||||
|
||||
const constraints = [
|
||||
"minLength",
|
||||
"maxLength",
|
||||
"minimum",
|
||||
"maximum",
|
||||
"enum",
|
||||
"maxItems",
|
||||
"minItems",
|
||||
"uniqueItems",
|
||||
"multipleOf",
|
||||
"pattern"
|
||||
]
|
||||
|
||||
export const exampleInSpec = "example-in-spec"
|
||||
|
||||
const BodyParameterValid = "BODY_PARAMETER_VALID"
|
||||
|
||||
export const Errors = "Errors"
|
||||
|
||||
export const Warnings = "Warnings"
|
||||
|
||||
export const ErrorCodes = {
|
||||
InternalError: { name: "INTERNAL_ERROR", id: "OAV100" },
|
||||
InitializationError: { name: "INITIALIZATION_ERROR", id: "OAV101" },
|
||||
ResolveSpecError: { name: "RESOLVE_SPEC_ERROR", id: "OAV102" },
|
||||
RefNotFoundError: { name: "REF_NOTFOUND_ERROR", id: "OAV103" },
|
||||
JsonParsingError: { name: "JSON_PARSING_ERROR", id: "OAV104" },
|
||||
RequiredParameterExampleNotFound: {
|
||||
name: "REQUIRED_PARAMETER_EXAMPLE_NOT_FOUND",
|
||||
id: "OAV105"
|
||||
},
|
||||
EnvironmentVariables: {
|
||||
ClientId: "CLIENT_ID",
|
||||
Domain: "DOMAIN",
|
||||
ApplicationSecret: "APPLICATION_SECRET",
|
||||
AzureSubscriptionId: "AZURE_SUBSCRIPTION_ID",
|
||||
AzureLocation: "AZURE_LOCATION",
|
||||
AzureResourcegroup: "AZURE_RESOURCE_GROUP"
|
||||
ErrorInPreparingRequest: { name: "ERROR_IN_PREPARING_REQUEST", id: "OAV106" },
|
||||
XmsExampleNotFoundError: { name: "X-MS-EXAMPLE_NOTFOUND_ERROR", id: "OAV107" },
|
||||
ResponseValidationError: { name: "RESPONSE_VALIDATION_ERROR", id: "OAV108" },
|
||||
RequestValidationError: { name: "REQUEST_VALIDATION_ERROR", id: "OAV109" },
|
||||
ResponseBodyValidationError: { name: "RESPONSE_BODY_VALIDATION_ERROR", id: "OAV110" },
|
||||
ResponseStatusCodeNotInExample: { name: "RESPONSE_STATUS_CODE_NOT_IN_EXAMPLE", id: "OAV111" },
|
||||
ResponseStatusCodeNotInSpec: { name: "RESPONSE_STATUS_CODE_NOT_IN_SPEC", id: "OAV112" },
|
||||
ResponseSchemaNotInSpec: { name: "RESPONSE_SCHEMA_NOT_IN_SPEC", id: "OAV113" },
|
||||
RequiredParameterNotInExampleError: {
|
||||
name: "REQUIRED_PARAMETER_NOT_IN_EXAMPLE_ERROR",
|
||||
id: "OAV114"
|
||||
},
|
||||
unknownResourceProvider: "microsoft.unknown",
|
||||
unknownApiVersion: "unknown-api-version",
|
||||
knownTitleToResourceProviders: {
|
||||
ResourceManagementClient: "Microsoft.Resources"
|
||||
}
|
||||
BodyParameterValidationError: { name: "BODY_PARAMETER_VALIDATION_ERROR", id: "OAV115" },
|
||||
TypeValidationError: { name: "TYPE_VALIDATION_ERROR", id: "OAV116" },
|
||||
ConstraintValidationError: { name: "CONSTRAINT_VALIDATION_ERROR", id: "OAV117" },
|
||||
StatuscodeNotInExampleError: { name: "STATUS_CODE_NOT_IN_EXAMPLE_ERROR", id: "OAV118" },
|
||||
SemanticValidationError: { name: "SEMANTIC_VALIDATION_ERROR", id: "OAV119" },
|
||||
MultipleOperationsFound: { name: "MULTIPLE_OPERATIONS_FOUND", id: "OAV120" },
|
||||
NoOperationFound: { name: "NO_OPERATION_FOUND", id: "OAV121" },
|
||||
IncorrectInput: { name: "INCORRECT_INPUT", id: "OAV122" },
|
||||
PotentialOperationSearchError: { name: "POTENTIAL_OPERATION_SEARCH_ERROR", id: "OAV123" },
|
||||
PathNotFoundInRequestUrl: { name: "PATH_NOT_FOUND_IN_REQUEST_URL", id: "OAV124" },
|
||||
OperationNotFoundInCache: { name: "OPERATION_NOT_FOUND_IN_CACHE", id: "OAV125" },
|
||||
OperationNotFoundInCacheWithVerb: {
|
||||
name: "OPERATION_NOT_FOUND_IN_CACHE_WITH_VERB",
|
||||
id: "OAV126"
|
||||
}, // Implies we found correct api-version + provider in cache
|
||||
OperationNotFoundInCacheWithApi: {
|
||||
name: "OPERATION_NOT_FOUND_IN_CACHE_WITH_API",
|
||||
id: "OAV127"
|
||||
}, // Implies we found correct provider in cache
|
||||
OperationNotFoundInCacheWithProvider: {
|
||||
name: "OPERATION_NOT_FOUND_IN_CACHE_WITH_PROVIDER",
|
||||
id: "OAV128"
|
||||
}, // Implies we never found correct provider in cache
|
||||
DoubleForwardSlashesInUrl: { name: "DOUBLE_FORWARD_SLASHES_IN_URL", id: "OAV129" }
|
||||
}
|
||||
|
||||
export const knownTitleToResourceProviders = {
|
||||
ResourceManagementClient: "Microsoft.Resources"
|
||||
}
|
||||
|
||||
export const EnvironmentVariables = {
|
||||
ClientId: "CLIENT_ID",
|
||||
Domain: "DOMAIN",
|
||||
ApplicationSecret: "APPLICATION_SECRET",
|
||||
AzureSubscriptionId: "AZURE_SUBSCRIPTION_ID",
|
||||
AzureLocation: "AZURE_LOCATION",
|
||||
AzureResourcegroup: "AZURE_RESOURCE_GROUP"
|
||||
}
|
||||
|
||||
export const unknownResourceProvider = "microsoft.unknown"
|
||||
export const unknownApiVersion = "unknown-api-version"
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import { Unknown } from "./unknown"
|
||||
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export interface Error {
|
||||
code: any
|
||||
id: any
|
||||
message: any
|
||||
innerErrors: Error[]
|
||||
path?: any
|
||||
inner?: Error[]
|
||||
readonly code: string
|
||||
readonly id: string
|
||||
readonly message: string
|
||||
readonly innerErrors?: Error[]
|
||||
readonly path?: string[]
|
||||
readonly inner?: Error[]
|
||||
readonly errors?: Error[]
|
||||
}
|
||||
|
|
|
@ -5,10 +5,11 @@ import * as winston from "winston"
|
|||
import * as path from "path"
|
||||
import * as fs from "fs"
|
||||
import * as os from "os"
|
||||
import { Unknown } from "./unknown"
|
||||
|
||||
let logDir = path.resolve(os.homedir(), "oav_output")
|
||||
|
||||
let currentLogFile: any
|
||||
let currentLogFile: string
|
||||
|
||||
/*
|
||||
* Provides current time in custom format that will be used in naming log files. Example:
|
||||
|
@ -43,9 +44,9 @@ const customLogLevels = {
|
|||
}
|
||||
|
||||
export type ILogger = winston.LoggerInstance & {
|
||||
consoleLogLevel: any
|
||||
filepath: any
|
||||
directory: any
|
||||
consoleLogLevel: Unknown
|
||||
filepath: Unknown
|
||||
directory: Unknown
|
||||
}
|
||||
|
||||
export const log: ILogger = new (winston.Logger)({
|
||||
|
@ -74,15 +75,14 @@ Object.defineProperties(log, {
|
|||
`The logging level provided is "${level}". Valid values are: "${validLevels}".`)
|
||||
}
|
||||
this.transports.console.level = level
|
||||
return
|
||||
}
|
||||
},
|
||||
directory: {
|
||||
enumerable: true,
|
||||
get() {
|
||||
get(): string {
|
||||
return logDir
|
||||
},
|
||||
set(logDirectory) {
|
||||
set(logDirectory: string): void {
|
||||
if (!logDirectory || logDirectory && typeof logDirectory.valueOf() !== "string") {
|
||||
throw new Error('logDirectory cannot be null or undefined and must be of type "string".')
|
||||
}
|
||||
|
@ -91,12 +91,11 @@ Object.defineProperties(log, {
|
|||
fs.mkdirSync(logDirectory)
|
||||
}
|
||||
logDir = logDirectory
|
||||
return
|
||||
}
|
||||
},
|
||||
filepath: {
|
||||
enumerable: true,
|
||||
get() {
|
||||
get(): string {
|
||||
if (!currentLogFile) {
|
||||
const filename = `validate_log_${getTimeStamp()}.log`
|
||||
currentLogFile = path.join(this.directory, filename)
|
||||
|
@ -104,7 +103,7 @@ Object.defineProperties(log, {
|
|||
|
||||
return currentLogFile
|
||||
},
|
||||
set(logFilePath) {
|
||||
set(logFilePath: string): void {
|
||||
if (!logFilePath || logFilePath && typeof logFilePath.valueOf() !== "string") {
|
||||
throw new Error(
|
||||
"filepath cannot be null or undefined and must be of type string. It must be " +
|
||||
|
@ -122,7 +121,6 @@ Object.defineProperties(log, {
|
|||
filename: logFilePath
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export type Unknown = undefined|null|string|boolean|number|{}
|
|
@ -11,6 +11,27 @@ import { log } from "./logging"
|
|||
import request = require("request")
|
||||
import * as lodash from "lodash"
|
||||
import * as http from "http"
|
||||
import { Unknown } from "./unknown"
|
||||
import { Spec } from "../validators/specResolver"
|
||||
|
||||
export interface Definition {
|
||||
readonly allOf: Unknown[]
|
||||
}
|
||||
|
||||
export interface Definitions {
|
||||
readonly [name: string]: Definition
|
||||
}
|
||||
|
||||
/*
|
||||
export interface Doc {
|
||||
readonly documents: Unknown
|
||||
readonly definitions: Definitions
|
||||
}
|
||||
*/
|
||||
|
||||
export interface DocCache {
|
||||
[name: string]: Promise<Spec>
|
||||
}
|
||||
|
||||
/*
|
||||
* Caches the json docs that were successfully parsed by parseJson().
|
||||
|
@ -18,7 +39,7 @@ import * as http from "http"
|
|||
* key: docPath
|
||||
* value: parsed doc in JSON format
|
||||
*/
|
||||
export let docCache: any = {}
|
||||
export let docCache: DocCache = {}
|
||||
|
||||
export function clearCache(): void {
|
||||
docCache = {}
|
||||
|
@ -30,7 +51,7 @@ export function clearCache(): void {
|
|||
* because the buffer-to-string conversion in `fs.readFile()`
|
||||
* translates it to FEFF, the UTF-16 BOM.
|
||||
*/
|
||||
export function stripBOM(content: any) {
|
||||
export function stripBOM(content: Buffer|string): string {
|
||||
if (Buffer.isBuffer(content)) {
|
||||
content = content.toString()
|
||||
}
|
||||
|
@ -48,15 +69,14 @@ export function stripBOM(content: any) {
|
|||
*
|
||||
* @returns {object} jsonDoc - Parsed document in JSON format.
|
||||
*/
|
||||
export function parseJson(specPath: string): Promise<any> {
|
||||
export async function parseJson(specPath: string): Promise<Spec> {
|
||||
if (!specPath || (specPath && typeof specPath.valueOf() !== "string")) {
|
||||
const err = new Error(
|
||||
throw new Error(
|
||||
"A (github) url or a local file path to the swagger spec is required and must be of type " +
|
||||
"string.")
|
||||
return Promise.reject(err)
|
||||
}
|
||||
if (docCache[specPath]) {
|
||||
return Promise.resolve(docCache[specPath])
|
||||
return await docCache[specPath]
|
||||
}
|
||||
// url
|
||||
if (specPath.match(/^http.*/ig) !== null) {
|
||||
|
@ -68,21 +88,21 @@ export function parseJson(specPath: string): Promise<any> {
|
|||
}
|
||||
const res = makeRequest({ url: specPath, errorOnNon200Response: true })
|
||||
docCache[specPath] = res
|
||||
return res
|
||||
return await res
|
||||
} else {
|
||||
// local filepath
|
||||
try {
|
||||
const fileContent = stripBOM(fs.readFileSync(specPath, "utf8"))
|
||||
const result = parseContent(specPath, fileContent)
|
||||
docCache[specPath] = result
|
||||
return Promise.resolve(result)
|
||||
docCache[specPath] = Promise.resolve(result)
|
||||
return result
|
||||
} catch (err) {
|
||||
const msg =
|
||||
`Unable to read the content or execute "JSON.parse()" on the content of file ` +
|
||||
`"${specPath}". The error is:\n${err}`
|
||||
const e: any = new Error(msg)
|
||||
log.error(e)
|
||||
return Promise.reject(e)
|
||||
const e = new Error(msg)
|
||||
log.error(e.toString())
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -96,7 +116,7 @@ export function parseJson(specPath: string): Promise<any> {
|
|||
*
|
||||
* @returns {object} jsonDoc - Parsed document in JSON format.
|
||||
*/
|
||||
export function parseContent(filePath: string, fileContent: string) {
|
||||
export function parseContent(filePath: string, fileContent: string): Spec {
|
||||
let result = null
|
||||
if (/.*\.json$/ig.test(filePath)) {
|
||||
result = JSON.parse(fileContent)
|
||||
|
@ -111,38 +131,9 @@ export function parseContent(filePath: string, fileContent: string) {
|
|||
return result
|
||||
}
|
||||
|
||||
/*
|
||||
* A utility function to help us acheive stuff in the same way as async/await but with yield
|
||||
* statement and generator functions.
|
||||
* It waits till the task is over.
|
||||
* @param {function} A generator function as an input
|
||||
*/
|
||||
export function run(genfun: () => any) {
|
||||
// instantiate the generator object
|
||||
const gen = genfun()
|
||||
// This is the async loop pattern
|
||||
function next(err?: any, answer?: any) {
|
||||
let res
|
||||
if (err) {
|
||||
// if err, throw it into the wormhole
|
||||
return gen.throw(err)
|
||||
} else {
|
||||
// if good value, send it
|
||||
res = gen.next(answer)
|
||||
}
|
||||
if (!res.done) {
|
||||
// if we are not at the end
|
||||
// we have an async request to
|
||||
// fulfill, we do this by calling
|
||||
// `value` as a function
|
||||
// and passing it a callback
|
||||
// that receives err, answer
|
||||
// for which we'll just use `next()`
|
||||
res.value(next)
|
||||
}
|
||||
}
|
||||
// Kick off the async loop
|
||||
next()
|
||||
export type Options = request.CoreOptions & request.UrlOptions & {
|
||||
readonly url: string
|
||||
readonly errorOnNon200Response: Unknown
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -157,9 +148,9 @@ export function run(genfun: () => any) {
|
|||
*
|
||||
* @return {Promise} promise - A promise that resolves to the responseBody or rejects to an error.
|
||||
*/
|
||||
export function makeRequest(options: any): Promise<any> {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
request(options, (err: any, response: request.Response, responseBody: any): void => {
|
||||
export function makeRequest(options: Options): Promise<Spec> {
|
||||
const promise = new Promise<Spec>((resolve, reject) => {
|
||||
request(options, (err, response, responseBody) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
|
@ -211,8 +202,8 @@ export async function executePromisesSequentially(
|
|||
*
|
||||
* @return {string} result A random string
|
||||
*/
|
||||
export function generateRandomId(prefix: string, existingIds: any): string {
|
||||
let randomStr
|
||||
export function generateRandomId(prefix: string, existingIds: {}): string {
|
||||
let randomStr: string
|
||||
while (true) {
|
||||
randomStr = Math.random().toString(36).substr(2, 12)
|
||||
if (prefix && typeof prefix.valueOf() === "string") {
|
||||
|
@ -259,7 +250,6 @@ export function parseReferenceInSwagger(reference: string): Reference {
|
|||
throw new Error("reference cannot be null or undefined and it must be a non-empty string.")
|
||||
}
|
||||
|
||||
// let result: any = {}
|
||||
if (reference.includes("#")) {
|
||||
// local reference in the doc
|
||||
if (reference.startsWith("#/")) {
|
||||
|
@ -325,6 +315,10 @@ export function parseJsonWithPathFragments(...args: string[]) {
|
|||
return parseJson(specPath)
|
||||
}
|
||||
|
||||
export interface Map<T> {
|
||||
[name: string]: T
|
||||
}
|
||||
|
||||
/*
|
||||
* Merges source object into the target object
|
||||
* @param {object} source The object that needs to be merged
|
||||
|
@ -333,7 +327,7 @@ export function parseJsonWithPathFragments(...args: string[]) {
|
|||
*
|
||||
* @returns {object} target - Returns the merged target object.
|
||||
*/
|
||||
export function mergeObjects(source: any, target: any) {
|
||||
export function mergeObjects<T extends Map<any>>(source: T, target: T): T {
|
||||
Object.keys(source).forEach((key) => {
|
||||
if (Array.isArray(source[key])) {
|
||||
if (target[key] && !Array.isArray(target[key])) {
|
||||
|
@ -360,7 +354,7 @@ export function mergeObjects(source: any, target: any) {
|
|||
*
|
||||
* @returns {array} target - Returns the merged target array.
|
||||
*/
|
||||
export function mergeArrays(source: any[], target: any[]) {
|
||||
export function mergeArrays<T>(source: T[], target: T[]): T[] {
|
||||
if (!Array.isArray(target) || (!Array.isArray(source))) {
|
||||
return target
|
||||
}
|
||||
|
@ -379,7 +373,7 @@ export function mergeArrays(source: any[], target: any[]) {
|
|||
*
|
||||
* @returns {any} result - Returns the value that the ptr points to, in the doc.
|
||||
*/
|
||||
export function getObject(doc: any, ptr: string) {
|
||||
export function getObject(doc: {}, ptr: string): any {
|
||||
let result
|
||||
try {
|
||||
result = jsonPointer.get(doc, ptr)
|
||||
|
@ -399,7 +393,7 @@ export function getObject(doc: any, ptr: string) {
|
|||
* @param {any} value The value that needs to be set at the
|
||||
* location provided by the ptr in the doc.
|
||||
*/
|
||||
export function setObject(doc: any, ptr: string, value: any) {
|
||||
export function setObject(doc: {}, ptr: string, value: any) {
|
||||
let result
|
||||
try {
|
||||
result = jsonPointer.set(doc, ptr, value)
|
||||
|
@ -415,7 +409,7 @@ export function setObject(doc: any, ptr: string, value: any) {
|
|||
*
|
||||
* @param {string} ptr The json reference pointer.
|
||||
*/
|
||||
export function removeObject(doc: any, ptr: string) {
|
||||
export function removeObject(doc: {}, ptr: string) {
|
||||
let result
|
||||
try {
|
||||
result = jsonPointer.remove(doc, ptr)
|
||||
|
@ -530,7 +524,7 @@ export function gitClone(directory: string, url: string, branch: string|undefine
|
|||
* Removes given directory recursively.
|
||||
* @param {string} dir directory to be deleted.
|
||||
*/
|
||||
export function removeDirSync(dir: string) {
|
||||
export function removeDirSync(dir: string): void {
|
||||
if (fs.existsSync(dir)) {
|
||||
fs.readdirSync(dir).forEach(file => {
|
||||
const current = dir + "/" + file
|
||||
|
@ -551,14 +545,10 @@ export function removeDirSync(dir: string) {
|
|||
* @param {array} consumesOrProduces Array of content-types.
|
||||
* @returns {string} firstMatchedJson content-type that contains "/json".
|
||||
*/
|
||||
export function getJsonContentType(consumesOrProduces: any[]) {
|
||||
let firstMatchedJson = null
|
||||
if (consumesOrProduces) {
|
||||
firstMatchedJson = consumesOrProduces.find((contentType) => {
|
||||
return (contentType.match(/.*\/json.*/ig) !== null)
|
||||
})
|
||||
}
|
||||
return firstMatchedJson
|
||||
export function getJsonContentType(consumesOrProduces: string[]): string|undefined {
|
||||
return consumesOrProduces
|
||||
? consumesOrProduces.find(contentType => contentType.match(/.*\/json.*/ig) !== null)
|
||||
: undefined
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -571,13 +561,40 @@ export function isUrlEncoded(str: string): boolean {
|
|||
return str !== decodeURIComponent(str)
|
||||
}
|
||||
|
||||
export interface Properties {
|
||||
[name: string]: Model
|
||||
}
|
||||
|
||||
export interface Ref {
|
||||
readonly $ref: string
|
||||
}
|
||||
|
||||
export interface Model {
|
||||
type?: string
|
||||
items?: Model
|
||||
properties?: Properties
|
||||
additionalProperties?: Model|false
|
||||
"x-nullable"?: Unknown
|
||||
in?: Unknown
|
||||
oneOf?: Unknown
|
||||
$ref?: string
|
||||
required?: Unknown[]|false
|
||||
schema?: Model
|
||||
allOf?: Ref[]
|
||||
description?: Unknown
|
||||
discriminator?: string
|
||||
"x-ms-discriminator-value"?: string
|
||||
enum?: Unknown
|
||||
"x-ms-azure-resource"?: Unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given model is a pure (free-form) object candidate (i.e. equivalent of the
|
||||
* C# Object type).
|
||||
* @param {object} model - The model to be verified
|
||||
* @returns {boolean} result - true if model is a pure object; false otherwise.
|
||||
*/
|
||||
export function isPureObject(model: any): boolean {
|
||||
export function isPureObject(model: Model): boolean {
|
||||
if (!model) {
|
||||
throw new Error(`model cannot be null or undefined and must be of type "object"`)
|
||||
}
|
||||
|
@ -613,7 +630,7 @@ export function isPureObject(model: any): boolean {
|
|||
* @returns {object} entity - The transformed entity if it is a pure object else the same entity is
|
||||
* returned as-is.
|
||||
*/
|
||||
export function relaxEntityType(entity: any, isRequired?: boolean) {
|
||||
export function relaxEntityType(entity: Model, isRequired?: Unknown): Model {
|
||||
if (isPureObject(entity) && entity.type) {
|
||||
delete entity.type
|
||||
}
|
||||
|
@ -628,7 +645,7 @@ export function relaxEntityType(entity: any, isRequired?: boolean) {
|
|||
/**
|
||||
* Relaxes/Transforms model definition like entities recursively
|
||||
*/
|
||||
export function relaxModelLikeEntities(model: any) {
|
||||
export function relaxModelLikeEntities(model: Model): Model {
|
||||
model = relaxEntityType(model)
|
||||
if (model.properties) {
|
||||
const modelProperties = model.properties
|
||||
|
@ -652,7 +669,7 @@ export function relaxModelLikeEntities(model: any) {
|
|||
* If true then it is required. If false or undefined then it is not required.
|
||||
* @returns {object} entity - The processed entity
|
||||
*/
|
||||
export function allowNullType(entity: any, isPropRequired?: boolean) {
|
||||
export function allowNullType(entity: Model, isPropRequired?: boolean|{}): Model {
|
||||
// if entity has a type
|
||||
if (entity && entity.type) {
|
||||
// if type is an array
|
||||
|
@ -668,11 +685,15 @@ export function allowNullType(entity: any, isPropRequired?: boolean) {
|
|||
}
|
||||
|
||||
// takes care of string 'false' and 'true'
|
||||
if (typeof entity["x-nullable"] === "string") {
|
||||
if (entity["x-nullable"].toLowerCase() === "false") {
|
||||
entity["x-nullable"] = false
|
||||
} else if (entity["x-nullable"].toLowerCase() === "true") {
|
||||
entity["x-nullable"] = true
|
||||
const xNullable = entity["x-nullable"]
|
||||
if (typeof xNullable === "string") {
|
||||
switch (xNullable.toLowerCase()) {
|
||||
case "false":
|
||||
entity["x-nullable"] = false
|
||||
break
|
||||
case "true":
|
||||
entity["x-nullable"] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -690,7 +711,9 @@ export function allowNullType(entity: any, isPropRequired?: boolean) {
|
|||
}
|
||||
|
||||
// if there's a $ref
|
||||
if (entity && entity.$ref && shouldAcceptNullValue(entity["x-nullable"], isPropRequired)) {
|
||||
if (entity
|
||||
&& entity.$ref
|
||||
&& shouldAcceptNullValue(entity["x-nullable"], isPropRequired)) {
|
||||
const savedEntity = entity
|
||||
entity = {}
|
||||
entity.oneOf = [savedEntity, { type: "null" }]
|
||||
|
@ -704,14 +727,14 @@ export function allowNullType(entity: any, isPropRequired?: boolean) {
|
|||
* Yes | convert to oneOf[] | |
|
||||
* No | convert to oneOf[] | | convert to oneOf[]
|
||||
*/
|
||||
export function shouldAcceptNullValue(xnullable: any, isPropRequired: any): boolean {
|
||||
export function shouldAcceptNullValue(xnullable: Unknown, isPropRequired: Unknown): Unknown {
|
||||
const isPropNullable = xnullable && typeof xnullable === "boolean"
|
||||
return (isPropNullable === undefined && !isPropRequired) || isPropNullable
|
||||
}
|
||||
/**
|
||||
* Relaxes/Transforms model definition to allow null values
|
||||
*/
|
||||
export function allowNullableTypes(model: any) {
|
||||
export function allowNullableTypes(model: Model) {
|
||||
// process additionalProperties if present
|
||||
if (model && typeof model.additionalProperties === "object") {
|
||||
if (model.additionalProperties.properties || model.additionalProperties.additionalProperties) {
|
||||
|
@ -772,7 +795,7 @@ export function allowNullableTypes(model: any) {
|
|||
/**
|
||||
* Relaxes/Transforms parameter definition to allow null values for non-path parameters
|
||||
*/
|
||||
export function allowNullableParams(parameter: any) {
|
||||
export function allowNullableParams(parameter: Model) {
|
||||
if (parameter.in && parameter.in === "body" && parameter.schema) {
|
||||
parameter.schema = allowNullableTypes(parameter.schema)
|
||||
} else {
|
||||
|
@ -800,7 +823,7 @@ export function sanitizeFileName(str: string): string {
|
|||
* The check is necessary because Object.values does not coerce parameters to object type.
|
||||
* @param {*} obj
|
||||
*/
|
||||
export function getValues(obj: any): any[] {
|
||||
export function getValues<T>(obj: Map<T>|null): T[] {
|
||||
if (obj === undefined || obj === null) {
|
||||
return []
|
||||
}
|
||||
|
@ -812,7 +835,7 @@ export function getValues(obj: any): any[] {
|
|||
.* The check is necessary because Object.keys does not coerce parameters to object type.
|
||||
* @param {*} obj
|
||||
*/
|
||||
export function getKeys(obj: any): string[] {
|
||||
export function getKeys(obj: Map<any>): string[] {
|
||||
if (obj === undefined || obj === null) {
|
||||
return []
|
||||
}
|
||||
|
@ -823,8 +846,8 @@ export function getKeys(obj: any): string[] {
|
|||
/**
|
||||
* Checks if the property is required in the model.
|
||||
*/
|
||||
function isPropertyRequired(propName: any, model: any) {
|
||||
return model.required ? model.required.some((p: any) => p === propName) : false
|
||||
function isPropertyRequired(propName: Unknown, model: Model) {
|
||||
return model.required ? model.required.some(p => p === propName) : false
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -833,7 +856,7 @@ function isPropertyRequired(propName: any, model: any) {
|
|||
export const statusCodeStringToStatusCode = lodash.invert(
|
||||
lodash.mapValues(
|
||||
http.STATUS_CODES,
|
||||
(value: any) => value.replace(/ |-/g, "").toLowerCase()))
|
||||
(value: string) => value.replace(/ |-/g, "").toLowerCase()))
|
||||
|
||||
/**
|
||||
* Models an ARM cloud error schema.
|
||||
|
@ -848,7 +871,7 @@ export const CloudErrorSchema = {
|
|||
/**
|
||||
* Models an ARM cloud error wrapper.
|
||||
*/
|
||||
export const CloudErrorWrapper = {
|
||||
export const CloudErrorWrapper: Model = {
|
||||
type: "object",
|
||||
properties: {
|
||||
error: {
|
||||
|
@ -861,7 +884,7 @@ export const CloudErrorWrapper = {
|
|||
/**
|
||||
* Models a Cloud Error
|
||||
*/
|
||||
export const CloudError = {
|
||||
export const CloudError: Model = {
|
||||
type: "object",
|
||||
properties: {
|
||||
code: {
|
||||
|
|
|
@ -3,10 +3,28 @@
|
|||
|
||||
import * as pointer from "json-pointer"
|
||||
import { Error } from "./error"
|
||||
import { Unknown } from "./unknown"
|
||||
import { Map } from "./utils"
|
||||
|
||||
interface ValidationError {
|
||||
validationCategory: string
|
||||
code?: string
|
||||
providerNamespace: Unknown
|
||||
type: string
|
||||
inner?: Error|Error[]
|
||||
id?: Unknown
|
||||
message?: string
|
||||
jsonref?: string
|
||||
"json-path"?: string
|
||||
}
|
||||
|
||||
interface Warning {
|
||||
readonly code: Unknown
|
||||
}
|
||||
|
||||
export class ValidateResponse {
|
||||
|
||||
private mapper = {
|
||||
private readonly mapper: Map<string> = {
|
||||
SWAGGER_SCHEMA_VALIDATION_ERROR: "M6000",
|
||||
INVALID_PARAMETER_COMBINATION: "M6001",
|
||||
MULTIPLE_BODY_PARAMETERS: "M6002",
|
||||
|
@ -25,26 +43,27 @@ export class ValidateResponse {
|
|||
}
|
||||
|
||||
public constructErrors(
|
||||
validationError: Error, specPath: any, providerNamespace: any): any[] {
|
||||
validationError: Error, specPath: Unknown, providerNamespace: Unknown): ValidationError[] {
|
||||
const self = this
|
||||
if (!validationError) {
|
||||
throw new Error("validationError cannot be null or undefined.")
|
||||
}
|
||||
return validationError.innerErrors.map(error => {
|
||||
const e: any = {
|
||||
const errors = validationError.innerErrors as Error[]
|
||||
return errors.map(error => {
|
||||
const e: ValidationError = {
|
||||
validationCategory: "SwaggerViolation",
|
||||
providerNamespace,
|
||||
type: "error",
|
||||
inner: error.inner
|
||||
}
|
||||
if (error.code && (self.mapper as any)[error.code]) {
|
||||
if (error.code && self.mapper[error.code]) {
|
||||
e.code = error.code
|
||||
e.id = (self.mapper as any)[error.code]
|
||||
e.id = self.mapper[error.code]
|
||||
e.message = error.message
|
||||
} else {
|
||||
e.code = "SWAGGER_SCHEMA_VALIDATION_ERROR"
|
||||
e.message = validationError.message
|
||||
e.id = (self.mapper as any)[e.code]
|
||||
e.id = self.mapper[e.code]
|
||||
e.inner = error
|
||||
}
|
||||
if (error.path && error.path.length) {
|
||||
|
@ -57,7 +76,7 @@ export class ValidateResponse {
|
|||
})
|
||||
}
|
||||
|
||||
public sanitizeWarnings(warnings: any[]): any[] {
|
||||
public sanitizeWarnings(warnings: Warning[]): Warning[] {
|
||||
if (!warnings) {
|
||||
throw new Error("validationError cannot be null or undefined.")
|
||||
}
|
||||
|
@ -67,7 +86,7 @@ export class ValidateResponse {
|
|||
}
|
||||
|
||||
private seralize() {
|
||||
const result: { ["json-path"]?: any } = {}
|
||||
const result: { ["json-path"]?: Unknown } = {}
|
||||
for (const prop in this) {
|
||||
if (this[prop] !== null && this[prop] !== undefined) {
|
||||
if (prop === "jsonpath") {
|
||||
|
|
|
@ -8,14 +8,21 @@ import * as msrestazure from "ms-rest-azure"
|
|||
import { ResourceManagementClient } from "azure-arm-resource"
|
||||
import { log } from "./util/logging"
|
||||
import * as utils from "./util/utils"
|
||||
import { SpecValidator } from "./validators/specValidator"
|
||||
import { SpecValidator, SpecValidationResult } from "./validators/specValidator"
|
||||
import { WireFormatGenerator } from "./wireFormatGenerator"
|
||||
import { XMsExampleExtractor } from "./xMsExampleExtractor"
|
||||
import { SpecResolver } from "./validators/specResolver"
|
||||
import * as specResolver from "./validators/specResolver"
|
||||
import { UmlGenerator } from "./umlGenerator"
|
||||
import * as umlGeneratorLib from "./umlGenerator"
|
||||
import { Unknown } from "./util/unknown"
|
||||
|
||||
export const finalValidationResult: any = { validityStatus: true };
|
||||
interface FinalValidationResult {
|
||||
[name: string]: Unknown
|
||||
}
|
||||
|
||||
export const finalValidationResult: FinalValidationResult = {
|
||||
validityStatus: true
|
||||
}
|
||||
|
||||
export async function getDocumentsFromCompositeSwagger(compositeSpecPath: string)
|
||||
: Promise<string[]> {
|
||||
|
@ -50,12 +57,13 @@ export async function getDocumentsFromCompositeSwagger(compositeSpecPath: string
|
|||
}
|
||||
}
|
||||
|
||||
export async function validateSpec(specPath: any, options: any, _?: any): Promise<any> {
|
||||
export async function validateSpec(specPath: string, options: Options|undefined)
|
||||
: Promise<SpecValidationResult> {
|
||||
if (!options) { options = {} }
|
||||
log.consoleLogLevel = options.consoleLogLevel || log.consoleLogLevel
|
||||
log.filepath = options.logFilepath || log.filepath
|
||||
// As a part of resolving discriminators we replace all the parent references
|
||||
// with a oneof array containing references to the parent and its children.
|
||||
// with a oneOf array containing references to the parent and its children.
|
||||
// This breaks the swagger specification 2.0 schema since oneOf is not supported.
|
||||
// Hence we disable it since it is not required for semantic check.
|
||||
|
||||
|
@ -65,8 +73,8 @@ export async function validateSpec(specPath: any, options: any, _?: any): Promis
|
|||
// and cause the semantic validation to fail.
|
||||
options.shouldResolveParameterizedHost = false
|
||||
|
||||
// We shoudln't be resolving nullable types for semantic validaiton as we'll replace nodes
|
||||
// with oneof arrays which are not semantically valid in swagger 2.0 schema.
|
||||
// We shouldn't be resolving nullable types for semantic validation as we'll replace nodes
|
||||
// with oneOf arrays which are not semantically valid in swagger 2.0 schema.
|
||||
options.shouldResolveNullableTypes = false
|
||||
const validator = new SpecValidator(specPath, null, options)
|
||||
finalValidationResult[specPath] = validator.specValidationResult
|
||||
|
@ -83,7 +91,9 @@ export async function validateSpec(specPath: any, options: any, _?: any): Promis
|
|||
}
|
||||
}
|
||||
|
||||
export async function validateCompositeSpec(compositeSpecPath: any, options: any): Promise<void> {
|
||||
export async function validateCompositeSpec(compositeSpecPath: string, options: Options)
|
||||
: Promise<void> {
|
||||
|
||||
if (!options) { options = {} }
|
||||
log.consoleLogLevel = options.consoleLogLevel || log.consoleLogLevel
|
||||
log.filepath = options.logFilepath || log.filepath
|
||||
|
@ -91,16 +101,17 @@ export async function validateCompositeSpec(compositeSpecPath: any, options: any
|
|||
const docs = await getDocumentsFromCompositeSwagger(compositeSpecPath)
|
||||
options.consoleLogLevel = log.consoleLogLevel
|
||||
options.logFilepath = log.filepath
|
||||
const promiseFactories = docs.map((doc: any) => () => validateSpec(doc, options))
|
||||
return await utils.executePromisesSequentially(promiseFactories)
|
||||
const promiseFactories = docs.map(doc => async () => { await validateSpec(doc, options) })
|
||||
await utils.executePromisesSequentially(promiseFactories)
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
export async function validateExamples(specPath: any, operationIds: any, options?: any)
|
||||
: Promise<any> {
|
||||
export async function validateExamples(
|
||||
specPath: string, operationIds: string|undefined, options?: Options)
|
||||
: Promise<SpecValidationResult> {
|
||||
|
||||
if (!options) { options = {} }
|
||||
log.consoleLogLevel = options.consoleLogLevel || log.consoleLogLevel
|
||||
|
@ -120,7 +131,7 @@ export async function validateExamples(specPath: any, operationIds: any, options
|
|||
}
|
||||
}
|
||||
|
||||
export async function validateExamplesInCompositeSpec(compositeSpecPath: any, options: any)
|
||||
export async function validateExamplesInCompositeSpec(compositeSpecPath: string, options: Options)
|
||||
: Promise<void> {
|
||||
|
||||
if (!options) { options = {} }
|
||||
|
@ -130,7 +141,8 @@ export async function validateExamplesInCompositeSpec(compositeSpecPath: any, op
|
|||
const docs = await getDocumentsFromCompositeSwagger(compositeSpecPath)
|
||||
options.consoleLogLevel = log.consoleLogLevel
|
||||
options.logFilepath = log.filepath
|
||||
const promiseFactories = docs.map(doc => () => validateExamples(doc, options))
|
||||
const promiseFactories = docs.map(
|
||||
doc => async () => { await validateExamples(doc, undefined, options) })
|
||||
await utils.executePromisesSequentially(promiseFactories)
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
|
@ -138,12 +150,14 @@ export async function validateExamplesInCompositeSpec(compositeSpecPath: any, op
|
|||
}
|
||||
}
|
||||
|
||||
export interface Options extends specResolver.Options {
|
||||
consoleLogLevel?: any
|
||||
logFilepath?: any
|
||||
export interface Options extends specResolver.Options, umlGeneratorLib.Options {
|
||||
consoleLogLevel?: Unknown
|
||||
logFilepath?: Unknown
|
||||
}
|
||||
|
||||
export async function resolveSpec(specPath: any, outputDir: any, options: Options): Promise<void> {
|
||||
export async function resolveSpec(specPath: string, outputDir: string, options: Options)
|
||||
: Promise<void> {
|
||||
|
||||
if (!options) { options = {} }
|
||||
log.consoleLogLevel = options.consoleLogLevel || log.consoleLogLevel
|
||||
log.filepath = options.logFilepath || log.filepath
|
||||
|
@ -165,8 +179,9 @@ export async function resolveSpec(specPath: any, outputDir: any, options: Option
|
|||
}
|
||||
}
|
||||
|
||||
export async function resolveCompositeSpec(specPath: any, outputDir: any, options: any)
|
||||
: Promise<void> {
|
||||
export async function resolveCompositeSpec(specPath: string, outputDir: string, options: Options)
|
||||
: Promise<void> {
|
||||
|
||||
if (!options) { options = {} }
|
||||
log.consoleLogLevel = options.consoleLogLevel || log.consoleLogLevel
|
||||
log.filepath = options.logFilepath || log.filepath
|
||||
|
@ -183,7 +198,11 @@ export async function resolveCompositeSpec(specPath: any, outputDir: any, option
|
|||
}
|
||||
|
||||
export async function generateWireFormat(
|
||||
specPath: any, outDir: any, emitYaml: any, operationIds: any, options: any)
|
||||
specPath: Unknown,
|
||||
outDir: Unknown,
|
||||
emitYaml: Unknown,
|
||||
operationIds: string|null,
|
||||
options: Options)
|
||||
: Promise<void> {
|
||||
|
||||
if (!options) { options = {} }
|
||||
|
@ -201,7 +220,8 @@ export async function generateWireFormat(
|
|||
}
|
||||
|
||||
export async function generateWireFormatInCompositeSpec(
|
||||
compositeSpecPath: any, outDir: any, emitYaml: any, options: any): Promise<void> {
|
||||
compositeSpecPath: string, outDir: Unknown, emitYaml: Unknown, options: Options): Promise<void> {
|
||||
|
||||
if (!options) { options = {} }
|
||||
log.consoleLogLevel = options.consoleLogLevel || log.consoleLogLevel
|
||||
log.filepath = options.logFilepath || log.filepath
|
||||
|
@ -218,7 +238,9 @@ export async function generateWireFormatInCompositeSpec(
|
|||
}
|
||||
}
|
||||
|
||||
export async function generateUml(specPath: any, outputDir: any, options?: Options): Promise<void> {
|
||||
export async function generateUml(specPath: string, outputDir: string, options?: Options)
|
||||
: Promise<void> {
|
||||
|
||||
if (!options) { options = {} }
|
||||
log.consoleLogLevel = options.consoleLogLevel || log.consoleLogLevel
|
||||
log.filepath = options.logFilepath || log.filepath
|
||||
|
@ -236,7 +258,7 @@ export async function generateUml(specPath: any, outputDir: any, options?: Optio
|
|||
try {
|
||||
const result = await utils.parseJson(specPath)
|
||||
const resolver = new SpecResolver(specPath, result, resolverOptions)
|
||||
const umlGenerator = new UmlGenerator(resolver.specInJson, options)
|
||||
const umlGenerator = new umlGeneratorLib.UmlGenerator(resolver.specInJson, options)
|
||||
const svgGraph = await umlGenerator.generateDiagramFromGraph()
|
||||
if (outputDir !== "./" && !fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir)
|
||||
|
@ -252,7 +274,7 @@ export async function generateUml(specPath: any, outputDir: any, options?: Optio
|
|||
}
|
||||
}
|
||||
|
||||
export function updateEndResultOfSingleValidation(validator: any): void {
|
||||
export function updateEndResultOfSingleValidation(validator: SpecValidator): void {
|
||||
if (validator.specValidationResult.validityStatus) {
|
||||
if (!(log.consoleLogLevel === "json" || log.consoleLogLevel === "off")) {
|
||||
log.info("No Errors were found.")
|
||||
|
@ -264,17 +286,19 @@ export function updateEndResultOfSingleValidation(validator: any): void {
|
|||
}
|
||||
}
|
||||
|
||||
export function logDetailedInfo(validator: any): void {
|
||||
export function logDetailedInfo(validator: SpecValidator): void {
|
||||
if (log.consoleLogLevel === "json") {
|
||||
/* tslint:disable-next-line */
|
||||
console.dir(validator.specValidationResult, { depth: null, colors: true })
|
||||
}
|
||||
log.silly("############################")
|
||||
log.silly(validator.specValidationResult)
|
||||
log.silly(validator.specValidationResult.toString())
|
||||
log.silly("----------------------------")
|
||||
}
|
||||
|
||||
export function extractXMsExamples(specPath: any, recordings: any, options: any) {
|
||||
export function extractXMsExamples(specPath: string, recordings: Unknown, options: Options)
|
||||
: Promise<void> {
|
||||
|
||||
if (!options) { options = {} }
|
||||
log.consoleLogLevel = options.consoleLogLevel || log.consoleLogLevel
|
||||
log.filepath = options.logFilepath || log.filepath
|
||||
|
|
|
@ -9,12 +9,15 @@ import * as _ from "lodash"
|
|||
import * as glob from "glob"
|
||||
import * as msRest from "ms-rest"
|
||||
import { SpecValidator } from "./specValidator"
|
||||
import { Constants } from "../util/constants"
|
||||
import * as C from "../util/constants"
|
||||
import { log } from "../util/logging"
|
||||
import * as utils from "../util/utils"
|
||||
import * as models from "../models"
|
||||
import * as http from "http"
|
||||
import { PotentialOperationsResult } from "../models/potentialOperationsResult"
|
||||
import { Operation, PathObject } from "sway"
|
||||
import { ParsedUrlQuery } from "querystring"
|
||||
import { Unknown } from "../util/unknown"
|
||||
|
||||
export interface Options {
|
||||
swaggerPaths: string[]
|
||||
|
@ -29,22 +32,6 @@ export interface Options {
|
|||
isPathCaseSensitive?: boolean
|
||||
}
|
||||
|
||||
export interface Operation {
|
||||
pathObject: {
|
||||
path: string
|
||||
regexp: RegExp
|
||||
}
|
||||
responses: {
|
||||
default: {
|
||||
schema: {
|
||||
properties: {
|
||||
[property: string]: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ApiVersion {
|
||||
[method: string]: Operation[]
|
||||
}
|
||||
|
@ -53,19 +40,48 @@ export interface Provider {
|
|||
[apiVersion: string]: ApiVersion
|
||||
}
|
||||
|
||||
export interface RequestResponseObj {
|
||||
readonly liveRequest: {
|
||||
query: ParsedUrlQuery
|
||||
readonly url: string
|
||||
readonly method: string
|
||||
}
|
||||
readonly liveResponse: {
|
||||
statusCode: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface RequestValidationResult {
|
||||
successfulRequest: Unknown
|
||||
operationInfo?: Unknown
|
||||
errors?: Unknown
|
||||
}
|
||||
|
||||
export interface ResponseValidationResult {
|
||||
successfulResponse: Unknown
|
||||
operationInfo?: Unknown
|
||||
errors?: Unknown
|
||||
}
|
||||
|
||||
export interface ValidationResult {
|
||||
readonly requestValidationResult: RequestValidationResult
|
||||
readonly responseValidationResult: ResponseValidationResult
|
||||
errors: Unknown[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @class
|
||||
* Live Validator for Azure swagger APIs.
|
||||
*/
|
||||
export class LiveValidator {
|
||||
public cache: {
|
||||
public readonly cache: {
|
||||
[provider: string]: Provider
|
||||
}
|
||||
} = {}
|
||||
public options: Options
|
||||
/**
|
||||
* Constructs LiveValidator based on provided options.
|
||||
*
|
||||
* @param {object} options The configuration options.
|
||||
* @param {object} optionsRaw The configuration options.
|
||||
*
|
||||
* @param {array} [options.swaggerPaths] Array of swagger paths to be used for initializing Live
|
||||
* Validator. This has precedence over {@link options.swaggerPathsPattern}.
|
||||
|
@ -93,51 +109,51 @@ export class LiveValidator {
|
|||
*
|
||||
* @returns {object} CacheBuilder Returns the configured CacheBuilder object.
|
||||
*/
|
||||
constructor(options?: any) {
|
||||
constructor(optionsRaw?: any) {
|
||||
|
||||
this.options = options === null || options === undefined
|
||||
optionsRaw = optionsRaw === null || optionsRaw === undefined
|
||||
? { }
|
||||
: options
|
||||
: optionsRaw
|
||||
|
||||
if (typeof this.options !== "object") {
|
||||
if (typeof optionsRaw !== "object") {
|
||||
throw new Error('options must be of type "object".')
|
||||
}
|
||||
if (this.options.swaggerPaths === null || this.options.swaggerPaths === undefined) {
|
||||
this.options.swaggerPaths = []
|
||||
if (optionsRaw.swaggerPaths === null || optionsRaw.swaggerPaths === undefined) {
|
||||
optionsRaw.swaggerPaths = []
|
||||
}
|
||||
if (!Array.isArray(this.options.swaggerPaths)) {
|
||||
const paths = typeof this.options.swaggerPaths
|
||||
if (!Array.isArray(optionsRaw.swaggerPaths)) {
|
||||
const paths = typeof optionsRaw.swaggerPaths
|
||||
throw new Error(
|
||||
`options.swaggerPaths must be of type "array" instead of type "${paths}".`)
|
||||
}
|
||||
if (this.options.git === null || this.options.git === undefined) {
|
||||
this.options.git = {
|
||||
if (optionsRaw.git === null || optionsRaw.git === undefined) {
|
||||
optionsRaw.git = {
|
||||
url: "https://github.com/Azure/azure-rest-api-specs.git",
|
||||
shouldClone: false
|
||||
}
|
||||
}
|
||||
if (typeof this.options.git !== "object") {
|
||||
if (typeof optionsRaw.git !== "object") {
|
||||
throw new Error('options.git must be of type "object".')
|
||||
}
|
||||
if (this.options.git.url === null || this.options.git.url === undefined) {
|
||||
this.options.git.url = "https://github.com/Azure/azure-rest-api-specs.git"
|
||||
if (optionsRaw.git.url === null || optionsRaw.git.url === undefined) {
|
||||
optionsRaw.git.url = "https://github.com/Azure/azure-rest-api-specs.git"
|
||||
}
|
||||
if (typeof this.options.git.url.valueOf() !== "string") {
|
||||
if (typeof optionsRaw.git.url.valueOf() !== "string") {
|
||||
throw new Error('options.git.url must be of type "string".')
|
||||
}
|
||||
if (this.options.git.shouldClone === null || this.options.git.shouldClone === undefined) {
|
||||
this.options.git.shouldClone = false
|
||||
if (optionsRaw.git.shouldClone === null || optionsRaw.git.shouldClone === undefined) {
|
||||
optionsRaw.git.shouldClone = false
|
||||
}
|
||||
if (typeof this.options.git.shouldClone !== "boolean") {
|
||||
if (typeof optionsRaw.git.shouldClone !== "boolean") {
|
||||
throw new Error('options.git.shouldClone must be of type "boolean".')
|
||||
}
|
||||
if (this.options.directory === null || this.options.directory === undefined) {
|
||||
this.options.directory = path.resolve(os.homedir(), "repo")
|
||||
if (optionsRaw.directory === null || optionsRaw.directory === undefined) {
|
||||
optionsRaw.directory = path.resolve(os.homedir(), "repo")
|
||||
}
|
||||
if (typeof this.options.directory.valueOf() !== "string") {
|
||||
if (typeof optionsRaw.directory.valueOf() !== "string") {
|
||||
throw new Error('options.directory must be of type "string".')
|
||||
}
|
||||
this.cache = {}
|
||||
this.options = optionsRaw
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -220,9 +236,10 @@ export class LiveValidator {
|
|||
const operations = api.getOperations()
|
||||
let apiVersion = api.info.version.toLowerCase()
|
||||
|
||||
operations.forEach((operation: any) => {
|
||||
operations.forEach(operation => {
|
||||
const httpMethod = operation.method.toLowerCase()
|
||||
const pathStr = operation.pathObject.path
|
||||
const pathObject = operation.pathObject as PathObject
|
||||
const pathStr = pathObject.path
|
||||
let provider = utils.getProvider(pathStr)
|
||||
log.debug(`${apiVersion}, ${operation.operationId}, ${pathStr}, ${httpMethod}`)
|
||||
|
||||
|
@ -231,15 +248,15 @@ export class LiveValidator {
|
|||
|
||||
// Whitelist lookups: Look up knownTitleToResourceProviders
|
||||
// Putting the provider namespace onto operation for future use
|
||||
if (title && (Constants.knownTitleToResourceProviders as any)[title]) {
|
||||
operation.provider = (Constants.knownTitleToResourceProviders as any)[title]
|
||||
if (title && (C.knownTitleToResourceProviders as any)[title]) {
|
||||
operation.provider = (C.knownTitleToResourceProviders as any)[title]
|
||||
}
|
||||
|
||||
// Put the operation into 'Microsoft.Unknown' RPs
|
||||
provider = Constants.unknownResourceProvider
|
||||
apiVersion = Constants.unknownApiVersion
|
||||
provider = C.unknownResourceProvider
|
||||
apiVersion = C.unknownApiVersion
|
||||
log.debug(
|
||||
`Unable to find provider for path : "${operation.pathObject.path}". ` +
|
||||
`Unable to find provider for path : "${pathObject.path}". ` +
|
||||
`Bucketizing into provider: "${provider}"`)
|
||||
}
|
||||
provider = provider.toLowerCase()
|
||||
|
@ -249,7 +266,7 @@ export class LiveValidator {
|
|||
// Get methods for given apiVersion or initialize it
|
||||
const allMethods = apiVersions[apiVersion] || {}
|
||||
// Get specific http methods array for given verb or initialize it
|
||||
const operationsForHttpMethod = allMethods[httpMethod] || []
|
||||
const operationsForHttpMethod: Operation[] = allMethods[httpMethod] || []
|
||||
|
||||
// Builds the cache
|
||||
operationsForHttpMethod.push(operation)
|
||||
|
@ -284,7 +301,7 @@ export class LiveValidator {
|
|||
* @returns {Array<Operation>} List of potential operations matching the requestPath.
|
||||
*/
|
||||
public getPotentialOperationsHelper(
|
||||
requestPath: string, requestMethod: string, operations: Operation[]): any[] {
|
||||
requestPath: string, requestMethod: string, operations: Operation[]): Operation[] {
|
||||
if (requestPath === null
|
||||
|| requestPath === undefined
|
||||
|| typeof requestPath.valueOf() !== "string"
|
||||
|
@ -306,26 +323,27 @@ export class LiveValidator {
|
|||
}
|
||||
|
||||
const self = this
|
||||
let potentialOperations = []
|
||||
potentialOperations = operations.filter((operation) => {
|
||||
const pathMatch = operation.pathObject.regexp.exec(requestPath)
|
||||
let potentialOperations = operations.filter(operation => {
|
||||
const pathObject = operation.pathObject as PathObject
|
||||
const pathMatch = pathObject.regexp.exec(requestPath)
|
||||
return pathMatch === null ? false : true
|
||||
})
|
||||
|
||||
// If we do not find any match then we'll look into Microsoft.Unknown -> unknown-api-version
|
||||
// for given requestMethod as the fall back option
|
||||
if (!potentialOperations.length) {
|
||||
if (self.cache[Constants.unknownResourceProvider] &&
|
||||
self.cache[Constants.unknownResourceProvider][Constants.unknownApiVersion]) {
|
||||
if (self.cache[C.unknownResourceProvider] &&
|
||||
self.cache[C.unknownResourceProvider][C.unknownApiVersion]) {
|
||||
operations = self.cache
|
||||
[Constants.unknownResourceProvider][Constants.unknownApiVersion][requestMethod]
|
||||
potentialOperations = operations.filter((operation) => {
|
||||
let pathTemplate = operation.pathObject.path
|
||||
[C.unknownResourceProvider][C.unknownApiVersion][requestMethod]
|
||||
potentialOperations = operations.filter(operation => {
|
||||
const pathObject = operation.pathObject as PathObject
|
||||
let pathTemplate = pathObject.path
|
||||
if (pathTemplate && pathTemplate.includes("?")) {
|
||||
pathTemplate = pathTemplate.slice(0, pathTemplate.indexOf("?"))
|
||||
operation.pathObject.path = pathTemplate
|
||||
pathObject.path = pathTemplate
|
||||
}
|
||||
const pathMatch = operation.pathObject.regexp.exec(requestPath)
|
||||
const pathMatch = pathObject.regexp.exec(requestPath)
|
||||
return pathMatch === null ? false : true
|
||||
})
|
||||
}
|
||||
|
@ -370,32 +388,32 @@ export class LiveValidator {
|
|||
}
|
||||
|
||||
const self = this
|
||||
let potentialOperations: any[] = []
|
||||
let potentialOperations: Operation[] = []
|
||||
const parsedUrl = url.parse(requestUrl, true)
|
||||
const pathStr = parsedUrl.pathname
|
||||
requestMethod = requestMethod.toLowerCase()
|
||||
let result
|
||||
let msg
|
||||
let code
|
||||
let liveValidationError
|
||||
let liveValidationError: models.LiveValidationError|undefined
|
||||
if (pathStr === null || pathStr === undefined) {
|
||||
msg = `Could not find path from requestUrl: "${requestUrl}".`
|
||||
liveValidationError = new models.LiveValidationError(
|
||||
Constants.ErrorCodes.PathNotFoundInRequestUrl.name, msg)
|
||||
C.ErrorCodes.PathNotFoundInRequestUrl.name, msg)
|
||||
result = new models.PotentialOperationsResult(potentialOperations, liveValidationError)
|
||||
return result
|
||||
}
|
||||
|
||||
// Lower all the keys of query parameters before searching for `api-version`
|
||||
const queryObject = _.transform(
|
||||
parsedUrl.query, (obj, value, key) => obj[key.toLowerCase()] = value)
|
||||
let apiVersion: any = queryObject["api-version"]
|
||||
parsedUrl.query, (obj: ParsedUrlQuery, value, key) => obj[key.toLowerCase()] = value)
|
||||
let apiVersion = queryObject["api-version"] as string
|
||||
let provider = utils.getProvider(pathStr)
|
||||
|
||||
// Provider would be provider found from the path or Microsoft.Unknown
|
||||
provider = provider || Constants.unknownResourceProvider
|
||||
if (provider === Constants.unknownResourceProvider) {
|
||||
apiVersion = Constants.unknownApiVersion
|
||||
provider = provider || C.unknownResourceProvider
|
||||
if (provider === C.unknownResourceProvider) {
|
||||
apiVersion = C.unknownApiVersion
|
||||
}
|
||||
provider = provider.toLowerCase()
|
||||
|
||||
|
@ -416,36 +434,36 @@ export class LiveValidator {
|
|||
msg =
|
||||
`Could not find best match operation for verb "${requestMethod}" for api-version ` +
|
||||
`"${apiVersion}" and provider "${provider}" in the cache.`
|
||||
code = Constants.ErrorCodes.OperationNotFoundInCache
|
||||
code = C.ErrorCodes.OperationNotFoundInCache
|
||||
} else {
|
||||
msg =
|
||||
`Could not find any methods with verb "${requestMethod}" for api-version ` +
|
||||
`"${apiVersion}" and provider "${provider}" in the cache.`
|
||||
code = Constants.ErrorCodes.OperationNotFoundInCacheWithVerb
|
||||
code = C.ErrorCodes.OperationNotFoundInCacheWithVerb
|
||||
log.debug(msg)
|
||||
}
|
||||
} else {
|
||||
msg =
|
||||
`Could not find exact api-version "${apiVersion}" for provider "${provider}" ` +
|
||||
`in the cache.`
|
||||
code = Constants.ErrorCodes.OperationNotFoundInCacheWithApi
|
||||
code = C.ErrorCodes.OperationNotFoundInCacheWithApi
|
||||
log.debug(`${msg} We'll search in the resource provider "Microsoft.Unknown".`)
|
||||
potentialOperations = self.getPotentialOperationsHelper(pathStr, requestMethod, [])
|
||||
}
|
||||
} else {
|
||||
msg = `Could not find api-version in requestUrl "${requestUrl}".`
|
||||
code = Constants.ErrorCodes.OperationNotFoundInCacheWithApi
|
||||
code = C.ErrorCodes.OperationNotFoundInCacheWithApi
|
||||
log.debug(msg)
|
||||
}
|
||||
} else {
|
||||
// provider does not exist in cache
|
||||
msg = `Could not find provider "${provider}" in the cache.`
|
||||
code = Constants.ErrorCodes.OperationNotFoundInCacheWithProvider
|
||||
code = C.ErrorCodes.OperationNotFoundInCacheWithProvider
|
||||
log.debug(`${msg} We'll search in the resource provider "Microsoft.Unknown".`)
|
||||
potentialOperations = self.getPotentialOperationsHelper(pathStr, requestMethod, [])
|
||||
}
|
||||
|
||||
// Provide reason when we do not find any potential operaion in cache
|
||||
// Provide reason when we do not find any potential operation in cache
|
||||
if (potentialOperations.length === 0) {
|
||||
liveValidationError = new models.LiveValidationError(code.name, msg)
|
||||
}
|
||||
|
@ -457,29 +475,25 @@ export class LiveValidator {
|
|||
/**
|
||||
* Validates live request and response.
|
||||
*
|
||||
* @param {object} requestResponseObj - The wrapper that constains the live request and response
|
||||
* @param {object} requestResponseObj - The wrapper that contains the live request and response
|
||||
* @param {object} requestResponseObj.liveRequest - The live request
|
||||
* @param {object} requestResponseObj.liveResponse - The live response
|
||||
* @returns {object} validationResult - Validation result for given input
|
||||
*/
|
||||
public validateLiveRequestResponse(requestResponseObj: any) {
|
||||
public validateLiveRequestResponse(requestResponseObj: RequestResponseObj): ValidationResult {
|
||||
const self = this
|
||||
const validationResult = {
|
||||
const validationResult: ValidationResult = {
|
||||
requestValidationResult: {
|
||||
successfulRequest: false,
|
||||
operationInfo: undefined as any,
|
||||
errors: undefined as any
|
||||
},
|
||||
responseValidationResult: {
|
||||
successfulResponse: false,
|
||||
operationInfo: undefined as any,
|
||||
errors: undefined as any
|
||||
},
|
||||
errors: [] as any[]
|
||||
errors: []
|
||||
};
|
||||
if (!requestResponseObj || (requestResponseObj && typeof requestResponseObj !== "object")) {
|
||||
const msg = 'requestResponseObj cannot be null or undefined and must be of type "object".'
|
||||
const e = new models.LiveValidationError(Constants.ErrorCodes.IncorrectInput.name, msg)
|
||||
const e = new models.LiveValidationError(C.ErrorCodes.IncorrectInput.name, msg)
|
||||
validationResult.errors.push(e)
|
||||
return validationResult
|
||||
}
|
||||
|
@ -493,14 +507,14 @@ export class LiveValidator {
|
|||
const msg =
|
||||
`Found errors "${err.message}" in the provided input:\n` +
|
||||
`${util.inspect(requestResponseObj, { depth: null })}.`
|
||||
const e = new models.LiveValidationError(Constants.ErrorCodes.IncorrectInput.name, msg)
|
||||
const e = new models.LiveValidationError(C.ErrorCodes.IncorrectInput.name, msg)
|
||||
validationResult.errors.push(e)
|
||||
return validationResult
|
||||
}
|
||||
const request = requestResponseObj.liveRequest
|
||||
const response = requestResponseObj.liveResponse
|
||||
|
||||
// If status code is passed as a status code string (e.g. "OK") tranform it to the status code
|
||||
// If status code is passed as a status code string (e.g. "OK") transform it to the status code
|
||||
// number (e.g. '200').
|
||||
if (response
|
||||
&& !http.STATUS_CODES[response.statusCode]
|
||||
|
@ -511,7 +525,7 @@ export class LiveValidator {
|
|||
if (!request.query) {
|
||||
request.query = url.parse(request.url, true).query
|
||||
}
|
||||
const currentApiVersion = request.query["api-version"] || Constants.unknownApiVersion
|
||||
const currentApiVersion = request.query["api-version"] || C.unknownApiVersion
|
||||
let potentialOperationsResult
|
||||
let potentialOperations = []
|
||||
try {
|
||||
|
@ -519,10 +533,10 @@ export class LiveValidator {
|
|||
potentialOperations = potentialOperationsResult.operations
|
||||
} catch (err) {
|
||||
const msg =
|
||||
`An error occured while trying to search for potential operations:\n` +
|
||||
`An error occurred while trying to search for potential operations:\n` +
|
||||
`${util.inspect(err, { depth: null })}`
|
||||
const e = new models.LiveValidationError(
|
||||
Constants.ErrorCodes.PotentialOperationSearchError.name, msg)
|
||||
C.ErrorCodes.PotentialOperationSearchError.name, msg)
|
||||
validationResult.errors.push(e)
|
||||
return validationResult
|
||||
}
|
||||
|
@ -545,14 +559,14 @@ export class LiveValidator {
|
|||
reqResult = operation.validateRequest(request)
|
||||
validationResult.requestValidationResult.errors = reqResult.errors || []
|
||||
log.debug("Request Validation Result")
|
||||
log.debug(reqResult)
|
||||
log.debug(reqResult.toString())
|
||||
} catch (reqValidationError) {
|
||||
const msg =
|
||||
`An error occurred while validating the live request for operation ` +
|
||||
`"${operation.operationId}". The error is:\n ` +
|
||||
`${util.inspect(reqValidationError, { depth: null })}`
|
||||
const err = new models.LiveValidationError(
|
||||
Constants.ErrorCodes.RequestValidationError.name, msg)
|
||||
C.ErrorCodes.RequestValidationError.name, msg)
|
||||
validationResult.requestValidationResult.errors = [err]
|
||||
}
|
||||
let resResult
|
||||
|
@ -560,14 +574,14 @@ export class LiveValidator {
|
|||
resResult = operation.validateResponse(response)
|
||||
validationResult.responseValidationResult.errors = resResult.errors || []
|
||||
log.debug("Response Validation Result")
|
||||
log.debug(resResult)
|
||||
log.debug(resResult.toString())
|
||||
} catch (resValidationError) {
|
||||
const msg =
|
||||
`An error occurred while validating the live response for operation ` +
|
||||
`"${operation.operationId}". The error is:\n ` +
|
||||
`${util.inspect(resValidationError, { depth: null })}`
|
||||
const err = new models.LiveValidationError(
|
||||
Constants.ErrorCodes.ResponseValidationError.name, msg)
|
||||
C.ErrorCodes.ResponseValidationError.name, msg)
|
||||
validationResult.responseValidationResult.errors = [err]
|
||||
}
|
||||
if (reqResult
|
||||
|
@ -584,13 +598,13 @@ export class LiveValidator {
|
|||
}
|
||||
// Found more than 1 potentialOperations
|
||||
} else {
|
||||
const operationIds = potentialOperations.map((op: any) => op.operationId).join()
|
||||
const operationIds = potentialOperations.map(op => op.operationId).join()
|
||||
const msg =
|
||||
`Found multiple matching operations with operationIds "${operationIds}" ` +
|
||||
`for request url "${request.url}" with HTTP Method "${request.method}".`;
|
||||
log.debug(msg)
|
||||
const err = new models.LiveValidationError(
|
||||
Constants.ErrorCodes.MultipleOperationsFound.name, msg)
|
||||
C.ErrorCodes.MultipleOperationsFound.name, msg)
|
||||
validationResult.errors = [err]
|
||||
}
|
||||
|
||||
|
|
|
@ -5,11 +5,13 @@ import * as _ from "lodash"
|
|||
import * as path from "path"
|
||||
import * as JsonRefs from "json-refs"
|
||||
import * as utils from "../util/utils"
|
||||
import { Constants } from "../util/constants"
|
||||
import * as C from "../util/constants"
|
||||
import { log } from "../util/logging"
|
||||
import { PolymorphicTree } from "./polymorphicTree"
|
||||
import { Unknown } from "../util/unknown"
|
||||
import { Model } from "../util/utils"
|
||||
|
||||
const ErrorCodes = Constants.ErrorCodes
|
||||
const ErrorCodes = C.ErrorCodes
|
||||
|
||||
export interface Options {
|
||||
shouldResolveRelativePaths?: boolean
|
||||
|
@ -23,23 +25,91 @@ export interface Options {
|
|||
shouldModelImplicitDefaultResponse?: boolean
|
||||
}
|
||||
|
||||
export interface Operation {
|
||||
parameters: Model[]
|
||||
consumes: string[]
|
||||
produces: string[]
|
||||
responses: {
|
||||
[name: string]: Model
|
||||
}
|
||||
}
|
||||
|
||||
export interface Path {
|
||||
parameters: Model[]
|
||||
get?: Operation
|
||||
put?: Operation
|
||||
post?: Operation
|
||||
delete?: Operation
|
||||
options?: Operation
|
||||
head?: Operation
|
||||
patch?: Operation
|
||||
}
|
||||
|
||||
function *getOperations(p: Path) {
|
||||
function *all() {
|
||||
yield p.get
|
||||
yield p.put
|
||||
yield p.post
|
||||
yield p.delete
|
||||
yield p.options
|
||||
yield p.head
|
||||
yield p.patch
|
||||
}
|
||||
for (const v of all()) {
|
||||
if (v !== undefined) {
|
||||
yield v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface Paths {
|
||||
[name: string]: Path
|
||||
}
|
||||
|
||||
export interface Spec {
|
||||
"x-ms-paths": Paths
|
||||
paths: Paths
|
||||
definitions: {
|
||||
[name: string]: Model
|
||||
}
|
||||
"x-ms-parameterized-host": {
|
||||
parameters: Unknown
|
||||
}
|
||||
consumes: string[]
|
||||
produces: string[]
|
||||
parameters: {
|
||||
[name: string]: Model
|
||||
}
|
||||
readonly documents: Unknown
|
||||
}
|
||||
|
||||
export interface RefDetails {
|
||||
def: {
|
||||
$ref: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @class
|
||||
* Resolves the swagger spec by unifying x-ms-paths, resolving relative file references if any,
|
||||
* resolving the allof is present in any model definition and then setting additionalProperties
|
||||
* resolving the allOf is present in any model definition and then setting additionalProperties
|
||||
* to false if it is not previously set to true or an object in that definition.
|
||||
*/
|
||||
export class SpecResolver {
|
||||
|
||||
public specInJson: any
|
||||
public specInJson: Spec
|
||||
|
||||
private specPath: string
|
||||
|
||||
private specDir: any
|
||||
private specDir: Unknown
|
||||
|
||||
private visitedEntities: any
|
||||
private visitedEntities: {
|
||||
[name: string]: Unknown
|
||||
}
|
||||
|
||||
private resolvedAllOfModels: any
|
||||
private resolvedAllOfModels: {
|
||||
[name: string]: Unknown
|
||||
}
|
||||
|
||||
private options: Options
|
||||
|
||||
|
@ -82,10 +152,10 @@ export class SpecResolver {
|
|||
*
|
||||
* @return {object} An instance of the SpecResolver class.
|
||||
*/
|
||||
constructor(specPath: string, specInJson: any, options: Options) {
|
||||
constructor(specPath: string, specInJson: Spec, options: Options) {
|
||||
if (specPath === null
|
||||
|| specPath === undefined
|
||||
|| typeof specPath.valueOf() !== "string"
|
||||
|| typeof specPath !== "string"
|
||||
|| !specPath.trim().length) {
|
||||
throw new Error(
|
||||
"specPath is a required property of type string and it cannot be an empty string.")
|
||||
|
@ -150,74 +220,49 @@ export class SpecResolver {
|
|||
* resolving the allof is present in any model definition and then setting additionalProperties
|
||||
* to false if it is not previously set to true or an object in that definition.
|
||||
*/
|
||||
public async resolve(): Promise<any> {
|
||||
const self = this
|
||||
return self.unifyXmsPaths().then(() => {
|
||||
if (self.options.shouldResolveRelativePaths) {
|
||||
return self.resolveRelativePaths()
|
||||
} else {
|
||||
return Promise.resolve(self)
|
||||
public async resolve(): Promise<this> {
|
||||
try {
|
||||
await this.unifyXmsPaths()
|
||||
if (this.options.shouldResolveRelativePaths) {
|
||||
await this.resolveRelativePaths()
|
||||
}
|
||||
}).then(() => {
|
||||
if (self.options.shouldResolveAllOf) {
|
||||
return self.resolveAllOfInDefinitions()
|
||||
} else {
|
||||
return Promise.resolve(self)
|
||||
if (this.options.shouldResolveAllOf) {
|
||||
await this.resolveAllOfInDefinitions()
|
||||
}
|
||||
}).then(() => {
|
||||
if (self.options.shouldResolveDiscriminator) {
|
||||
return self.resolveDiscriminator()
|
||||
} else {
|
||||
return Promise.resolve(self)
|
||||
if (this.options.shouldResolveDiscriminator) {
|
||||
await this.resolveDiscriminator()
|
||||
}
|
||||
}).then(() => {
|
||||
if (self.options.shouldResolveAllOf) {
|
||||
return self.deleteReferencesToAllOf()
|
||||
} else {
|
||||
return Promise.resolve(self)
|
||||
if (this.options.shouldResolveAllOf) {
|
||||
await this.deleteReferencesToAllOf()
|
||||
}
|
||||
}).then(() => {
|
||||
if (self.options.shouldSetAdditionalPropertiesFalse) {
|
||||
return self.setAdditionalPropertiesFalse()
|
||||
} else {
|
||||
return Promise.resolve(self)
|
||||
if (this.options.shouldSetAdditionalPropertiesFalse) {
|
||||
await this.setAdditionalPropertiesFalse()
|
||||
}
|
||||
}).then(() => {
|
||||
if (self.options.shouldResolveParameterizedHost) {
|
||||
return self.resolveParameterizedHost()
|
||||
} else {
|
||||
return Promise.resolve(self)
|
||||
if (this.options.shouldResolveParameterizedHost) {
|
||||
await this.resolveParameterizedHost()
|
||||
}
|
||||
}).then(() => {
|
||||
if (self.options.shouldResolvePureObjects) {
|
||||
return self.resolvePureObjects()
|
||||
} else {
|
||||
return Promise.resolve(self)
|
||||
if (this.options.shouldResolvePureObjects) {
|
||||
await this.resolvePureObjects()
|
||||
}
|
||||
}).then(() => {
|
||||
if (self.options.shouldResolveNullableTypes) {
|
||||
return self.resolveNullableTypes()
|
||||
} else {
|
||||
return Promise.resolve(self)
|
||||
if (this.options.shouldResolveNullableTypes) {
|
||||
await this.resolveNullableTypes()
|
||||
}
|
||||
}).then((): any => {
|
||||
if (self.options.shouldModelImplicitDefaultResponse) {
|
||||
return self.modelImplicitDefaultResponse()
|
||||
} else {
|
||||
return Promise.resolve(self)
|
||||
if (this.options.shouldModelImplicitDefaultResponse) {
|
||||
this.modelImplicitDefaultResponse()
|
||||
}
|
||||
}).catch((err: any) => {
|
||||
} catch (err) {
|
||||
const e = {
|
||||
message:
|
||||
`An Error occurred while resolving relative references and allOf in model definitions ` +
|
||||
`in the swagger spec: "${self.specPath}".`,
|
||||
`in the swagger spec: "${this.specPath}".`,
|
||||
code: ErrorCodes.ResolveSpecError.name,
|
||||
id: ErrorCodes.ResolveSpecError.id,
|
||||
innerErrors: [err]
|
||||
}
|
||||
log.error(err)
|
||||
return Promise.reject(e)
|
||||
});
|
||||
throw e
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -236,8 +281,8 @@ export class SpecResolver {
|
|||
*
|
||||
* @return {object} doc fully resolved json document
|
||||
*/
|
||||
public async resolveRelativePaths(doc?: any, docPath?: string, filterType?: string)
|
||||
: Promise<any> {
|
||||
public async resolveRelativePaths(doc?: Unknown, docPath?: string, filterType?: string)
|
||||
: Promise<void> {
|
||||
|
||||
const self = this
|
||||
let docDir
|
||||
|
@ -263,14 +308,12 @@ export class SpecResolver {
|
|||
}
|
||||
|
||||
const allRefsRemoteRelative = JsonRefs.findRefs(doc, options)
|
||||
const promiseFactories = utils.getKeys(allRefsRemoteRelative).map((refName: any) => {
|
||||
const promiseFactories = utils.getKeys(allRefsRemoteRelative).map(refName => {
|
||||
const refDetails = allRefsRemoteRelative[refName]
|
||||
return () => self.resolveRelativeReference(refName, refDetails, doc, docPath)
|
||||
});
|
||||
if (promiseFactories.length) {
|
||||
return await utils.executePromisesSequentially(promiseFactories)
|
||||
} else {
|
||||
return doc
|
||||
await utils.executePromisesSequentially(promiseFactories)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,7 +321,7 @@ export class SpecResolver {
|
|||
* Merges the x-ms-paths object into the paths object in swagger spec. The method assumes that the
|
||||
* paths present in "x-ms-paths" and "paths" are unique. Hence it does a simple union.
|
||||
*/
|
||||
private async unifyXmsPaths(): Promise<this> {
|
||||
private async unifyXmsPaths(): Promise<void> {
|
||||
// unify x-ms-paths into paths
|
||||
const xmsPaths = this.specInJson["x-ms-paths"]
|
||||
const paths = this.specInJson.paths
|
||||
|
@ -288,7 +331,6 @@ export class SpecResolver {
|
|||
}
|
||||
this.specInJson.paths = utils.mergeObjects(xmsPaths, paths)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -306,7 +348,8 @@ export class SpecResolver {
|
|||
* @return undefined the modified object
|
||||
*/
|
||||
private async resolveRelativeReference(
|
||||
refName: string, refDetails: any, doc: any, docPath: string|undefined): Promise<any> {
|
||||
refName: string, refDetails: RefDetails, doc: Unknown, docPath: string|undefined)
|
||||
: Promise<void> {
|
||||
|
||||
if (!refName || (refName && typeof refName.valueOf() !== "string")) {
|
||||
throw new Error('refName cannot be null or undefined and must be of type "string".')
|
||||
|
@ -346,7 +389,6 @@ export class SpecResolver {
|
|||
|| (!self.options.shouldResolveXmsExamples && slicedRefName.match(regex) === null)) {
|
||||
utils.setObject(doc, slicedRefName, result)
|
||||
}
|
||||
return doc
|
||||
} else {
|
||||
// resolve the local reference.
|
||||
// make the reference local to the doc being processed
|
||||
|
@ -374,10 +416,10 @@ export class SpecResolver {
|
|||
|
||||
function processDefinition(defName: string) {
|
||||
unresolvedDefinitions.push(async () => {
|
||||
if (result.definitions[defName].allOf) {
|
||||
const matchFound = result.definitions[defName].allOf.some((item: any) => {
|
||||
return (!self.visitedEntities[`/definitions/${defName}`])
|
||||
})
|
||||
const allOf = result.definitions[defName].allOf
|
||||
if (allOf) {
|
||||
const matchFound = allOf.some(
|
||||
item => !self.visitedEntities[`/definitions/${defName}`])
|
||||
if (matchFound) {
|
||||
const slicedDefinitionRef = `/definitions/${defName}`
|
||||
const definitionObj = result.definitions[defName]
|
||||
|
@ -393,11 +435,8 @@ export class SpecResolver {
|
|||
processDefinition(defName)
|
||||
}
|
||||
|
||||
return await utils.executePromisesSequentially(unresolvedDefinitions)
|
||||
await utils.executePromisesSequentially(unresolvedDefinitions)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
return doc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -406,24 +445,23 @@ export class SpecResolver {
|
|||
* Resolves the "allOf" array present in swagger model definitions by composing all the properties
|
||||
* of the parent model into the child model.
|
||||
*/
|
||||
private async resolveAllOfInDefinitions(): Promise<this> {
|
||||
private async resolveAllOfInDefinitions(): Promise<void> {
|
||||
const self = this
|
||||
const spec = self.specInJson
|
||||
const definitions = spec.definitions
|
||||
const modelNames = utils.getKeys(definitions)
|
||||
modelNames.map(modelName => {
|
||||
modelNames.forEach(modelName => {
|
||||
const model = definitions[modelName]
|
||||
const modelRef = "/definitions/" + modelName
|
||||
return self.resolveAllOfInModel(model, modelRef)
|
||||
self.resolveAllOfInModel(model, modelRef)
|
||||
})
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the "allOf" array present in swagger model definitions by composing all the properties
|
||||
* of the parent model into the child model.
|
||||
*/
|
||||
private resolveAllOfInModel(model: any, modelRef: any) {
|
||||
private resolveAllOfInModel(model: Model, modelRef: string|undefined) {
|
||||
const self = this
|
||||
const spec = self.specInJson
|
||||
if (!model || (model && typeof model !== "object")) {
|
||||
|
@ -438,18 +476,18 @@ export class SpecResolver {
|
|||
|
||||
if (!self.resolvedAllOfModels[modelRef]) {
|
||||
if (model && model.allOf) {
|
||||
model.allOf.map((item: any) => {
|
||||
let referencedModel = item
|
||||
model.allOf.map(item => {
|
||||
let referencedModel: Model = item
|
||||
const ref = item.$ref
|
||||
const slicedRef = ref ? ref.slice(1) : undefined
|
||||
if (ref) {
|
||||
if (slicedRef !== undefined) {
|
||||
referencedModel = utils.getObject(spec, slicedRef)
|
||||
}
|
||||
if (referencedModel.allOf) {
|
||||
self.resolveAllOfInModel(referencedModel, slicedRef)
|
||||
}
|
||||
model = self.mergeParentAllOfInChild(referencedModel, model)
|
||||
self.resolvedAllOfModels[slicedRef] = referencedModel
|
||||
self.resolvedAllOfModels[slicedRef as string] = referencedModel
|
||||
return model
|
||||
})
|
||||
} else {
|
||||
|
@ -468,7 +506,7 @@ export class SpecResolver {
|
|||
*
|
||||
* @return {object} returns the merged child oject
|
||||
*/
|
||||
private mergeParentAllOfInChild(parent: any, child: any) {
|
||||
private mergeParentAllOfInChild(parent: Model, child: Model) {
|
||||
const self = this
|
||||
if (!parent || (parent && typeof parent !== "object")) {
|
||||
throw new Error(`parent must be of type "object".`)
|
||||
|
@ -498,30 +536,30 @@ export class SpecResolver {
|
|||
/**
|
||||
* Deletes all the references to allOf from all the model definitions in the swagger spec.
|
||||
*/
|
||||
private async deleteReferencesToAllOf(): Promise<this> {
|
||||
private async deleteReferencesToAllOf(): Promise<void> {
|
||||
const self = this
|
||||
const spec = self.specInJson
|
||||
const definitions = spec.definitions
|
||||
const modelNames = utils.getKeys(definitions)
|
||||
modelNames.map(modelName => {
|
||||
modelNames.forEach(modelName => {
|
||||
if (definitions[modelName].allOf) {
|
||||
delete definitions[modelName].allOf
|
||||
}
|
||||
})
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets additionalProperties of the given modelNames to false.
|
||||
*
|
||||
* @param {array} [modelNames] An array of strings that specifies the modelNames to be processed.
|
||||
* Default: All the modelnames from the definitions section in the swagger spec.
|
||||
* Default: All the model names from the definitions section in the swagger spec.
|
||||
*
|
||||
* @param {boolean} [force] A boolean value that indicates whether to ignore the
|
||||
* additionalProperties
|
||||
* set to true or an object and forcefully set it to false. Default: false.
|
||||
*/
|
||||
private async setAdditionalPropertiesFalse(modelNames?: any[], force?: boolean): Promise<this> {
|
||||
private async setAdditionalPropertiesFalse(modelNames?: string[], force?: boolean)
|
||||
: Promise<void> {
|
||||
const self = this
|
||||
const spec = self.specInJson
|
||||
const definitions = spec.definitions
|
||||
|
@ -540,7 +578,6 @@ export class SpecResolver {
|
|||
}
|
||||
}
|
||||
})
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -558,15 +595,15 @@ export class SpecResolver {
|
|||
* be a mismatch between the number of path parameters provided in the operation
|
||||
* definition and the number of parameters actually present in the path template.
|
||||
*/
|
||||
private async resolveParameterizedHost(): Promise<this> {
|
||||
private async resolveParameterizedHost(): Promise<void> {
|
||||
const self = this
|
||||
const spec = self.specInJson
|
||||
const parameterizedHost = spec[Constants.xmsParameterizedHost]
|
||||
const parameterizedHost = spec[C.xmsParameterizedHost]
|
||||
const hostParameters = parameterizedHost ? parameterizedHost.parameters : null
|
||||
if (parameterizedHost && hostParameters) {
|
||||
const paths = spec.paths
|
||||
for (const verbs of utils.getValues(paths)) {
|
||||
for (const operation of utils.getValues(verbs)) {
|
||||
for (const operation of getOperations(verbs)) {
|
||||
let operationParameters = operation.parameters
|
||||
if (!operationParameters) { operationParameters = [] }
|
||||
// merge host parameters into parameters for that operation.
|
||||
|
@ -574,8 +611,6 @@ export class SpecResolver {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -584,7 +619,7 @@ export class SpecResolver {
|
|||
* i.e `"type": "object"` and `"properties": {}` or `"properties"` is absent or the entity has
|
||||
* "additionalProperties": { "type": "object" }.
|
||||
*/
|
||||
private async resolvePureObjects(): Promise<this> {
|
||||
private async resolvePureObjects(): Promise<void> {
|
||||
const self = this
|
||||
const spec = self.specInJson
|
||||
const definitions = spec.definitions
|
||||
|
@ -594,7 +629,7 @@ export class SpecResolver {
|
|||
utils.relaxModelLikeEntities(model)
|
||||
}
|
||||
|
||||
const resolveOperation = (operation: any) => {
|
||||
const resolveOperation = (operation: Operation) => {
|
||||
// scan every parameter in the operation
|
||||
const consumes = _.isUndefined(operation.consumes) ?
|
||||
_.isUndefined(spec.consumes) ?
|
||||
|
@ -608,13 +643,11 @@ export class SpecResolver {
|
|||
: spec.produces
|
||||
: operation.produces
|
||||
|
||||
const octetStream = (elements: any) => {
|
||||
return elements.some((e: any) => {
|
||||
return e.toLowerCase() === "application/octet-stream"
|
||||
})
|
||||
const octetStream = (elements: string[]) => {
|
||||
return elements.some(e => e.toLowerCase() === "application/octet-stream")
|
||||
}
|
||||
|
||||
const resolveParameter2 = (param: any) => {
|
||||
const resolveParameter2 = (param: Model) => {
|
||||
if (param.in && param.in === "body" && param.schema && !octetStream(consumes)) {
|
||||
param.schema = utils.relaxModelLikeEntities(param.schema)
|
||||
} else {
|
||||
|
@ -635,7 +668,7 @@ export class SpecResolver {
|
|||
}
|
||||
}
|
||||
|
||||
const resolveParameter = (param: any) => {
|
||||
const resolveParameter = (param: Model) => {
|
||||
if (param.in && param.in === "body" && param.schema) {
|
||||
param.schema = utils.relaxModelLikeEntities(param.schema)
|
||||
} else {
|
||||
|
@ -645,7 +678,7 @@ export class SpecResolver {
|
|||
|
||||
// scan every operation
|
||||
for (const pathObj of utils.getValues(spec.paths)) {
|
||||
for (const operation of utils.getValues(pathObj)) {
|
||||
for (const operation of getOperations(pathObj)) {
|
||||
resolveOperation(operation)
|
||||
}
|
||||
// scan path level parameters if any
|
||||
|
@ -655,15 +688,15 @@ export class SpecResolver {
|
|||
}
|
||||
// scan global parameters
|
||||
for (const param of utils.getKeys(spec.parameters)) {
|
||||
if (spec.parameters[param].in
|
||||
&& spec.parameters[param].in === "body"
|
||||
&& spec.parameters[param].schema) {
|
||||
spec.parameters[param].schema = utils.relaxModelLikeEntities(spec.parameters[param].schema)
|
||||
const parameter = spec.parameters[param]
|
||||
if (parameter.in
|
||||
&& parameter.in === "body"
|
||||
&& parameter.schema) {
|
||||
parameter.schema = utils.relaxModelLikeEntities(parameter.schema)
|
||||
}
|
||||
spec.parameters[param] = utils.relaxEntityType(
|
||||
spec.parameters[param], spec.parameters[param].required)
|
||||
parameter, parameter.required)
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -677,7 +710,7 @@ export class SpecResolver {
|
|||
spec.definitions.CloudError = utils.CloudError
|
||||
}
|
||||
for (const pathObj of utils.getValues(spec.paths)) {
|
||||
for (const operation of utils.getValues(pathObj)) {
|
||||
for (const operation of getOperations(pathObj)) {
|
||||
|
||||
if (operation.responses && !operation.responses.default) {
|
||||
operation.responses.default = utils.CloudErrorSchema
|
||||
|
@ -712,7 +745,7 @@ export class SpecResolver {
|
|||
* "Cat": { "required": [ "animalType" ], "properties": { "animalType": { "type": "string",
|
||||
* "enum": [ "Cat" ] }, . . } }.
|
||||
*/
|
||||
private async resolveDiscriminator(): Promise<this> {
|
||||
private async resolveDiscriminator(): Promise<void> {
|
||||
const self = this
|
||||
const spec = self.specInJson
|
||||
const definitions = spec.definitions
|
||||
|
@ -720,18 +753,17 @@ export class SpecResolver {
|
|||
const subTreeMap = new Map()
|
||||
const references = JsonRefs.findRefs(spec)
|
||||
|
||||
modelNames.map((modelName) => {
|
||||
if (definitions[modelName].discriminator) {
|
||||
modelNames.forEach(modelName => {
|
||||
const discriminator = definitions[modelName].discriminator
|
||||
if (discriminator) {
|
||||
let rootNode = subTreeMap.get(modelName)
|
||||
if (!rootNode) {
|
||||
rootNode = self.createPolymorphicTree(
|
||||
modelName, definitions[modelName].discriminator, subTreeMap)
|
||||
modelName, discriminator, subTreeMap)
|
||||
}
|
||||
self.updateReferencesWithOneOf(subTreeMap, references)
|
||||
}
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -744,7 +776,7 @@ export class SpecResolver {
|
|||
* The way we're relaxing the type is to have the model be a "oneOf" array with one value being
|
||||
* the original content of the model and the second value "type": "null".
|
||||
*/
|
||||
private resolveNullableTypes(): Promise<this> {
|
||||
private async resolveNullableTypes(): Promise<void> {
|
||||
const self = this
|
||||
const spec = self.specInJson
|
||||
const definitions = spec.definitions
|
||||
|
@ -756,18 +788,20 @@ export class SpecResolver {
|
|||
}
|
||||
// scan every operation response
|
||||
for (const pathObj of utils.getValues(spec.paths)) {
|
||||
// need to handle paramaters at this level
|
||||
// need to handle parameters at this level
|
||||
if (pathObj.parameters) {
|
||||
for (const parameter of utils.getKeys(pathObj.parameters)) {
|
||||
pathObj.parameters[parameter] = utils.allowNullableParams(pathObj.parameters[parameter])
|
||||
const n = parseInt(parameter)
|
||||
pathObj.parameters[n] = utils.allowNullableParams(pathObj.parameters[n])
|
||||
}
|
||||
}
|
||||
for (const operation of utils.getValues(pathObj)) {
|
||||
for (const operation of getOperations(pathObj)) {
|
||||
// need to account for parameters, except for path parameters
|
||||
if (operation.parameters) {
|
||||
for (const parameter of utils.getKeys(operation.parameters)) {
|
||||
operation.parameters[parameter] = utils.allowNullableParams(
|
||||
operation.parameters[parameter])
|
||||
const n = parseInt(parameter)
|
||||
operation.parameters[n] = utils.allowNullableParams(
|
||||
operation.parameters[n])
|
||||
}
|
||||
}
|
||||
// going through responses
|
||||
|
@ -785,8 +819,6 @@ export class SpecResolver {
|
|||
for (const parameter of utils.getKeys(spec.parameters)) {
|
||||
spec.parameters[parameter] = utils.allowNullableParams(spec.parameters[parameter])
|
||||
}
|
||||
|
||||
return Promise.resolve(self)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -801,7 +833,7 @@ export class SpecResolver {
|
|||
* [here](https://bit.ly/2sw5MOa)
|
||||
* for detailed structure of the object.
|
||||
*/
|
||||
private updateReferencesWithOneOf(subTreeMap: Map<string, PolymorphicTree>, references: any)
|
||||
private updateReferencesWithOneOf(subTreeMap: Map<string, PolymorphicTree>, references: any[])
|
||||
: void {
|
||||
|
||||
const spec = this.specInJson
|
||||
|
@ -847,6 +879,7 @@ export class SpecResolver {
|
|||
private createPolymorphicTree(
|
||||
name: string, discriminator: string, subTreeMap: Map<string, PolymorphicTree>)
|
||||
: PolymorphicTree {
|
||||
|
||||
if (name === null
|
||||
|| name === undefined
|
||||
|| typeof name.valueOf() !== "string"
|
||||
|
@ -872,30 +905,29 @@ export class SpecResolver {
|
|||
|
||||
// Adding the model name or it's discriminator value as an enum constraint with one value
|
||||
// (constant) on property marked as discriminator
|
||||
if (definitions[name]
|
||||
&& definitions[name].properties
|
||||
&& definitions[name].properties[discriminator]) {
|
||||
let val = name
|
||||
if (definitions[name]["x-ms-discriminator-value"]) {
|
||||
val = definitions[name]["x-ms-discriminator-value"]
|
||||
}
|
||||
const definition = definitions[name]
|
||||
if (definition
|
||||
&& definition.properties
|
||||
&& definition.properties[discriminator]) {
|
||||
|
||||
const val = definition["x-ms-discriminator-value"] || name
|
||||
// Ensure that the property marked as a discriminator has only one value in the enum
|
||||
// constraint for that model and it
|
||||
// should be the one that is the model name or the value indicated by
|
||||
// x-ms-discriminator-value. This will make the discriminator
|
||||
// property a constant (in json schema terms).
|
||||
if (definitions[name].properties[discriminator].$ref) {
|
||||
delete definitions[name].properties[discriminator].$ref
|
||||
if (definition.properties[discriminator].$ref) {
|
||||
delete definition.properties[discriminator].$ref
|
||||
}
|
||||
// We will set "type" to "string". It is safe to assume that properties marked as
|
||||
// "discriminator" will be of type "string"
|
||||
// as it needs to refer to a model definition name. Model name would be a key in the
|
||||
// definitions object/dictionary in the
|
||||
// swagger spec. keys would always be a string in a JSON object/dictionary.
|
||||
if (!definitions[name].properties[discriminator].type) {
|
||||
definitions[name].properties[discriminator].type = "string"
|
||||
if (!definition.properties[discriminator].type) {
|
||||
definition.properties[discriminator].type = "string"
|
||||
}
|
||||
definitions[name].properties[discriminator].enum = [`${val}`]
|
||||
definition.properties[discriminator].enum = [`${val}`]
|
||||
}
|
||||
|
||||
const children = this.findChildren(name)
|
||||
|
@ -915,7 +947,7 @@ export class SpecResolver {
|
|||
* @returns {Set} result- A set of model names that are the children of the given model in the
|
||||
* inheritance chain.
|
||||
*/
|
||||
private findChildren(name: string): Set<any> {
|
||||
private findChildren(name: string): Set<string> {
|
||||
if (name === null
|
||||
|| name === undefined
|
||||
|| typeof name.valueOf() !== "string"
|
||||
|
@ -927,10 +959,10 @@ export class SpecResolver {
|
|||
const reference = `#/definitions/${name}`
|
||||
const result = new Set()
|
||||
|
||||
const findReferences = (definitionName: any) => {
|
||||
const findReferences = (definitionName: string) => {
|
||||
const definition = definitions[definitionName]
|
||||
if (definition && definition.allOf) {
|
||||
definition.allOf.forEach((item: any) => {
|
||||
definition.allOf.forEach(item => {
|
||||
// TODO: What if there is an inline definition instead of $ref
|
||||
if (item.$ref && item.$ref === reference) {
|
||||
log.debug(`reference found: ${reference} in definition: ${definitionName}`)
|
||||
|
@ -952,10 +984,10 @@ export class SpecResolver {
|
|||
*
|
||||
* @param {PolymorphicTree} rootNode- A PolymorphicTree that represents the model in the
|
||||
* inheritance chain.
|
||||
* @returns {PolymorphicTree} result- An array of reference objects that comprise of the
|
||||
* @returns {PolymorphicTree} An array of reference objects that comprise of the
|
||||
* parent and its children.
|
||||
*/
|
||||
private buildOneOfReferences(rootNode: PolymorphicTree): Set<any> {
|
||||
private buildOneOfReferences(rootNode: PolymorphicTree): Set<PolymorphicTree> {
|
||||
let result = new Set()
|
||||
result.add({ $ref: `#/definitions/${rootNode.name}` })
|
||||
for (const entry of rootNode.children.entries()) {
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import * as util from "util"
|
||||
import * as fs from "fs"
|
||||
import * as yaml from "js-yaml"
|
||||
import * as path from "path"
|
||||
import * as Sway from "sway"
|
||||
import * as msRest from "ms-rest"
|
||||
|
@ -13,16 +11,107 @@ const HttpRequest = msRest.WebResource
|
|||
import { SpecResolver } from "./specResolver"
|
||||
import * as specResolver from "./specResolver"
|
||||
import * as utils from "../util/utils"
|
||||
import { Constants } from "../util/constants"
|
||||
import { log } from "../util/logging"
|
||||
import { ResponseWrapper } from "../models/responseWrapper"
|
||||
import { validateResponse } from "../util/validationResponse"
|
||||
import { Error } from "../util/error"
|
||||
import { Unknown } from "../util/unknown"
|
||||
import * as C from "../util/constants"
|
||||
import { Operation } from "sway"
|
||||
|
||||
const ErrorCodes = Constants.ErrorCodes;
|
||||
const ErrorCodes = C.ErrorCodes;
|
||||
|
||||
export interface Options extends specResolver.Options {
|
||||
isPathCaseSensitive?: boolean
|
||||
readonly isPathCaseSensitive?: boolean
|
||||
}
|
||||
|
||||
export interface ErrorCode {
|
||||
readonly name: string
|
||||
readonly id: string
|
||||
}
|
||||
|
||||
interface Validation {
|
||||
readonly errors: Error[]
|
||||
readonly warnings: Unknown[]
|
||||
}
|
||||
|
||||
interface RequestValidation {
|
||||
request?: Unknown
|
||||
validationResult?: Validation
|
||||
}
|
||||
|
||||
interface ResponseValidation {
|
||||
readonly [name: string]: Validation
|
||||
}
|
||||
|
||||
interface Scenario {
|
||||
readonly requestValidation: RequestValidation
|
||||
readonly responseValidation: ResponseValidation
|
||||
}
|
||||
|
||||
interface Scenarios {
|
||||
[name: string]: Scenario
|
||||
}
|
||||
|
||||
interface ValidationResult {
|
||||
exampleNotFound?: Error
|
||||
scenarios?: Scenarios
|
||||
requestValidation?: RequestValidation
|
||||
responseValidation?: ResponseValidation
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
isValid?: Unknown
|
||||
error?: Error
|
||||
warning?: Unknown
|
||||
result?: Unknown
|
||||
}
|
||||
|
||||
export interface SpecScenarios {
|
||||
[name: string]: {
|
||||
isValid: Unknown
|
||||
request: Result
|
||||
responses: {
|
||||
[name: string]: Result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface SpecValidation {
|
||||
isValid: Unknown
|
||||
errors: Unknown
|
||||
warnings: Unknown
|
||||
result?: Unknown
|
||||
error?: Unknown
|
||||
}
|
||||
|
||||
export interface OperationResult {
|
||||
isValid?: Unknown
|
||||
scenarios?: SpecScenarios
|
||||
error?: Unknown
|
||||
request?: Result
|
||||
responses?: {
|
||||
[name: string]: Result
|
||||
}
|
||||
}
|
||||
|
||||
export interface SpecValidationResult {
|
||||
resolveSpec?: Unknown
|
||||
validityStatus: Unknown
|
||||
operations: {
|
||||
[name: string]: {
|
||||
[name: string]: OperationResult
|
||||
}
|
||||
}
|
||||
validateSpec?: SpecValidation
|
||||
initialize?: Unknown
|
||||
}
|
||||
|
||||
export interface ExampleResponse {
|
||||
readonly headers: {
|
||||
[name: string]: Unknown
|
||||
}
|
||||
readonly body: Unknown
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -31,13 +120,13 @@ export interface Options extends specResolver.Options {
|
|||
*/
|
||||
export class SpecValidator {
|
||||
|
||||
public specValidationResult: any
|
||||
public specValidationResult: SpecValidationResult
|
||||
|
||||
private specPath: string
|
||||
|
||||
private specDir: any
|
||||
private specDir: Unknown
|
||||
|
||||
private specInJson: any
|
||||
private specInJson: specResolver.Spec
|
||||
|
||||
private specResolver: SpecResolver|null
|
||||
|
||||
|
@ -45,9 +134,9 @@ export class SpecValidator {
|
|||
|
||||
private options: Options
|
||||
|
||||
private sampleRequest: any
|
||||
private sampleRequest: Unknown
|
||||
|
||||
private sampleResponse: any
|
||||
private sampleResponse: Unknown
|
||||
|
||||
/*
|
||||
* @constructor
|
||||
|
@ -87,7 +176,7 @@ export class SpecValidator {
|
|||
*
|
||||
* @return {object} An instance of the SpecValidator class.
|
||||
*/
|
||||
constructor(specPath: string, specInJson: any, options: Options) {
|
||||
constructor(specPath: string, specInJson: specResolver.Spec|undefined|null, options: Options) {
|
||||
if (specPath === null
|
||||
|| specPath === undefined
|
||||
|| typeof specPath.valueOf() !== "string"
|
||||
|
@ -103,7 +192,7 @@ export class SpecValidator {
|
|||
}
|
||||
this.specPath = specPath
|
||||
this.specDir = path.dirname(this.specPath)
|
||||
this.specInJson = specInJson
|
||||
this.specInJson = specInJson as specResolver.Spec
|
||||
this.specResolver = null
|
||||
this.specValidationResult = { validityStatus: true, operations: {} }
|
||||
this.swaggerApi = null
|
||||
|
@ -154,7 +243,7 @@ export class SpecValidator {
|
|||
}
|
||||
}
|
||||
|
||||
public async validateSpec(): Promise<any> {
|
||||
public async validateSpec(): Promise<Unknown> {
|
||||
const self = this
|
||||
self.specValidationResult.validateSpec = {
|
||||
isValid: true,
|
||||
|
@ -182,7 +271,7 @@ export class SpecValidator {
|
|||
validationResult.errors)
|
||||
self.specValidationResult.validateSpec.errors = validateResponse.constructErrors(
|
||||
e, self.specPath, self.getProviderNamespace())
|
||||
log.error(Constants.Errors)
|
||||
log.error(C.Errors)
|
||||
log.error("------")
|
||||
self.updateValidityStatus()
|
||||
log.error(e as any)
|
||||
|
@ -194,7 +283,7 @@ export class SpecValidator {
|
|||
const warnings = validateResponse.sanitizeWarnings(validationResult.warnings)
|
||||
if (warnings && warnings.length) {
|
||||
self.specValidationResult.validateSpec.warnings = warnings
|
||||
log.debug(Constants.Warnings)
|
||||
log.debug(C.Warnings)
|
||||
log.debug("--------")
|
||||
log.debug(util.inspect(warnings))
|
||||
}
|
||||
|
@ -236,25 +325,26 @@ export class SpecValidator {
|
|||
|
||||
let operations = this.swaggerApi.getOperations()
|
||||
if (operationIds) {
|
||||
const operationIdsObj: any = {}
|
||||
const operationIdsObj: { [name: string]: Unknown } = {}
|
||||
operationIds.trim().split(",").map(item => operationIdsObj[item.trim()] = 1)
|
||||
const operationsToValidate = operations
|
||||
.filter((item: any) => Boolean(operationIdsObj[item.operationId]))
|
||||
.filter(item => Boolean(operationIdsObj[item.operationId]))
|
||||
if (operationsToValidate.length) { operations = operationsToValidate }
|
||||
}
|
||||
|
||||
for (const operation of operations) {
|
||||
this.specValidationResult.operations[operation.operationId] = {}
|
||||
this.specValidationResult.operations[operation.operationId][Constants.xmsExamples] = {}
|
||||
this.specValidationResult.operations[operation.operationId][Constants.exampleInSpec] = {}
|
||||
this.specValidationResult.operations[operation.operationId] = {
|
||||
[C.xmsExamples]: {},
|
||||
[C.exampleInSpec]: {}
|
||||
}
|
||||
this.validateOperation(operation)
|
||||
if (utils
|
||||
.getKeys(this.specValidationResult.operations
|
||||
[operation.operationId]
|
||||
[Constants.exampleInSpec])
|
||||
[C.exampleInSpec])
|
||||
.length
|
||||
=== 0) {
|
||||
delete this.specValidationResult.operations[operation.operationId][Constants.exampleInSpec]
|
||||
delete this.specValidationResult.operations[operation.operationId][C.exampleInSpec]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -280,7 +370,7 @@ export class SpecValidator {
|
|||
/*
|
||||
* Updates the validityStatus of the internal specValidationResult based on the provided value.
|
||||
*
|
||||
* @param {boolean} value A truthy or a falsy value.
|
||||
* @param {boolean} value
|
||||
*/
|
||||
private updateValidityStatus(value?: boolean): void {
|
||||
this.specValidationResult.validityStatus = Boolean(value)
|
||||
|
@ -290,7 +380,7 @@ export class SpecValidator {
|
|||
* Constructs the Error object and updates the validityStatus unless indicated to not update the
|
||||
* status.
|
||||
*
|
||||
* @param {string} code The Error code that uniquely idenitifies the error.
|
||||
* @param {string} code The Error code that uniquely identifiers the error.
|
||||
*
|
||||
* @param {string} message The message that provides more information about the error.
|
||||
*
|
||||
|
@ -302,13 +392,17 @@ export class SpecValidator {
|
|||
* @return {object} err Return the constructed Error object.
|
||||
*/
|
||||
private constructErrorObject(
|
||||
code: any, message: string, innerErrors?: null|Error[], skipValidityStatusUpdate?: boolean) {
|
||||
code: ErrorCode,
|
||||
message: string,
|
||||
innerErrors?: null|Error[],
|
||||
skipValidityStatusUpdate?: boolean)
|
||||
: Error {
|
||||
|
||||
const err: Error = {
|
||||
code: code.name,
|
||||
id: code.id,
|
||||
message: message,
|
||||
innerErrors: innerErrors ? innerErrors : (undefined as any)
|
||||
innerErrors: innerErrors ? innerErrors : undefined
|
||||
}
|
||||
if (!skipValidityStatusUpdate) {
|
||||
this.updateValidityStatus()
|
||||
|
@ -332,7 +426,7 @@ export class SpecValidator {
|
|||
if (!id) {
|
||||
throw new Error(`id cannot be null or undefined and must be of type string.`)
|
||||
}
|
||||
const result = this.swaggerApi.getOperations().find((item: any) => item.operationId === id)
|
||||
const result = this.swaggerApi.getOperations().find(item => item.operationId === id)
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -343,24 +437,23 @@ export class SpecValidator {
|
|||
*
|
||||
* @return {object} xmsExample - The xmsExample object.
|
||||
*/
|
||||
private getXmsExamples(idOrObj: any) {
|
||||
private getXmsExamples(idOrObj: string|Sway.Operation) {
|
||||
if (!idOrObj) {
|
||||
throw new Error(`idOrObj cannot be null or undefined and must be of type string or object.`)
|
||||
}
|
||||
let operation: any = {}
|
||||
if (typeof idOrObj.valueOf() === "string") {
|
||||
operation = this.getOperationById(idOrObj)
|
||||
} else {
|
||||
operation = idOrObj
|
||||
}
|
||||
const operation: Sway.Operation|undefined = typeof idOrObj === "string"
|
||||
? this.getOperationById(idOrObj)
|
||||
: idOrObj
|
||||
let result
|
||||
if (operation && operation[Constants.xmsExamples]) {
|
||||
result = operation[Constants.xmsExamples]
|
||||
if (operation && operation[C.xmsExamples]) {
|
||||
result = operation[C.xmsExamples]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private initializeExampleResult(operationId: any, exampleType: any, scenarioName: any): void {
|
||||
private initializeExampleResult(
|
||||
operationId: string, exampleType: string, scenarioName: string|undefined)
|
||||
: void {
|
||||
const initialResult = {
|
||||
isValid: true,
|
||||
request: {
|
||||
|
@ -372,31 +465,40 @@ export class SpecValidator {
|
|||
if (!operationResult) {
|
||||
operationResult = {}
|
||||
}
|
||||
if (exampleType === Constants.exampleInSpec) {
|
||||
if (exampleType === C.exampleInSpec) {
|
||||
if (!operationResult[exampleType] ||
|
||||
(operationResult[exampleType] && !utils.getKeys(operationResult[exampleType]).length)) {
|
||||
operationResult[exampleType] = initialResult
|
||||
}
|
||||
}
|
||||
|
||||
if (exampleType === Constants.xmsExamples) {
|
||||
if (!operationResult[exampleType].scenarios) {
|
||||
operationResult[exampleType].scenarios = {}
|
||||
if (exampleType === C.xmsExamples) {
|
||||
const o = operationResult[exampleType]
|
||||
if (!o.scenarios) {
|
||||
o.scenarios = {}
|
||||
}
|
||||
if (!operationResult[exampleType].scenarios[scenarioName]) {
|
||||
operationResult[exampleType].scenarios[scenarioName] = initialResult
|
||||
if (scenarioName === undefined) {
|
||||
throw new Error("scenarioName === undefined")
|
||||
}
|
||||
if (!o.scenarios[scenarioName]) {
|
||||
o.scenarios[scenarioName] = initialResult
|
||||
}
|
||||
}
|
||||
this.specValidationResult.operations[operationId] = operationResult
|
||||
return
|
||||
}
|
||||
|
||||
private constructRequestResult(
|
||||
operationResult: any,
|
||||
isValid: any,
|
||||
msg: any,
|
||||
requestValidationErrors?: any,
|
||||
requestValidationWarnings?: any): void {
|
||||
operationResult: OperationResult,
|
||||
isValid: Unknown,
|
||||
msg: string,
|
||||
requestValidationErrors?: Error[]|null,
|
||||
requestValidationWarnings?: Unknown)
|
||||
: void {
|
||||
|
||||
if (operationResult.request === undefined) {
|
||||
throw new Error("operationResult.result is undefined")
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
operationResult.isValid = false
|
||||
operationResult.request.isValid = false
|
||||
|
@ -415,13 +517,17 @@ export class SpecValidator {
|
|||
}
|
||||
|
||||
private constructResponseResult(
|
||||
operationResult: any,
|
||||
responseStatusCode: any,
|
||||
isValid: any,
|
||||
msg: any,
|
||||
responseValidationErrors?: any,
|
||||
responseValidationWarnings?: any)
|
||||
operationResult: OperationResult,
|
||||
responseStatusCode: string,
|
||||
isValid: Unknown,
|
||||
msg: string,
|
||||
responseValidationErrors?: Error[]|null,
|
||||
responseValidationWarnings?: Unknown)
|
||||
: void {
|
||||
|
||||
if (operationResult.responses === undefined) {
|
||||
throw new Error("operationResult.responses is undefined")
|
||||
}
|
||||
if (!operationResult.responses[responseStatusCode]) {
|
||||
operationResult.responses[responseStatusCode] = {}
|
||||
}
|
||||
|
@ -443,11 +549,13 @@ export class SpecValidator {
|
|||
}
|
||||
|
||||
private constructRequestResultWrapper(
|
||||
operationId: any,
|
||||
requestValidationErrors: any,
|
||||
requestValidationWarnings: any,
|
||||
exampleType: any,
|
||||
scenarioName?: any): void {
|
||||
operationId: string,
|
||||
requestValidationErrors: Error[],
|
||||
requestValidationWarnings: Unknown[],
|
||||
exampleType: string,
|
||||
scenarioName?: string)
|
||||
: void {
|
||||
|
||||
this.initializeExampleResult(operationId, exampleType, scenarioName)
|
||||
let operationResult
|
||||
let part
|
||||
|
@ -455,9 +563,10 @@ export class SpecValidator {
|
|||
let infoMsg
|
||||
let errorMsg
|
||||
let warnMsg
|
||||
if (exampleType === Constants.xmsExamples) {
|
||||
operationResult =
|
||||
this.specValidationResult.operations[operationId][exampleType].scenarios[scenarioName]
|
||||
if (exampleType === C.xmsExamples) {
|
||||
const scenarios =
|
||||
this.specValidationResult.operations[operationId][exampleType].scenarios as SpecScenarios
|
||||
operationResult = scenarios[scenarioName as string]
|
||||
part = `for x-ms-example "${scenarioName}" in operation "${operationId}"`
|
||||
} else {
|
||||
operationResult = this.specValidationResult.operations[operationId][exampleType]
|
||||
|
@ -478,13 +587,14 @@ export class SpecValidator {
|
|||
}
|
||||
|
||||
private constructResponseResultWrapper(
|
||||
operationId: any,
|
||||
responseStatusCode: any,
|
||||
responseValidationErrors: any,
|
||||
responseValidationWarnings: any,
|
||||
exampleType: any,
|
||||
scenarioName?: any)
|
||||
operationId: string,
|
||||
responseStatusCode: string,
|
||||
responseValidationErrors: Error[],
|
||||
responseValidationWarnings: Unknown[],
|
||||
exampleType: string,
|
||||
scenarioName?: string)
|
||||
: void {
|
||||
|
||||
this.initializeExampleResult(operationId, exampleType, scenarioName)
|
||||
let operationResult
|
||||
let part
|
||||
|
@ -492,9 +602,10 @@ export class SpecValidator {
|
|||
let infoMsg
|
||||
let errorMsg
|
||||
let warnMsg
|
||||
if (exampleType === Constants.xmsExamples) {
|
||||
operationResult =
|
||||
this.specValidationResult.operations[operationId][exampleType].scenarios[scenarioName]
|
||||
if (exampleType === C.xmsExamples) {
|
||||
const scenarios =
|
||||
this.specValidationResult.operations[operationId][exampleType].scenarios as SpecScenarios
|
||||
operationResult = scenarios[scenarioName as string]
|
||||
part = `for x-ms-example "${scenarioName}" in operation "${operationId}"`
|
||||
} else {
|
||||
operationResult = this.specValidationResult.operations[operationId][exampleType]
|
||||
|
@ -529,20 +640,24 @@ export class SpecValidator {
|
|||
*
|
||||
* @return {object} xmsExample - The xmsExample object.
|
||||
*/
|
||||
private constructOperationResult(operation: any, result: any, exampleType: string): void {
|
||||
private constructOperationResult(
|
||||
operation: Operation, result: ValidationResult, exampleType: string): void {
|
||||
|
||||
const operationId = operation.operationId
|
||||
if (result.exampleNotFound) {
|
||||
this.specValidationResult.operations[operationId][exampleType].error = result.exampleNotFound
|
||||
// log.error(result.exampleNotFound)
|
||||
}
|
||||
if (exampleType === Constants.xmsExamples) {
|
||||
if (exampleType === C.xmsExamples) {
|
||||
if (result.scenarios) {
|
||||
for (const scenario of utils.getKeys(result.scenarios)) {
|
||||
const validationResult = result.scenarios[scenario].requestValidation.validationResult
|
||||
if (validationResult === undefined) {
|
||||
throw new Error("validationResult is undefined")
|
||||
}
|
||||
// requestValidation
|
||||
const requestValidationErrors =
|
||||
result.scenarios[scenario].requestValidation.validationResult.errors
|
||||
const requestValidationWarnings =
|
||||
result.scenarios[scenario].requestValidation.validationResult.warnings
|
||||
const requestValidationErrors = validationResult.errors
|
||||
const requestValidationWarnings = validationResult.warnings
|
||||
this.constructRequestResultWrapper(
|
||||
operationId, requestValidationErrors, requestValidationWarnings, exampleType, scenario)
|
||||
// responseValidation
|
||||
|
@ -563,11 +678,15 @@ export class SpecValidator {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if (exampleType === Constants.exampleInSpec) {
|
||||
} else if (exampleType === C.exampleInSpec) {
|
||||
if (result.requestValidation && utils.getKeys(result.requestValidation).length) {
|
||||
// requestValidation
|
||||
const requestValidationErrors = result.requestValidation.validationResult.errors
|
||||
const requestValidationWarnings = result.requestValidation.validationResult.warnings
|
||||
const validationResult = result.requestValidation.validationResult
|
||||
if (validationResult === undefined) {
|
||||
throw new Error("validationResult is undefined")
|
||||
}
|
||||
const requestValidationErrors = validationResult.errors
|
||||
const requestValidationWarnings = validationResult.warnings
|
||||
this.constructRequestResultWrapper(
|
||||
operationId, requestValidationErrors, requestValidationWarnings, exampleType)
|
||||
}
|
||||
|
@ -592,7 +711,7 @@ export class SpecValidator {
|
|||
*
|
||||
* @param {object} operation - The operation object.
|
||||
*/
|
||||
private validateOperation(operation: any): void {
|
||||
private validateOperation(operation: Operation): void {
|
||||
this.validateXmsExamples(operation)
|
||||
this.validateExample(operation)
|
||||
}
|
||||
|
@ -602,17 +721,16 @@ export class SpecValidator {
|
|||
*
|
||||
* @param {object} operation - The operation object.
|
||||
*/
|
||||
private validateXmsExamples(operation: any): void {
|
||||
private validateXmsExamples(operation: Operation): void {
|
||||
const self = this
|
||||
if (operation === null || operation === undefined || typeof operation !== "object") {
|
||||
throw new Error("operation cannot be null or undefined and must be of type 'object'.")
|
||||
}
|
||||
const xmsExamples = operation[Constants.xmsExamples]
|
||||
const result = {
|
||||
scenarios: {} as any,
|
||||
exampleNotFound: undefined as any
|
||||
const xmsExamples = operation[C.xmsExamples]
|
||||
const resultScenarios: Scenarios = {}
|
||||
const result: ValidationResult = {
|
||||
scenarios: resultScenarios
|
||||
}
|
||||
const resultScenarios = result.scenarios
|
||||
if (xmsExamples) {
|
||||
for (const scenario of Object.keys(xmsExamples)) {
|
||||
const xmsExample = xmsExamples[scenario]
|
||||
|
@ -621,12 +739,13 @@ export class SpecValidator {
|
|||
responseValidation: self.validateXmsExampleResponses(operation, xmsExample.responses)
|
||||
}
|
||||
}
|
||||
result.scenarios = resultScenarios
|
||||
} else {
|
||||
const msg = `x-ms-example not found in ${operation.operationId}.`
|
||||
result.exampleNotFound = self.constructErrorObject(
|
||||
ErrorCodes.XmsExampleNotFoundError, msg, null, true)
|
||||
}
|
||||
self.constructOperationResult(operation, result, Constants.xmsExamples)
|
||||
self.constructOperationResult(operation, result, C.xmsExamples)
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -634,16 +753,16 @@ export class SpecValidator {
|
|||
*
|
||||
* @param {object} operation - The operation object.
|
||||
*/
|
||||
private validateExample(operation: any): void {
|
||||
private validateExample(operation: Operation): void {
|
||||
const self = this
|
||||
if (operation === null || operation === undefined || typeof operation !== "object") {
|
||||
throw new Error("operation cannot be null or undefined and must be of type 'object'.")
|
||||
}
|
||||
const result = {
|
||||
const result: ValidationResult = {
|
||||
requestValidation: self.validateExampleRequest(operation),
|
||||
responseValidation: self.validateExampleResponses(operation)
|
||||
responseValidation: self.validateExampleResponses(operation) as any
|
||||
}
|
||||
self.constructOperationResult(operation, result, Constants.exampleInSpec)
|
||||
self.constructOperationResult(operation, result, C.exampleInSpec)
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -655,7 +774,9 @@ export class SpecValidator {
|
|||
*
|
||||
* @return {object} result - The validation result.
|
||||
*/
|
||||
private validateRequest(operation: any, exampleParameterValues: any) {
|
||||
private validateRequest(operation: Operation, exampleParameterValues: { [name: string]: string })
|
||||
: RequestValidation {
|
||||
|
||||
const self = this
|
||||
if (operation === null || operation === undefined || typeof operation !== "object") {
|
||||
throw new Error("operation cannot be null or undefined and must be of type 'object'.")
|
||||
|
@ -671,14 +792,20 @@ export class SpecValidator {
|
|||
}
|
||||
|
||||
const parameters = operation.getParameters()
|
||||
const result = {
|
||||
const result: RequestValidation = {
|
||||
request: null,
|
||||
validationResult: { errors: [] as any[], warnings: [] }
|
||||
validationResult: { errors: [], warnings: [] }
|
||||
}
|
||||
let foundIssues = false
|
||||
const options: any = { headers: {} }
|
||||
let formDataFiles: any = null
|
||||
const parameterizedHost = operation.pathObject.api[Constants.xmsParameterizedHost]
|
||||
const options: {
|
||||
baseUrl?: string
|
||||
[name: string]: any
|
||||
} = { headers: {} }
|
||||
let formDataFiles: {
|
||||
[name: string]: Unknown
|
||||
}|null = null
|
||||
const pathObject = operation.pathObject as Sway.PathObject
|
||||
const parameterizedHost = pathObject.api[C.xmsParameterizedHost]
|
||||
const hostTemplate = parameterizedHost && parameterizedHost.hostTemplate
|
||||
? parameterizedHost.hostTemplate
|
||||
: null
|
||||
|
@ -694,7 +821,7 @@ export class SpecValidator {
|
|||
}
|
||||
if (operation.pathObject.api.schemes
|
||||
&& !operation.pathObject.api.schemes.some(
|
||||
(item: any) => item && item.toLowerCase() === "https")) {
|
||||
item => !!item && item.toLowerCase() === "https")) {
|
||||
scheme = operation.pathObject.api.schemes[0]
|
||||
}
|
||||
if (operation.pathObject.api.basePath) {
|
||||
|
@ -712,10 +839,10 @@ export class SpecValidator {
|
|||
options.baseUrl = baseUrl
|
||||
}
|
||||
options.method = operation.method
|
||||
let pathTemplate = operation.pathObject.path
|
||||
let pathTemplate = pathObject.path
|
||||
if (pathTemplate && pathTemplate.includes("?")) {
|
||||
pathTemplate = pathTemplate.slice(0, pathTemplate.indexOf("?"))
|
||||
operation.pathObject.path = pathTemplate
|
||||
pathObject.path = pathTemplate
|
||||
}
|
||||
options.pathTemplate = pathTemplate
|
||||
for (const parameter of parameters) {
|
||||
|
@ -726,6 +853,9 @@ export class SpecValidator {
|
|||
`In operation "${operation.operationId}", parameter ${parameter.name} is required in ` +
|
||||
`the swagger spec but is not present in the provided example parameter values.`
|
||||
const e = self.constructErrorObject(ErrorCodes.RequiredParameterExampleNotFound, msg)
|
||||
if (result.validationResult === undefined) {
|
||||
throw new Error("result.validationResult is undefined")
|
||||
}
|
||||
result.validationResult.errors.push(e)
|
||||
foundIssues = true
|
||||
break
|
||||
|
@ -734,7 +864,7 @@ export class SpecValidator {
|
|||
}
|
||||
const location = parameter.in
|
||||
if (location === "path" || location === "query") {
|
||||
if (location === "path" && parameterValue && typeof parameterValue.valueOf() === "string") {
|
||||
if (location === "path" && parameterValue && typeof parameterValue === "string") {
|
||||
// "/{scope}/scopes/resourceGroups/{resourceGroupName}" In the aforementioned path
|
||||
// template, we will search for the path parameter based on it's name
|
||||
// for example: "scope". Find it's index in the string and move backwards by 2 positions.
|
||||
|
@ -751,6 +881,9 @@ export class SpecValidator {
|
|||
`the parameter starts. This will cause double forward slashes ` +
|
||||
` in the request url. Thus making it incorrect. Please rectify the example.`
|
||||
const e = self.constructErrorObject(ErrorCodes.DoubleForwardSlashesInUrl, msg)
|
||||
if (result.validationResult === undefined) {
|
||||
throw new Error("result.validationResult is undefined")
|
||||
}
|
||||
result.validationResult.errors.push(e)
|
||||
foundIssues = true
|
||||
break
|
||||
|
@ -761,7 +894,7 @@ export class SpecValidator {
|
|||
}
|
||||
const paramType = location + "Parameters"
|
||||
if (!options[paramType]) { options[paramType] = {} }
|
||||
if (parameter[Constants.xmsSkipUrlEncoding] || utils.isUrlEncoded(parameterValue)) {
|
||||
if (parameter[C.xmsSkipUrlEncoding] || utils.isUrlEncoded(parameterValue)) {
|
||||
options[paramType][parameter.name] = {
|
||||
value: parameterValue,
|
||||
skipUrlEncoding: true
|
||||
|
@ -773,11 +906,8 @@ export class SpecValidator {
|
|||
options.body = parameterValue
|
||||
options.disableJsonStringifyOnBody = true
|
||||
if (operation.consumes) {
|
||||
const isOctetStream = (consumes: any) => {
|
||||
return consumes.some((contentType: any) => {
|
||||
return contentType === "application/octet-stream"
|
||||
})
|
||||
}
|
||||
const isOctetStream = (consumes: string[]) => consumes.some(
|
||||
contentType => contentType === "application/octet-stream")
|
||||
|
||||
if (parameter.schema.format === "file" && isOctetStream(operation.consumes)) {
|
||||
options.headers["Content-Type"] = "application/octet-stream"
|
||||
|
@ -789,11 +919,8 @@ export class SpecValidator {
|
|||
options.headers[parameter.name] = parameterValue
|
||||
} else if (location === "formData") {
|
||||
// helper function
|
||||
const isFormUrlEncoded = (consumes: any) => {
|
||||
return consumes.some((contentType: any) => {
|
||||
return contentType === "application/x-www-form-urlencoded"
|
||||
})
|
||||
}
|
||||
const isFormUrlEncoded = (consumes: string[]) => consumes.some(
|
||||
contentType => contentType === "application/x-www-form-urlencoded")
|
||||
|
||||
if (!options.formData) { options.formData = {} }
|
||||
options.formData[parameter.name] = parameterValue
|
||||
|
@ -822,16 +949,19 @@ export class SpecValidator {
|
|||
options.headers["Content-Type"] = utils.getJsonContentType(operation.consumes)
|
||||
}
|
||||
|
||||
let request: any = null
|
||||
let validationResult: any = {}
|
||||
validationResult.errors = []
|
||||
let request: msRest.WebResource|null = null
|
||||
let validationResult: {
|
||||
errors: Error[]
|
||||
} = {
|
||||
errors: []
|
||||
}
|
||||
if (!foundIssues) {
|
||||
try {
|
||||
request = new HttpRequest()
|
||||
request = request.prepare(options)
|
||||
request = request.prepare(options as any)
|
||||
// set formData in the way sway expects it.
|
||||
if (formDataFiles) {
|
||||
request.files = formDataFiles
|
||||
(request as any).files = formDataFiles
|
||||
} else if (options.formData) {
|
||||
request.body = options.formData
|
||||
}
|
||||
|
@ -845,7 +975,7 @@ export class SpecValidator {
|
|||
}
|
||||
|
||||
result.request = request
|
||||
result.validationResult = utils.mergeObjects(validationResult, result.validationResult)
|
||||
result.validationResult = utils.mergeObjects(validationResult, result.validationResult as any)
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -858,7 +988,7 @@ export class SpecValidator {
|
|||
*
|
||||
* @return {object} result - The validation result.
|
||||
*/
|
||||
private validateResponse(operationOrResponse: any, responseWrapper: any) {
|
||||
private validateResponse(operationOrResponse: Operation, responseWrapper: Unknown) {
|
||||
const self = this
|
||||
if (operationOrResponse === null
|
||||
|| operationOrResponse === undefined
|
||||
|
@ -868,7 +998,8 @@ export class SpecValidator {
|
|||
}
|
||||
|
||||
if (responseWrapper === null
|
||||
|| responseWrapper === undefined || typeof responseWrapper !== "object") {
|
||||
|| responseWrapper === undefined
|
||||
|| typeof responseWrapper !== "object") {
|
||||
throw new Error("responseWrapper cannot be null or undefined and must be of type 'object'.")
|
||||
}
|
||||
self.sampleResponse = responseWrapper
|
||||
|
@ -882,7 +1013,7 @@ export class SpecValidator {
|
|||
*
|
||||
* @return {object} result - The validation result.
|
||||
*/
|
||||
private validateExampleRequest(operation: any) {
|
||||
private validateExampleRequest(operation: Operation): RequestValidation {
|
||||
const self = this
|
||||
if (operation === null || operation === undefined || typeof operation !== "object") {
|
||||
throw new Error("operation cannot be null or undefined and must be of type 'object'.")
|
||||
|
@ -895,11 +1026,11 @@ export class SpecValidator {
|
|||
// values for other parameters and create a request object.
|
||||
// This request object will be used to validate the body parameter example. Otherwise, we will
|
||||
// skip it.
|
||||
const bodyParam = parameters.find((item: any) => item.in === "body")
|
||||
const bodyParam = parameters.find(item => item.in === "body")
|
||||
|
||||
let result = {}
|
||||
let result: RequestValidation = {}
|
||||
if (bodyParam && bodyParam.schema && bodyParam.schema.example) {
|
||||
const exampleParameterValues: any = {}
|
||||
const exampleParameterValues: { [name: string]: string } = {}
|
||||
for (const parameter of parameters) {
|
||||
log.debug(
|
||||
`Getting sample value for parameter "${parameter.name}" in operation ` +
|
||||
|
@ -927,7 +1058,9 @@ export class SpecValidator {
|
|||
*
|
||||
* @return {object} result - The validation result.
|
||||
*/
|
||||
private validateXmsExampleResponses(operation: any, exampleResponseValue: any) {
|
||||
private validateXmsExampleResponses(
|
||||
operation: Operation, exampleResponseValue: { [name: string]: ExampleResponse }) {
|
||||
|
||||
const self = this
|
||||
const result: any = {}
|
||||
if (operation === null || operation === undefined || typeof operation !== "object") {
|
||||
|
@ -939,10 +1072,11 @@ export class SpecValidator {
|
|||
|| typeof exampleResponseValue !== "object") {
|
||||
throw new Error("operation cannot be null or undefined and must be of type 'object'.")
|
||||
}
|
||||
const responsesInSwagger: any = {}
|
||||
const responses = operation.getResponses().map((response: any) => {
|
||||
const responsesInSwagger: {
|
||||
[name: string]: Unknown
|
||||
} = {}
|
||||
operation.getResponses().forEach(response => {
|
||||
responsesInSwagger[response.statusCode] = response.statusCode
|
||||
return response.statusCode
|
||||
})
|
||||
for (const exampleResponseStatusCode of utils.getKeys(exampleResponseValue)) {
|
||||
const response = operation.getResponse(exampleResponseStatusCode)
|
||||
|
@ -1017,12 +1151,14 @@ export class SpecValidator {
|
|||
*
|
||||
* @return {object} result - The validation result.
|
||||
*/
|
||||
private validateExampleResponses(operation: any) {
|
||||
private validateExampleResponses(operation: Operation) {
|
||||
const self = this
|
||||
if (operation === null || operation === undefined || typeof operation !== "object") {
|
||||
throw new Error("operation cannot be null or undefined and must be of type 'object'.")
|
||||
}
|
||||
const result: any = {}
|
||||
const result: {
|
||||
[name: string]: Sway.ResponseValidation
|
||||
} = {}
|
||||
const responses = operation.getResponses()
|
||||
for (const response of responses) {
|
||||
if (response.examples) {
|
||||
|
|
|
@ -15,9 +15,9 @@ import { SpecResolver } from "./validators/specResolver"
|
|||
import { ResponseWrapper } from "./models/responseWrapper"
|
||||
import { MarkdownHttpTemplate } from "./templates/markdownHttpTemplate"
|
||||
import { YamlHttpTemplate } from "./templates/yamlHttpTemplate"
|
||||
import { Constants } from "./util/constants"
|
||||
import * as C from "./util/constants"
|
||||
|
||||
const ErrorCodes = Constants.ErrorCodes
|
||||
const ErrorCodes = C.ErrorCodes
|
||||
|
||||
export class WireFormatGenerator {
|
||||
private specPath: any
|
||||
|
@ -99,13 +99,13 @@ export class WireFormatGenerator {
|
|||
}
|
||||
|
||||
/*
|
||||
* Generates wireformat for the given operationIds or all the operations in the spec.
|
||||
* Generates wire-format for the given operationIds or all the operations in the spec.
|
||||
*
|
||||
* @param {string} [operationIds] - A comma sparated string specifying the operations for
|
||||
* @param {string} [operationIds] - A comma separated string specifying the operations for
|
||||
* which the wire format needs to be generated. If not specified then the entire spec is
|
||||
* processed.
|
||||
*/
|
||||
public processOperations(operationIds: string): void {
|
||||
public processOperations(operationIds: string|null): void {
|
||||
const self = this
|
||||
if (!self.swaggerApi) {
|
||||
throw new Error(
|
||||
|
@ -266,7 +266,7 @@ export class WireFormatGenerator {
|
|||
if (operation === null || operation === undefined || typeof operation !== "object") {
|
||||
throw new Error("operation cannot be null or undefined and must be of type 'object'.")
|
||||
}
|
||||
const xmsExamples = operation[Constants.xmsExamples]
|
||||
const xmsExamples = operation[C.xmsExamples]
|
||||
if (xmsExamples) {
|
||||
for (const scenario of utils.getKeys(xmsExamples)) {
|
||||
// If we do not see the value property then we assume that the swagger spec had
|
||||
|
@ -331,7 +331,7 @@ export class WireFormatGenerator {
|
|||
if (location === "path" || location === "query") {
|
||||
const paramType = location + "Parameters"
|
||||
if (!options[paramType]) { options[paramType] = {} }
|
||||
if (parameter[Constants.xmsSkipUrlEncoding]
|
||||
if (parameter[C.xmsSkipUrlEncoding]
|
||||
|| utils.isUrlEncoded(exampleParameterValues[parameter.name])) {
|
||||
options[paramType][parameter.name] = {
|
||||
value: exampleParameterValues[parameter.name],
|
||||
|
|
|
@ -98,7 +98,7 @@ export class XMsExampleExtractor {
|
|||
|
||||
try {
|
||||
const api = await parser.parse(swaggerObject)
|
||||
for (const recordingFileName of utils.getValues(recordingFiles)) {
|
||||
for (const recordingFileName of utils.getValues<any>(recordingFiles)) {
|
||||
log.debug(`Processing recording file: ${recordingFileName}`)
|
||||
|
||||
try {
|
||||
|
@ -112,7 +112,7 @@ export class XMsExampleExtractor {
|
|||
const pathParts = path.split("/")
|
||||
let pathToMatch = path
|
||||
pathParams = {}
|
||||
for (const match of utils.getValues(searchResult)) {
|
||||
for (const match of utils.getValues<any>(searchResult)) {
|
||||
const splitRegEx = /[{}]/
|
||||
const pathParam = match.split(splitRegEx)[1]
|
||||
|
||||
|
@ -262,7 +262,7 @@ export class XMsExampleExtractor {
|
|||
}
|
||||
}
|
||||
|
||||
private getFileList(dir: any, fileList: string[]): string[] {
|
||||
private getFileList(dir: string, fileList: string[]): string[] {
|
||||
const self = this
|
||||
const files = fs.readdirSync(dir)
|
||||
fileList = fileList || []
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "oav",
|
||||
"version": "0.4.40",
|
||||
"version": "0.4.41",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -5870,9 +5870,9 @@
|
|||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.3.tgz",
|
||||
"integrity": "sha512-K7g15Bb6Ra4lKf7Iq2l/I5/En+hLIHmxWZGq3D4DIRNFxMNV6j2SHSvDOqs2tGd4UvD/fJvrwopzQXjLrT7Itw==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.1.tgz",
|
||||
"integrity": "sha512-h6pM2f/GDchCFlldnriOhs1QHuwbnmj6/v7499eMHqPeW4V2G0elua2eIc2nu8v2NdHV0Gm+tzX83Hr6nUFjQA==",
|
||||
"dev": true
|
||||
},
|
||||
"underscore": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "oav",
|
||||
"version": "0.4.40",
|
||||
"version": "0.4.41",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation",
|
||||
"email": "azsdkteam@microsoft.com",
|
||||
|
@ -10,6 +10,7 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@microsoft.azure/autorest-extension-base": "^1.0.13",
|
||||
"@types/winston": "^2.3.9",
|
||||
"azure-arm-resource": "^2.0.0-preview",
|
||||
"glob": "^5.0.14",
|
||||
"js-yaml": "^3.8.2",
|
||||
|
@ -29,8 +30,7 @@
|
|||
"vscode-jsonrpc": "^3.6.1",
|
||||
"winston": "^2.4.2",
|
||||
"yargs": "^6.6.0",
|
||||
"yuml2svg": "^3.1.0",
|
||||
"@types/winston": "^2.3.9"
|
||||
"yuml2svg": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/glob": "^5.0.35",
|
||||
|
@ -47,7 +47,7 @@
|
|||
"should": "5.2.0",
|
||||
"ts-node": "^6.0.5",
|
||||
"tslint": "^5.10.0",
|
||||
"typescript": "^2.8.3"
|
||||
"typescript": "^2.9.1"
|
||||
},
|
||||
"homepage": "https://github.com/azure/oav",
|
||||
"repository": {
|
||||
|
|
|
@ -6,8 +6,9 @@ import * as path from "path"
|
|||
import * as os from "os"
|
||||
import * as glob from "glob"
|
||||
import { LiveValidator } from "../lib/validators/liveValidator"
|
||||
import { Constants } from "../lib/util/constants"
|
||||
import * as Constants from "../lib/util/constants"
|
||||
import * as utils from "../lib/util/utils"
|
||||
import { Responses } from "sway"
|
||||
|
||||
const livePaths = glob.sync(path.join(__dirname, "liveValidation/swaggers/**/live/*.json"))
|
||||
describe("Live Validator", () => {
|
||||
|
@ -229,33 +230,44 @@ describe("Live Validator", () => {
|
|||
validator.initialize().then(() => {
|
||||
// Operations to match is StorageAccounts_List
|
||||
let operations = validator.getPotentialOperations(listRequestUrl, "Get").operations
|
||||
assert.equal(
|
||||
1, operations.length)
|
||||
let pathObject = operations[0].pathObject
|
||||
if (pathObject === undefined) {
|
||||
throw new Error("pathObject is undefined")
|
||||
}
|
||||
assert.equal(1, operations.length)
|
||||
assert.equal(
|
||||
"/subscriptions/{subscriptionId}/providers/Microsoft.Storage/storageAccounts",
|
||||
operations[0].pathObject.path)
|
||||
pathObject.path)
|
||||
|
||||
// Operations to match is StorageAccounts_CheckNameAvailability
|
||||
operations = validator.getPotentialOperations(postRequestUrl, "PoSt").operations
|
||||
pathObject = operations[0].pathObject
|
||||
if (pathObject === undefined) {
|
||||
throw new Error("pathObject is undefined")
|
||||
}
|
||||
assert.equal(1, operations.length)
|
||||
assert.equal(
|
||||
"/subscriptions/{subscriptionId}/providers/Microsoft.Storage/checkNameAvailability",
|
||||
operations[0].pathObject.path)
|
||||
pathObject.path)
|
||||
|
||||
// Operations to match is StorageAccounts_Delete
|
||||
operations = validator.getPotentialOperations(deleteRequestUrl, "delete").operations
|
||||
pathObject = operations[0].pathObject
|
||||
if (pathObject === undefined) {
|
||||
throw new Error("pathObject is undefined")
|
||||
}
|
||||
assert.equal(1, operations.length)
|
||||
assert.equal(
|
||||
"/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/" +
|
||||
"Microsoft.Storage/storageAccounts/{accountName}",
|
||||
operations[0].pathObject.path)
|
||||
pathObject.path)
|
||||
done()
|
||||
}).catch((err: any) => {
|
||||
assert.ifError(err)
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
it("should return reason for not matched operations", (done) => {
|
||||
it("should return reason for not matched operations", async () => {
|
||||
const options = {
|
||||
directory: "./test/liveValidation/swaggers/"
|
||||
}
|
||||
|
@ -276,44 +288,51 @@ describe("Live Validator", () => {
|
|||
"subscriptions/subscriptionId/providers/Microsoft.Storage/storageAccounts/accountName/" +
|
||||
"properties?api-version=2015-06-15"
|
||||
const validator = new LiveValidator(options)
|
||||
validator.initialize().then(() => {
|
||||
// Operations to match is StorageAccounts_List with api-version 2015-08-15
|
||||
// [non cached api version]
|
||||
let result = validator.getPotentialOperations(nonCachedApiUrl, "Get")
|
||||
let operations = result.operations
|
||||
let reason = result.reason
|
||||
assert.equal(0, operations.length)
|
||||
assert.equal(Constants.ErrorCodes.OperationNotFoundInCacheWithApi.name, reason.code)
|
||||
await validator.initialize()
|
||||
// Operations to match is StorageAccounts_List with api-version 2015-08-15
|
||||
// [non cached api version]
|
||||
let result = validator.getPotentialOperations(nonCachedApiUrl, "Get")
|
||||
let operations = result.operations
|
||||
let reason = result.reason
|
||||
assert.equal(0, operations.length)
|
||||
if (reason === undefined) {
|
||||
throw new Error("reason is undefined")
|
||||
}
|
||||
assert.equal(Constants.ErrorCodes.OperationNotFoundInCacheWithApi.name, reason.code)
|
||||
|
||||
// Operations to match is StorageAccounts_CheckNameAvailability with provider "Hello.World"
|
||||
// [non cached provider]
|
||||
result = validator.getPotentialOperations(nonCachedProviderUrl, "PoSt")
|
||||
operations = result.operations
|
||||
reason = result.reason
|
||||
assert.equal(0, operations.length)
|
||||
assert.equal(Constants.ErrorCodes.OperationNotFoundInCacheWithProvider.name, reason.code)
|
||||
// Operations to match is StorageAccounts_CheckNameAvailability with provider "Hello.World"
|
||||
// [non cached provider]
|
||||
result = validator.getPotentialOperations(nonCachedProviderUrl, "PoSt")
|
||||
operations = result.operations
|
||||
reason = result.reason
|
||||
assert.equal(0, operations.length)
|
||||
if (reason === undefined) {
|
||||
throw new Error("reason is undefined")
|
||||
}
|
||||
assert.equal(Constants.ErrorCodes.OperationNotFoundInCacheWithProvider.name, reason.code)
|
||||
|
||||
// Operations to match is StorageAccounts_Delete with verb "head" [non cached http verb]
|
||||
result = validator.getPotentialOperations(nonCachedVerbUrl, "head")
|
||||
operations = result.operations
|
||||
reason = result.reason
|
||||
assert.equal(0, operations.length)
|
||||
assert.equal(Constants.ErrorCodes.OperationNotFoundInCacheWithVerb.name, reason.code)
|
||||
// Operations to match is StorageAccounts_Delete with verb "head" [non cached http verb]
|
||||
result = validator.getPotentialOperations(nonCachedVerbUrl, "head")
|
||||
operations = result.operations
|
||||
reason = result.reason
|
||||
assert.equal(0, operations.length)
|
||||
if (reason === undefined) {
|
||||
throw new Error("reason is undefined")
|
||||
}
|
||||
assert.equal(Constants.ErrorCodes.OperationNotFoundInCacheWithVerb.name, reason.code)
|
||||
|
||||
// Operations to match is with path
|
||||
// "subscriptions/subscriptionId/providers/Microsoft.Storage/" +
|
||||
// "storageAccounts/storageAccounts/accountName/properties/"
|
||||
// [non cached path]
|
||||
result = validator.getPotentialOperations(nonCachedPath, "get")
|
||||
operations = result.operations
|
||||
reason = result.reason
|
||||
assert.equal(0, operations.length)
|
||||
assert.equal(Constants.ErrorCodes.OperationNotFoundInCache.name, reason.code)
|
||||
done()
|
||||
}).catch((err: any) => {
|
||||
assert.ifError(err)
|
||||
done()
|
||||
}).catch(done)
|
||||
// Operations to match is with path
|
||||
// "subscriptions/subscriptionId/providers/Microsoft.Storage/" +
|
||||
// "storageAccounts/storageAccounts/accountName/properties/"
|
||||
// [non cached path]
|
||||
result = validator.getPotentialOperations(nonCachedPath, "get")
|
||||
operations = result.operations
|
||||
reason = result.reason
|
||||
assert.equal(0, operations.length)
|
||||
if (reason === undefined) {
|
||||
throw new Error("reason is undefined")
|
||||
}
|
||||
assert.equal(Constants.ErrorCodes.OperationNotFoundInCache.name, reason.code)
|
||||
})
|
||||
it("it should create an implicit default response and find it", (done) => {
|
||||
const options = {
|
||||
|
@ -332,8 +351,9 @@ describe("Live Validator", () => {
|
|||
const operations = validator.cache["microsoft.test"]["2016-01-01"].post
|
||||
|
||||
for (const operation of operations) {
|
||||
assert(operation.responses.default)
|
||||
assert.deepEqual(operation.responses.default.schema.properties.error, utils.CloudError)
|
||||
const responses = operation.responses as Responses
|
||||
assert(responses.default)
|
||||
assert.deepEqual(responses.default.schema.properties.error, utils.CloudError)
|
||||
}
|
||||
done()
|
||||
}).catch((err: any) => {
|
||||
|
|
|
@ -107,16 +107,32 @@ describe("Model Validation", () => {
|
|||
assert(
|
||||
result.validityStatus === false,
|
||||
`swagger "${specPath2}" with operation "${operationIds}" contains model validation errors.`)
|
||||
const responseError = result.operations
|
||||
const scenarios = result
|
||||
.operations
|
||||
.CircularAnimal_IncorrectSibling_List
|
||||
["x-ms-examples"].scenarios
|
||||
["x-ms-examples"]
|
||||
.scenarios
|
||||
if (scenarios === undefined) {
|
||||
throw new Error("scenarios === undefined")
|
||||
}
|
||||
const responseError = scenarios
|
||||
["Tests ploymorphic circular array, " +
|
||||
"dictionary of animals with incorrect sibling (negative)"]
|
||||
.responses
|
||||
["200"]
|
||||
assert.equal(responseError.isValid, false)
|
||||
if (responseError.error === undefined) {
|
||||
throw new Error("no error")
|
||||
}
|
||||
assert.equal(responseError.error.code, "RESPONSE_VALIDATION_ERROR")
|
||||
assert.equal(responseError.error.innerErrors[0].errors[0].code, "ONE_OF_MISSING")
|
||||
if (responseError.error.innerErrors === undefined) {
|
||||
throw new Error("innerErrors is undefined")
|
||||
}
|
||||
const errors = responseError.error.innerErrors[0].errors
|
||||
if (errors === undefined) {
|
||||
throw new Error("innerErrors is undefined")
|
||||
}
|
||||
assert.equal(errors[0].code, "ONE_OF_MISSING")
|
||||
})
|
||||
|
||||
it("should pass for Entities_Search", async () => {
|
||||
|
|
|
@ -2,11 +2,7 @@
|
|||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
// Sample standalone script to call live validator.
|
||||
import assert from "assert"
|
||||
import * as path from "path"
|
||||
import * as os from "os"
|
||||
import { LiveValidator } from "../lib/validators/liveValidator"
|
||||
import { Constants } from "../lib/util/constants"
|
||||
|
||||
const options = {
|
||||
directory: "./test/swaggers/arm-storage"
|
||||
|
|
|
@ -7,29 +7,21 @@ import assert from "assert"
|
|||
import * as validate from "../lib/validate"
|
||||
|
||||
describe("Semantic validation", () => {
|
||||
it("should validate correctly when the spec contains an x-ms-parameterized-host", (done) => {
|
||||
it("should validate correctly when the spec contains an x-ms-parameterized-host", async () => {
|
||||
const specPath = `${__dirname}/semanticValidation/specification/parameterizedhost/face.json`
|
||||
validate.validateSpec(specPath, undefined, { consoleLogLevel: "off" }).then((result: any) => {
|
||||
console.dir(result, { depth: null })
|
||||
assert(
|
||||
result.validityStatus === true, `swagger "${specPath}" contains model validation errors.`)
|
||||
console.log(result)
|
||||
done()
|
||||
}).catch((err: any) => {
|
||||
done(err)
|
||||
})
|
||||
const result = await validate.validateSpec(specPath, undefined)
|
||||
console.dir(result, { depth: null })
|
||||
assert(
|
||||
result.validityStatus === true, `swagger "${specPath}" contains model validation errors.`)
|
||||
console.log(result)
|
||||
})
|
||||
|
||||
it("should validate correctly when the spec does not contain a definitions section", (done) => {
|
||||
it("should validate correctly when the spec does not contain a definitions section", async () => {
|
||||
const specPath = `${__dirname}/semanticValidation/specification/definitions/definitions.json`
|
||||
validate.validateSpec(specPath, undefined, { consoleLogLevel: "off" }).then((result: any) => {
|
||||
console.dir(result, { depth: null })
|
||||
assert(
|
||||
result.validityStatus === true, `swagger "${specPath}" contains model validation errors.`)
|
||||
console.log(result)
|
||||
done()
|
||||
}).catch((err: any) => {
|
||||
done(err)
|
||||
})
|
||||
const result = await validate.validateSpec(specPath, undefined)
|
||||
console.dir(result, { depth: null })
|
||||
assert(
|
||||
result.validityStatus === true, `swagger "${specPath}" contains model validation errors.`)
|
||||
console.log(result)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -3,17 +3,74 @@
|
|||
|
||||
declare module "sway" {
|
||||
interface Options {
|
||||
definition: any
|
||||
jsonRefs: { relativeBase: any }
|
||||
isPathCaseSensitive?: boolean
|
||||
readonly definition: any
|
||||
readonly jsonRefs: {
|
||||
readonly relativeBase: any
|
||||
}
|
||||
readonly isPathCaseSensitive?: boolean
|
||||
}
|
||||
interface RequestValidation {
|
||||
readonly errors: any
|
||||
}
|
||||
interface ResponseValidation {
|
||||
readonly errors: any
|
||||
}
|
||||
interface PathObject {
|
||||
path: string
|
||||
readonly regexp: RegExp
|
||||
api: {
|
||||
"x-ms-parameterized-host": any
|
||||
host: any
|
||||
schemes: string[]
|
||||
basePath: any
|
||||
}
|
||||
}
|
||||
interface Responses {
|
||||
readonly default: {
|
||||
readonly schema: {
|
||||
readonly properties: {
|
||||
readonly [property: string]: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
interface Parameter {
|
||||
schema: any
|
||||
name: any
|
||||
format: any
|
||||
required: any
|
||||
in: any
|
||||
"x-ms-skip-url-encoding": any
|
||||
type: any
|
||||
getSample(): any
|
||||
}
|
||||
interface Response {
|
||||
readonly statusCode: any
|
||||
readonly schema: any
|
||||
readonly examples: any
|
||||
}
|
||||
interface Operation {
|
||||
readonly operationId?: any
|
||||
readonly method?: any
|
||||
readonly pathObject?: PathObject
|
||||
provider?: any
|
||||
readonly responses?: Responses
|
||||
"x-ms-examples": any
|
||||
readonly consumes: string[]
|
||||
readonly produces: any
|
||||
validateRequest(_: any): RequestValidation
|
||||
validateResponse(_: any): ResponseValidation
|
||||
getParameters(): Parameter[]
|
||||
getResponses(): Response[]
|
||||
getResponse(_: string): Response
|
||||
}
|
||||
interface SwaggerApi {
|
||||
info: {
|
||||
version: string
|
||||
title: any
|
||||
readonly info: {
|
||||
readonly version: string
|
||||
readonly title: any
|
||||
}
|
||||
validate(): any
|
||||
getOperations(): any[]
|
||||
getOperations(): Operation[]
|
||||
}
|
||||
function create(options: Options): Promise<SwaggerApi>
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче