Mongoose: La mejor manera de validar un ObjectId

Creado en 12 mar. 2014  ·  29Comentarios  ·  Fuente: Automattic/mongoose

Estoy insertando algunos objectIds como referencias y quiero asegurarme de que sean objectids válidos (en este momento tengo un bloqueo cuando no son IDs válidos).

Estoy comprobando si se refieren a objetos válidos simplemente haciendo una llamada a find().

¿Cómo debo verificar si las cadenas están en el formato correcto? He visto algunas expresiones regulares que verifican si la cadena es una cadena de 24 caracteres y cosas así, parece un truco. ¿Hay un validador interno en el ObjectId? No pude averiguar cómo usarlo. ¡Muchas gracias!

Comentario más útil

Esto funciona para mí:

var mangosta = require('./node_modules/mongoose');
consola.log(mangoose.Types.ObjectId.isValid);
// [Función: es válido]
console.log(mongoose.Types.ObjectId.isValid('53cb6b9b4f4ddef1ad47f943'));
// cierto
console.log(mongoose.Types.ObjectId.isValid('bleurgh'));
// falso

Todos 29 comentarios

Estoy teniendo el mismo problema. Probé mongoose.Schema.ObjectId.isValid() así como mongoose.Types.ObjectId.isValid() pero ninguna de esas propiedades tiene un método isValid. ¿Cómo terminaste resolviendo este problema? También veo que mongodb tiene uno y también hay expresiones regulares como otra opción. Preferiría no usar expresiones regulares ni tener que requerir ('mongodb')

Esto funciona para mí:

var mangosta = require('./node_modules/mongoose');
consola.log(mangoose.Types.ObjectId.isValid);
// [Función: es válido]
console.log(mongoose.Types.ObjectId.isValid('53cb6b9b4f4ddef1ad47f943'));
// cierto
console.log(mongoose.Types.ObjectId.isValid('bleurgh'));
// falso

Por cierto, el método en mongodb es el método bson lib, que solo verifica que no sea una cadena hexadecimal de 12 o 24 caracteres no nula; es una expresión regular como esta:

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

así que no puede ser demasiado raro si se usa allí

sValid() siempre devuelve True si la cadena contiene 12 letras

console.log(mangoose.Types.ObjectId.isValid("zzzzzzzzzzzz")); // cierto

Debido a que "zzzzzzzzzzzz" es técnicamente un ObjectId válido, la única característica definitoria de un ID de objeto es que tiene 12 bytes de longitud. Consulte mongodb/js-bson#112. Una cadena JS de longitud 12 tiene 12 bytes, rarezas módulo unicode. Si desea verificar una cadena hexadecimal de longitud 24, simplemente verifique que la cadena coincida con /^[a-fA-F0-9]{24}$/

"zzzzzzzzzzzz" no es un ObjectId válido. Por ejemplo, la lista de shell de Mongo (versión 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 el constructor ObjectId del shell mongodb está escrito de tal manera que solo acepta cadenas hexadecimales. Es una restricción en el shell mongo por conveniencia, no con el ObjectId de tipo BSON. Es cierto que este es un caso algo contrario a la intuición dado que las cadenas hexadecimales son la forma en que generalmente se representan los ObjectIds, pero si no le gusta, simplemente use la expresión regular /^[a-fA-F0-9]{24}$/ :)

¿Por qué obtenemos false cuando intentamos hacer isValid en un ObjectId en sí mismo y no en una cadena? ¿No debería ser verdadero ya que ObjectId es un ObjectId válido? Eso no tiene sentido, ¿tal vez llame a .toString() si es un objeto que se pasa a isValid ?

Los comentarios de @niftylettuce son bienvenidos en el n.º 3365. En este momento, solo nos remitimos a la función ObjectId.isValid() del paquete bson , que no se alinea exactamente con la forma en que la gente piensa en ObjectIds en mongoose. Sin embargo, abriré un PR para devolver verdadero si te dan un ObjectId, eso parece perfectamente razonable.

Volviendo a un problema antiguo aquí... @atcwells solution mongoose.Types.ObjectId.isValid('53cb6b9b4f4ddef1ad47f943') funciona lo suficientemente bien para mí, pero aún parece un poco extraño tener que hacer que mi controlador verifique si una ID de objeto es válida, cuando seguramente es un caso de uso bastante común querer poder enviar una identificación mal formada al servidor y que no se bloquee.

Idealmente, simplemente devolvería algo en err en la devolución de llamada para que podamos manejarlo correctamente y enviar el estado HTTP correcto usando nuestro controlador.

