ti-enxame.com

Manipulando erros de validação do Mongoose - onde e como?

Estou tentando decidir como quero lidar com erros de validação no Mongoose.

Mensagens de erro customizadas usando o validador de nó

Eu defini minhas próprias regras de validação usando validador de nó , por exemplo:

UserSchema.path('username')
  .validate(function (username) {
    return validator.check(username).notEmpty()
  }, 'Username cannot be blank')

O que gerará um erro parecido com este:

  username: 
   { message: 'Validator "Username cannot be blank" failed for path username',
     name: 'ValidatorError',
     path: 'username',
     type: 'Username cannot be blank' },

Usando o validador de mangusto

No entanto, o validador de nó fornece suas próprias mensagens de erro. Se eu usar o módulo mongoose-validator Node para conectar o validador de nó diretamente ao meu esquema, será possível usar essas mensagens de erro diretamente:

var UserSchema = new Schema({
  name: { type: String, validate: [validate('notEmpty')] }
});

O que gerará uma mensagem de erro parecida com:

  name: 
   { message: 'Validator "String is empty" failed for path name',
     name: 'ValidatorError',
     path: 'name',
     type: 'String is empty' } }

Também posso fornecer uma mensagem de erro personalizada aqui também:

var UserSchema = new Schema({
  name: { type: String, validate: [validate({message: 'Name cannot be blank' }, 'notEmpty')] }
});

Sinalizador required de mangusto

O Mongoose permite definir um campo conforme necessário:

var UserSchema = new Schema({
  name: { type: String, required: true }
});

O que gerará uma mensagem de erro parecida com:

  name: 
   { message: 'Validator "required" failed for path name',
     name: 'ValidatorError',
     path: 'name',
     type: 'required' } }

A questão

Parece que esses validadores desejam que você use suas mensagens de erro internas. Por exemplo, desejo declarar um campo como required, como visto acima, mas não consigo encontrar uma maneira de personalizar a mensagem de erro. E o módulo validador de mangusto não suportava mensagens personalizadas até muito recentemente, o que me faz pensar que elas são um anti-padrão no nível do modelo.

Qual é a melhor maneira de implementar esses validadores? Devo deixá-los gerar seus próprios erros e depois interpretá-los de alguma forma?

44
user1082754

Nesse ponto, parece lógico entender como o mangusto lida com os erros.

Você não gostaria que seus modelos manipulassem mensagens de erro. A camada de apresentação (controladores?) Deve contar com type para decidir qual é a melhor mensagem amigável a ser exibida (considerado i18n).

Há também o caso em que a validação pode ocorrer por sando um middleware . Nesse caso, a mensagem de erro que aparecerá no seu controlador é o que você passa para o retorno de chamada next().

Portanto, no caso do middleware, embora não documentado, para manter uma API de validação consistente em seus modelos, você deve usar diretamente os construtores Mongoose's Error:

var mongoose = require('mongoose');
var ValidationError = mongoose.Error.ValidationError;
var ValidatorError  = mongoose.Error.ValidatorError;

schema.pre('save', function (next) {
  if (/someregex/i.test(this.email)) {
    var error = new ValidationError(this);
    error.errors.email = new ValidatorError('email', 'Email is not valid', 'notvalid', this.email);
    return next(error);
  }

  next();
});

Dessa forma, você garante um tratamento consistente de erros de validação, mesmo que o erro de validação se origine de um middleware.

Para corresponder corretamente as mensagens de erro aos tipos, eu criaria um enum que atuaria como um mapa estático para todos os tipos possíveis:

// my controller.js

var ValidationErrors = {
  REQUIRED: 'required',
  NOTVALID: 'notvalid',
  /* ... */
};


app.post('/register', function(req, res){
  var user = new userModel.Model(req.body);

  user.save(function(err){
    if (err) {
      var errMessage = '';

      // go through all the errors...
      for (var errName in err.errors) {
        switch(err.errors[errName].type) {
          case ValidationErrors.REQUIRED:
            errMessage = i18n('Field is required');
            break;
          case ValidationErrors.NOTVALID:
            errMessage = i18n('Field is not valid');
            break;
        }
      }
      res.send(errMessage);

    }
  });
});
30
thanpolas

Eu sei que os plugins do validador provavelmente são úteis, mas acho que o material de validação do mangusto é mais intimidador do que realmente é complicado. Definitivamente, parece complicado do lado de fora, mas uma vez que você começa a usá-lo, não é tão ruim.

Se você verificar o código abaixo, verá um exemplo de como uma mensagem de erro personalizada pode ser retornada usando validadores internos.

Tudo o que você precisa fazer é definir um segundo parâmetro, com sua própria mensagem de erro personalizada, ao configurar seus campos.

Faça check-out dos campos required e minlength e maxlength abaixo para ver como configurei uma mensagem de erro personalizada e verifique os métodos abaixo para saber como o objeto de erro pode ser acessado ou enviado para o front end:

// Grab dependencies:
var mongoose = require('mongoose');

// Setup a schema:
var UserSchema = new mongoose.Schema (
    {
        username: {
            type: String,
            minlength: [2, 'Username must be at least 2 characters.'],
            maxlength: [20, 'Username must be less than 20 characters.'],
            required: [true, 'Your username cannot be blank.'],
            trim: true,
            unique: true,
            dropDups: true,
        }, // end username field
    },
    {
        timestamps: true,
    },
);

// Export the schema:
module.exports = mongoose.model('User', UserSchema);

