fastman
Version:
快速api测试及文档生成
307 lines (272 loc) • 16.5 kB
text/typescript
/**
* @license
* Copyright 2017 Red Hat
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Oas20ValidationRule} from "./common.rule";
import {OasNode} from "../../models/node.model";
import {Oas20Parameter} from "../../models/2.0/parameter.model";
import {Oas20SecurityScheme} from "../../models/2.0/security-scheme.model";
import {Oas20SecurityRequirement} from "../../models/2.0/security-requirement.model";
import {Oas20Document} from "../../models/2.0/document.model";
import {Oas20Operation} from "../../models/2.0/operation.model";
import {Oas20Items} from "../../models/2.0/items.model";
import {Oas20Header} from "../../models/2.0/header.model";
import {Oas20XML} from "../../models/2.0/xml.model";
import {Oas20PathItem} from "../../models/2.0/path-item.model";
import {Oas20Schema} from "../../models/2.0/schema.model";
import {Oas20SecurityDefinitions} from "../../models/2.0/security-definitions.model";
import {Oas20Scopes} from "../../models/2.0/scopes.model";
import {OasValidationRuleUtil} from "../validation";
/**
* Implements the Invalid Property Value validation rule. This rule is responsible
* for reporting whenever the **value** of a property fails to conform to requirements
* outlined by the specification. This is typically things like enums, where the
* *format* of the value is fine (e.g. correct data-type) but the valid is somehow
* invalid.
*/
export class Oas20InvalidPropertyValueValidationRule extends Oas20ValidationRule {
/**
* Reports a validation error if the property is not valid.
* @param code
* @param isValid
* @param node
* @param message
*/
private reportIfInvalid(code: string, isValid: boolean, node: OasNode, message: string): void {
if (!isValid) {
this.report(code, node, message);
}
}
/**
* Returns true if the given value is a valid operationId.
* @param id
*/
private isValidOperationId(id: string): boolean {
// TODO implement a regex for this? should be something like camelCase
return true;
}
/**
* Parses the given path template for segments. For example, a path template might be
*
* /foo/{fooId}/resources/{resourceId}
*
* In this case, this method will return [ "fooId", "resourceId" ]
*
* @param pathTemplate
* @return {Array}
*/
private parsePathTemplate(pathTemplate: string): string[] {
let segments: string[] = [];
let split: string[] = pathTemplate.split('/');
split.forEach( seg => {
if (seg.indexOf('{') === 0) {
let segment: string = seg.substring(1, seg.lastIndexOf('}')).trim();
segments.push(segment);
}
});
return segments;
}
/**
* Returns true if it's OK to use "wrapped" in the XML node. It's only OK to do this if
* the type being defined is an 'array' type.
* @param xml
* @return {boolean}
*/
private isWrappedOK(xml: Oas20XML): boolean {
let schema: Oas20Schema = <Oas20Schema>xml.parent();
return schema.type === "array";
}
/**
* Returns true if the given required scopes are all actually defined by the security definition.
* @param requiredScopes
* @param definedScopes
*/
private isValidScopes(requiredScopes: string[], definedScopes: Oas20Scopes) {
let rval: boolean = true;
let dscopes: string[] = definedScopes.scopes();
requiredScopes.forEach( requiredScope => {
if (dscopes.indexOf(requiredScope) === -1) {
rval = false;
}
});
return rval;
}
public visitDocument(node: Oas20Document): void {
if (this.hasValue(node.schemes)) {
node.schemes.forEach(scheme => {
this.reportIfInvalid("R-006", OasValidationRuleUtil.isValidEnumItem(scheme, ["http", "https", "ws", "wss"]), node,
"Invalid property value. Each \"schemes\" property value must be one of: http, https, ws, wss (Invalid value found: '" + scheme + "')");
});
}
if (this.hasValue(node.consumes)) {
this.reportIfInvalid("R-007", OasValidationRuleUtil.isValidMimeType(node.consumes), node, "Invalid property value. The \"consumes\" property value must be a valid mime-type.");
}
if (this.hasValue(node.produces)) {
this.reportIfInvalid("R-008", OasValidationRuleUtil.isValidMimeType(node.produces), node, "Invalid property value. The \"produces\" property value must be a valid mime-type.");
}
}
public visitOperation(node: Oas20Operation): void {
if (this.hasValue(node.summary)) {
this.reportIfInvalid("OP-001", node.summary.length < 120, node, "The \"summary\" property value should be less than 120 characters long.");
}
if (this.hasValue(node.operationId)) {
this.reportIfInvalid("OP-004", this.isValidOperationId(node.operationId), node, "The \"operationId\" property value is invalid - it should be simple *camelCase* format.");
}
if (this.hasValue(node.schemes)) {
node.schemes.forEach( scheme => {
this.reportIfInvalid("OP-010", OasValidationRuleUtil.isValidEnumItem(scheme, ["http", "https", "ws", "wss"]), node,
"Invalid property value. Each \"schemes\" property value must be one of: http, https, ws, wss (Invalid value found: '" + scheme + "')");
});
}
}
public visitParameter(node: Oas20Parameter): void {
// Note: parent may be an operation *or* a path-item.
if (node.in === "path") {
let pathItem: Oas20PathItem;
if (node.parent()["_path"]) {
pathItem = <Oas20PathItem>(node.parent());
} else {
pathItem = <Oas20PathItem>(node.parent().parent());
}
let path: string = pathItem.path();
let pathVars: string[] = this.parsePathTemplate(path);
this.reportIfInvalid("PAR-007", OasValidationRuleUtil.isValidEnumItem(node.name, pathVars), node,
"The \"name\" property value for a 'path' style parameter must match one of the items in the path template. Invalid path property name found: " + node.name);
}
if (node.in === "formData") {
let consumes: string[] = (<Oas20Document>(node.ownerDocument())).consumes;
if (!node.parent()["_path"]) {
let operation: Oas20Operation = <Oas20Operation>(node.parent());
if (this.hasValue(operation.consumes)) {
consumes = operation.consumes;
}
}
if (!this.hasValue(consumes)) {
consumes = [];
}
let valid: boolean = consumes.indexOf("application/x-www-form-urlencoded") >= 0 || consumes.indexOf("multipart/form-data") >= 0;
this.reportIfInvalid("PAR-008", valid, node, "A parameter located in \"formData\" may only be used when the operation @consumes 'application/x-www-form-urlencoded' or 'multipart/form-data' data.");
}
if (this.hasValue(node.in)) {
this.reportIfInvalid("PAR-009", OasValidationRuleUtil.isValidEnumItem(node.in, [ "query", "header", "path", "formData", "body" ]), node,
"Invalid property value. The \"in\" property value must be one of: query, header, path, formData, body (Found value: '" + node.in + "')");
}
if (this.hasValue(node.type)) {
this.reportIfInvalid("PAR-011", OasValidationRuleUtil.isValidEnumItem(node.type, [ "string", "number", "integer", "boolean", "array", "file" ]), node,
"Invalid property value. The \"type\" property value must be one of: string, number, integer, boolean, array, file (Found value: '" + node.type + "')");
}
if (this.hasValue(node.format)) {
this.reportIfInvalid("PAR-012", OasValidationRuleUtil.isValidEnumItem(node.format, [ "int32", "int64", "float", "double", "byte", "binary", "date", "date-time", "password" ]), node,
"Invalid property value. The \"format\" property value must be one of: int32, int64, float, double, byte, binary, date, date-time, password (Found value: '" + node.format + "')");
}
if (this.hasValue(node.allowEmptyValue)) {
this.reportIfInvalid("PAR-013", OasValidationRuleUtil.isValidEnumItem(node.in, [ "query", "formData" ]), node,
"The \"allowEmptyValue\" property is only allowed for 'query' or 'formData' parameters.");
}
if (this.hasValue(node.collectionFormat)) {
this.reportIfInvalid("PAR-014", node.type === "array", node,
"The \"collectionFormat\" property is only allowed for 'array' type parameters.");
}
if (this.hasValue(node.collectionFormat)) {
this.reportIfInvalid("PAR-015", OasValidationRuleUtil.isValidEnumItem(node.collectionFormat, [ "csv", "ssv", "tsv", "pipes", "multi" ]), node,
"Invalid property value. The \"collectionFormat\" property value must be one of: csv, ssv, tsv, pipes, multi (Found value: '" + node.collectionFormat + "')");
}
if (node.collectionFormat === "multi") {
this.reportIfInvalid("PAR-016", OasValidationRuleUtil.isValidEnumItem(node.in, [ "query", "formData" ]), node,
"Invalid property value. The \"collectionFormat\" property value can only be 'multi' for 'query' or 'formData' parameters.");
}
if (this.hasValue(node.default)) {
this.reportIfInvalid("PAR-017", node.required === undefined || node.required === null || node.required === false, node,
"Invalid property value. The \"default\" property is not valid when the parameter is required.");
}
}
public visitItems(node: Oas20Items): void {
if (this.hasValue(node.type)) {
this.reportIfInvalid("IT-003", OasValidationRuleUtil.isValidEnumItem(node.type, [ "string", "number", "integer", "boolean", "array" ]), node,
"Invalid property value. The \"type\" property value must be one of: string, number, integer, boolean, array (Found value: '" + node.type + "')");
}
if (this.hasValue(node.format)) {
this.reportIfInvalid("IT-004", OasValidationRuleUtil.isValidEnumItem(node.format, [ "int32", "int64", "float", "double", "byte", "binary", "date", "date-time", "password" ]), node,
"Invalid property value. The \"format\" property value must be one of: int32, int64, float, double, byte, binary, date, date-time, password (Found value: '" + node.format + "')");
}
if (this.hasValue(node.collectionFormat)) {
this.reportIfInvalid("IT-005", OasValidationRuleUtil.isValidEnumItem(node.collectionFormat, [ "csv", "ssv", "tsv", "pipes" ]), node,
"Invalid property value. The \"collectionFormat\" property value must be one of: csv, ssv, tsv, pipes (Found value: '" + node.collectionFormat + "')");
}
if (this.hasValue(node.collectionFormat)) {
this.reportIfInvalid("IT-006", node.type === "array", node,
"The \"collectionFormat\" property is only allowed for 'array' type parameters.");
}
}
public visitHeader(node: Oas20Header): void {
if (this.hasValue(node.type)) {
this.reportIfInvalid("HEAD-003", OasValidationRuleUtil.isValidEnumItem(node.type, [ "string", "number", "integer", "boolean", "array" ]), node,
"Invalid property value. The \"type\" property value must be one of: string, number, integer, boolean, array (Found value: '" + node.type + "')");
}
if (this.hasValue(node.format)) {
this.reportIfInvalid("HEAD-004", OasValidationRuleUtil.isValidEnumItem(node.format, [ "int32", "int64", "float", "double", "byte", "binary", "date", "date-time", "password" ]), node,
"Invalid property value. The \"format\" property value must be one of: int32, int64, float, double, byte, binary, date, date-time, password (Found value: '" + node.format + "')");
}
if (this.hasValue(node.collectionFormat)) {
this.reportIfInvalid("HEAD-006", node.type === "array", node,
"The \"collectionFormat\" property is only allowed for 'array' type headers.");
}
if (this.hasValue(node.collectionFormat)) {
this.reportIfInvalid("HEAD-007", OasValidationRuleUtil.isValidEnumItem(node.collectionFormat, [ "csv", "ssv", "tsv", "pipes" ]), node,
"Invalid property value. The \"collectionFormat\" property value must be one of: csv, ssv, tsv, pipes (Found value: '" + node.collectionFormat + "')");
}
}
public visitXML(node: Oas20XML): void {
if (this.hasValue(node.wrapped)) {
this.reportIfInvalid("XML-002", this.isWrappedOK(node), node,
"The \"wrapped\" property is only valid for 'array' types.");
}
}
public visitSecurityScheme(node: Oas20SecurityScheme): void {
if (this.hasValue(node.type)) {
this.reportIfInvalid("SS-008", OasValidationRuleUtil.isValidEnumItem(node.type, [ "apiKey", "basic", "oauth2" ]), node,
"Invalid property value. The \"type\" property value must be one of: basic, apiKey, oauth2 (Found value: '" + node.type + "')");
}
if (this.hasValue(node.in)) {
this.reportIfInvalid("SS-009", OasValidationRuleUtil.isValidEnumItem(node.in, [ "query", "header" ]), node,
"Invalid property value. The \"in\" property value must be one of: query, header (Found value: '" + node.in + "')");
}
if (this.hasValue(node.flow)) {
this.reportIfInvalid("SS-010", OasValidationRuleUtil.isValidEnumItem(node.flow, [ "implicit", "password", "application", "accessCode" ]), node,
"Invalid property value. The \"flow\" property value must be one of: implicit, password, application, accessCode (Found value: '" + node.flow + "')");
}
}
public visitSecurityRequirement(node: Oas20SecurityRequirement): void {
let snames: string[] = node.securityRequirementNames();
snames.forEach( sname => {
let sdefs: Oas20SecurityDefinitions = (<Oas20Document>node.ownerDocument()).securityDefinitions;
if (this.hasValue(sdefs)) {
let scheme: Oas20SecurityScheme = (<Oas20Document>node.ownerDocument()).securityDefinitions.securityScheme(sname);
if (this.hasValue(scheme)) {
if (scheme.type !== "oauth2") {
let scopes: string[] = node.scopes(sname);
this.reportIfInvalid("SREQ-002", this.hasValue(scopes) && scopes.length === 0, node,
"Security Requirement '" + sname + "' field value must be an empty array because the referenced Security Definition \"type\" is not 'oauth2'.");
} else {
let definedScopes: Oas20Scopes = scheme.scopes;
let requiredScopes: string[] = node.scopes(sname);
this.reportIfInvalid("SREQ-003", this.isValidScopes(requiredScopes, definedScopes), node,
"Security Requirement '" + sname + "' field value must be an array of scopes from the possible scopes defined by the referenced Security Definition.");
}
}
}
});
}
}