Mongoose: Melhor maneira de validar um ObjectId

Criado em 12 mar. 2014  ·  29Comentários  ·  Fonte: Automattic/mongoose

Estou inserindo alguns objectIds como referências e quero ter certeza de que são objectids válidos (no momento estou tendo uma falha quando não são ids válidos).

Estou verificando se eles se referem a objetos válidos apenas fazendo uma chamada find().

Como devo verificar se as strings estão no formato correto? Eu vi alguns regexs que verificam se a string é uma string de 24 caracteres e coisas assim - parece um hack. Existe um validador interno no ObjectId? Não consegui descobrir como usá-lo. Muito obrigado!

Comentários muito úteis

Isso funciona para mim:

var mangusto = require('./node_modules/mongoose');
console.log(mongoose.Types.ObjectId.isValid);
// [Função: éválido]
console.log(mongoose.Types.ObjectId.isValid('53cb6b9b4f4ddef1ad47f943'));
// verdadeiro
console.log(mongoose.Types.ObjectId.isValid('bleurgh'));
// falso

Todos 29 comentários

Estou tendo o mesmo problema. Eu tentei mongoose.Schema.ObjectId.isValid() assim como mongoose.Types.ObjectId.isValid() mas nenhuma dessas propriedades tem um método isValid. Como você acabou resolvendo esse problema? Também vejo que o mongodb tem um e também há regex como outra opção. Eu preferiria não usar regex nem exigir ('mongodb')

Isso funciona para mim:

var mangusto = require('./node_modules/mongoose');
console.log(mongoose.Types.ObjectId.isValid);
// [Função: éválido]
console.log(mongoose.Types.ObjectId.isValid('53cb6b9b4f4ddef1ad47f943'));
// verdadeiro
console.log(mongoose.Types.ObjectId.isValid('bleurgh'));
// falso

Aliás, o método no mongodb é o método bson lib, que apenas verifica se não é nulo, 12 ou 24 caracteres hexadecimais - é um regex assim:

var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");

então não pode ser muito hacky se for usado lá

sValid() sempre retorna True se a string contiver 12 letras

console.log(mongoose.Types.ObjectId.isValid("zzzzzzzzzzzz")); // verdadeiro

Porque "zzzzzzzzzzzz" é tecnicamente um ObjectId válido - o único recurso de definição de um id de objeto é que tem 12 bytes de comprimento. Veja mongodb/js-bson#112. Uma string JS de comprimento 12 tem 12 bytes, estranhezas de módulo unicode. Se você quiser verificar se há uma string hexadecimal de 24 comprimentos, basta verificar se a string corresponde /^[a-fA-F0-9]{24}$/

"zzzzzzzzzzzz" não é um ObjectId válido. Por exemplo, listagem de shell Mongo (versão mongodb - 3.0.2):

> ObjectId('zzzzzzzzzzzz')
2015-04-29T18:05:20.705+0300 E QUERY    Error: invalid object id: length
    at (shell):1:1
> ObjectId('zzzzzzzzzzzzzzzzzzzzzzzz')
2015-04-29T18:06:09.773+0300 E QUERY    Error: invalid object id: not hex
    at (shell):1:1
> ObjectId('ffffffffffff')
2015-04-29T18:09:17.303+0300 E QUERY    Error: invalid object id: length
    at (shell):1:1
> ObjectId('ffffffffffffffffffffffff')
ObjectId("ffffffffffffffffffffffff")

Porque o construtor ObjectId do shell mongodb é escrito de tal forma que só aceita strings hexadecimais. É uma restrição no shell mongo por conveniência, não com o tipo BSON ObjectId. É certo que este é um caso um pouco contra-intuitivo, uma vez que as strings hexadecimais são como os ObjectIds geralmente são representados, mas se você não gostar, basta usar o regex /^[a-fA-F0-9]{24}$/ :)

Por que ficamos falsos quando tentamos fazer isValid em um ObjectId em si e não em uma String? Isso não deveria retornar true já que ObjectId é um ObjectId válido? Isso não faz sentido -- talvez chame .toString() se for um objeto sendo passado para isValid ?

