// Copyright IBM Corp. and LoopBack contributors 2019. All Rights Reserved. // Node module: @loopback/boot // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT import { config, CoreBindings, extensionPoint, extensions, Getter, inject, } from '@loopback/core'; import { ModelApiBuilder, ModelApiConfig, MODEL_API_BUILDER_PLUGINS, } from '@loopback/model-api-builder'; import {ApplicationWithRepositories} from '@loopback/repository'; import debugFactory from 'debug'; import * as path from 'path'; import {BootBindings} from '../keys'; import {ArtifactOptions, booter} from '../types'; import {BaseArtifactBooter} from './base-artifact.booter'; const debug = debugFactory('loopback:boot:model-api'); @booter('modelApi') @extensionPoint(MODEL_API_BUILDER_PLUGINS) export class ModelApiBooter extends BaseArtifactBooter { constructor( @inject(CoreBindings.APPLICATION_INSTANCE) public app: ApplicationWithRepositories, @inject(BootBindings.PROJECT_ROOT) projectRoot: string, @extensions() public getModelApiBuilders: Getter, @config() public booterConfig: ArtifactOptions = {}, ) { // TODO assert that `app` has RepositoryMixin members super( projectRoot, // Set booter options if passed in via bootConfig Object.assign({}, RestDefaults, booterConfig), ); } /** * Load the the model config files */ async load(): Promise { // Important: don't call `super.load()` here, it would try to load // classes via `loadClassesFromFiles` - that won't work for JSON files await Promise.all( this.discovered.map(async f => { try { // It's important to await before returning, // otherwise the catch block won't receive errors await this.setupModel(f); } catch (err) { const shortPath = path.relative(this.projectRoot, f); err.message += ` (while loading ${shortPath})`; throw err; } }), ); } /** * Set up the loaded model classes */ async setupModel(configFile: string): Promise { const cfg: ModelApiConfig = require(configFile); debug( 'Loaded model config from %s', path.relative(this.projectRoot, configFile), cfg, ); const modelClass = cfg.model; if (typeof modelClass !== 'function') { throw new Error( `Invalid "model" field. Expected a Model class, found ${modelClass}`, ); } const builder = await this.getApiBuilderForPattern(cfg.pattern); await builder.build(this.app, modelClass, cfg); } /** * Retrieve the API builder that matches the pattern provided * @param pattern - name of pattern for an API builder */ async getApiBuilderForPattern(pattern: string): Promise { const allBuilders = await this.getModelApiBuilders(); const builder = allBuilders.find(b => b.pattern === pattern); if (!builder) { const availableBuilders = allBuilders.map(b => b.pattern).join(', '); throw new Error( `Unsupported API pattern "${pattern}". ` + `Available patterns: ${availableBuilders || ''}`, ); } return builder; } } /** * Default ArtifactOptions for ControllerBooter. */ export const RestDefaults: ArtifactOptions = { dirs: ['model-endpoints'], extensions: ['-config.js'], nested: true, };