text: // Copyright IBM Corp. 2015,2019. 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');
  var _ = require('lodash');
  
  /**
   * 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, opts) {
      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);
      });
  
      // check if ModelClass has defined getUpdateOnlyProperties() function to
      // avoid version issues
      if (opts && opts.generateOperationScopedModels && modelCtor.getUpdateOnlyProperties) {
        const excludeProps = modelCtor.getUpdateOnlyProperties();
        // if at least one excludeProp is found, we need to create another model
        // to be used only for create operation and this model will not have
        // excludeProp included in the model. e.g generated "id" property
        if (excludeProps && excludeProps.length > 0) {
          const modelName = '$new_' + name;
          typeRegistry.registerModel(modelName, function() {
            return definitionFunction(modelCtor, typeRegistry, {excludeProps});
          });
        }
      }
  
      if (opts && opts.generateRelationProperties) {
        const modelNameWithRelations = name + 'WithRelations';
        typeRegistry.registerModel(modelNameWithRelations, function() {
          return definitionFunction(modelCtor, typeRegistry, {includeRelations: true});
        });
      }
    },
  
    isHiddenProperty: function(definition, propName) {
      return definition.settings &&
        Array.isArray(definition.settings.hidden) &&
        definition.settings.hidden.indexOf(propName) !== -1;
    },
  };
  
  var definitionFunction = function(modelCtor, typeRegistry, options) {
    var lbdef = modelCtor.definition;
  
    var swaggerDef = {
      description: typeConverter.convertText(
        lbdef.description || (lbdef.settings && lbdef.settings.description)
      ),
      properties: {},
      required: [],
    };
  
    if (lbdef.settings && lbdef.settings.swagger && lbdef.settings.swagger.example) {
      swaggerDef.example = lbdef.settings.swagger.example;
    }
  
    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);
      }
  
      // if model has excludeProps properties, skip adding to the model since this
      // model is used for create operation only where this property
      // should not exist
      if (options && options.excludeProps && _.includes(options.excludeProps, key)) {
        return;
      }
      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);
      }
  
      var prop = null;
      var relationOptions = rel.options || {};
  
      if (options && !!options.includeRelations) {
        if (!relationOptions.disableInclude) {
          const modelNameWithRelations = rel.modelTo.modelName + 'WithRelations';
          // now we make it look like a loopback property definition
          switch (rel.type) {
            case 'hasMany':
            case 'hasManyThrough':
            case 'hasAndBelongsToMany':
            case 'embedsMany':
            case 'referencesMany':
              prop = [modelNameWithRelations];
              break;
            case 'hasOne':
            case 'belongsTo':
            case 'embedsOne':
              prop = modelNameWithRelations;
              break;
            default:
              // there above were all relation types covered in the docs
              // so we shouldn't be here -- log some error?
          }
  
          if (prop) {
            // Get a type out of the constructors we were passed.
            var schema = schemaBuilder.buildFromLoopBackType(prop, typeRegistry, options);
  
            // Assign the schema to the properties object.
            swaggerDef.properties[r] = schema;
          }
        }
      }
    }
    /* eslint-enable one-var */
    return swaggerDef;
  
    function addSwaggerExtensions(defs) {
      for (var def in defs) {
        if (def.match(/^x\-/)) {
          swaggerDef[def] = defs[def];
        }
      }
    };
  };
  
  