Skip to content
This repository has been archived by the owner on Jul 2, 2019. It is now read-only.

Commit

Permalink
Merge pull request #124 from apiaryio/kylef/params
Browse files Browse the repository at this point in the history
Validate and discard invalid parameter values
  • Loading branch information
pksunkara authored Aug 25, 2017
2 parents e7537dd + f954c1f commit 78f29ca
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 130 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Bug Fixes

- Parameter default, example and enumerations values are now validated against
the parameter type. Invalid values will emit warnings and be discarded, this
resolves further problems when handling the invalid values.
- Data Structure generation will now support integer JSON Schema type.

# 0.13.2
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"lodash": "^4.15.0",
"swagger-parser": "^3.3.0",
"yaml-js": "^0.1.5",
"media-typer": "^0.3.0"
"media-typer": "^0.3.0",
"z-schema": "^3.16.1"
},
"peerDependencies": {
"fury": "3.0.0-beta.4"
Expand Down
158 changes: 97 additions & 61 deletions src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import _ from 'lodash';
import yaml from 'js-yaml';
import typer from 'media-typer';
import SwaggerParser from 'swagger-parser';
import ZSchema from 'z-schema';
import annotations from './annotations';
import { bodyFromSchema, bodyFromFormParameter } from './generator';
import uriTemplate from './uri-template';
Expand Down Expand Up @@ -887,7 +888,7 @@ export default class Parser {
_.set(parametersGenerator, [param.in, param.name], _.bind(this.withPath, this, parameters[1], 'parameters', index, () => {
this.formDataParameterCheck(param);
formParamsSchema = bodyFromFormParameter(param, formParamsSchema);
const member = this.convertParameterToMember(param, this.path);
const member = this.convertParameterToMember(param);
formParams.push(member);
}));
break;
Expand Down Expand Up @@ -1138,132 +1139,167 @@ export default class Parser {
}
}

// Convert a Swagger parameter into a Refract element.
convertParameterToElement(parameter, path, setAttributes = false) {
/* eslint-disable class-methods-use-this */
schemaForParameterValue(parameter) {
const schema = {
type: parameter.type,
};

if (schema.type === 'integer') {
schema.type = 'number';
}

if (parameter.items) {
schema.items = parameter.items;
}

return schema;
}

typeForParameter(parameter) {
const {
Array: ArrayElement, Boolean: BooleanElement, Number: NumberElement,
String: StringElement, Element,
String: StringElement,
} = this.minim.elements;

const types = {
string: StringElement,
number: NumberElement,
integer: NumberElement,
boolean: BooleanElement,
array: ArrayElement,
file: StringElement,
};

return types[parameter.type];
}

convertValueToElement(value, schema) {
const validator = new ZSchema();
let element;
let Type;
let example = parameter['x-example'];

// Convert from Swagger types to Minim elements
if (parameter.type === 'string') {
Type = StringElement;
} else if (parameter.type === 'integer' || parameter.type === 'number') {
Type = NumberElement;
} else if (parameter.type === 'boolean') {
Type = BooleanElement;
} else if (parameter.type === 'array') {
Type = ArrayElement;

if (example && !Array.isArray(example)) {
example = [];

this.createAnnotation(annotations.VALIDATION_WARNING, path.concat(['x-example']),
('Value of example should be an array'));

if (validator.validate(value, schema)) {
element = this.minim.toElement(value);

if (this.generateSourceMap) {
this.createSourceMap(element, this.path);
}
} else {
// Default to a string in case we get a type we haven't seen
Type = StringElement;
validator.getLastError().details.forEach((detail) => {
this.createAnnotation(annotations.VALIDATION_WARNING, this.path, detail.message);
});

// Coerce parameter to correct type
if (schema.type === 'string') {
if (typeof value === 'number' || typeof value === 'boolean') {
element = new this.minim.elements.String(String(value));
}
}
}

const sampleContent = new Type(example);
return element;
}

// Convert a Swagger parameter into a Refract element.
convertParameterToElement(parameter, setAttributes = false) {
const { Element, Array: ArrayElement } = this.minim.elements;

const Type = this.typeForParameter(parameter);
const schema = this.schemaForParameterValue(parameter);

if (parameter['x-example'] !== undefined && this.generateSourceMap) {
this.createSourceMap(sampleContent, path.concat(['x-example']));
let element = new Type();

if (parameter['x-example'] !== undefined) {
this.withPath('x-example', () => {
const value = this.convertValueToElement(parameter['x-example'], schema);

if (value) {
element = value;
}
});
}

if (parameter.enum) {
const example = element;
element = new Element();
element.element = 'enum';

const enumerations = new ArrayElement();
if (example.content) {
element.content = example;
}

_.forEach(parameter.enum, (value, index) => {
const e = new Type(value);
const enumerations = new ArrayElement();

if (this.generateSourceMap) {
this.createSourceMap(e, path.concat('enum', index));
}
parameter.enum.forEach((value, index) => {
this.withPath('enum', index, () => {
const enumeration = this.convertValueToElement(value, schema);

enumerations.push(e);
if (enumeration) {
enumerations.push(enumeration);
}
});
});

element.attributes.set('enumerations', enumerations);
element.content = example ? sampleContent : null;
} else {
element = sampleContent;
}

// If there is a default, it is set on the member value instead of the member
// element itself because the default value applies to the value.
if (parameter.default) {
if (parameter.type === 'array' && !Array.isArray(parameter.default)) {
this.createAnnotation(annotations.VALIDATION_WARNING, path.concat(['default']),
('Value of default should be an array'));
} else {
const defaultElement = this.minim.toElement(parameter.default);
this.withPath('default', () => {
const value = this.convertValueToElement(parameter.default, schema);

if (this.generateSourceMap) {
this.createSourceMap(defaultElement, path.concat(['default']));
if (value) {
element.attributes.set('default', value);
}

element.attributes.set('default', defaultElement);
}
});
}

if (parameter.type === 'array' && parameter.items && !example) {
element.content = [this.convertParameterToElement(parameter.items, (path || []).concat(['items']), true)];
if (parameter.type === 'array' && parameter.items && element.content.length === 0) {
this.withPath('items', () => {
element.content = [this.convertParameterToElement(parameter.items, true)];
});
}

if (this.generateSourceMap) {
this.createSourceMap(element, path);
this.createSourceMap(element, this.path);
}

if (setAttributes) {
if (parameter.description) {
element.description = parameter.description;

if (this.generateSourceMap) {
this.createSourceMap(element.meta.get('description'), path.concat(['description']));
this.createSourceMap(element.meta.get('description'), this.path.concat(['description']));
}
}

if (parameter.required) {
element.attributes.set('typeAttributes', ['required']);
}

if (parameter.default !== undefined) {
element.attributes.set('default', parameter.default);
}
}

return element;
}

// Convert a Swagger parameter into a Refract member element for use in an
// object element (or subclass).
convertParameterToMember(parameter, path = this.path) {
convertParameterToMember(parameter) {
const MemberElement = this.minim.getElementClass('member');
const memberValue = this.convertParameterToElement(parameter, path);
const memberValue = this.convertParameterToElement(parameter);

// TODO: Update when Minim has better support for elements as values
// should be: new MemberType(parameter.name, memberValue);
const member = new MemberElement(parameter.name);
member.content.value = memberValue;

if (this.generateSourceMap) {
this.createSourceMap(member, path);
this.createSourceMap(member, this.path);
}

if (parameter.description) {
member.description = parameter.description;

if (this.generateSourceMap) {
this.createSourceMap(member.meta.get('description'), path.concat(['description']));
this.createSourceMap(member.meta.get('description'), this.path.concat(['description']));
}
}

Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/parameter-array-default-warning.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@
]
}
},
"content": "Value of default should be an array"
"content": "Expected type array but found type string"
}
]
}
Loading

0 comments on commit 78f29ca

Please sign in to comment.