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 #104 from apiaryio/kylef/schema-structures
Browse files Browse the repository at this point in the history
Provide dataStructure from JSON Schema
  • Loading branch information
kylef authored Jun 8, 2017
2 parents 58b6deb + 2457af7 commit e720eac
Show file tree
Hide file tree
Showing 11 changed files with 866 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Generated by Peasant
{
"presets": ["env"],
"plugins": ["transform-runtime"]
"plugins": ["transform-runtime", "array-includes"]
}
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 0.12.0-beta.1

## Enhancements

- Data Structure elements are now generated from JSON Schema found in examples.

# 0.11.1 - 2017-04-25

## Bug Fixes
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fury-adapter-swagger",
"version": "0.11.1",
"version": "0.12.0-beta.1",
"description": "Swagger 2.0 parser for Fury.js",
"main": "./lib/adapter.js",
"tonicExampleFilename": "tonic-example.js",
Expand All @@ -26,13 +26,14 @@
"media-typer": "^0.3.0"
},
"devDependencies": {
"babel-plugin-array-includes": "^2.0.3",
"chai": "^3.2.0",
"glob": "^7.1.1",
"fury": "3.0.0-beta.0",
"peasant": "^1.1.0",
"glob": "^7.1.1",
"minim": "^0.15.0",
"minim-parse-result": "^0.4.0",
"swagger-zoo": "2.2.6"
"peasant": "^1.1.0",
"swagger-zoo": "2.3.0"
},
"engines": {
"node": ">=4"
Expand Down
15 changes: 15 additions & 0 deletions src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import uriTemplate from './uri-template';
import { origin } from './link';
import { pushHeader, pushHeaderObject } from './headers';
import Ast from './ast';
import DataStructureGenerator from './schema';

const FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded';

Expand Down Expand Up @@ -1345,6 +1346,7 @@ export default class Parser {
pushSchemaAsset(schema, payload, path) {
let actualSchema = _.omit(schema, ['discriminator', 'readOnly', 'xml', 'externalDocs', 'example']);
actualSchema = _.omitBy(actualSchema, isExtension);
let handledSchema = false;

try {
const Asset = this.minim.getElementClass('asset');
Expand All @@ -1358,10 +1360,23 @@ export default class Parser {
}

payload.content.push(schemaAsset);
handledSchema = true;
} catch (exception) {
this.createAnnotation(annotations.DATA_LOST, path,
('Circular references in schema are not yet supported'));
}

if (handledSchema) {
try {
const generator = new DataStructureGenerator(this.minim);
const dataStructure = generator.generateDataStructure(schema);
if (dataStructure) {
payload.content.push(dataStructure);
}
} catch (exception) {
// TODO: Expose errors once feature is more-complete
}
}
}

// Create a new Refract transition element with a blank request and response.
Expand Down
206 changes: 206 additions & 0 deletions src/schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/* eslint-disable class-methods-use-this */

import _ from 'lodash';

/*
* Data Structure Generator
* Generates a dataStructure element from a JSON schema.
*
* >>> const generator = new DataStructureGenerator(minimNamespace);
* >>> const dataStructure = generator.generateDataStructure({type: 'string'});
*/
export default class DataStructureGenerator {
constructor(minim) {
this.minim = minim;
}

// Generates a data structure element representing the given schema
generateDataStructure(schema) {
const element = this.generateElement(schema);

if (!element) {
return null;
}

const { DataStructure } = this.minim.elements;
const dataStructure = new DataStructure(element);
return dataStructure;
}

// Generates a member element for a property in a schema
generateMember(name, property) {
const {
String: StringElement,
Member: MemberElement,
} = this.minim.elements;

const member = new MemberElement();
member.key = new StringElement(name);
member.value = this.generateElement(property);

if (property.description) {
member.description = property.description;
}

return member;
}

// Generates an enum element for the given enum schema
generateEnum(schema) {
const { Array: ArrayElement } = this.minim.elements;
const element = new ArrayElement(schema.enum);
element.element = 'enum';
return element;
}

// Generates an object element from the given object schema
generateObject(schema) {
const {
Array: ArrayElement,
Object: ObjectElement,
String: StringElement,
} = this.minim.elements;

const element = new ObjectElement();

if (schema.properties) {
element.content = _.map(schema.properties, (subschema, property) => {
const member = this.generateMember(property, subschema);

const required = schema.required && schema.required.includes(property);
member.attributes.typeAttributes = new ArrayElement([
new StringElement(required ? 'required' : 'optional'),
]);

return member;
});
}

return element;
}

// Generates an array element from the given array schema
generateArray(schema) {
const { Array: ArrayElement } = this.minim.elements;
const element = new ArrayElement();

if (schema.items) {
if (_.isArray(schema.items)) {
schema.items.forEach((item) => {
element.push(this.generateElement(item));
});
} else {
element.push(this.generateElement(schema.items));
}
}

return element;
}

// Generates an array of descriptions for each validation rule in the given schema.
generateValidationDescriptions(schema) {
const validations = {
// String
pattern: value => `Matches regex pattern: \`${value}\``,
maxLength: value => `Length of string must be less than, or equal to ${value}`,
minLength: value => `Length of string must be greater than, or equal to ${value}`,

// Number
multipleOf: value => `Number must be a multiple of ${value}`,
maximum: value => `Number must be less than, or equal to ${value}`,
minimum: value => `Number must be more than, or equal to ${value}`,
exclusiveMaximum: value => `Number must be less than ${value}`,
exclusiveMinimum: value => `Number must be more than ${value}`,

// Object
minProperties: value => `Object must have more than, or equal to ${value} properties`,
maxProperties: value => `Object must have less than, or equal to ${value} properties`,

// Array
maxItems: value => `Array length must be less than, or equal to ${value}`,
minItems: value => `Array length must be more than, or equal to ${value}`,
uniqueItems: () => 'Array contents must be unique',

// Other
format: value => `Value must be of format '${value}'`,
};

return _
.chain(validations)
.map((value, key) => {
if (schema[key]) {
return value(schema[key]);
}

return null;
})
.compact()
.value();
}

// Generates an element representing the given schema
generateElement(schema) {
const {
String: StringElement,
Number: NumberElement,
Boolean: BooleanElement,
Null: NullElement,
} = this.minim.elements;

const typeGeneratorMap = {
boolean: BooleanElement,
string: StringElement,
number: NumberElement,
null: NullElement,
};

let element;

if (schema.enum) {
element = this.generateEnum(schema);
} else if (schema.type === 'array') {
element = this.generateArray(schema);
} else if (schema.type === 'object') {
element = this.generateObject(schema);
} else if (schema.type && typeGeneratorMap[schema.type]) {
element = new typeGeneratorMap[schema.type]();
} else if (_.isArray(schema.type)) {
// TODO: Support multiple `type`
}

if (element) {
if (schema.title) {
element.title = new StringElement(schema.title);
}

if (schema.description) {
element.description = new StringElement(schema.description);
}

if (schema.default !== undefined && !_.isArray(schema.default) &&
!_.isObject(schema.default)) {
// TODO Support defaults for arrays and objects
element.attributes.set('default', schema.default);
}

if (schema.examples && (schema.type === 'string' || schema.type === 'boolean' || schema.type === 'number')) {
// TODO examples for array/object or multiple types
element.attributes.set('samples', schema.examples);
}

const validationDescriptions = this.generateValidationDescriptions(schema);

if (validationDescriptions.length > 0) {
const description = validationDescriptions.map(value => `- ${value}`);

if (element.description && element.description.toValue()) {
description.splice(0, 0, `${element.description.toValue()}\n`);
}

element.description = new StringElement(description.join('\n'));
}
}

return element;
}
}
11 changes: 11 additions & 0 deletions test/fixtures/consumes-invalid-type.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@
"contentType": "application/schema+json"
},
"content": "{\"type\":\"object\"}"
},
{
"element": "dataStructure",
"meta": {},
"attributes": {},
"content": {
"element": "object",
"meta": {},
"attributes": {},
"content": []
}
}
]
},
Expand Down
11 changes: 11 additions & 0 deletions test/fixtures/operation-consumes-invalid-type.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@
"contentType": "application/schema+json"
},
"content": "{\"type\":\"object\"}"
},
{
"element": "dataStructure",
"meta": {},
"attributes": {},
"content": {
"element": "object",
"meta": {},
"attributes": {},
"content": []
}
}
]
},
Expand Down
11 changes: 11 additions & 0 deletions test/fixtures/operation-produces-invalid-type.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@
"contentType": "application/schema+json"
},
"content": "{\"type\":\"object\"}"
},
{
"element": "dataStructure",
"meta": {},
"attributes": {},
"content": {
"element": "object",
"meta": {},
"attributes": {},
"content": []
}
}
]
}
Expand Down
11 changes: 11 additions & 0 deletions test/fixtures/parameter-array-default-warning.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@
"contentType": "application/schema+json"
},
"content": "{\"type\":\"string\"}"
},
{
"element": "dataStructure",
"meta": {},
"attributes": {},
"content": {
"element": "string",
"meta": {},
"attributes": {},
"content": null
}
}
]
}
Expand Down
11 changes: 11 additions & 0 deletions test/fixtures/produces-invalid-type.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@
"contentType": "application/schema+json"
},
"content": "{\"type\":\"object\"}"
},
{
"element": "dataStructure",
"meta": {},
"attributes": {},
"content": {
"element": "object",
"meta": {},
"attributes": {},
"content": []
}
}
]
}
Expand Down
Loading

0 comments on commit e720eac

Please sign in to comment.