mirror of
https://github.com/Redocly/redoc.git
synced 2025-02-07 13:30:33 +03:00
chore: refactor, simplify AppStore
This commit is contained in:
parent
757a92e425
commit
9361ead8c4
|
@ -47,7 +47,7 @@ export class OneOfSchema extends React.Component<SchemaProps> {
|
|||
<OneOfLabel> {schema.oneOfType} </OneOfLabel>
|
||||
<OneOfList>
|
||||
{oneOf.map((subSchema, idx) => (
|
||||
<OneOfButton key={subSchema._$ref} schema={schema} subSchema={subSchema} idx={idx} />
|
||||
<OneOfButton key={subSchema.pointer} schema={schema} subSchema={subSchema} idx={idx} />
|
||||
))}
|
||||
</OneOfList>
|
||||
<Schema {...this.props} schema={oneOf[schema.activeOneOf]} />
|
||||
|
|
|
@ -61,15 +61,11 @@ export class OAuthFlow extends React.PureComponent<OAuthFlowProps> {
|
|||
}
|
||||
|
||||
export interface SecurityDefsProps {
|
||||
securitySchemes?: SecuritySchemesModel;
|
||||
securitySchemes: SecuritySchemesModel;
|
||||
}
|
||||
|
||||
export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
|
||||
render() {
|
||||
if (!this.props.securitySchemes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.props.securitySchemes.schemes.map(scheme => (
|
||||
|
|
|
@ -15,7 +15,6 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
"name": "packSize",
|
||||
"required": false,
|
||||
"schema": SchemaModel {
|
||||
"_$ref": "#/components/schemas/Dog/properties/packSize/schema",
|
||||
"activeOneOf": 0,
|
||||
"constraints": Array [],
|
||||
"default": undefined,
|
||||
|
@ -31,6 +30,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
"nullable": false,
|
||||
"options": "<<<filtered>>>",
|
||||
"pattern": undefined,
|
||||
"pointer": "#/components/schemas/Dog/properties/packSize",
|
||||
"rawSchema": Object {
|
||||
"default": undefined,
|
||||
"type": "number",
|
||||
|
@ -63,7 +63,6 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
"name": "type",
|
||||
"required": true,
|
||||
"schema": SchemaModel {
|
||||
"_$ref": "#/components/schemas/Dog/properties/type/schema",
|
||||
"activeOneOf": 0,
|
||||
"constraints": Array [],
|
||||
"default": undefined,
|
||||
|
@ -79,6 +78,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
"nullable": false,
|
||||
"options": "<<<filtered>>>",
|
||||
"pattern": undefined,
|
||||
"pointer": "#/components/schemas/Dog/properties/type",
|
||||
"rawSchema": Object {
|
||||
"default": undefined,
|
||||
"type": "string",
|
||||
|
|
|
@ -90,7 +90,32 @@ export class AppStore {
|
|||
this.updateMarkOnMenu(this.menu.activeItemIdx);
|
||||
}
|
||||
|
||||
updateMarkOnMenu(idx: number) {
|
||||
dispose() {
|
||||
this.scroll.dispose();
|
||||
this.menu.dispose();
|
||||
this.disposer();
|
||||
}
|
||||
|
||||
/**
|
||||
* serializes store
|
||||
* **SUPER HACKY AND NOT OPTIMAL IMPLEMENTATION**
|
||||
*/
|
||||
// TODO: improve
|
||||
async toJS(): Promise<StoreState> {
|
||||
return {
|
||||
menu: {
|
||||
activeItemIdx: this.menu.activeItemIdx,
|
||||
},
|
||||
spec: {
|
||||
url: this.spec.parser.specUrl,
|
||||
data: this.spec.parser.spec,
|
||||
},
|
||||
searchIndex: this.search ? await this.search.toJS() : undefined,
|
||||
options: this.rawOptions,
|
||||
};
|
||||
}
|
||||
|
||||
private updateMarkOnMenu(idx: number) {
|
||||
const start = Math.max(0, idx);
|
||||
const end = Math.min(this.menu.flatItems.length, start + 5);
|
||||
|
||||
|
@ -111,29 +136,4 @@ export class AppStore {
|
|||
this.marker.addOnly(elements);
|
||||
this.marker.mark();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.scroll.dispose();
|
||||
this.menu.dispose();
|
||||
this.disposer();
|
||||
}
|
||||
|
||||
/**
|
||||
* serializes store
|
||||
* **SUPER HACKY AND NOT OPTIMAL IMPLEMENTATION**
|
||||
*/
|
||||
// TODO:
|
||||
async toJS(): Promise<StoreState> {
|
||||
return {
|
||||
menu: {
|
||||
activeItemIdx: this.menu.activeItemIdx,
|
||||
},
|
||||
spec: {
|
||||
url: this.spec.parser.specUrl,
|
||||
data: this.spec.parser.spec,
|
||||
},
|
||||
searchIndex: this.search ? await this.search.toJS() : undefined,
|
||||
options: this.rawOptions,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { OpenAPIOperation, OpenAPIParameter, OpenAPISpec, OpenAPITag, Referenced } from '../types';
|
||||
import { isOperationName, JsonPointer } from '../utils';
|
||||
import { isOperationName } from '../utils';
|
||||
import { MarkdownRenderer } from './MarkdownRenderer';
|
||||
import { GroupModel, OperationModel } from './models';
|
||||
import { OpenAPIParser } from './OpenAPIParser';
|
||||
|
@ -11,7 +11,7 @@ export type TagInfo = OpenAPITag & {
|
|||
};
|
||||
|
||||
export type ExtendedOpenAPIOperation = {
|
||||
_$ref: string;
|
||||
pathName: string;
|
||||
httpVerb: string;
|
||||
pathParameters: Array<Referenced<OpenAPIParameter>>;
|
||||
} & OpenAPIOperation;
|
||||
|
@ -190,7 +190,7 @@ export class MenuBuilder {
|
|||
// empty tag
|
||||
operationTags = [''];
|
||||
}
|
||||
const operationPointer = JsonPointer.compile(['paths', pathName, operationName]);
|
||||
|
||||
for (const tagName of operationTags) {
|
||||
let tag = tags[tagName];
|
||||
if (tag === undefined) {
|
||||
|
@ -205,7 +205,7 @@ export class MenuBuilder {
|
|||
}
|
||||
tag.operations.push({
|
||||
...operationInfo,
|
||||
_$ref: operationPointer,
|
||||
pathName,
|
||||
httpVerb: operationName,
|
||||
pathParameters: path.parameters || [],
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { action, computed, observable } from 'mobx';
|
||||
import { action, observable } from 'mobx';
|
||||
import { querySelector } from '../utils/dom';
|
||||
import { GroupModel, OperationModel, SpecStore } from './models';
|
||||
import { SpecStore } from './models';
|
||||
|
||||
import { HistoryService } from './HistoryService';
|
||||
import { ScrollService } from './ScrollService';
|
||||
|
@ -55,20 +55,31 @@ export class MenuStore {
|
|||
*/
|
||||
@observable sideBarOpened: boolean = false;
|
||||
|
||||
items: IMenuItem[];
|
||||
flatItems: IMenuItem[];
|
||||
|
||||
/**
|
||||
* cached flattened menu items to support absolute indexing
|
||||
*/
|
||||
private _unsubscribe: () => void;
|
||||
private _hashUnsubscribe: () => void;
|
||||
private _items?: Array<GroupModel | OperationModel>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param spec [SpecStore](#SpecStore) which contains page content structure
|
||||
* @param _scrollService scroll service instance used by this menu
|
||||
* @param scroll scroll service instance used by this menu
|
||||
*/
|
||||
constructor(private spec: SpecStore, private _scrollService: ScrollService) {
|
||||
this._unsubscribe = _scrollService.subscribe(this.updateOnScroll);
|
||||
constructor(spec: SpecStore, public scroll: ScrollService) {
|
||||
this.items = spec.operationGroups;
|
||||
|
||||
this.flatItems = flattenByProp(this.items || [], 'items');
|
||||
this.flatItems.forEach((item, idx) => (item.absoluteIdx = idx));
|
||||
|
||||
this.subscribe();
|
||||
}
|
||||
|
||||
subscribe() {
|
||||
this._unsubscribe = this.scroll.subscribe(this.updateOnScroll);
|
||||
this._hashUnsubscribe = HistoryService.subscribe(this.updateOnHash);
|
||||
}
|
||||
|
||||
|
@ -82,23 +93,11 @@ export class MenuStore {
|
|||
this.sideBarOpened = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* top level menu items (not flattened)
|
||||
*/
|
||||
@computed
|
||||
get items(): IMenuItem[] {
|
||||
if (!this._items) {
|
||||
this._items = this.spec.operationGroups;
|
||||
}
|
||||
return this._items;
|
||||
}
|
||||
|
||||
/**
|
||||
* update active items on scroll
|
||||
* @param isScrolledDown whether last scroll was downside
|
||||
*/
|
||||
@action.bound
|
||||
updateOnScroll(isScrolledDown: boolean): void {
|
||||
updateOnScroll = (isScrolledDown: boolean): void => {
|
||||
const step = isScrolledDown ? 1 : -1;
|
||||
let itemIdx = this.activeItemIdx;
|
||||
while (true) {
|
||||
|
@ -112,12 +111,12 @@ export class MenuStore {
|
|||
|
||||
if (isScrolledDown) {
|
||||
const el = this.getElementAt(itemIdx + 1);
|
||||
if (this._scrollService.isElementBellow(el)) {
|
||||
if (this.scroll.isElementBellow(el)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const el = this.getElementAt(itemIdx);
|
||||
if (this._scrollService.isElementAbove(el)) {
|
||||
if (this.scroll.isElementAbove(el)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -125,14 +124,13 @@ export class MenuStore {
|
|||
}
|
||||
|
||||
this.activate(this.flatItems[itemIdx], true, true);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* update active items on hash change
|
||||
* @param hash current hash
|
||||
*/
|
||||
@action.bound
|
||||
updateOnHash(hash: string = HistoryService.hash): boolean {
|
||||
updateOnHash = (hash: string = HistoryService.hash): boolean => {
|
||||
if (!hash) {
|
||||
return false;
|
||||
}
|
||||
|
@ -143,10 +141,10 @@ export class MenuStore {
|
|||
if (item) {
|
||||
this.activateAndScroll(item, false);
|
||||
} else {
|
||||
this._scrollService.scrollIntoViewBySelector(`[${SECTION_ATTR}="${hash}"]`);
|
||||
this.scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${hash}"]`);
|
||||
}
|
||||
return item !== undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* get section/operation DOM Node related to the item or null if it doesn't exist
|
||||
|
@ -168,16 +166,6 @@ export class MenuStore {
|
|||
return this.flatItems.find(item => item.id === id);
|
||||
};
|
||||
|
||||
/**
|
||||
* flattened items as they appear in the tree depth-first (top to bottom in the view)
|
||||
*/
|
||||
@computed
|
||||
get flatItems(): IMenuItem[] {
|
||||
const flatItems = flattenByProp(this._items || [], 'items');
|
||||
flatItems.forEach((item, idx) => (item.absoluteIdx = idx));
|
||||
return flatItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* activate menu item
|
||||
* @param item item to activate
|
||||
|
@ -246,7 +234,7 @@ export class MenuStore {
|
|||
* scrolls to active section
|
||||
*/
|
||||
scrollToActive(): void {
|
||||
this._scrollService.scrollIntoView(this.getElementAt(this.activeItemIdx));
|
||||
this.scroll.scrollIntoView(this.getElementAt(this.activeItemIdx));
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { observable } from 'mobx';
|
||||
import { resolve as urlResolve } from 'url';
|
||||
|
||||
import { OpenAPIRef, OpenAPISchema, OpenAPISpec, Referenced } from '../types';
|
||||
|
@ -39,8 +38,8 @@ class RefCounter {
|
|||
* Loads and keeps spec. Provides raw spec operations
|
||||
*/
|
||||
export class OpenAPIParser {
|
||||
@observable specUrl?: string;
|
||||
@observable.ref spec: OpenAPISpec;
|
||||
specUrl?: string;
|
||||
spec: OpenAPISpec;
|
||||
|
||||
private _refCounter: RefCounter = new RefCounter();
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { computed, observable } from 'mobx';
|
||||
import { OpenAPISpec } from '../types';
|
||||
import { OpenAPIExternalDocumentation, OpenAPISpec } from '../types';
|
||||
|
||||
import { MenuBuilder } from './MenuBuilder';
|
||||
import { ContentItemModel, MenuBuilder } from './MenuBuilder';
|
||||
import { ApiInfoModel } from './models/ApiInfo';
|
||||
import { SecuritySchemesModel } from './models/SecuritySchemes';
|
||||
import { OpenAPIParser } from './OpenAPIParser';
|
||||
|
@ -10,7 +9,12 @@ import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
|||
* Store that containts all the specification related information in the form of tree
|
||||
*/
|
||||
export class SpecStore {
|
||||
@observable.ref parser: OpenAPIParser;
|
||||
parser: OpenAPIParser;
|
||||
|
||||
info: ApiInfoModel;
|
||||
externalDocs?: OpenAPIExternalDocumentation;
|
||||
operationGroups: ContentItemModel[];
|
||||
securitySchemes: SecuritySchemesModel;
|
||||
|
||||
constructor(
|
||||
spec: OpenAPISpec,
|
||||
|
@ -18,26 +22,10 @@ export class SpecStore {
|
|||
private options: RedocNormalizedOptions,
|
||||
) {
|
||||
this.parser = new OpenAPIParser(spec, specUrl, options);
|
||||
}
|
||||
this.info = new ApiInfoModel(this.parser);
|
||||
this.externalDocs = this.parser.spec.externalDocs;
|
||||
this.operationGroups = MenuBuilder.buildStructure(this.parser, this.options);
|
||||
|
||||
@computed
|
||||
get info(): ApiInfoModel {
|
||||
return new ApiInfoModel(this.parser);
|
||||
}
|
||||
|
||||
@computed
|
||||
get externalDocs() {
|
||||
return this.parser.spec.externalDocs;
|
||||
}
|
||||
|
||||
@computed
|
||||
get operationGroups() {
|
||||
return MenuBuilder.buildStructure(this.parser, this.options);
|
||||
}
|
||||
|
||||
@computed
|
||||
get securitySchemes() {
|
||||
const schemes = this.parser.spec.components && this.parser.spec.components.securitySchemes;
|
||||
return schemes && new SecuritySchemesModel(this.parser);
|
||||
this.securitySchemes = new SecuritySchemesModel(this.parser);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,8 +32,7 @@ export class FieldModel {
|
|||
this.name = infoOrRef.name || info.name;
|
||||
this.in = info.in;
|
||||
this.required = !!info.required;
|
||||
const schemaPointer = (parser.isRef(infoOrRef) ? infoOrRef.$ref : pointer) + '/schema';
|
||||
this.schema = new SchemaModel(parser, info.schema || {}, schemaPointer, options);
|
||||
this.schema = new SchemaModel(parser, info.schema || {}, pointer, options);
|
||||
this.description =
|
||||
info.description === undefined ? this.schema.description || '' : info.description;
|
||||
this.example = info.example || this.schema.example;
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
getStatusCodeType,
|
||||
isStatusCode,
|
||||
JsonPointer,
|
||||
memoize,
|
||||
mergeParams,
|
||||
normalizeServers,
|
||||
sortByRequired,
|
||||
|
@ -22,7 +23,6 @@ import { FieldModel } from './Field';
|
|||
import { RequestBodyModel } from './RequestBody';
|
||||
import { ResponseModel } from './Response';
|
||||
import { CodeSample } from './types';
|
||||
|
||||
/**
|
||||
* Operation model ready to be used by components
|
||||
*/
|
||||
|
@ -44,80 +44,41 @@ export class OperationModel implements IMenuItem {
|
|||
@observable active: boolean = false;
|
||||
//#endregion
|
||||
|
||||
_$ref: string;
|
||||
pointer: string;
|
||||
operationId?: string;
|
||||
httpVerb: string;
|
||||
deprecated: boolean;
|
||||
requestBody?: RequestBodyModel;
|
||||
parameters: FieldModel[];
|
||||
responses: ResponseModel[];
|
||||
path: string;
|
||||
servers: OpenAPIServer[];
|
||||
security: SecurityRequirementModel[];
|
||||
codeSamples: CodeSample[];
|
||||
|
||||
constructor(
|
||||
parser: OpenAPIParser,
|
||||
operationSpec: ExtendedOpenAPIOperation,
|
||||
private parser: OpenAPIParser,
|
||||
private operationSpec: ExtendedOpenAPIOperation,
|
||||
parent: GroupModel | undefined,
|
||||
options: RedocNormalizedOptions,
|
||||
private options: RedocNormalizedOptions,
|
||||
) {
|
||||
this.pointer = JsonPointer.compile(['paths', operationSpec.pathName, operationSpec.httpVerb]);
|
||||
|
||||
this.id =
|
||||
operationSpec.operationId !== undefined
|
||||
? 'operation/' + operationSpec.operationId
|
||||
: parent !== undefined
|
||||
? parent.id + operationSpec._$ref
|
||||
: operationSpec._$ref;
|
||||
? parent.id + this.pointer
|
||||
: this.pointer;
|
||||
|
||||
this.name = getOperationSummary(operationSpec);
|
||||
this.description = operationSpec.description;
|
||||
|
||||
this.parent = parent;
|
||||
this.externalDocs = operationSpec.externalDocs;
|
||||
|
||||
this._$ref = operationSpec._$ref;
|
||||
this.deprecated = !!operationSpec.deprecated;
|
||||
this.httpVerb = operationSpec.httpVerb;
|
||||
this.deprecated = !!operationSpec.deprecated;
|
||||
this.operationId = operationSpec.operationId;
|
||||
this.requestBody =
|
||||
operationSpec.requestBody && new RequestBodyModel(parser, operationSpec.requestBody, options);
|
||||
this.codeSamples = operationSpec['x-code-samples'] || [];
|
||||
this.path = JsonPointer.baseName(this._$ref, 2);
|
||||
|
||||
this.parameters = mergeParams(
|
||||
parser,
|
||||
operationSpec.pathParameters,
|
||||
operationSpec.parameters,
|
||||
).map(paramOrRef => new FieldModel(parser, paramOrRef, this._$ref, options));
|
||||
|
||||
if (options.requiredPropsFirst) {
|
||||
sortByRequired(this.parameters);
|
||||
}
|
||||
|
||||
let hasSuccessResponses = false;
|
||||
this.responses = Object.keys(operationSpec.responses || [])
|
||||
.filter(code => {
|
||||
if (code === 'default') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getStatusCodeType(code) === 'success') {
|
||||
hasSuccessResponses = true;
|
||||
}
|
||||
|
||||
return isStatusCode(code);
|
||||
}) // filter out other props (e.g. x-props)
|
||||
.map(code => {
|
||||
return new ResponseModel(
|
||||
parser,
|
||||
code,
|
||||
hasSuccessResponses,
|
||||
operationSpec.responses[code],
|
||||
options,
|
||||
);
|
||||
});
|
||||
|
||||
this.path = operationSpec.pathName;
|
||||
this.servers = normalizeServers(
|
||||
parser.specUrl,
|
||||
operationSpec.servers || parser.spec.servers || [],
|
||||
|
@ -143,4 +104,53 @@ export class OperationModel implements IMenuItem {
|
|||
deactivate() {
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
@memoize
|
||||
get requestBody() {
|
||||
return (
|
||||
this.operationSpec.requestBody &&
|
||||
new RequestBodyModel(this.parser, this.operationSpec.requestBody, this.options)
|
||||
);
|
||||
}
|
||||
|
||||
@memoize
|
||||
get parameters() {
|
||||
const _parameters = mergeParams(
|
||||
this.parser,
|
||||
this.operationSpec.pathParameters,
|
||||
this.operationSpec.parameters,
|
||||
// TODO: fix pointer
|
||||
).map(paramOrRef => new FieldModel(this.parser, paramOrRef, this.pointer, this.options));
|
||||
|
||||
if (this.options.requiredPropsFirst) {
|
||||
sortByRequired(_parameters);
|
||||
}
|
||||
return _parameters;
|
||||
}
|
||||
|
||||
@memoize
|
||||
get responses() {
|
||||
let hasSuccessResponses = false;
|
||||
return Object.keys(this.operationSpec.responses || [])
|
||||
.filter(code => {
|
||||
if (code === 'default') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getStatusCodeType(code) === 'success') {
|
||||
hasSuccessResponses = true;
|
||||
}
|
||||
|
||||
return isStatusCode(code);
|
||||
}) // filter out other props (e.g. x-props)
|
||||
.map(code => {
|
||||
return new ResponseModel(
|
||||
this.parser,
|
||||
code,
|
||||
hasSuccessResponses,
|
||||
this.operationSpec.responses[code],
|
||||
this.options,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
|
||||
// TODO: refactor this model, maybe use getters instead of copying all the values
|
||||
export class SchemaModel {
|
||||
_$ref: string;
|
||||
pointer: string;
|
||||
|
||||
type: string;
|
||||
displayType: string;
|
||||
|
@ -60,13 +60,13 @@ export class SchemaModel {
|
|||
constructor(
|
||||
parser: OpenAPIParser,
|
||||
schemaOrRef: Referenced<OpenAPISchema>,
|
||||
$ref: string,
|
||||
pointer: string,
|
||||
private options: RedocNormalizedOptions,
|
||||
isChild: boolean = false,
|
||||
) {
|
||||
this._$ref = schemaOrRef.$ref || $ref || '';
|
||||
this.pointer = schemaOrRef.$ref || pointer || '';
|
||||
this.rawSchema = parser.deref(schemaOrRef);
|
||||
this.schema = parser.mergeAllOf(this.rawSchema, this._$ref, isChild);
|
||||
this.schema = parser.mergeAllOf(this.rawSchema, this.pointer, isChild);
|
||||
this.init(parser, isChild);
|
||||
|
||||
parser.exitRef(schemaOrRef);
|
||||
|
@ -91,7 +91,7 @@ export class SchemaModel {
|
|||
this.isCircular = schema['x-circular-ref'];
|
||||
|
||||
this.title =
|
||||
schema.title || (isNamedDefinition(this._$ref) && JsonPointer.baseName(this._$ref)) || '';
|
||||
schema.title || (isNamedDefinition(this.pointer) && JsonPointer.baseName(this.pointer)) || '';
|
||||
this.description = schema.description || '';
|
||||
this.type = schema.type || detectType(schema);
|
||||
this.format = schema.format;
|
||||
|
@ -123,7 +123,7 @@ export class SchemaModel {
|
|||
this.oneOfType = 'One of';
|
||||
if (schema.anyOf !== undefined) {
|
||||
console.warn(
|
||||
`oneOf and anyOf are not supported on the same level. Skipping anyOf at ${this._$ref}`,
|
||||
`oneOf and anyOf are not supported on the same level. Skipping anyOf at ${this.pointer}`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
|
@ -136,9 +136,9 @@ export class SchemaModel {
|
|||
}
|
||||
|
||||
if (this.type === 'object') {
|
||||
this.fields = buildFields(parser, schema, this._$ref, this.options);
|
||||
this.fields = buildFields(parser, schema, this.pointer, this.options);
|
||||
} else if (this.type === 'array' && schema.items) {
|
||||
this.items = new SchemaModel(parser, schema.items, this._$ref + '/items', this.options);
|
||||
this.items = new SchemaModel(parser, schema.items, this.pointer + '/items', this.options);
|
||||
this.displayType = this.items.displayType;
|
||||
this.displayFormat = this.items.format;
|
||||
this.typePrefix = this.items.typePrefix + 'Array of ';
|
||||
|
@ -162,7 +162,7 @@ export class SchemaModel {
|
|||
// merge base schema into each of oneOf's subschemas
|
||||
allOf: [variant, { ...this.schema, oneOf: undefined, anyOf: undefined }],
|
||||
} as OpenAPISchema,
|
||||
this._$ref + '/oneOf/' + idx,
|
||||
this.pointer + '/oneOf/' + idx,
|
||||
this.options,
|
||||
),
|
||||
);
|
||||
|
@ -177,7 +177,7 @@ export class SchemaModel {
|
|||
) {
|
||||
const discriminator = getDiscriminator(schema)!;
|
||||
this.discriminatorProp = discriminator.propertyName;
|
||||
const derived = parser.findDerived([...(schema.parentRefs || []), this._$ref]);
|
||||
const derived = parser.findDerived([...(schema.parentRefs || []), this.pointer]);
|
||||
|
||||
if (schema.oneOf) {
|
||||
for (const variant of schema.oneOf) {
|
||||
|
|
|
@ -7,3 +7,4 @@ export * from './loadAndBundleSpec';
|
|||
export * from './dom';
|
||||
export * from './decorators';
|
||||
export * from './debug';
|
||||
export * from './memoize';
|
||||
|
|
49
src/utils/memoize.ts
Normal file
49
src/utils/memoize.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
// source: https://github.com/andreypopp/memoize-decorator
|
||||
const SENTINEL = {};
|
||||
|
||||
export function memoize<T>(target: any, name: string, descriptor: TypedPropertyDescriptor<T>) {
|
||||
if (typeof descriptor.value === 'function') {
|
||||
return (_memoizeMethod(target, name, descriptor) as any) as TypedPropertyDescriptor<T>;
|
||||
} else if (typeof descriptor.get === 'function') {
|
||||
return _memoizeGetter(target, name, descriptor) as TypedPropertyDescriptor<T>;
|
||||
} else {
|
||||
throw new Error(
|
||||
'@memoize decorator can be applied to methods or getters, got ' +
|
||||
String(descriptor.value) +
|
||||
' instead',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function _memoizeGetter(target: any, name: string, descriptor: PropertyDescriptor) {
|
||||
const memoizedName = `_memoized_${name}`;
|
||||
const get = descriptor.get!;
|
||||
target[memoizedName] = SENTINEL;
|
||||
return {
|
||||
...descriptor,
|
||||
get() {
|
||||
if (this[memoizedName] === SENTINEL) {
|
||||
this[memoizedName] = get.call(this);
|
||||
}
|
||||
return this[memoizedName];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function _memoizeMethod<T>(target: any, name: string, descriptor: TypedPropertyDescriptor<T>) {
|
||||
if (!descriptor.value || (descriptor.value as any).length > 0) {
|
||||
throw new Error('@memoize decorator can only be applied to methods of zero arguments');
|
||||
}
|
||||
const memoizedName = `_memoized_${name}`;
|
||||
const value = descriptor.value;
|
||||
target[memoizedName] = SENTINEL;
|
||||
return {
|
||||
...descriptor,
|
||||
value() {
|
||||
if (this[memoizedName] === SENTINEL) {
|
||||
this[memoizedName] = (value as any).call(this);
|
||||
}
|
||||
return this[memoizedName] as any;
|
||||
},
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user