Suppression support for `from` field. (#324)

* assignOptional

* handle path

* handle "from"

* 0.6.5

* `where` implementation

* address comments

* more comments.

* remove assignOptional
This commit is contained in:
Sergey Shandar 2018-09-28 11:20:39 -07:00 коммит произвёл GitHub
Родитель 2a68bde234
Коммит 8c3786ccf4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 421 добавлений и 27 удалений

2
.vscode/launch.json поставляемый
Просмотреть файл

@ -110,7 +110,7 @@
"stopOnEntry": false,
"args": [
"--no-timeouts",
"test/modelValidatorTests.ts",
"test/suppressionTests.ts",
"-r",
"ts-node/register",
"--no-timeouts"

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

@ -1,5 +1,9 @@
# Changelog
### 09/27/2018 0.6.5
- Suppression supports relative file and object paths.
### 09/25/2018 0.6.4
- Source Map for `.d.ts` files.

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

@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import { fold, isArray } from "@ts-common/iterator"
export interface Dummy {
[v: string]: Dummy | undefined
}
export const createDummy = (): Dummy => ({})
/**
* Creates a dummy object which has the given JSON path.
* @param objectPath a JSON path.
*/
export const createDummyByPath = (objectPath: string | string[] | undefined): Dummy => {
const result = createDummy()
if (objectPath === undefined) {
return result
}
const split = isArray(objectPath) ? objectPath : objectPath.split("/").filter(v => v.length > 0)
fold(
split,
(o, key) => {
const value = createDummy()
o[key] = value
return value
},
result
)
return result
}

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

@ -2,13 +2,16 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
import { NodeError } from "./validationError"
import { isArray, filterMap } from "@ts-common/iterator"
import { isArray, filterMap, flatMap } from "@ts-common/iterator"
import { jsonSymbol } from "z-schema"
import { getInfo, getRootObjectInfo } from '@ts-common/source-map'
import { TitleObject } from '../validators/specTransformer'
import { log } from './logging'
import { getDescendantFilePosition } from "@ts-common/source-map"
import { Suppression } from '@ts-common/azure-openapi-markdown';
import { Suppression } from "@ts-common/azure-openapi-markdown"
import jp = require("jsonpath")
import { createDummyByPath } from "./createDummy"
import { setMutableProperty } from '@ts-common/property-set';
export const processErrors = <T extends NodeError<T>>(
suppression: Suppression | undefined,
@ -35,10 +38,16 @@ const addFileInfo = <T extends NodeError<T>>(error: T): T => {
if (json !== undefined) {
const jsonInfo = getInfo(json)
if (jsonInfo !== undefined) {
const path = error.path
error.jsonPosition = getDescendantFilePosition(
json,
path === undefined ? undefined : isArray(path) ? path : path.split("/")
const errorPath = error.path
setMutableProperty(
error,
"jsonPosition",
getDescendantFilePosition(
json,
errorPath === undefined ? undefined :
isArray(errorPath) ? errorPath :
errorPath.split("/")
)
)
error.jsonUrl = getRootObjectInfo(jsonInfo).url
}
@ -46,26 +55,74 @@ const addFileInfo = <T extends NodeError<T>>(error: T): T => {
return error
}
const splitPathAndReverse = (p: string | undefined) =>
p === undefined ? undefined : Array.from(flatMap(p.split("/"), s => s.split("\\"))).reverse()
const isSubPath = (mainPath: ReadonlyArray<string> | undefined, subPath: ReadonlyArray<string>) =>
mainPath !== undefined &&
mainPath.length > subPath.length &&
subPath.every((s, i) => mainPath[i] === s)
const createErrorProcessor = <T extends NodeError<T>>(suppression: Suppression | undefined) => {
const isSuppressed = suppression === undefined ?
() => false :
(error: T): boolean =>
// TODO: JSONPath: https://www.npmjs.com/package/jsonpath using jp.nodes() function.
(error: T): boolean => {
const urlReversed = splitPathAndReverse(error.url)
const jsonUrlReversed = splitPathAndReverse(error.url)
// create dummy object which have the `error.title` path and `error.path`.
// we use the dummy object to test against suppression path expressions.
const oPath = createDummyByPath(error.title)
const jPath = createDummyByPath(error.path)
// See error codes:
// https://github.com/Azure/oav/blob/master/documentation/oav-errors-reference.md#errors-index
suppression.directive.some(item => error.code === item.suppress)
return suppression.directive.some(s => {
// error code
if (error.code !== s.suppress) {
return false
}
// file path
const fromReversed = splitPathAndReverse(s.from)
if (fromReversed !== undefined) {
const match =
isSubPath(urlReversed, fromReversed) ||
isSubPath(jsonUrlReversed, fromReversed)
if (!match) {
return false
}
}
const where = s.where
if (where !== undefined) {
// TODO: JSONPath: https://www.npmjs.com/package/jsonpath using jp.nodes() function.
const match =
jp.value(oPath, where) ||
jp.value(jPath, where)
if (!match) {
return false
}
}
return true
})
}
const one = (error: T): T | undefined => {
error = addFileInfo(error)
if (isSuppressed(error)) {
return undefined
}
error.errors = multiple(error.errors)
error.inner = multiple(error.inner)
setMutableProperty(error, "errors", multiple(error.errors))
setMutableProperty(error, "inner", multiple(error.inner))
return error
}
const multiple = (errors: T[] | undefined) =>
errors === undefined ? undefined : Array.from(filterMap(errors, one))
return multiple
}

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

@ -5,6 +5,7 @@ import * as path from "path"
import * as fs from "fs"
import * as md from "@ts-common/commonmark-to-markdown"
import * as amd from "@ts-common/azure-openapi-markdown"
import { isArray } from '@ts-common/iterator';
export const getSuppressions = (specPath: string): undefined | amd.Suppression => {
// find readme.md
@ -19,6 +20,9 @@ export const getSuppressions = (specPath: string): undefined | amd.Suppression =
return undefined
}
const suppression = amd.getYamlFromNode(suppressionCodeBlock) as amd.Suppression
if (!isArray(suppression.directive)) {
return undefined
}
return suppression
}

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

@ -1,6 +1,6 @@
{
"name": "oav",
"version": "0.6.4",
"version": "0.6.5",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -71,14 +71,21 @@
"@ts-common/json": "0.0.18",
"@ts-common/property-set": "0.0.7",
"@ts-common/string-map": "0.0.26"
},
"dependencies": {
"@ts-common/property-set": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@ts-common/property-set/-/property-set-0.0.7.tgz",
"integrity": "sha512-T+ZQN0IAE+awuu6aNsvLpC2WtDfGf6t6A0WucywDBkuygk4GUrfLJUShNLKWhIZTENNLhw8Z77biybJiUWTPwg=="
}
}
}
}
},
"@ts-common/property-set": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@ts-common/property-set/-/property-set-0.0.7.tgz",
"integrity": "sha512-T+ZQN0IAE+awuu6aNsvLpC2WtDfGf6t6A0WucywDBkuygk4GUrfLJUShNLKWhIZTENNLhw8Z77biybJiUWTPwg=="
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/@ts-common/property-set/-/property-set-0.0.8.tgz",
"integrity": "sha512-HaHrM0hEbUvaUtYEg6v98roMl4s2YyM3NdmxvuK2c+5T1GkZzFoghUpanjVvTaa7nWs548FJSNAYSZuT3NX/BQ=="
},
"@ts-common/source-map": {
"version": "0.2.7",
@ -89,6 +96,13 @@
"@ts-common/json": "0.0.18",
"@ts-common/property-set": "0.0.7",
"@ts-common/string-map": "0.0.26"
},
"dependencies": {
"@ts-common/property-set": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@ts-common/property-set/-/property-set-0.0.7.tgz",
"integrity": "sha512-T+ZQN0IAE+awuu6aNsvLpC2WtDfGf6t6A0WucywDBkuygk4GUrfLJUShNLKWhIZTENNLhw8Z77biybJiUWTPwg=="
}
}
},
"@ts-common/string-map": {
@ -2125,9 +2139,9 @@
}
},
"make-error": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz",
"integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==",
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
"integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==",
"dev": true
},
"mdurl": {
@ -5934,7 +5948,7 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
}
@ -6038,9 +6052,9 @@
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
},
"typescript": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.3.tgz",
"integrity": "sha512-kk80vLW9iGtjMnIv11qyxLqZm20UklzuR2tL0QAnDIygIUIemcZMxlMWudl9OOt76H3ntVzcTiddQ1/pAAJMYg==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.1.tgz",
"integrity": "sha512-Veu0w4dTc/9wlWNf2jeRInNodKlcdLgemvPsrNpfu5Pq39sgfFjvIIgTsvUHCoLBnMhPoUA+tFxsXjU6VexVRQ==",
"dev": true
},
"underscore": {

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

@ -1,6 +1,6 @@
{
"name": "oav",
"version": "0.6.4",
"version": "0.6.5",
"author": {
"name": "Microsoft Corporation",
"email": "azsdkteam@microsoft.com",
@ -15,7 +15,7 @@
"@ts-common/iterator": "0.0.36",
"@ts-common/json": "0.0.18",
"@ts-common/json-parser": "0.2.1",
"@ts-common/property-set": "0.0.7",
"@ts-common/property-set": "0.0.8",
"@ts-common/source-map": "0.2.7",
"@ts-common/string-map": "0.0.26",
"@ts-common/tuple": "0.0.0",
@ -62,7 +62,7 @@
"should": "^5.2.0",
"ts-node": "^6.0.5",
"tslint": "^5.11.0",
"typescript": "^3.0.3"
"typescript": "^3.1.1"
},
"homepage": "https://github.com/azure/oav",
"repository": {

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

@ -0,0 +1,14 @@
{
"parameters":{
"api-version": "2016-01-01",
"scope": "subcriptionID",
"accountName":{"name":"storage4db9202c66274d529","type":"Microsoft.Storage/storageAccounts"}
},
"responses":{
"200":{
"body": {
"nameAvailable": "string instead of bool"
}
}
}
}

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

@ -0,0 +1,7 @@
## Suppression
```yaml
directive:
- suppress: INVALID_TYPE
from: "test.json"
```

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

@ -0,0 +1,110 @@
{
"swagger": "2.0",
"info": {
"title": "StorageManagementClient",
"description": "The Storage Management Client.",
"version": "2016-01-01"
},
"host": "management.azure.com",
"schemes": [
"https"
],
"consumes": [
"application/json",
"text/json"
],
"produces": [
"application/json",
"text/json"
],
"paths": {
"/subscriptions/{scope}/providers/Microsoft.Test/checkNameAvailability": {
"post": {
"tags": [
"StorageAccounts"
],
"operationId": "StorageAccounts_CheckNameAvailability",
"description": "Checks that the storage account name is valid and is not already in use.",
"x-ms-examples": {
"storageAccountCheckNameAvailability": {
"$ref": "./examples/storageAccountCheckNameAvailability.json"
}
},
"parameters": [
{
"name": "accountName",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/StorageAccountCheckNameAvailabilityParameters"
},
"description": "The name of the storage account within the specified resource group. Storage account names must be between 3 and 24 characters in length and use numbers and lower-case letters only."
},
{
"$ref": "#/parameters/ApiVersionParameter"
},
{
"name": "scope",
"in": "path",
"required": true,
"type": "string",
"description": "The scope. Can be \"/foo/foo1/foo2\""
}
],
"responses": {
"200": {
"description": "OK -- Operation to check the storage account name availability was successful.",
"schema": {
"$ref": "#/definitions/CheckNameAvailabilityResult"
}
}
}
}
}
},
"definitions": {
"StorageAccountCheckNameAvailabilityParameters": {
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string"
}
},
"required": [
"name",
"type"
]
},
"CheckNameAvailabilityResult": {
"properties": {
"nameAvailable": {
"readOnly": true,
"type": "boolean",
"description": "Gets a boolean value that indicates whether the name is available for you to use. If true, the name is available. If false, the name has already been taken or is invalid and cannot be used."
},
"reason": {
"readOnly": true,
"type": "string",
"description": "Gets the reason that a storage account name could not be used. The Reason element is only returned if NameAvailable is false."
},
"message": {
"readOnly": true,
"type": "string",
"description": "Gets an error message explaining the Reason value in more detail."
}
},
"description": "The CheckNameAvailability operation response."
}
},
"parameters": {
"ApiVersionParameter": {
"name": "api-version",
"in": "query",
"required": true,
"type": "string",
"description": "Client Api Version."
}
}
}

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

