model-helper.js 4.08 KB
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
// Node module: loopback-swagger
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

'use strict';

/**
 * Module dependencies.
 */
var schemaBuilder = require('./schema-builder');
var typeConverter = require('./type-converter');
var TypeRegistry = require('./type-registry');

/**
 * Export the modelHelper singleton.
 */
var modelHelper = module.exports = {
  /**
   * Given a class (from remotes.classes()), generate a model definition.
   * This is used to generate the schema at the top of many endpoints.
   * @param  {Class} modelClass Model class.
   * @param {TypeRegistry} typeRegistry Registry of types and models.
   * @return {Object} Associated model definition.
   */
  registerModelDefinition: function(modelCtor, typeRegistry) {
    var lbdef = modelCtor.definition;

    if (!lbdef) {
      // The model does not have any definition, it was most likely
      // created as a placeholder for an unknown property type
      return;
    }

    var name = lbdef.name;
    if (typeRegistry.isDefined(name)) {
      // The model is already included
      return;
    }

    typeRegistry.registerModel(name, function() {
      return definitionFunction(modelCtor, typeRegistry);
    });
  },

  isHiddenProperty: function(definition, propName) {
    return definition.settings &&
      Array.isArray(definition.settings.hidden) &&
      definition.settings.hidden.indexOf(propName) !== -1;
  },
};

var definitionFunction = function(modelCtor, typeRegistry) {
  var lbdef = modelCtor.definition;

  var swaggerDef = {
    description: typeConverter.convertText(
      lbdef.description || (lbdef.settings && lbdef.settings.description)),
    properties: {},
    required: [],
  };
  addSwaggerExtensions(lbdef.settings);

  var properties = lbdef.rawProperties || lbdef.properties;

  // Iterate through each property in the model definition.
  // Types may be defined as constructors (e.g. String, Date, etc.),
  // or as strings; swaggerSchema.buildFromLoopBackType() will take
  // care of the conversion.
  Object.keys(properties).forEach(function(key) {
    var prop = properties[key];

    // Hide hidden properties.
    if (modelHelper.isHiddenProperty(lbdef, key))
      return;

    // Get a type out of the constructors we were passed.
    var schema = schemaBuilder.buildFromLoopBackType(prop, typeRegistry);

    var desc = typeConverter.convertText(prop.description || prop.doc);
    if (desc) schema.description = desc;

    // Required props sit in a per-model array.
    if (prop.required || (prop.id && !prop.generated)) {
      swaggerDef.required.push(key);
    }

    // Assign the schema to the properties object.
    swaggerDef.properties[key] = schema;
  });

  if (lbdef.settings) {
    var strict = lbdef.settings.strict;
    var additionalProperties = lbdef.settings.additionalProperties;
    var notAllowAdditionalProperties = strict || (additionalProperties !== true);
    if (notAllowAdditionalProperties) {
      swaggerDef.additionalProperties = false;
    }
  }

  if (!swaggerDef.required.length) {
    // "required" must have at least one item when present
    delete swaggerDef.required;
  }

  // Add models from settings
  if (lbdef.settings && lbdef.settings.models) {
    for (var m in lbdef.settings.models) {
      var model = modelCtor[m];
      if (typeof model !== 'function' || !model.modelName) continue;
      modelHelper.registerModelDefinition(model, typeRegistry);
      typeRegistry.reference(model.modelName);
    }
  }

  // Generate model definitions for related models
  /* eslint-disable one-var */
  for (var r in modelCtor.relations) {
    var rel = modelCtor.relations[r];
    if (rel.modelTo) {
      modelHelper.registerModelDefinition(rel.modelTo, typeRegistry);
    }
    if (rel.modelThrough) {
      modelHelper.registerModelDefinition(rel.modelThrough, typeRegistry);
    }
  }
  /* eslint-enable one-var */
  return swaggerDef;

  function addSwaggerExtensions(defs) {
    for (var def in defs) {
      if (def.match(/^x\-/)) {
        swaggerDef[def] = defs[def];
      }
    }
  };
};