Merge branch 'master' into releases

This commit is contained in:
RedocBot 2016-03-15 14:19:25 +00:00 committed by travis@localhost
commit 20915ed40f
6 changed files with 141 additions and 33 deletions

View File

@ -139,21 +139,21 @@ api-logo {
color: $red; color: $red;
border: 1px solid rgba(38,50,56,0.1); border: 1px solid rgba(38,50,56,0.1);
} }
}
footer {
text-align: right;
padding: 10px;
font-size: 15px;
background-color: white;
strong { strong {
font-size: 18px; font-size: 18px;
color: $headers-color; color: $headers-color;
} }
footer {
text-align: right;
padding: 10px;
font-size: 15px;
}
} }
/* markdown elements */ /* markdown elements */
:host .redoc-markdown-block { :host .redoc-markdown-block {

View File

@ -17,6 +17,21 @@ function safeConcat(a, b) {
return res.concat(b); return res.concat(b);
} }
function defaults(target, src) {
var props = Object.keys(src);
var index = -1,
length = props.length;
while (++index < length) {
var key = props[index];
if (target[key] === undefined) {
target[key] = src[key];
}
}
return target;
}
function snapshot(obj) { function snapshot(obj) {
if(obj == null || typeof(obj) != 'object') { if(obj == null || typeof(obj) != 'object') {
return obj; return obj;
@ -156,41 +171,57 @@ export class BaseComponent {
} }
joinAllOf(schema = this.componentSchema, opts) { joinAllOf(schema = this.componentSchema, opts) {
var self = this;
function merge(into, schemas) { function merge(into, schemas) {
if (into.required || into.properties) {
let errMessage = `Can\'t merge allOf: properties or required fields are specified on the same level as allOf
${into}`;
throw new Error(errMessage);
}
into.required = [];
into.properties = {};
for (let subSchema of schemas) { for (let subSchema of schemas) {
if (opts && opts.omitParent && subSchema.discriminator) continue; if (opts && opts.omitParent && subSchema.discriminator) continue;
// TODO: add support for merge array schemas // TODO: add support for merge array schemas
if (typeof subSchema !== 'object' || subSchema.type !== 'object') { if (typeof subSchema !== 'object') {
let errMessage = `Can\'t merge allOf: only subschemas with type: object can be merged let errMessage = `Items of allOf should be Object: ${typeof subSchema} found
${subSchema}`; ${subSchema}`;
throw new Error(errMessage); throw new Error(errMessage);
} }
self.joinAllOf(subSchema); if (into.type && into.type !== subSchema.type) {
let errMessage = `allOf merging error: schemas with different types can't be merged`;
throw new Error(errMessage);
}
if (into.type === 'array') {
console.warn('allOf: subschemas with type array are not supported yet');
}
// TODO: add check if can be merged correctly (no different properties with the same name) // TODO: add check if can be merged correctly (no different properties with the same name)
if (subSchema.properties) { if (subSchema.type === 'object' && subSchema.properties) {
into.properties || (into.properties = {});
Object.assign(into.properties, subSchema.properties); Object.assign(into.properties, subSchema.properties);
} }
if (subSchema.required) { if (subSchema.type === 'object' && subSchema.required) {
into.required || (into.required = []);
into.required.push(...subSchema.required); into.required.push(...subSchema.required);
} }
defaults(into, subSchema);
} }
into.type = 'object';
into.allOf = null; into.allOf = null;
} }
if (schema.allOf) {
merge(schema, schema.allOf); function traverse(obj) {
if (obj === null || typeof(obj) !== 'object') {
return;
} }
for(var key in obj) {
if (obj.hasOwnProperty(key)) {
traverse(obj[key]);
}
}
if (obj.allOf) {
merge(obj, obj.allOf);
}
}
traverse(schema);
} }
/** /**

View File

@ -217,19 +217,66 @@ describe('Redoc components', () => {
}); });
}); });
describe('Incorrect or not supported allOf', () => { describe('AllOf with other properties on the allOf level', () => {
it('should throw when properties or required is set on the same level as allOf', () => { let joined;
component.pointer = '/definitions/BadAllOf2'; beforeAll(() => {
component.pointer = '/definitions/AllOfWithOther';
component.ngOnInit();
component.dereference();
component.joinAllOf();
joined = component.componentSchema;
});
it('should remove $allOf field', () => {
expect(joined.allOf).toBeNull();
});
it('should set type object', () => {
joined.type.should.be.equal('object');
});
it('should merge properties', () => {
Object.keys(joined.properties).length.should.be.equal(1);
Object.keys(joined.properties).should.be.deepEqual(['id']);
});
it('should merge required', () => {
joined.required.length.should.be.equal(1);
joined.required.should.be.deepEqual(['id']);
});
it('should preserve parent properties', () => {
joined.description.should.be.equal('Test');
joined.readOnly.should.be.equal(true);
});
});
describe('allOf edgecases', () => {
it('should merge properties and required when defined on allOf level', () => {
component.pointer = '/definitions/PropertiesOnAllOfLevel';
component.ngOnInit();
component.dereference();
(() => component.joinAllOf()).should.not.throw();
let joined = component.componentSchema;
Object.keys(joined.properties).length.should.be.equal(3);
});
it('should throw when merging schemas with different types', () => {
component.pointer = '/definitions/BadAllOf1';
component.ngOnInit(); component.ngOnInit();
component.dereference(); component.dereference();
(() => component.joinAllOf()).should.throw(); (() => component.joinAllOf()).should.throw();
}); });
it('should throw when merging non-object schemas', () => { it('should handle nested allOF', () => {
component.pointer = '/definitions/BadAllOf1'; component.pointer = '/definitions/NestedAllOf';
component.ngOnInit(); component.ngOnInit();
component.dereference(); component.dereference();
(() => component.joinAllOf()).should.throw(); (() => component.joinAllOf()).should.not.throw();
let joined = component.componentSchema;
Object.keys(joined.properties).length.should.be.equal(4);
Object.keys(joined.properties).should.be.deepEqual(['prop1', 'prop2', 'prop3', 'prop4']);
joined.required.should.be.deepEqual(['prop1', 'prop3']);
}); });
}); });

View File

@ -1,7 +1,7 @@
{ {
"name": "redoc", "name": "redoc",
"description": "Swagger-generated API Reference Documentation", "description": "Swagger-generated API Reference Documentation",
"version": "0.7.2", "version": "0.7.3",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/Rebilly/ReDoc" "url": "git://github.com/Rebilly/ReDoc"

View File

@ -61,6 +61,7 @@ describe('APIs.guru specs test', ()=> {
delete apisGuruList['googleapis.com:mirror']; // bad urls in images delete apisGuruList['googleapis.com:mirror']; // bad urls in images
delete apisGuruList['googleapis.com:discovery']; // non-string references delete apisGuruList['googleapis.com:discovery']; // non-string references
delete apisGuruList['clarify.io']; // non-string references delete apisGuruList['clarify.io']; // non-string references
delete apisGuruList['pushpay.com']; // https://github.com/Rebilly/ReDoc/issues/30
// run quick version of e2e test on all builds except releases // run quick version of e2e test on all builds except releases
if (process.env.TRAVIS && !process.env.TRAVIS_TAG) { if (process.env.TRAVIS && !process.env.TRAVIS_TAG) {

View File

@ -8,6 +8,7 @@
"basePath": "/v2/", "basePath": "/v2/",
"definitions": { "definitions": {
"Simple": { "Simple": {
"description": "simple",
"type": "object", "type": "object",
"required": ["id"], "required": ["id"],
"properties": { "properties": {
@ -57,6 +58,15 @@
} }
] ]
}, },
"AllOfWithOther": {
"description": "Test",
"readOnly": true,
"allOf": [
{
"$ref": "#/definitions/Simple"
}
]
},
"BadAllOf1": { "BadAllOf1": {
"allOf": [ "allOf": [
{ {
@ -67,8 +77,12 @@
} }
] ]
}, },
"BadAllOf2": { "PropertiesOnAllOfLevel": {
"properties": {}, "properties": {
"prop": {
"type": "string"
}
},
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/Simple" "$ref": "#/definitions/Simple"
@ -83,6 +97,21 @@
} }
} }
] ]
},
"NestedAllOf": {
"allOf": [
{
"$ref": "#/definitions/SimpleAllOf"
},
{
"type": "object",
"properties": {
"prop4": {
"type": "string"
}
}
}
]
} }
}, },
"paths": { "paths": {