Comentários de @niftylettuce são bem-vindos em #3365. Agora nós apenas adiamos para a função ObjectId.isValid() do pacote bson , que não se alinha exatamente com como as pessoas pensam sobre ObjectIds no mangusto. Vou abrir um PR para retornar true se você receber um ObjectId, isso parece perfeitamente razoável.

Voltando a um problema antigo aqui ... @atcwells solução mongoose.Types.ObjectId.isValid('53cb6b9b4f4ddef1ad47f943') funciona bem o suficiente para mim, mas ainda parece um pouco complicado ter que fazer meu controlador verificar se um ID de objeto é válido - quando certamente é um caso de uso bastante comum querer enviar um ID malformado para o servidor e não travar.

Idealmente, ele simplesmente retornaria algo em err no retorno de chamada para que pudéssemos lidar com isso corretamente e enviar o status HTTP correto usando nosso controlador.

Existe um caso de uso em que isso não seria uma funcionalidade útil no núcleo? Se não, talvez possamos fazer um plugin. Eu fiz uma pesquisa rápida e não parece haver nada que faça o trabalho - https://github.com/CampbellSoftwareSolutions/mongoose-id-validator é para validar que o ID realmente existe, o que não é o que queremos fazer aqui – simplesmente queremos ter certeza de que não geramos um erro não detectado.

No momento, no meu controlador Express, toda vez que eu faço uma solicitação que tenha um ObjectId, por exemplo, GET to https://myproject/organisations/ {id}, tenho que fazer algo como:

if( !mongoose.Types.ObjectId.isValid(id) ){
    return res.sendStatus(400); // They didn't send an object ID
  }

... antes de fazer Organisation.findOne();

Parece bastante clichê. Fico feliz em escrever um plugin ou algo assim se alguém puder me apontar na direção certa de onde começar. Não parece um plugin, pois não é realmente uma coisa de esquema ...

@shankiesan você não precisa fazer isso, o mangusto rejeitará a promessa de consulta se o id for inválido.

var assert = require('assert');
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');
mongoose.set('debug', true);

var MyModel = mongoose.model('test', new Schema({ name: String }));

MyModel.findOne({ _id: 'invalid' }).exec().catch(error => console.error('error', error));

Saída:

$ node gh-1959.js 
error { CastError: Cast to ObjectId failed for value "invalid" at path "_id"
    at MongooseError.CastError (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/error/cast.js:19:11)
    at ObjectId.cast (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/schema/objectid.js:147:13)
    at ObjectId.castForQuery (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/schema/objectid.js:187:15)
    at cast (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/cast.js:174:32)
    at Query.cast (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/query.js:2563:10)
    at Query.findOne (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/query.js:1239:10)
    at /home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/query.js:2163:21
    at new Promise.ES6 (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/promise.js:45:3)
    at Query.exec (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/query.js:2156:10)
    at Object.<anonymous> (/home/val/Workspace/10gen/troubleshoot-mongoose/gh-1959.js:10:37)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)
  message: 'Cast to ObjectId failed for value "invalid" at path "_id"',
  name: 'CastError',
  kind: 'ObjectId',
  value: 'invalid',
  path: '_id',
  reason: undefined }
^C
$ 

Ah, que muppet eu fui! Claro, lide com a promessa sendo rejeitada... arg, meu cérebro. Obrigado @vkarpov15

Os retornos de chamada também funcionam se as promessas forem uma grande dor de cabeça MyModel.findOne({ _id: 'invalid' }).exec(error => console.error('error', error)); :)

NÃO USE ObjectId.isValid() se ainda não estiver claro a partir do que Valeri disse acima de 12 bytes. Acabei de ser queimado muito bem por este:
ObjectId.isValid('The Flagship') === true

@atcwells , se você pudesse atualizar seu comentário altamente votado para incluir essa parte, acho que outras pessoas podem apreciá-lo, já que eu estava fazendo isso inicialmente com base no que você disse: ObjectId.isValid('The Flagship') === true

A parte engraçada é:
a seguinte declaração está retornando true
mangusto.Types.ObjectId.isValid("África do Sul")

