This commit is contained in:
Sergey Shandar 2018-06-08 16:35:55 -07:00
Родитель 9b86c7b41c
Коммит 09d132ea09
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 1C1B75FAA0B9B3C8
33 изменённых файлов: 1144 добавлений и 767 удалений

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;
}

Просмотреть файл

@ -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
}
}
})

4
lib/util/unknown.ts Normal file
Просмотреть файл

@ -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 || []

8
package-lock.json сгенерированный
Просмотреть файл

@ -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)
})
})

71
types/sway.d.ts поставляемый
Просмотреть файл

@ -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>
}