mirror of
https://github.com/Redocly/redoc.git
synced 2025-01-31 10:04:08 +03:00
Refactor + perf optimizations
This commit is contained in:
parent
9341d4904e
commit
1e97ea655e
|
@ -59,9 +59,10 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
|||
setTimeout( ()=> {
|
||||
let $element = compRef.location.nativeElement;
|
||||
|
||||
// skip caching view with tabs inside (discriminator) as it needs attached controller
|
||||
// FIXME: get rid of dependency on selector
|
||||
if ($element.querySelector('.discriminator-wrap')) {
|
||||
// skip caching view with tabs inside (discriminator)
|
||||
// as it needs attached controller
|
||||
if (compRef.instance.hasDiscriminator) {
|
||||
this._loadAfterSelf();
|
||||
return;
|
||||
}
|
||||
insertAfter($element.cloneNode(true), this.elementRef.nativeElement);
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
</span>
|
||||
<table *ngIf="!schema.isTrivial" class="params-wrap" [ngClass]="{'params-array': schema._isArray}">
|
||||
<!-- <caption> {{_displayType}} </caption> -->
|
||||
<template ngFor [ngForOf]="schema.properties" let-prop="$implicit" let-last="last">
|
||||
<template ngFor [ngForOf]="schema._properties" let-prop="$implicit" let-last="last">
|
||||
<tr class="param" [ngClass]="{'last': last,
|
||||
'discriminator': prop.isDiscriminator && !derivedEmtpy,
|
||||
'complex': prop._pointer,
|
||||
|
@ -40,11 +40,11 @@
|
|||
<span *ngFor="let enumItem of prop.enum" class="enum-value {{enumItem.type}}"> {{enumItem.val | json}} </span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="param-description" innerHtml="{{prop.description | marked}}"></div>
|
||||
<div class="param-description" [innerHtml]="prop.description | marked"></div>
|
||||
<div class="discriminator-info" *ngIf="prop.isDiscriminator">
|
||||
<span>This field value determines the exact schema:</span>
|
||||
<drop-down (change)="selectDerived($event)">
|
||||
<option *ngFor="let derived of schema.derived; let i=index"
|
||||
<option *ngFor="let derived of schema._derived; let i=index"
|
||||
[value]="i">{{derived.name}}</option>
|
||||
</drop-down>
|
||||
</div>
|
||||
|
@ -58,9 +58,9 @@
|
|||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr *ngIf="schema.derived.length" class="param-wrap discriminator-wrap" [ngClass]="{'empty': derivedEmtpy}">
|
||||
<tr *ngIf="schema._derived.length" class="param-wrap discriminator-wrap" [ngClass]="{'empty': derivedEmtpy}">
|
||||
<td colspan="2">
|
||||
<div class="derived-schema" *ngFor="let derived of schema.derived" [ngClass]="{active: derived.active}">
|
||||
<div class="derived-schema" *ngFor="let derived of schema._derived" [ngClass]="{active: derived.active}">
|
||||
<json-schema class="discriminator-part" *ngIf="!derived.empty" [childFor]="pointer"
|
||||
pointer="{{derived.$ref}}" [final]="derived.final" [isRequestSchema]="isRequestSchema">
|
||||
</json-schema>
|
||||
|
|
|
@ -56,7 +56,7 @@ describe('Redoc components', () => {
|
|||
test: {}
|
||||
}};
|
||||
fixture.detectChanges();
|
||||
component.schema.properties[0]._displayType.should.be.equal('< * >');
|
||||
component.schema._properties[0]._displayType.should.be.equal('< * >');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@ import { ElementRef, Input } from '@angular/core';
|
|||
import { RedocComponent, BaseComponent, SchemaManager } from '../base';
|
||||
import { DropDown } from '../../shared/components/index';
|
||||
import JsonPointer from '../../utils/JsonPointer';
|
||||
import { SchemaNormalizator } from '../../services/spec-helper.service';
|
||||
|
||||
@RedocComponent({
|
||||
selector: 'json-schema',
|
||||
|
@ -17,11 +18,13 @@ export class JsonSchema extends BaseComponent {
|
|||
$element: any;
|
||||
schema: any;
|
||||
derivedEmtpy: boolean;
|
||||
hasDiscriminator: boolean = false;
|
||||
@Input() isArray: boolean;
|
||||
@Input() final: boolean = false;
|
||||
@Input() nestOdd: boolean;
|
||||
@Input() childFor: string;
|
||||
@Input() isRequestSchema: boolean;
|
||||
normalizer: SchemaNormalizator;
|
||||
|
||||
static injectPropertyData(propertySchema, propertyName, propPointer, hostPointer?) {
|
||||
propertySchema = Object.assign({}, propertySchema);
|
||||
|
@ -35,12 +38,13 @@ export class JsonSchema extends BaseComponent {
|
|||
constructor(schemaMgr:SchemaManager, elementRef:ElementRef) {
|
||||
super(schemaMgr);
|
||||
this.$element = elementRef.nativeElement;
|
||||
this.normalizer = new SchemaNormalizator(schemaMgr);
|
||||
}
|
||||
|
||||
selectDerived(subClassIdx) {
|
||||
let subClass = this.schema.derived[subClassIdx];
|
||||
let subClass = this.schema._derived[subClassIdx];
|
||||
if (!subClass || subClass.active) return;
|
||||
this.schema.derived.forEach((subSchema) => {
|
||||
this.schema._derived.forEach((subSchema) => {
|
||||
subSchema.active = false;
|
||||
});
|
||||
subClass.active = true;
|
||||
|
@ -65,14 +69,18 @@ export class JsonSchema extends BaseComponent {
|
|||
if (!this.componentSchema) {
|
||||
throw new Error(`Can't load component schema at ${this.pointer}`);
|
||||
}
|
||||
this.dereference();
|
||||
if (this.componentSchema['x-redoc-js-precompiled']) {
|
||||
this.schema = this.unwrapArray(this.componentSchema);
|
||||
return;
|
||||
}
|
||||
this.componentSchema = this.normalizer.normalize(this.componentSchema, this.pointer);
|
||||
this.componentSchema['x-redoc-js-precompiled'] = true;
|
||||
|
||||
let schema = this.componentSchema;
|
||||
BaseComponent.joinAllOf(schema, {omitParent: true});
|
||||
this.schema = schema = this.unwrapArray(schema);
|
||||
runInjectors(schema, schema, schema._pointer || this.pointer, this.pointer);
|
||||
|
||||
schema.derived = schema.derived || [];
|
||||
this.schema._derived = this.schema._derived || [];
|
||||
|
||||
if (!schema.isTrivial) {
|
||||
this.prepareObjectPropertiesData(schema);
|
||||
|
@ -82,16 +90,17 @@ export class JsonSchema extends BaseComponent {
|
|||
}
|
||||
|
||||
initDerived() {
|
||||
if (!this.schema.derived.length) return;
|
||||
let enumArr = this.schema.properties[this.schema.properties.length - 1].enum;
|
||||
if (!this.schema._derived.length) return;
|
||||
this.hasDiscriminator = true;
|
||||
let enumArr = this.schema._properties[this.schema._properties.length - 1].enum;
|
||||
if (enumArr) {
|
||||
let enumOrder = {};
|
||||
enumArr.forEach((enumItem, idx) => {
|
||||
enumOrder[enumItem.val] = idx;
|
||||
});
|
||||
|
||||
this.schema.derived.sort((a, b) => {
|
||||
return enumOrder[a.name] > enumOrder[b.name];
|
||||
this.schema._derived.sort((a, b) => {
|
||||
return enumOrder[a.name] > enumOrder[b.name] ? 1 : -1;
|
||||
});
|
||||
}
|
||||
this.selectDerived(0);
|
||||
|
@ -138,7 +147,7 @@ export class JsonSchema extends BaseComponent {
|
|||
if (this.isRequestSchema) {
|
||||
props = props.filter(prop => !prop.readOnly);
|
||||
}
|
||||
schema.properties = props;
|
||||
schema._properties = props;
|
||||
}
|
||||
|
||||
prepareAdditionalProperties(schema) {
|
||||
|
@ -165,7 +174,7 @@ const injectors = {
|
|||
discriminator: {
|
||||
check: (propertySchema) => propertySchema.discriminator,
|
||||
inject: (injectTo, propertySchema = injectTo, pointer) => {
|
||||
injectTo.derived = SchemaManager.instance().findDerivedDefinitions(pointer);
|
||||
injectTo._derived = SchemaManager.instance().findDerivedDefinitions(pointer);
|
||||
injectTo.discriminator = propertySchema.discriminator;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
import { provide, enableProdMode, ElementRef,
|
||||
ComponentRef, AfterViewInit } from '@angular/core';
|
||||
import {enableProdMode as compilerProd} from '@angular/compiler/src/facade/lang';
|
||||
import {enableProdMode as browserProd } from '@angular/platform-browser/src/facade/lang';
|
||||
import {CompilerConfig} from '@angular/compiler';
|
||||
import { bootstrap } from '@angular/platform-browser-dynamic';
|
||||
import { BrowserDomAdapter } from '@angular/platform-browser/src/browser/browser_adapter';
|
||||
import { RedocComponent, BaseComponent } from '../base';
|
||||
|
@ -60,7 +63,8 @@ export class Redoc extends BaseComponent implements AfterViewInit {
|
|||
optionsService.options = options;
|
||||
optionsService.options.specUrl = optionsService.options.specUrl || specUrl;
|
||||
var providers = [
|
||||
provide(OptionsService, {useValue: optionsService})
|
||||
provide(OptionsService, {useValue: optionsService}),
|
||||
provide(CompilerConfig, {useValue: new CompilerConfig({genDebugInfo: false, logBindingUpdate: false})})
|
||||
];
|
||||
|
||||
if (Redoc.appRef) {
|
||||
|
@ -71,6 +75,8 @@ export class Redoc extends BaseComponent implements AfterViewInit {
|
|||
.then(() => {
|
||||
if (!_modeLocked && !optionsService.options.debugMode) {
|
||||
enableProdMode();
|
||||
compilerProd();
|
||||
browserProd();
|
||||
_modeLocked = true;
|
||||
}
|
||||
return bootstrap(Redoc, providers);
|
||||
|
|
|
@ -6,6 +6,7 @@ import * as OpenAPISampler from 'openapi-sampler';
|
|||
|
||||
import { RedocComponent, BaseComponent, SchemaManager } from '../base';
|
||||
import { JsonFormatter } from '../../utils/JsonFormatterPipe';
|
||||
import { SchemaNormalizator } from '../../services/spec-helper.service';
|
||||
|
||||
@RedocComponent({
|
||||
selector: 'schema-sample',
|
||||
|
@ -17,12 +18,17 @@ export class SchemaSample extends BaseComponent {
|
|||
element: any;
|
||||
data: any;
|
||||
@Input() skipReadOnly:boolean;
|
||||
|
||||
private _normalizer:SchemaNormalizator;
|
||||
|
||||
constructor(schemaMgr:SchemaManager, elementRef:ElementRef) {
|
||||
super(schemaMgr);
|
||||
this.element = elementRef.nativeElement;
|
||||
this._normalizer = new SchemaNormalizator(schemaMgr);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.bindEvents();
|
||||
this.data = {};
|
||||
|
||||
let base:any = {};
|
||||
|
@ -37,7 +43,10 @@ export class SchemaSample extends BaseComponent {
|
|||
if (base.examples && base.examples['application/json']) {
|
||||
sample = base.examples['application/json'];
|
||||
} else {
|
||||
this.dereference(this.componentSchema);
|
||||
this.componentSchema = this._normalizer.normalize(this.componentSchema, this.pointer);
|
||||
if (this.fromCache()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sample = OpenAPISampler.sample(this.componentSchema, {
|
||||
skipReadOnly: this.skipReadOnly
|
||||
|
@ -46,10 +55,30 @@ export class SchemaSample extends BaseComponent {
|
|||
// no sample available
|
||||
}
|
||||
}
|
||||
|
||||
this.cache(sample);
|
||||
this.data.sample = sample;
|
||||
}
|
||||
|
||||
cache(sample) {
|
||||
if (this.skipReadOnly) {
|
||||
this.componentSchema['x-redoc-ro-sample'] = sample;
|
||||
} else {
|
||||
this.componentSchema['x-redoc-rw-sample'] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
fromCache() {
|
||||
if (this.skipReadOnly && this.componentSchema['x-redoc-ro-sample']) {
|
||||
this.data.sample = this.componentSchema['x-redoc-ro-sample'];
|
||||
return true;
|
||||
} else if (this.componentSchema['x-redoc-rw-sample']) {
|
||||
this.data.sample = this.componentSchema['x-redoc-rw-sample'];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.element.addEventListener('click', (event) => {
|
||||
var collapsed, target = event.target;
|
||||
if (event.target.className === 'collapser') {
|
||||
|
|
|
@ -39,253 +39,5 @@ describe('Redoc components', () => {
|
|||
component.prepareModel.and.callThrough();
|
||||
component.init.and.callThrough();
|
||||
});
|
||||
|
||||
describe('dereference', () => {
|
||||
beforeAll((done) => {
|
||||
schemaMgr.load('/tests/schemas/base-component-dereference.json').then(
|
||||
() => done()
|
||||
);
|
||||
});
|
||||
|
||||
describe('simple dereference', () => {
|
||||
let paramWithRef;
|
||||
beforeAll(() => {
|
||||
component.pointer = '/paths/test1/get';
|
||||
component.ngOnInit();
|
||||
component.dereference();
|
||||
paramWithRef = component.componentSchema.parameters[0];
|
||||
});
|
||||
|
||||
it('should not contain $ref property', () => {
|
||||
expect(paramWithRef.$ref).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should inject Title if not exist based on reference', () => {
|
||||
paramWithRef.title.should.be.equal('Simple');
|
||||
});
|
||||
|
||||
it('should inject pointer', () => {
|
||||
paramWithRef._pointer.should.be.equal('#/definitions/Simple');
|
||||
});
|
||||
|
||||
it('should insert correct definition instead of reference', () => {
|
||||
delete paramWithRef.title;
|
||||
delete paramWithRef._pointer;
|
||||
paramWithRef.should.be.deepEqual(schemaMgr.schema.definitions.Simple);
|
||||
});
|
||||
});
|
||||
|
||||
describe('nested dereference', () => {
|
||||
let paramWithRef;
|
||||
beforeAll(() => {
|
||||
component.pointer = '/paths/test2/get';
|
||||
component.ngOnInit();
|
||||
component.dereference();
|
||||
paramWithRef = component.componentSchema.parameters[0];
|
||||
});
|
||||
|
||||
it('should not touch title if exist', () => {
|
||||
paramWithRef.title.should.be.equal('NesteTitle');
|
||||
});
|
||||
|
||||
it('should resolve nested schema', () => {
|
||||
expect(paramWithRef.properties.subschema.$ref).toBeUndefined();
|
||||
paramWithRef._pointer.should.be.equal('#/definitions/Nested');
|
||||
paramWithRef.properties.subschema._pointer.should.be.equal('#/definitions/Simple');
|
||||
paramWithRef.properties.subschema.type.should.be.equal('object');
|
||||
});
|
||||
});
|
||||
|
||||
describe('array schema dereference', () => {
|
||||
let paramWithRef;
|
||||
beforeAll(() => {
|
||||
component.pointer = '/paths/test3/get';
|
||||
component.ngOnInit();
|
||||
component.dereference();
|
||||
paramWithRef = component.componentSchema.parameters[0];
|
||||
});
|
||||
|
||||
it('should resolve array schema', () => {
|
||||
expect(paramWithRef.$ref).toBeUndefined();
|
||||
expect(paramWithRef.items.schema.$ref).toBeUndefined();
|
||||
paramWithRef.type.should.be.equal('array');
|
||||
paramWithRef._pointer.should.be.equal('#/definitions/ArrayOfSimple');
|
||||
paramWithRef.items.schema._pointer.should.be.equal('#/definitions/Simple');
|
||||
paramWithRef.items.schema.type.should.be.equal('object');
|
||||
});
|
||||
});
|
||||
|
||||
describe('circular dereference', () => {
|
||||
let paramWithRef;
|
||||
beforeAll(() => {
|
||||
component.pointer = '/paths/test4/get';
|
||||
component.ngOnInit();
|
||||
component.dereference();
|
||||
paramWithRef = component.componentSchema.parameters[0];
|
||||
});
|
||||
|
||||
it('should resolve circular schema', () => {
|
||||
expect(paramWithRef.$ref).toBeUndefined();
|
||||
expect(paramWithRef.items.schema.$ref).toBeUndefined();
|
||||
paramWithRef.type.should.be.equal('array');
|
||||
paramWithRef._pointer.should.be.equal('#/definitions/Circular');
|
||||
expect(paramWithRef.items.schema._pointer).toBeUndefined();
|
||||
paramWithRef.items.schema.title.should.be.equal('Circular');
|
||||
});
|
||||
});
|
||||
|
||||
describe('$ref with other fields on the same level', () => {
|
||||
let paramWithRef;
|
||||
beforeAll(() => {
|
||||
component.pointer = '/paths/test5/get';
|
||||
component.ngOnInit();
|
||||
component.dereference();
|
||||
paramWithRef = component.componentSchema.parameters[0];
|
||||
});
|
||||
|
||||
it('should skip other fields', () => {
|
||||
expect(paramWithRef.$ref).toBeUndefined();
|
||||
expect(paramWithRef.title).toBeDefined();
|
||||
paramWithRef.title.should.be.equal('Simple');
|
||||
});
|
||||
|
||||
it('should preserve description field', () => {
|
||||
expect(paramWithRef.$ref).toBeUndefined();
|
||||
expect(paramWithRef.description).toBeDefined();
|
||||
paramWithRef.description.should.be.equal('test');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergeAllOf', () => {
|
||||
beforeAll((done) => {
|
||||
schemaMgr.load('tests/schemas/base-component-joinallof.json').then(() => done());
|
||||
});
|
||||
|
||||
describe('Simple allOf merge', () => {
|
||||
let joined;
|
||||
beforeAll(() => {
|
||||
component.pointer = '/definitions/SimpleAllOf';
|
||||
component.ngOnInit();
|
||||
component.dereference();
|
||||
BaseComponent.joinAllOf(component.componentSchema);
|
||||
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(3);
|
||||
Object.keys(joined.properties).should.be.deepEqual(['prop1', 'prop2', 'prop3']);
|
||||
});
|
||||
|
||||
it('should merge required', () => {
|
||||
joined.required.length.should.be.equal(2);
|
||||
joined.required.should.be.deepEqual(['prop1', 'prop3']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AllOf with refrence', () => {
|
||||
let joined;
|
||||
beforeAll(() => {
|
||||
component.pointer = '/definitions/AllOfWithRef';
|
||||
component.ngOnInit();
|
||||
component.dereference();
|
||||
BaseComponent.joinAllOf(component.componentSchema);
|
||||
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(2);
|
||||
Object.keys(joined.properties).should.be.deepEqual(['id', 'prop3']);
|
||||
});
|
||||
|
||||
it('should merge required', () => {
|
||||
joined.required.length.should.be.equal(2);
|
||||
joined.required.should.be.deepEqual(['id', 'prop3']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AllOf with other properties on the allOf level', () => {
|
||||
let joined;
|
||||
beforeAll(() => {
|
||||
component.pointer = '/definitions/AllOfWithOther';
|
||||
component.ngOnInit();
|
||||
component.dereference();
|
||||
BaseComponent.joinAllOf(component.componentSchema);
|
||||
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();
|
||||
(() => BaseComponent.joinAllOf(component.componentSchema)).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.dereference();
|
||||
(() => BaseComponent.joinAllOf(component.componentSchema)).should.throw();
|
||||
});
|
||||
|
||||
it('should handle nested allOF', () => {
|
||||
component.pointer = '/definitions/NestedAllOf';
|
||||
component.ngOnInit();
|
||||
component.dereference();
|
||||
(() => BaseComponent.joinAllOf(component.componentSchema)).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']);
|
||||
});
|
||||
});
|
||||
|
||||
xdescribe('Merge array allOf', () => {
|
||||
//emtpy
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CORE_DIRECTIVES, JsonPipe, AsyncPipe } from '@angular/common';
|
||||
import { SchemaManager } from '../utils/SchemaManager';
|
||||
import JsonPointer from '../utils/JsonPointer';
|
||||
import { MarkedPipe, JsonPointerEscapePipe } from '../utils/pipes';
|
||||
|
||||
export { SchemaManager };
|
||||
|
@ -17,21 +16,6 @@ function safeConcat(a, 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) {
|
||||
if(obj == undefined || typeof(obj) !== 'object') {
|
||||
return obj;
|
||||
|
@ -98,69 +82,7 @@ export function RedocComponent(options) {
|
|||
export class BaseComponent implements OnInit, OnDestroy {
|
||||
componentSchema: any = null;
|
||||
pointer: String;
|
||||
|
||||
static joinAllOf(schema: any, opts?: any) {
|
||||
function merge(into, schemas) {
|
||||
for (let subSchema of schemas) {
|
||||
if (opts && opts.omitParent && subSchema.discriminator) continue;
|
||||
// TODO: add support for merge array schemas
|
||||
if (typeof subSchema !== 'object') {
|
||||
let errMessage = `Items of allOf should be Object: ${typeof subSchema} found
|
||||
${subSchema}`;
|
||||
throw new Error(errMessage);
|
||||
}
|
||||
|
||||
if (into.type && subSchema.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)
|
||||
into.type = into.type || subSchema.type;
|
||||
if (into.type === 'object' && subSchema.properties) {
|
||||
if (!into.properties) into.properties = {};
|
||||
Object.assign(into.properties, subSchema.properties);
|
||||
Object.keys(subSchema.properties).forEach(propName => {
|
||||
if (!subSchema.properties[propName]._pointer) {
|
||||
subSchema.properties[propName]._pointer = subSchema._pointer ?
|
||||
JsonPointer.join(subSchema._pointer, ['properties', propName]) : null;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (into.type === 'object' && subSchema.required) {
|
||||
if (!into.required) into.required = [];
|
||||
into.required.push(...subSchema.required);
|
||||
}
|
||||
// don't merge _pointer
|
||||
subSchema._pointer = null;
|
||||
defaults(into, subSchema);
|
||||
}
|
||||
into.allOf = null;
|
||||
}
|
||||
|
||||
function traverse(obj) {
|
||||
if (obj == undefined || 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);
|
||||
}
|
||||
dereferencedCache = {};
|
||||
|
||||
constructor(public schemaMgr: SchemaManager) {
|
||||
}
|
||||
|
@ -169,7 +91,7 @@ export class BaseComponent implements OnInit, OnDestroy {
|
|||
* onInit method is run by angular2 after all component inputs are resolved
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.componentSchema = snapshot(this.schemaMgr.byPointer(this.pointer || ''));
|
||||
this.componentSchema = this.schemaMgr.byPointer(this.pointer || '');
|
||||
this.prepareModel();
|
||||
this.init();
|
||||
}
|
||||
|
@ -178,61 +100,6 @@ export class BaseComponent implements OnInit, OnDestroy {
|
|||
this.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* simple in-place schema dereferencing. Schema is already bundled so no need in global dereferencing.
|
||||
*/
|
||||
dereference(schema = Object.assign({}, this.componentSchema)) {
|
||||
let dereferencedCache = {};
|
||||
|
||||
let resolve = (schema) => {
|
||||
let resolvedRef;
|
||||
if (schema && schema.$ref) {
|
||||
resolvedRef = schema.$ref;
|
||||
let resolved = this.schemaMgr.byPointer(schema.$ref);
|
||||
let baseName = JsonPointer.baseName(schema.$ref);
|
||||
if (!dereferencedCache[schema.$ref]) {
|
||||
// if resolved schema doesn't have title use name from ref
|
||||
resolved = Object.assign({}, resolved);
|
||||
resolved._pointer = schema.$ref;
|
||||
} else {
|
||||
// for circular referenced save only title and type
|
||||
resolved = {
|
||||
title: resolved.title,
|
||||
type: resolved.type
|
||||
};
|
||||
}
|
||||
|
||||
dereferencedCache[schema.$ref] = dereferencedCache[schema.$ref] ? dereferencedCache[schema.$ref] + 1 : 1;
|
||||
|
||||
resolved.title = resolved.title || baseName;
|
||||
|
||||
let keysCount = Object.keys(schema).length;
|
||||
if ( keysCount > 2 || (keysCount === 2 && !schema.description) ) {
|
||||
// allow only description field on the same level as $ref because it is
|
||||
// common pattern over specs in the wild
|
||||
console.warn(`other properties defined at the same level as $ref at '${this.pointer}'.
|
||||
They are IGNORRED according to JsonSchema spec`);
|
||||
}
|
||||
|
||||
schema = schema.description ? {
|
||||
description: schema.description
|
||||
} : {};
|
||||
Object.assign(schema, resolved);
|
||||
}
|
||||
|
||||
Object.keys(schema).forEach((key) => {
|
||||
let value = schema[key];
|
||||
if (value && typeof value === 'object') {
|
||||
schema[key] = resolve(value);
|
||||
}
|
||||
});
|
||||
if (resolvedRef) dereferencedCache[resolvedRef] = dereferencedCache[resolvedRef] ? dereferencedCache[resolvedRef] - 1 : 0;
|
||||
return schema;
|
||||
};
|
||||
|
||||
this.componentSchema = snapshot(resolve(schema));
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to prepare model based on component schema
|
||||
* @abstract
|
||||
|
|
238
lib/services/spec-helper.service.spec.ts
Normal file
238
lib/services/spec-helper.service.spec.ts
Normal file
|
@ -0,0 +1,238 @@
|
|||
'use strict';
|
||||
import { SchemaNormalizator } from './spec-helper.service';
|
||||
import {
|
||||
describe,
|
||||
it
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { SchemaManager } from '../utils/SchemaManager';;
|
||||
|
||||
describe('Spec Helper', () => {
|
||||
let schemaMgr:SchemaManager = new SchemaManager();
|
||||
let normalizer = new SchemaNormalizator(schemaMgr);
|
||||
|
||||
describe('Dereference', () => {
|
||||
beforeAll(done => {
|
||||
schemaMgr.load('/tests/schemas/base-component-dereference.json').then(
|
||||
() => done()
|
||||
);
|
||||
});
|
||||
|
||||
describe('simple dereference', () => {
|
||||
let resolved;
|
||||
let pointer;
|
||||
beforeAll(() => {
|
||||
pointer = '/paths/test1/get/parameters/0';
|
||||
resolved = normalizer.normalize(schemaMgr.byPointer(pointer), pointer);
|
||||
});
|
||||
|
||||
it('should not contain $ref property', () => {
|
||||
expect(resolved.$ref).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should inject Title if not exist based on reference', () => {
|
||||
resolved.title.should.be.equal('Simple');
|
||||
});
|
||||
|
||||
it('should inject pointer', () => {
|
||||
resolved._pointer.should.be.equal('#/definitions/Simple');
|
||||
});
|
||||
|
||||
it('should insert correct definition instead of reference', () => {
|
||||
delete resolved.title;
|
||||
delete resolved._pointer;
|
||||
resolved.should.be.deepEqual(schemaMgr.schema.definitions.Simple);
|
||||
});
|
||||
});
|
||||
|
||||
describe('nested dereference', () => {
|
||||
let resolved;
|
||||
beforeAll(() => {
|
||||
let pointer = '/paths/test2/get/parameters/0';
|
||||
resolved = normalizer.normalize(schemaMgr.byPointer(pointer), pointer);
|
||||
});
|
||||
|
||||
it('should not touch title if exist', () => {
|
||||
resolved.title.should.be.equal('NesteTitle');
|
||||
});
|
||||
|
||||
it('should resolve nested schema', () => {
|
||||
expect(resolved.properties.subschema.$ref).toBeUndefined();
|
||||
resolved._pointer.should.be.equal('#/definitions/Nested');
|
||||
resolved.properties.subschema._pointer.should.be.equal('#/definitions/Simple');
|
||||
resolved.properties.subschema.type.should.be.equal('object');
|
||||
});
|
||||
});
|
||||
|
||||
describe('array schema dereference', () => {
|
||||
let resolved;
|
||||
beforeAll(() => {
|
||||
let pointer = '/paths/test3/get/parameters/0';
|
||||
resolved = normalizer.normalize(schemaMgr.byPointer(pointer), pointer);
|
||||
});
|
||||
|
||||
it('should resolve array schema', () => {
|
||||
console.log(resolved);
|
||||
expect(resolved.$ref).toBeUndefined();
|
||||
expect(resolved.items.$ref).toBeUndefined();
|
||||
resolved.type.should.be.equal('array');
|
||||
resolved._pointer.should.be.equal('#/definitions/ArrayOfSimple');
|
||||
resolved.items._pointer.should.be.equal('#/definitions/Simple');
|
||||
resolved.items.type.should.be.equal('object');
|
||||
});
|
||||
});
|
||||
|
||||
describe('circular dereference', () => {
|
||||
let resolved;
|
||||
beforeAll(() => {
|
||||
let pointer = '/paths/test4/get/parameters/0';
|
||||
resolved = normalizer.normalize(schemaMgr.byPointer(pointer), pointer);
|
||||
});
|
||||
|
||||
it('should resolve circular schema', () => {
|
||||
expect(resolved.$ref).toBeUndefined();
|
||||
expect(resolved.items.$ref).toBeUndefined();
|
||||
resolved.type.should.be.equal('array');
|
||||
resolved._pointer.should.be.equal('#/definitions/Circular');
|
||||
expect(resolved.items._pointer).toBeUndefined();
|
||||
resolved.items.title.should.be.equal('Circular');
|
||||
});
|
||||
});
|
||||
|
||||
describe('$ref with other fields on the same level', () => {
|
||||
let resolved;
|
||||
beforeAll(() => {
|
||||
let pointer = '/paths/test5/get/parameters/0';
|
||||
resolved = normalizer.normalize(schemaMgr.byPointer(pointer), pointer);
|
||||
});
|
||||
|
||||
it('should skip other fields', () => {
|
||||
expect(resolved.$ref).toBeUndefined();
|
||||
expect(resolved.title).toBeDefined();
|
||||
resolved.title.should.be.equal('Simple');
|
||||
});
|
||||
|
||||
it('should preserve description field', () => {
|
||||
expect(resolved.$ref).toBeUndefined();
|
||||
expect(resolved.description).toBeDefined();
|
||||
resolved.description.should.be.equal('test');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergeAllOf', () => {
|
||||
beforeAll((done) => {
|
||||
schemaMgr.load('tests/schemas/base-component-joinallof.json').then(() => done());
|
||||
});
|
||||
|
||||
describe('Simple allOf merge', () => {
|
||||
let joined;
|
||||
beforeAll(() => {
|
||||
let pointer = '/definitions/SimpleAllOf';
|
||||
joined = normalizer.normalize(schemaMgr.byPointer(pointer), pointer);
|
||||
});
|
||||
|
||||
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(3);
|
||||
Object.keys(joined.properties).should.be.deepEqual(['prop1', 'prop2', 'prop3']);
|
||||
});
|
||||
|
||||
it('should merge required', () => {
|
||||
joined.required.length.should.be.equal(2);
|
||||
joined.required.should.be.deepEqual(['prop1', 'prop3']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AllOf with refrence', () => {
|
||||
let joined;
|
||||
beforeAll(() => {
|
||||
let pointer = '/definitions/AllOfWithRef';
|
||||
joined = normalizer.normalize(schemaMgr.byPointer(pointer), pointer);
|
||||
});
|
||||
|
||||
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(2);
|
||||
Object.keys(joined.properties).should.be.deepEqual(['id', 'prop3']);
|
||||
});
|
||||
|
||||
it('should merge required', () => {
|
||||
joined.required.length.should.be.equal(2);
|
||||
joined.required.should.be.deepEqual(['id', 'prop3']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AllOf with other properties on the allOf level', () => {
|
||||
let joined;
|
||||
beforeAll(() => {
|
||||
let pointer = '/definitions/AllOfWithOther';
|
||||
joined = normalizer.normalize(schemaMgr.byPointer(pointer), pointer);
|
||||
});
|
||||
|
||||
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', () => {
|
||||
let pointer = '/definitions/PropertiesOnAllOfLevel';
|
||||
let joined;
|
||||
(() => joined = normalizer.normalize(schemaMgr.byPointer(pointer), pointer)).should.not.throw();
|
||||
Object.keys(joined.properties).length.should.be.equal(3);
|
||||
});
|
||||
|
||||
it('should throw when merging schemas with different types', () => {
|
||||
let pointer = '/definitions/BadAllOf1';
|
||||
(() => normalizer.normalize(schemaMgr.byPointer(pointer), pointer)).should.throw();
|
||||
});
|
||||
|
||||
it('should handle nested allOF', () => {
|
||||
let pointer = '/definitions/NestedAllOf';
|
||||
let joined;
|
||||
(() => joined = normalizer.normalize(schemaMgr.byPointer(pointer), pointer)).should.not.throw();
|
||||
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']);
|
||||
});
|
||||
});
|
||||
|
||||
xdescribe('Merge array allOf', () => {
|
||||
//emtpy
|
||||
});
|
||||
});
|
||||
});
|
196
lib/services/spec-helper.service.ts
Normal file
196
lib/services/spec-helper.service.ts
Normal file
|
@ -0,0 +1,196 @@
|
|||
'use strict';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { SchemaManager } from '../utils/SchemaManager';
|
||||
import { JsonPointer } from '../utils/JsonPointer';
|
||||
import { defaults } from '../utils/helpers';
|
||||
|
||||
interface Reference {
|
||||
$ref: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface Schema {
|
||||
properties: any;
|
||||
allOf: any;
|
||||
items: any;
|
||||
additionalProperties: any;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SchemaNormalizator {
|
||||
_dereferencer:SchemaDereferencer;
|
||||
constructor(private _schema:any) {
|
||||
this._dereferencer = new SchemaDereferencer(_schema, this);
|
||||
}
|
||||
normalize(schema, ptr) {
|
||||
if (schema['x-redoc-normalized']) return schema;
|
||||
let res = SchemaWalker.walk(schema, ptr, (subSchema, ptr) => {
|
||||
let resolved = this._dereferencer.dereference(subSchema, ptr);
|
||||
if (resolved.allOf) {
|
||||
resolved._pointer = resolved._pointer || ptr;
|
||||
AllOfMerger.merge(resolved, resolved.allOf, {omitParent: true});
|
||||
}
|
||||
return resolved;
|
||||
});
|
||||
res['x-redoc-normalized'] = true;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
class SchemaWalker {
|
||||
static walk(obj:Schema, pointer:string, visitor:Function) {
|
||||
if (obj == undefined || typeof(obj) !== 'object') {
|
||||
return;
|
||||
}
|
||||
if (obj.properties) {
|
||||
let ptr = JsonPointer.join(pointer, ['properties']);
|
||||
SchemaWalker.walkEach(obj.properties, ptr, visitor);
|
||||
}
|
||||
if (obj.additionalProperties) {
|
||||
let ptr = JsonPointer.join(pointer, ['additionalProperties']);
|
||||
SchemaWalker.walkEach(obj.additionalProperties, ptr, visitor);
|
||||
}
|
||||
|
||||
if (obj.allOf) {
|
||||
let ptr = JsonPointer.join(pointer, ['allOf']);
|
||||
SchemaWalker.walkEach(obj.allOf, ptr, visitor);
|
||||
}
|
||||
|
||||
if (obj.items) {
|
||||
let ptr = JsonPointer.join(pointer, ['items']);
|
||||
if (Array.isArray(obj.items)) {
|
||||
SchemaWalker.walkEach(obj.items, ptr, visitor);
|
||||
} else {
|
||||
let res = SchemaWalker.walk(obj.items, ptr, visitor);
|
||||
if (res) obj.items = res;
|
||||
}
|
||||
}
|
||||
|
||||
return visitor(obj, pointer);
|
||||
}
|
||||
|
||||
private static walkEach(obj:Object, pointer:string, visitor:Function) {
|
||||
for(let key of Object.keys(obj)) {
|
||||
let ptr = JsonPointer.join(pointer, [key]);
|
||||
let res = SchemaWalker.walk(obj[key], ptr, visitor);
|
||||
if (res) obj[key] = res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AllOfMerger {
|
||||
static merge(into, schemas, opts) {
|
||||
into['x-derived-from'] = [];
|
||||
for (let i=0; i < schemas.length; i++) {
|
||||
let subSchema = schemas[i];
|
||||
into['x-derived-from'].push(subSchema._pointer);
|
||||
if (opts && opts.omitParent && subSchema.discriminator) continue;
|
||||
|
||||
AllOfMerger.checkCanMerge(subSchema, into);
|
||||
|
||||
into.type = into.type || subSchema.type;
|
||||
if (into.type === 'object') {
|
||||
AllOfMerger.mergeObject(into, subSchema, i);
|
||||
}
|
||||
// don't merge _pointer
|
||||
subSchema._pointer = null;
|
||||
defaults(into, subSchema);
|
||||
}
|
||||
into.allOf = null;
|
||||
}
|
||||
|
||||
private static mergeObject(into, subSchema, allOfNumber) {
|
||||
if (subSchema.properties) {
|
||||
if (!into.properties) into.properties = {};
|
||||
Object.assign(into.properties, subSchema.properties);
|
||||
Object.keys(subSchema.properties).forEach(propName => {
|
||||
let prop = subSchema.properties[propName];
|
||||
if (!prop._pointer) {
|
||||
let schemaPtr = subSchema._pointer || JsonPointer.join(into._pointer, ['allOf', allOfNumber]);
|
||||
prop._pointer = prop._pointer || JsonPointer.join(schemaPtr, ['properties', propName]);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (subSchema.required) {
|
||||
if (!into.required) into.required = [];
|
||||
into.required.push(...subSchema.required);
|
||||
}
|
||||
}
|
||||
|
||||
private static checkCanMerge(subSchema, into) {
|
||||
// TODO: add support for merge array schemas
|
||||
if (typeof subSchema !== 'object') {
|
||||
let errMessage = `Items of allOf should be Object: ${typeof subSchema} found
|
||||
${subSchema}`;
|
||||
throw new Error(errMessage);
|
||||
}
|
||||
|
||||
if (into.type && subSchema.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)
|
||||
}
|
||||
}
|
||||
|
||||
class RefCounter {
|
||||
private _counter = {};
|
||||
|
||||
reset():void {
|
||||
this._counter = {};
|
||||
}
|
||||
|
||||
visit(ref:string):void {
|
||||
this._counter[ref] = this._counter[ref] ? this._counter[ref] + 1 : 1;
|
||||
}
|
||||
|
||||
exit(ref:string):void {
|
||||
this._counter[ref] = this._counter[ref] && this._counter[ref] - 1;
|
||||
}
|
||||
|
||||
visited(ref:string):boolean {
|
||||
return !!this._counter[ref];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SchemaDereferencer {
|
||||
private _refCouner = new RefCounter();
|
||||
|
||||
constructor(private _spec: SchemaManager, private normalizator: SchemaNormalizator) {
|
||||
}
|
||||
|
||||
dereference(schema: Reference, pointer:string):any {
|
||||
if (!schema || !schema.$ref) return schema;
|
||||
window['derefCount'] = window['derefCount'] ? window['derefCount'] + 1 : 1;
|
||||
let $ref = schema.$ref;
|
||||
let resolved = this._spec.byPointer($ref);
|
||||
if (!this._refCouner.visited($ref)) {
|
||||
resolved._pointer = $ref;
|
||||
} else {
|
||||
// for circular referenced save only title and type
|
||||
resolved = {
|
||||
title: resolved.title,
|
||||
type: resolved.type
|
||||
};
|
||||
}
|
||||
this._refCouner.visit($ref);
|
||||
// if resolved schema doesn't have title use name from ref
|
||||
resolved.title = resolved.title || JsonPointer.baseName($ref);
|
||||
|
||||
let keysCount = Object.keys(schema).length;
|
||||
if ( keysCount > 2 || (keysCount === 2 && !schema.description) ) {
|
||||
console.warn(`other properties defined at the same level as $ref at '${pointer}'.
|
||||
They are IGNORRED according to JsonSchema spec`);
|
||||
resolved.description = resolved.description || schema.description;
|
||||
}
|
||||
|
||||
resolved = this.normalizator.normalize(resolved, $ref);
|
||||
this._refCouner.exit($ref);
|
||||
return resolved;
|
||||
}
|
||||
}
|
|
@ -173,13 +173,11 @@ export class SchemaManager {
|
|||
let globalDefs = this._schema.definitions || {};
|
||||
let res = [];
|
||||
for (let defName of Object.keys(globalDefs)) {
|
||||
if (!globalDefs[defName].allOf) continue;
|
||||
|
||||
let subTypes = globalDefs[defName].allOf;
|
||||
let idx = subTypes.findIndex((subType) => {
|
||||
if (subType.$ref === defPointer) return true;
|
||||
return false;
|
||||
});
|
||||
if (!globalDefs[defName].allOf &&
|
||||
!globalDefs[defName]['x-derived-from']) continue;
|
||||
let subTypes = globalDefs[defName]['x-derived-from'] ||
|
||||
globalDefs[defName].allOf.map(subType => subType.$ref);
|
||||
let idx = subTypes.findIndex(ref => ref === defPointer);
|
||||
if (idx < 0) continue;
|
||||
|
||||
let empty = false;
|
||||
|
|
|
@ -14,3 +14,18 @@ export function statusCodeType(statusCode) {
|
|||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export 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;
|
||||
}
|
||||
|
|
|
@ -27,17 +27,13 @@
|
|||
"ArrayOfSimple": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Simple"
|
||||
}
|
||||
"$ref": "#/definitions/Simple"
|
||||
}
|
||||
},
|
||||
"Circular": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Circular"
|
||||
}
|
||||
"$ref": "#/definitions/Circular"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue
Block a user