Merge branch 'master' into releases

This commit is contained in:
RedocBot 2016-03-26 22:49:42 +00:00 committed by travis@localhost
commit e5397389d8
10 changed files with 122 additions and 74 deletions

View File

@ -5,9 +5,31 @@
var schemaUrlInput = document.getElementById('schema-url-input'); var schemaUrlInput = document.getElementById('schema-url-input');
schemaUrlForm.addEventListener('submit', function(event) { schemaUrlForm.addEventListener('submit', function(event) {
event.preventDefault(); event.preventDefault();
Redoc.init(schemaUrlInput.value); event.stopPropagation();
location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value)
return false; return false;
}) })
var url = window.location.search.match(/url=([^&]+)/);
if (url && url.length > 1) {
url = decodeURIComponent(url[1]);
document.getElementsByTagName('redoc')[0].setAttribute('spec-url', url);
schemaUrlInput.value = url;
}
function updateQueryStringParameter(uri, key, value) {
var re = new RegExp("([?|&])" + key + "=.*?(&|#|$)", "i");
if (uri.match(re)) {
return uri.replace(re, '$1' + key + "=" + value + '$2');
} else {
var hash = '';
if( uri.indexOf('#') !== -1 ){
hash = uri.replace(/.*#/, '#');
uri = uri.replace(/#.*/, '');
}
var separator = uri.indexOf('?') !== -1 ? "&" : "?";
return uri + separator + key + "=" + value + hash;
}
}
//window.redocDebugMode = true; //window.redocDebugMode = true;
})(); })();

View File

