mirror of
				https://github.com/Redocly/redoc.git
				synced 2025-10-31 15:57:30 +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