O item acima configura nossos campos para receber mensagens de erro personalizadas. Mas como podemos acessá-los ou enviá-los para nosso front-end? Poderíamos ter o seguinte método configurado em nosso controlador de servidor, cujos dados de resposta são enviados de volta para angular:

var myControllerMethods = {
    register : function(req, res) {
        // Create a user based on the schema we created:
        User.create(req.body)
            .then(function(newUser) {
                console.log('New User Created!', newUser);
                res.json(newUser);
            })
            .catch(function(err) {
                if (err.name == 'ValidationError') {
                    console.error('Error Validating!', err);
                    res.status(422).json(err);
                } else {
                    console.error(err);
                    res.status(500).json(err);
                }
            })
    },
};

Se você executou o código acima e qualquer um dos nossos validadores de mangusto não passou, o objeto error (err) será capturado pelo .catch() na promessa. Se você registrar esse erro no console, verá que nesse objeto está a nossa mensagem personalizada, dependendo de qual erro foi sinalizado.

Nota: O exemplo acima é apenas para adicionar mensagens de validação personalizadas às validações já incorporadas que o Mongoose possui (como required, minlength, maxlength e assim por diante).

Se você deseja criar validações mais avançadas, como validar campos em relação a padrões regex ou similares, precisará criar funções customizadas validator.

Consulte a seção "Validadores personalizados" neste link para obter um ótimo exemplo de como adicionar um validador diretamente ao seu campo: http://mongoosejs.com/docs/validation.html .

Nota: Você também pode usar "ganchos de pré-salvamento" e "métodos de instância", mas isso está além do escopo desta pergunta e os validadores internos e "Validadores Personalizados" (link acima mencionado) são rotas mais fáceis.

Espero que isto ajude!

8
twknab

Do Mongoose: https://github.com/leepowellcouk/mongoose-validator

Mensagens de erro As mensagens de erro personalizadas agora estão de volta à 0.2.1 e podem ser definidas através do objeto de opções:

validate({message: "String should be between 3 and 50 characters"}, 'len', 3, 50)


Como eu implementei isso:

var emailValidator = [validate({message: "Email Address should be between 5 and 64 characters"},'len', 5, 64), validate({message: "Email Address is not correct"},'isEmail')];

var XXXX = new Schema({
email : {type: String, required: true, validate: emailValidator} }); 

Meu front-end lida com o exigido, portanto, nunca espero que o erro "obrigatório" do mangusto chegue ao usuário, mais como um protetor de back-end.

4
Matt Ritz

A pergunta que você precisa se perguntar é quem é responsável por causar o erro em primeiro lugar?

Se isso acontecer no seu sistema, sobre o qual você está no controle, deixe que os erros atinjam você como você normalmente faria e eliminem os bugs à medida que avança, mas suspeito que você esteja executando um aplicativo voltado para usuários do mundo real e você deseja higienizar suas entradas.

Eu recomendaria que o lado do cliente verifique se a entrada está correta antes de enviá-la ao servidor e mostre boas mensagens auxiliares como "Seu nome de usuário deve ter entre caracteres x e y".

No lado do servidor, você espera que, em 99% dos casos, a entrada venha diretamente do seu cliente de limpeza, e assim você ainda a valida usando as técnicas que você já sugeriu, mas se houver um erro, você simplesmente retornará uma mensagem de erro geral para a interface do usuário - desde que você confie que a interface do usuário mostraria mensagens auxiliares, o erro de validação deve ser causado por um bug ou tentativa de hacking.

Lembre-se de registrar todos os erros de validação no servidor, pois podem ser erros graves ou alguém procurando explorações.

3
ExxKA

Aviso: No Mongoose 4.1.3, a assinatura da função ValidatorError mudou completamente e as informações abaixo não são mais aplicáveis:

No Mongoose 3.8.12, a assinatura da função ValidatorError é:

function ValidatorError (path, msg, type, val) 

Onde o tipo pode ser "não válido" ou "obrigatório"

Por exemplo, se a validação do campo "email" gerar um erro de validação, você pode simplesmente:

var error = new ValidationError(this);
error.errors.email = 
      new ValidatorError('email', "Your err message.", 'notvalid', this.email);
2
salihcenap

Consulte o pacote hmv , que ajuda a personalizar os modelos de mensagem de erro do mangusto, incluindo erros de índice exclusivos:

template : {PATH_NAME} must be at least {MIN_LENGTH} characters long
schema   : { fullname : { type : String, min : 3, $name : 'Full name' } }
message  : Full name must be at least 3 characters long

template : {PATH_NAME} {VALUE} have been used, please choose another
schema   : { username : { type : String, unique : true } }
message  : username MrBean have been used, please choose another

E suporta especificamente a localização, ex em vietnamita:

template : {PATH_NAME} dài ít nhất {MIN_LENGTH} kí tự
schema   : { fullname : { type : String, min : 3, $name : 'tên tài khoản' } }
message  : Tên tài khoản dài ít nhất 5 kí tự

O ponto positivo é que você só precisa personalizar o modelo de mensagem uma vez, em vez de personalizar todos os campos de cada esquema na abordagem anterior.

1
Dinh Hoang

A partir do mangusto 4.5.0, o documento # invalidate retorna um ValidationError. Veja isto https://github.com/Automattic/mongoose/issues/3964

Além disso, ao tentar invalidar um gancho de consulta findOneAndUpdate, você pode:

// pass null because there is no document instance
let err = new ValidationError(null)
err.errors[path] = new ValidatorError({
    path: 'postalCode',
    message: 'postalCode not supported on zones',
    type: 'notvalid',
    value,
})
throw(err)
1
ztrange