¿Hay algún caso de uso en el que esta funcionalidad no sea útil en el núcleo? Si no, tal vez podamos hacer un complemento. Hice una búsqueda rápida y no parece haber nada que haga el trabajo: https://github.com/CampbellSoftwareSolutions/mongoose-id-validator es para validar que la ID realmente existe, que no es lo que queremos hacer aquí; simplemente queremos asegurarnos de no generar un error no detectado.

En este momento, en mi controlador Express, cada vez que recibo una solicitud que tiene un ObjectId, por ejemplo, GET to https://myproject/organisations/ {id}, tengo que hacer algo como:

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

... antes de continuar con Organisation.findOne();

Parece bastante repetitivo. Estoy feliz de escribir un complemento o algo si alguien puede indicarme la dirección correcta de dónde comenzar. No parece un complemento, ya que no es realmente una cosa de esquema ...

@shankiesan , no necesita hacer eso, mongoose rechazará la promesa de consulta si la identificación no es válida.

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));

Producción:

$ 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, qué muppet he sido! Por supuesto, manejar la promesa siendo rechazada... arg, mi cerebro. Gracias @vkarpov15

Las devoluciones de llamada también funcionan si las promesas son demasiado dolor de cabeza MyModel.findOne({ _id: 'invalid' }).exec(error => console.error('error', error)); :)

NO UTILICE ObjectId.isValid() si aún no está claro a partir de los 12 bytes anteriores que dijo Valeri. Acabo de quemarme bastante bien con este:
ObjectId.isValid('The Flagship') === true

@atcwells si pudiera actualizar su comentario altamente votado para incluir esa parte, creo que otras personas podrían apreciarlo, ya que inicialmente lo estaba haciendo en función de lo que dijo: ObjectId.isValid('The Flagship') === true

La parte divertida es:
la siguiente declaración está devolviendo verdadero
mangosta.Types.ObjectId.isValid("Sudáfrica")

Lo que estoy haciendo (por ahora) es verificar el tipo de error en la captura de promesa. Si es 'ObjectId', me devuelven un 404. Devuelvo un 404 porque el consumidor de la API/servicio web no encontró el recurso o no existe.

Ver ejemplo:

  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);
      }
    });

ACTUALIZAR:
En lugar de agregar esto a cada controlador de ruta en el controlador. Estoy agregando al controlador global catch all.

Ver ejemplo:

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');
});

¿Es posible validar en el esquema? No es una mejor práctica repetir eso si, pero quiero registrar un evento de error de conversión.

¿Por qué mongoose no puede implementar la expresión regular /^[a-fA-F0-9]{24}$/ para isValid cuando solo se trata de MongoDB? Esto es tan confuso. Pasamos una hora depurando el problema y resultó ser algo tan tonto que nunca lo notará.

Recomendaría usar este paquete npm https://www.npmjs.com/package/valid-objectid
funciona perfectamente

He creado una solución para esto:

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 Creo que debe intentarse/capturarse, si el valor es "123", arrojará un error.

En caso de que ayude a alguien, he estado haciendo una adaptación del enfoque ObjectId.isValid() anterior. Porque en mi caso de uso, quiero poder obtener un recurso por su ID o su url-slug, por ejemplo:

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

... Descubrí que esto funciona bien en mi 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) => { ... }

En este caso, una cadena de 12 bytes sigue siendo un ID de objeto válido; buscarlo simplemente devuelve una matriz de longitud cero, en lugar de arrojar un error. Y debido a que estoy usando una consulta $or , luego busca por el slug de URL (mi otra opción posible).

Puede que no sea la solución más elegante, pero a mí me funciona.

@victorbadila sí, exacto. Solo di una pista. Edité mi comentario, en realidad solo coloca el código que realmente uso.

validator.js tiene un método isMongoId incorporado

Cada vez que uso mangosta, siempre lo extiendo con algunos 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);
};

Puede ejecutar esto como parte de los scripts de inicialización de su base de datos, de modo que los métodos estén siempre disponibles en su aplicación.

Si ObjectId.isValid(id) es verdadero, podemos juzgar el valor y la identificación de (new ObjectId(id).toString()).

const mongoose = require('mongoose');
const {Types: {ObjectId}} = mongoose;
const validateObjectId = (id) => ObjectId.isValid(id) && (new ObjectId(id)).toString() === id;
¿Fue útil esta página
0 / 5 - 0 calificaciones