@ -0,0 +1,14 @@
{
"parameters":{
"api-version": "2016-01-01",
"scope": "subcriptionID",
"accountName":{"name":"storage4db9202c66274d529","type":"Microsoft.Storage/storageAccounts"}
},
"responses":{
"200":{
"body": {
"nameAvailable": "string instead of bool"
}
}
}
}

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

@ -0,0 +1,8 @@
## Suppression
```yaml
directive:
- suppress: INVALID_TYPE
from: "test.json"
where: "$.definitions"
```

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

@ -0,0 +1,110 @@
{
"swagger": "2.0",
"info": {
"title": "StorageManagementClient",
"description": "The Storage Management Client.",
"version": "2016-01-01"
},
"host": "management.azure.com",
"schemes": [
"https"
],
"consumes": [
"application/json",
"text/json"
],
"produces": [
"application/json",
"text/json"
],
"paths": {
"/subscriptions/{scope}/providers/Microsoft.Test/checkNameAvailability": {
"post": {
"tags": [
"StorageAccounts"
],
"operationId": "StorageAccounts_CheckNameAvailability",
"description": "Checks that the storage account name is valid and is not already in use.",
"x-ms-examples": {
"storageAccountCheckNameAvailability": {
"$ref": "./examples/storageAccountCheckNameAvailability.json"
}
},
"parameters": [
{
"name": "accountName",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/StorageAccountCheckNameAvailabilityParameters"
},
"description": "The name of the storage account within the specified resource group. Storage account names must be between 3 and 24 characters in length and use numbers and lower-case letters only."
},
{
"$ref": "#/parameters/ApiVersionParameter"
},
{
"name": "scope",
"in": "path",
"required": true,
"type": "string",
"description": "The scope. Can be \"/foo/foo1/foo2\""
}
],
"responses": {
"200": {
"description": "OK -- Operation to check the storage account name availability was successful.",
"schema": {
"$ref": "#/definitions/CheckNameAvailabilityResult"
}
}
}
}
}
},
"definitions": {
"StorageAccountCheckNameAvailabilityParameters": {
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string"
}
},
"required": [
"name",
"type"
]
},
"CheckNameAvailabilityResult": {
"properties": {
"nameAvailable": {
"readOnly": true,
"type": "boolean",
"description": "Gets a boolean value that indicates whether the name is available for you to use. If true, the name is available. If false, the name has already been taken or is invalid and cannot be used."
},
"reason": {
"readOnly": true,
"type": "string",
"description": "Gets the reason that a storage account name could not be used. The Reason element is only returned if NameAvailable is false."
},
"message": {
"readOnly": true,
"type": "string",
"description": "Gets an error message explaining the Reason value in more detail."
}
},
"description": "The CheckNameAvailability operation response."
}
},
"parameters": {
"ApiVersionParameter": {
"name": "api-version",
"in": "query",
"required": true,
"type": "string",
"description": "Client Api Version."
}
}
}

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

@ -5,7 +5,7 @@ import { validateExamples } from "../lib/validate"
import * as assert from "assert"
describe("suppression", () => {
it("no readme", async () => {
it("suppress all", async () => {
const result = await validateExamples(
"./test/modelValidation/swaggers/specification/suppressions/datalake.json",
undefined,
@ -15,4 +15,24 @@ describe("suppression", () => {
)
assert.strictEqual(result.length, 0)
})
it("suppress from", async () => {
const result = await validateExamples(
"./test/modelValidation/swaggers/specification/suppressions1/test.json",
undefined,
{
consoleLogLevel: "off"
}
)
assert.strictEqual(result.length, 0)
})
it("suppress where", async () => {
const result = await validateExamples(
"./test/modelValidation/swaggers/specification/suppressions2/test.json",
undefined,
{
consoleLogLevel: "off"
}
)
assert.strictEqual(result.length, 0)
})
})