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

Commit

Permalink
fix: Ensure that example values are dereferenced
Browse files Browse the repository at this point in the history
  • Loading branch information
kylef committed Oct 23, 2018
1 parent 233feac commit 6f3ac2b
Show file tree
Hide file tree
Showing 8 changed files with 467 additions and 39 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Fury Swagger Parser Changelog

## Master

### Bug Fixes

- Fixes a regression introduced in 0.22.0 where the parse result may contain
invalid references inside a JSON Schema for example values if they used
references. This regression also caused `$ref` to be incorrectly present in
Data Structure sample values.

## 0.22.0 (2018-10-11)

### Enhancements
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"minim": "^0.20.5",
"minim-parse-result": "^0.10.1",
"peasant": "1.1.0",
"swagger-zoo": "2.19.1"
"swagger-zoo": "2.19.2"
},
"engines": {
"node": ">=6"
Expand Down
109 changes: 75 additions & 34 deletions src/json-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,73 @@ function isExtension(value, key) {
return _.startsWith(key, 'x-');
}

function convertSubSchema(schema, references) {
export function parseReference(reference) {
const parts = reference.split('/');

if (parts[0] !== '#') {
throw new Error('Schema reference must start with document root (#)');
}

if (parts[1] !== 'definitions' || parts.length !== 3) {
throw new Error('Schema reference must be reference to #/definitions');
}

const id = parts[2];

return id;
}

function lookupReference(reference, root) {
const parts = reference.split('/').reverse();

if (parts.pop() !== '#') {
throw new Error('Schema reference must start with document root (#)');
}

if (parts.pop() !== 'definitions') {
throw new Error('Schema reference must be reference to #/definitions');
}

const id = parts[0];
let value = root.definitions;

while (parts.length > 0 && value !== undefined) {
const key = parts.pop();
value = value[key];
}

if (value === undefined) {
throw new Error(`Reference to ${reference} does not exist`);
}

return {
id,
referenced: value,
};
}

function convertExample(example, swagger) {
if (_.isArray(example)) {
return example.map(value => convertExample(value, swagger));
} else if (_.isObject(example)) {
if (example.$ref) {
const ref = lookupReference(example.$ref, swagger);
return convertExample(ref.referenced, swagger);
}

const result = {};

_.forEach(example, (value, key) => {
result[key] = convertExample(value, swagger);
});

return result;
}

return example;
}

function convertSubSchema(schema, references, swagger) {
if (schema.$ref) {
references.push(schema.$ref);
return { $ref: schema.$ref };
Expand All @@ -20,7 +86,7 @@ function convertSubSchema(schema, references) {
}

if (schema.example) {
actualSchema.examples = [schema.example];
actualSchema.examples = [convertExample(schema.example, swagger)];
}

if (schema['x-nullable']) {
Expand Down Expand Up @@ -87,35 +153,6 @@ function convertSubSchema(schema, references) {
return actualSchema;
}

export function parseReference(reference) {
const parts = reference.split('/');

if (parts[0] !== '#') {
throw new Error('Schema reference must start with document root (#)');
}

if (parts[1] !== 'definitions' || parts.length !== 3) {
throw new Error('Schema reference must be reference to #/definitions');
}

const id = parts[2];

return id;
}

function lookupReference(reference, root) {
const id = parseReference(reference);

if (!root.definitions || !root.definitions[id]) {
throw new Error(`Reference to ${reference} does not exist`);
}

return {
id,
referenced: root.definitions[id],
};
}

/** Returns true if the given schema contains any references
*/
function checkSchemaHasReferences(schema) {
Expand Down Expand Up @@ -196,10 +233,14 @@ function findReferences(schema) {
}

/** Convert Swagger schema to JSON Schema
* @param schema - The Swagger schema to convert
* @param root - The document root (this contains the JSON schema definitions)
* @param swagger - The swagger document root (this contains the Swagger schema definitions)
* @param copyDefinitins - Whether to copy the referenced definitions to the resulted schema
*/
export function convertSchema(schema, root, copyDefinitions = true) {
export function convertSchema(schema, root, swagger, copyDefinitions = true) {
let references = [];
const result = convertSubSchema(schema, references);
const result = convertSubSchema(schema, references, swagger);

if (copyDefinitions) {
if (references.length !== 0) {
Expand Down Expand Up @@ -240,7 +281,7 @@ export function convertSchemaDefinitions(definitions) {

if (definitions) {
_.forEach(definitions, (schema, key) => {
jsonSchemaDefinitions[key] = convertSchema(schema, { definitions }, false);
jsonSchemaDefinitions[key] = convertSchema(schema, { definitions }, { definitions }, false);
});
}

Expand Down
20 changes: 16 additions & 4 deletions src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -1073,7 +1073,8 @@ export default class Parser {
pushHeader('Content-Type', FORM_CONTENT_TYPE, request, this, 'form-data-content-type');
}

const jsonSchema = convertSchema(schema, { definitions: this.definitions });
const jsonSchema = convertSchema(schema, { definitions: this.definitions },
this.referencedSwagger);
bodyFromSchema(jsonSchema, request, this, contentType || FORM_CONTENT_TYPE);

// Generating data structure
Expand Down Expand Up @@ -1550,10 +1551,12 @@ export default class Parser {

pushAssets(schema, payload, contentType, pushBody) {
let jsonSchema;
const referencedPathValue = this.referencedPathValue();

try {
const root = { definitions: this.definitions };
jsonSchema = convertSchema(this.referencedPathValue() || schema, root);
jsonSchema = convertSchema(referencedPathValue || schema, root,
this.referencedSwagger);
} catch (error) {
this.createAnnotation(annotations.VALIDATION_ERROR, this.path, error.message);
return;
Expand All @@ -1564,7 +1567,16 @@ export default class Parser {
}

this.pushSchemaAsset(schema, jsonSchema, payload, this.path);
this.pushDataStructureAsset(schema, payload);

if (referencedPathValue && referencedPathValue.$ref) {
// If the schema is a reference just produce a data structure for the ref
this.pushDataStructureAsset(referencedPathValue, payload);
} else {
// Otherwise, we want to use the JSON Schema instead of Swagger Schema.
// In some cases, the created JSON Schema will dereference the $ref
// so we have the above if clause.
this.pushDataStructureAsset(jsonSchema, payload);
}
}

// Create a Refract asset element containing JSON Schema and push into payload
Expand Down Expand Up @@ -1603,7 +1615,7 @@ export default class Parser {
pushDataStructureAsset(schema, payload) {
try {
const generator = new DataStructureGenerator(this.minim);
const dataStructure = generator.generateDataStructure(this.referencedPathValue() || schema);
const dataStructure = generator.generateDataStructure(schema);
if (dataStructure) {
payload.content.push(dataStructure);
}
Expand Down
8 changes: 8 additions & 0 deletions src/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,14 @@ export class DataStructureGenerator {
null: NullElement,
};

if (schema.allOf && schema.allOf.length === 1 && schema.definitions &&
Object.keys(schema).length === 2) {
// Since we can't have $ref at root with definitions.
// `allOf` with a single item is used as a work around for this type of schema
// We can safely ignore the allOf and unwrap it as normal schema in this case
return this.generateElement(schema.allOf[0]);
}

let element;

if (schema.$ref) {
Expand Down
Loading

0 comments on commit 6f3ac2b

Please sign in to comment.