Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge validators from schemaType.prototype.validate definition and schemaType.set #14070

Closed
2 tasks done
chumager opened this issue Nov 9, 2023 · 3 comments
Closed
2 tasks done
Labels
help This issue can likely be resolved in GitHub issues. No bug fixes, features, or docs necessary priority Automatically set for Mongoose Pro subscribers Stale
Milestone

Comments

@chumager
Copy link

chumager commented Nov 9, 2023

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the feature has not already been requested

🚀 Feature Proposal

Hi, As validate attribute in a schemaType could be Function|Array|Object, and you can set it via schemaType.set I'll be nice to merge both definitions.

Motivation

I want to add a validation set for the schemaType Array (I need this for several types with different validations), to check min and max elements. but if I add other validator in the schemaType definition, it get overwritten.

For now my only choice is to create a plugin who searches in all paths and check if there is a min or max option in an "Array" instance and add a pre or post validate hook to the schema or merge the default validators with the ones declared in Schema, either way Schema.prototype.validate ain't accepts arrays

Example

import db from "mongoose";
await db.connect("mongodb://127.0.0.1/test");
//SchemaType.prototype.validate() can't get an array to set several validators but Schema.Types.set("validate", ...) can
//Comment this plugin to check the problem.
db.plugin(schema => {
  schema.eachPath((path, schemaType) => {
    if (schemaType.validators !== schemaType.constructor.defaultOptions.validate) {
      const validators = [
        ...new Set([].concat(schemaType.constructor.defaultOptions.validate ?? [], schemaType.validators ?? []))
      ];
      schemaType.validators = validators;
    }
  });
});
//custom validators for type Array
db.Schema.Types.Array.set("validate", [
  {
    validator(val, rest) {
      const {options} = this.constructor.schema.path(rest.path);
      if (options.min) {
        if (val.length < options.min) throw `El campo ${rest.path} debe tener al menos ${options.min} elementos`;
      }
      return true;
    },
    propsParameter: true
  },
  {
    validator(val, rest) {
      const {options} = this.constructor.schema.path(rest.path);
      if (options.max) {
        if (val.length > options.max) throw `El campo ${rest.path} debe tener a lo más ${options.max} elementos`;
      }
      return true;
    },
    propsParameter: true
  }
]);
const Schema = new db.Schema({
  a: {
    type: [String],
    min: 2,
    max: 3,
    validate: {
      validator(v) {
        return v.includes("test3");
      },
      message: "el campo {PATH} no incluye 'test3'"
    }
  }
});
const model = db.model("model", Schema);
//console.log("validators", Schema.path("a").validators);                                                                              
let doc;                                                                                                                               
const a = [];                                                                                                                          
for (let i = 1; i <= 5; i++) {                                                                                                         
  a.push("test" + i);                                                                                                                  
  doc = new model({a});                                                                                                                
  console.log(doc);                                                                                                                    
  const error = doc.validateSync();                                                                                                    
  console.log(error?.errors?.a?.reason ?? error?.errors?.a?.message ?? "Sin errores");                                                 
}                                                                                                                                      
db.disconnect();                                                                                                                       
@chumager chumager added enhancement This issue is a user-facing general improvement that doesn't fix a bug or add a new feature new feature This change adds new functionality, like a new method or class labels Nov 9, 2023
@mongoose-pro-bot mongoose-pro-bot added the priority Automatically set for Mongoose Pro subscribers label Nov 9, 2023
@vkarpov15 vkarpov15 added this to the 7.6.5 milestone Nov 14, 2023
@vkarpov15
Copy link
Collaborator

Does the following? It outputs the same result. Types.Array.set() is meant to set a default option, so overwriting is expected behavior.

import db from "mongoose";
await db.connect("mongodb://127.0.0.1/test");

const defaultValidators = [
  {
    validator(val, rest) {
      const {options} = this.constructor.schema.path(rest.path);
      if (options.min) {
        if (val.length < options.min) throw `El campo ${rest.path} debe tener al menos ${options.min} elementos`;
      }
      return true;
    },
    propsParameter: true
  },
  {
    validator(val, rest) {
      const {options} = this.constructor.schema.path(rest.path);
      if (options.max) {
        if (val.length > options.max) throw `El campo ${rest.path} debe tener a lo más ${options.max} elementos`;
      }
      return true;
    },
    propsParameter: true
  }
];

db.plugin(schema => {
  schema.eachPath((path, schemaType) => {
    defaultValidators.forEach(v => schemaType.validate(v));
  });
});

const Schema = new db.Schema({
  a: {
    type: [String],
    min: 2,
    max: 3,
    validate: {
      validator(v) {
        return v.includes("test3");
      },
      message: "el campo {PATH} no incluye 'test3'"
    }
  }
});
const model = db.model("model", Schema);
//console.log("validators", Schema.path("a").validators);                                                                              
let doc;                                                                                 
const a = [];                                                                            
for (let i = 1; i <= 5; i++) {                                                           
  a.push("test" + i);                                                                    
  doc = new model({a});                                                                  
  console.log(doc);                                                                      
  const error = doc.validateSync();                                                      
  console.log(error?.errors?.a?.reason ?? error?.errors?.a?.message ?? "Sin errores");   
}                                                                                        
db.disconnect();

Output:

$ node gh-14070.mjs 
{ a: [ 'test1' ], _id: new ObjectId('6553cad536df2a1c876da746') }
el campo a no incluye 'test3'
{
  a: [ 'test1', 'test2' ],
  _id: new ObjectId('6553cad536df2a1c876da747')
}
el campo a no incluye 'test3'
{
  a: [ 'test1', 'test2', 'test3' ],
  _id: new ObjectId('6553cad536df2a1c876da748')
}
Sin errores
{
  a: [ 'test1', 'test2', 'test3', 'test4' ],
  _id: new ObjectId('6553cad536df2a1c876da749')
}
El campo a debe tener a lo más 3 elementos
{
  a: [ 'test1', 'test2', 'test3', 'test4', 'test5' ],
  _id: new ObjectId('6553cad536df2a1c876da74a')
}
El campo a debe tener a lo más 3 elementos

@vkarpov15 vkarpov15 added help This issue can likely be resolved in GitHub issues. No bug fixes, features, or docs necessary and removed new feature This change adds new functionality, like a new method or class enhancement This issue is a user-facing general improvement that doesn't fix a bug or add a new feature labels Nov 14, 2023
@vkarpov15 vkarpov15 removed this from the 7.6.5 milestone Nov 14, 2023
@chumager
Copy link
Author

Hi, I know the set is to set a default option, but I think this is a special, case... if the Schema definition sets all the default options like a merge, then the strings, booleans, and functions will use the "last" definition, and objects and arrays will be merged.

Maybe I'm too used to "vue mixin way" to define defaults.

Regards.

@vkarpov15 vkarpov15 added this to the 8.0.2 milestone Nov 19, 2023
@vkarpov15 vkarpov15 modified the milestones: 8.0.2, 8.1 Nov 28, 2023
vkarpov15 added a commit that referenced this issue Nov 28, 2023
feat(schematype): merge rather than overwrite default schematype validators
Copy link

This issue is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 5 days

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help This issue can likely be resolved in GitHub issues. No bug fixes, features, or docs necessary priority Automatically set for Mongoose Pro subscribers Stale
Projects
None yet
Development

No branches or pull requests

3 participants