O que estou fazendo (por enquanto) é verificar o tipo de erro na captura de promessa. Se for 'ObjectId', recebo um 404. Estou retornando um 404 porque para o consumidor da API/serviço da Web o recurso não foi encontrado ou não existe.

Consultar exemplo:

  Widget.findByIdAndRemove(resourceId)
    .then((result) => {
      if (!result) {
        let error = new Error('Resource not found');
        error.status = 404;
        next(error);
      } else {
        res.redirect(303, '/');
      }
    })
    .catch((error) => {
      if (error.kind === 'ObjectId') {
        let error = new Error('Resource not found');
        error.status = 404;

        next(error);
      } else {
        next(error);
      }
    });

ATUALIZAR:
Em vez de adicionar isso a cada manipulador de rota no controlador. Estou adicionando ao manipulador global catch all.

Consultar exemplo:

app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  if (err.kind === 'ObjectId') {
    err.status = 404;
  }

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

É possível validar no esquema? Não é uma prática recomendada repetir isso se, mas eu quero registrar o evento de erro de conversão.

Por que o mongoose não pode implementar o regex /^[a-fA-F0-9]{24}$/ para isValid quando está lidando apenas com o MongoDB. Isso é tão confuso. Acabamos de passar uma hora depurando o problema e acabou sendo uma coisa tão boba que você nunca notará

Eu recomendaria usar este pacote npm https://www.npmjs.com/package/valid-objectid
Funciona perfeitamente

Eu criei uma solução para isso:

function isObjectId(value) {
  try {
      const { ObjectId } = mongoose.Types;
      const asString = value.toString(); // value is either ObjectId or string or anything
      const asObjectId = new ObjectId(asString);
      const asStringifiedObjectId = asObjectId.toString();
      return asString === asStringifiedObjectId;
    } catch (error) {
      return false;
    }
}

@mstmustisnt Acho que precisa ser try/catch, se o valor for "123", isso gerará um erro.

Caso ajude alguém, venho fazendo uma adaptação da abordagem ObjectId.isValid() acima. Porque no meu caso de uso, quero poder obter um recurso por seu ID ou seu url-slug, por exemplo:

GET /users/59f5e7257a92d900168ce49a ... ou ...
GET /users/andrew-shankie

...Descobri que isso funciona bem no meu controlador:

  const { id } = req.params;

  const query = {
    $or: [{ slug: id }],
  };

  if (mongoose.Types.ObjectId.isValid(id)) query.$or.push({ _id: id });

  User.findOne(query)
    .exec((err, user) => { ... }

Nesse caso, uma string de 12 bytes ainda é uma ID de objeto válida, procurando por ela simplesmente retorna uma matriz de comprimento zero, em vez de gerar um erro. E como estou usando uma consulta $or , ela pesquisa pelo slug de URL (minha outra opção possível).

Pode não ser a solução mais elegante, mas funciona para mim.

@victorbadila sim, exatamente. Eu só dei uma dica. Editei meu comentário, na verdade apenas coloca o código que eu realmente uso.

validator.js tem um método isMongoId embutido

Sempre que uso o mangusto, sempre o estendo com alguns métodos auxiliares estáticos:

const mongoose = require('mongoose');
const {Types: {ObjectId}} = mongoose;

//Helper to check if an ID is an object ID
mongoose.isObjectId = function(id) {
  return (id instanceof ObjectId);
};

//Helper to validate a string as object ID
mongoose.isValidObjectId = function(str) {
  if (typeof str !== 'string') {
    return false;
  }
  return str.match(/^[a-f\d]{24}$/i);
};

Você pode executar isso como parte de seus scripts de inicialização do banco de dados, para que os métodos estejam sempre disponíveis em seu aplicativo.

Se ObjectId.isValid(id) for true, podemos julgar o valor e o id de (new ObjectId(id).toString()).

const mongoose = require('mongoose');
const {Types: {ObjectId}} = mongoose;
const validateObjectId = (id) => ObjectId.isValid(id) && (new ObjectId(id)).toString() === id;
Esta página foi útil?
0 / 5 - 0 avaliações