@ -133,7 +133,7 @@ $sub-schema-offset: ($bullet-size/2) + $bullet-margin;
height: ($param-name-height/2) + $cell-padding + 1; height: ($param-name-height/2) + $cell-padding + 1;
} }
.param:last-of-type > .param-name { .param:last-of-type > .param-name, .param.last > .param-name {
position: relative; position: relative;
&:after { &:after {
@ -156,6 +156,10 @@ $sub-schema-offset: ($bullet-size/2) + $bullet-margin;
display: none !important; display: none !important;
} }
.param-schema.last > td {
border-left: 0;
}
.param-enum { .param-enum {
color: $text-color; color: $text-color;
font-size: 13px; font-size: 13px;

View File

@ -55,7 +55,8 @@ export default class JsonSchemaLazy {
let $element = compRef.location.nativeElement; let $element = compRef.location.nativeElement;
// skip caching view with tabs inside (discriminator) as it needs attached controller // skip caching view with tabs inside (discriminator) as it needs attached controller
if ($element.querySelector('tabs')) { // FIXME: get rid of dependency on selector
if ($element.querySelector('.discriminator-wrap')) {
this.dcl.loadNextToLocation(JsonSchema, this.elementRef).then((compRef) => { this.dcl.loadNextToLocation(JsonSchema, this.elementRef).then((compRef) => {
compRef.instance.pointer = this.pointer; compRef.instance.pointer = this.pointer;
compRef.hostView.changeDetectorRef.markForCheck(); compRef.hostView.changeDetectorRef.markForCheck();

View File

@ -1,10 +1,17 @@
<span *ngIf="isTrivial" class="param-wrap"> <span *ngIf="schema.isTrivial" class="param-wrap">
<span class="param-type param-type-trivial {{type}}" <span class="param-type param-type-trivial {{schema.type}}"
[ngClass]="{'with-hint': _displayTypeHint}" title="{{_displayTypeHint}}">{{_displayType}}</span> [ngClass]="{'with-hint': schema._displayTypeHint}"
title="{{schema._displayTypeHint}}">{{schema._displayType}} {{schema._displayFormat}}
<span class="param-range" *ngIf="schema._range"> {{schema._range}} </span>
</span> </span>
<table *ngIf="!isTrivial" class="params-wrap" [ngClass]="{'params-array': isArray}"> <div *ngIf="schema.enum" class="param-enum">
<template ngFor [ngForOf]="data.properties" #prop="$implicit"> <span *ngFor="#enumItem of schema.enum" class="enum-value {{enumItem.type}}"> {{enumItem.val | json}} </span>
<tr class="param" [ngClass]="{'discriminator': prop.isDiscriminator, 'complex': prop._pointer}"> </div>
</span>
<table *ngIf="!schema.isTrivial" class="params-wrap" [ngClass]="{'params-array': schema._isArray}">
<caption> {{_displayType}} </caption>
<template ngFor [ngForOf]="schema.properties" #prop="$implicit" #last="last">
<tr class="param" [ngClass]="{'last': last, 'discriminator': prop.isDiscriminator, 'complex': prop._pointer}">
<td class="param-name"> <td class="param-name">
<span class="param-name-content">{{prop._name}}</span> <span class="param-name-content">{{prop._name}}</span>
</td> </td>
@ -23,22 +30,22 @@
<div class="discriminator-info" *ngIf="prop.isDiscriminator"> <div class="discriminator-info" *ngIf="prop.isDiscriminator">
This field value determines the exact schema: This field value determines the exact schema:
<ul> <ul>
<li *ngFor="#derived of data.derived" <li *ngFor="#derived of schema.derived"
(click)="selectDerived(derived)" [ngClass]="{active: derived.active}"> {{derived.name}} </li> (click)="selectDerived(derived)" [ngClass]="{active: derived.active}"> {{derived.name}} </li>
</ul> </ul>
</div> </div>
</td> </td>
</tr> </tr>
<tr class="param-schema" [ngClass]="{'param-array': prop._isArray}" *ngIf="prop._pointer"> <tr class="param-schema" [ngClass]="{'param-array': prop._isArray, 'last': last}" *ngIf="prop._pointer">
<td colspan="2"> <td colspan="2">
<json-schema pointer="{{prop._pointer}}" [isArray]='prop._isArray' class="nested-schema"> <json-schema pointer="{{prop._pointer}}" [isArray]='prop._isArray' class="nested-schema">
</json-schema> </json-schema>
</td> </td>
</tr> </tr>
</template> </template>
<tr *ngIf="data.derived.length" class="param-wrap discriminator-wrap"> <tr *ngIf="schema.derived.length" class="param-wrap discriminator-wrap">
<td colspan="2"> <td colspan="2">
<div class="derived-schema" *ngFor="#derived of data.derived" [ngClass]="{active: derived.active}"> <div class="derived-schema" *ngFor="#derived of schema.derived" [ngClass]="{active: derived.active}">
<json-schema pointer="{{derived.$ref}}" [final]="derived.final" class="discriminator-part"> <json-schema pointer="{{derived.$ref}}" [final]="derived.final" class="discriminator-part">
</json-schema> </json-schema>
</div> </div>

View File

@ -23,67 +23,58 @@ export default class JsonSchema extends BaseComponent {
selectDerived(subClass) { selectDerived(subClass) {
if (subClass.active) return; if (subClass.active) return;
this.data.derived.forEach((subSchema) => { this.schema.derived.forEach((subSchema) => {
subSchema.active = false; subSchema.active = false;
}); });
subClass.active = true; subClass.active = true;
} }
prepareModel() { unwrapArray(schema) {
this.data = {}; var res = schema;
this.data.properties = []; if (schema && schema.type === 'array') {
this.data.derived = []; let ptr = schema.items._pointer
|| JsonPointer.join(schema._pointer || this.pointer, ['items']);
res = schema.items;
res._isArray = true;
res._pointer = ptr;
res = this.unwrapArray(res);
}
return res;
}
prepareModel() {
if (!this.componentSchema) { if (!this.componentSchema) {
throw new Error(`Can't load component schema at ${this.pointer}`); throw new Error(`Can't load component schema at ${this.pointer}`);
} }
this.dereference(); this.dereference();
let schema = this.componentSchema; let schema = this.componentSchema;
BaseComponent.joinAllOf(schema, {omitParent: true});
schema = this.unwrapArray(schema);
runInjectors(schema, schema, schema._pointer || this.pointer);
if (schema.type === 'array') { schema.derived = schema.derived || [];
this.isArray = true; if (schema.derived.length) schema.derived[0].active = true;
if (schema._pointer) {
this.pointer = JsonPointer.join(schema._pointer, 'items'); if (!schema.isTrivial) {
} this.prepareObjectPropertiesData(schema);
schema = schema.items;
}
let normPtr = schema._pointer || this.pointer;
let derived = this.schemaMgr.findDerivedDefinitions( normPtr );
if (!this.final && derived.length) {
derived[0].active = true;
this.data.derived = derived;
this.data.discriminator = schema.discriminator;
} }
this.joinAllOf(schema, {omitParent: true}); this.schema = schema;
if (schema.type !== 'object') {
this.isTrivial = true;
this._displayType = schema.type;
if (schema.format) this._displayType = `${this.displayType} <${schema.format}>`;
return;
} }
this.pointer = schema._pointer || this.pointer; prepareObjectPropertiesData(schema) {
let requiredMap = {};
this.requiredMap = {}; if (schema.required) {
if (this.componentSchema.required) { schema.required.forEach(prop => requiredMap[prop] = true);
this.componentSchema.required.forEach(prop => this.requiredMap[prop] = true);
}
if (!schema.properties) {
this.isTrivial = true;
this._displayType = schema.type;
return;
} }
let discriminatorFieldIdx = -1; let discriminatorFieldIdx = -1;
let props = Object.keys(schema.properties).map((prop, idx) => { let props = Object.keys(schema.properties).map((prop, idx) => {
let propertySchema = schema.properties[prop]; let propertySchema = schema.properties[prop];
let propPointer = JsonPointer.join(this.pointer, ['properties', prop]); let propPointer = JsonPointer.join(schema._pointer || this.pointer, ['properties', prop]);
propertySchema = JsonSchema.injectPropertyData(propertySchema, prop, propPointer); propertySchema = JsonSchema.injectPropertyData(propertySchema, prop, propPointer);
propertySchema.required = !!this.requiredMap[prop]; propertySchema.required = !!requiredMap[prop];
propertySchema.isDiscriminator = (schema.discriminator === prop); propertySchema.isDiscriminator = (schema.discriminator === prop);
if (propertySchema.isDiscriminator) discriminatorFieldIdx = idx; if (propertySchema.isDiscriminator) discriminatorFieldIdx = idx;
return propertySchema; return propertySchema;
@ -93,7 +84,7 @@ export default class JsonSchema extends BaseComponent {
let discrProp = props.splice(discriminatorFieldIdx, 1); let discrProp = props.splice(discriminatorFieldIdx, 1);
props.push(discrProp[0]); props.push(discrProp[0]);
} }
this.data.properties = props; schema.properties = props;
} }
static injectPropertyData(propertySchema, propertyName, propPointer) { static injectPropertyData(propertySchema, propertyName, propPointer) {
@ -118,7 +109,8 @@ function runInjectors(injectTo, propertySchema, propertyPointer) {
const injectors = { const injectors = {
general: { general: {
check: () => true, check: () => true,
inject: (injectTo, propertySchema) => { inject: (injectTo, propertySchema, pointer) => {
injectTo._pointer = propertySchema._pointer || pointer;
injectTo._displayType = propertySchema.type; injectTo._displayType = propertySchema.type;
if (propertySchema.format) injectTo._displayFormat = `<${propertySchema.format}>`; if (propertySchema.format) injectTo._displayFormat = `<${propertySchema.format}>`;
if (propertySchema.enum) { if (propertySchema.enum) {
@ -128,7 +120,13 @@ const injectors = {
} }
} }
}, },
discriminator: {
check: (propertySchema) => propertySchema.discriminator,
inject: (injectTo, propertySchema = injectTo, pointer) => {
injectTo.derived = SchemaManager.instance().findDerivedDefinitions(pointer);
injectTo.discriminator = propertySchema.discriminator;
}
},
array: { array: {
check: (propertySchema) => { check: (propertySchema) => {
return propertySchema.type === 'array'; return propertySchema.type === 'array';
@ -147,7 +145,8 @@ const injectors = {
return propertySchema.type === 'object' && propertySchema.properties; return propertySchema.type === 'object' && propertySchema.properties;
}, },
inject: (injectTo, propertySchema = injectTo) => { inject: (injectTo, propertySchema = injectTo) => {
injectTo._displayType = propertySchema.title || 'object'; let baseName = propertySchema._pointer && JsonPointer.baseName(propertySchema._pointer);
injectTo._displayType = propertySchema.title || baseName || 'object';
} }
}, },
noType: { noType: {
@ -155,6 +154,7 @@ const injectors = {
inject: (injectTo) => { inject: (injectTo) => {
injectTo._displayType = '< * >'; injectTo._displayType = '< * >';
injectTo._displayTypeHint = 'This field may contain data of any type'; injectTo._displayTypeHint = 'This field may contain data of any type';
injectTo.isTrivial = true;
} }
}, },
@ -166,6 +166,7 @@ const injectors = {
return (propertySchema.type !== 'array') && propertySchema.type; return (propertySchema.type !== 'array') && propertySchema.type;
}, },
inject: (injectTo, propertySchema = injectTo) => { inject: (injectTo, propertySchema = injectTo) => {
injectTo.isTrivial = true;
if (injectTo._pointer) { if (injectTo._pointer) {
injectTo._pointer = undefined; injectTo._pointer = undefined;
injectTo._displayType = propertySchema.title ? injectTo._displayType = propertySchema.title ?

View File

@ -118,8 +118,20 @@ json-schema.nested-schema {
} }
.discriminator-wrap > td { .discriminator-wrap > td {
border-left: $line-border; //border-left: $line-border;
padding: 0; padding: 0;
position: relative;
&:before {
content: "";
display: block;
position: absolute;
left: 0;
top: 0;
border-left: $line-border;
height: ($param-name-height/2) + $cell-padding + 1;
z-index: 1;
}
} }
ul { ul {

View File

@ -48,7 +48,7 @@ describe('Redoc components', () => {
component.pointer = ''; component.pointer = '';
schemaMgr._schema = {type: 'string'}; schemaMgr._schema = {type: 'string'};
fixture.detectChanges(); fixture.detectChanges();
component.isTrivial.should.be.true(); component.schema.isTrivial.should.be.true();
}); });
it('should use < * > notation for prop without type', () => { it('should use < * > notation for prop without type', () => {
@ -57,7 +57,7 @@ describe('Redoc components', () => {
test: {} test: {}
}}; }};
fixture.detectChanges(); fixture.detectChanges();
component.data.properties[0]._displayType.should.be.equal('< * >'); component.schema.properties[0]._displayType.should.be.equal('< * >');
}); });
}); });
}); });

View File

@ -98,7 +98,6 @@ export function RedocComponent(options) {
export class BaseComponent { export class BaseComponent {
constructor(schemaMgr) { constructor(schemaMgr) {
this.schemaMgr = schemaMgr; this.schemaMgr = schemaMgr;
this.schema = schemaMgr.schema;
this.componentSchema = null; this.componentSchema = null;
} }
@ -167,10 +166,10 @@ export class BaseComponent {
return schema; return schema;
}; };
this.componentSchema = resolve(schema, 1); this.componentSchema = snapshot(resolve(schema, 1));
} }
joinAllOf(schema = this.componentSchema, opts) { static joinAllOf(schema, opts) {
function merge(into, schemas) { function merge(into, schemas) {
for (let subSchema of schemas) { for (let subSchema of schemas) {
if (opts && opts.omitParent && subSchema.discriminator) continue; if (opts && opts.omitParent && subSchema.discriminator) continue;
@ -181,21 +180,23 @@ export class BaseComponent {
throw new Error(errMessage); throw new Error(errMessage);
} }
if (into.type && into.type !== subSchema.type) { if (into.type && subSchema.type && into.type !== subSchema.type) {
let errMessage = `allOf merging error: schemas with different types can't be merged`; let errMessage = `allOf merging error: schemas with different types can't be merged`;
throw new Error(errMessage); throw new Error(errMessage);
} }
into.type = into.type || subSchema.type;
if (into.type === 'array') { if (into.type === 'array') {
console.warn('allOf: subschemas with type array are not supported yet'); 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.type === 'object' && subSchema.properties) { if (into.type === 'object' && subSchema.properties) {
into.properties || (into.properties = {}); into.properties || (into.properties = {});
Object.assign(into.properties, subSchema.properties); Object.assign(into.properties, subSchema.properties);
} }
if (subSchema.type === 'object' && subSchema.required) { if (into.type === 'object' && subSchema.required) {
into.required || (into.required = []); into.required || (into.required = []);
into.required.push(...subSchema.required); into.required.push(...subSchema.required);
} }

View File

@ -19,7 +19,7 @@ describe('Redoc components', () => {
it('should set instance properties', () => { it('should set instance properties', () => {
component.schemaMgr.should.be.equal(schemaMgr); component.schemaMgr.should.be.equal(schemaMgr);
component.schema.should.be.of.type('object'); //component.schema.should.be.of.type('object');
expect(component.componentSchema).toBeNull(); expect(component.componentSchema).toBeNull();
}); });
@ -165,7 +165,7 @@ describe('Redoc components', () => {
component.pointer = '/definitions/SimpleAllOf'; component.pointer = '/definitions/SimpleAllOf';
component.ngOnInit(); component.ngOnInit();
component.dereference(); component.dereference();
component.joinAllOf(); BaseComponent.joinAllOf(component.componentSchema);
joined = component.componentSchema; joined = component.componentSchema;
}); });
@ -194,7 +194,7 @@ describe('Redoc components', () => {
component.pointer = '/definitions/AllOfWithRef'; component.pointer = '/definitions/AllOfWithRef';
component.ngOnInit(); component.ngOnInit();
component.dereference(); component.dereference();
component.joinAllOf(); BaseComponent.joinAllOf(component.componentSchema);
joined = component.componentSchema; joined = component.componentSchema;
}); });
@ -223,7 +223,7 @@ describe('Redoc components', () => {
component.pointer = '/definitions/AllOfWithOther'; component.pointer = '/definitions/AllOfWithOther';
component.ngOnInit(); component.ngOnInit();
component.dereference(); component.dereference();
component.joinAllOf(); BaseComponent.joinAllOf(component.componentSchema);
joined = component.componentSchema; joined = component.componentSchema;
}); });
@ -256,7 +256,7 @@ describe('Redoc components', () => {
component.pointer = '/definitions/PropertiesOnAllOfLevel'; component.pointer = '/definitions/PropertiesOnAllOfLevel';
component.ngOnInit(); component.ngOnInit();
component.dereference(); component.dereference();
(() => component.joinAllOf()).should.not.throw(); (() => BaseComponent.joinAllOf(component.componentSchema)).should.not.throw();
let joined = component.componentSchema; let joined = component.componentSchema;
Object.keys(joined.properties).length.should.be.equal(3); Object.keys(joined.properties).length.should.be.equal(3);
}); });
@ -265,14 +265,14 @@ describe('Redoc components', () => {
component.pointer = '/definitions/BadAllOf1'; component.pointer = '/definitions/BadAllOf1';
component.ngOnInit(); component.ngOnInit();
component.dereference(); component.dereference();
(() => component.joinAllOf()).should.throw(); (() => BaseComponent.joinAllOf(component.componentSchema)).should.throw();
}); });
it('should handle nested allOF', () => { it('should handle nested allOF', () => {
component.pointer = '/definitions/NestedAllOf'; component.pointer = '/definitions/NestedAllOf';
component.ngOnInit(); component.ngOnInit();
component.dereference(); component.dereference();
(() => component.joinAllOf()).should.not.throw(); (() => BaseComponent.joinAllOf(component.componentSchema)).should.not.throw();
let joined = component.componentSchema; let joined = component.componentSchema;
Object.keys(joined.properties).length.should.be.equal(4); Object.keys(joined.properties).length.should.be.equal(4);
Object.keys(joined.properties).should.be.deepEqual(['prop1', 'prop2', 'prop3', 'prop4']); Object.keys(joined.properties).should.be.deepEqual(['prop1', 'prop2', 'prop3', 'prop4']);

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.6", "version": "0.7.7",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/Rebilly/ReDoc" "url": "git://github.com/Rebilly/ReDoc"