不保存为新文档的情况下,根据Mongoose模式验证对象。

19

我试图验证一些将插入到新文档中的数据,但在许多其他事情发生之前。所以我打算添加一个函数到静态方法,希望可以根据模型架构验证数组中的对象。

目前为止的代码:

module.exports = Mongoose => {
    const Schema = Mongoose.Schema

    const peopleSchema = new Schema({
        name: {
            type: Schema.Types.String,
            required: true,
            minlength: 3,
            maxlength: 25
        },
        age: Schema.Types.Number
    })

    /**
     * Validate the settings of an array of people
     *
     * @param   {array}     people  Array of people (objects)
     * @return  {boolean}
     */
    peopleSchema.statics.validatePeople = function( people ) {
        return _.every(people, p => {
            /**
             * How can I validate the object `p` against the peopleSchema
             */
        })
    }

    return Mongoose.model( 'People', peopleSchema )
}
所以,peopleSchema.statics.validatePeople 是我试图进行验证的地方。我已经阅读了mongoose的验证文档,但它没有说明如何在不保存数据的情况下对模型进行验证。
这是可能的吗?

更新

这里的一个答案指向了我正确的验证方法,似乎可以正常工作,但现在它抛出了一个Unhandled rejection ValidationError
这是用于验证数据(无需插入)的静态方法。
peopleSchema.statics.testValidate = function( person ) {
    return new Promise( ( res, rej ) => {
        const personObj = new this( person )

        // FYI - Wrapping the personObj.validate() in a try/catch does NOT suppress the error
        personObj.validate( err => {
            if ( err ) return rej( err )

            res( 'SUCCESS' )
        } )
    })
}

接下来是我测试它的过程:

People.testValidate( { /* Data */ } )
    .then(data => {
        console.log('OK!', data)
    })
    .catch( err => {
        console.error('FAILED:',err)
    })
    .finally(() => Mongoose.connection.close())

测试不符合模式规则的数据会抛出错误,正如您所见,我试图捕获它,但似乎没有起作用。

P.S. 我正在使用Bluebird作为我的承诺库。


看起来是一个与https://dev59.com/c14b5IYBdhLWcg3w7Fgv相似的问题。 - JohnnyHK
@Justin,你是怎么解决这个问题的?我也在尝试同样的操作,但是遇到了你在评论中提到的UnhandledPromiseRejectionWarning错误。 - Avani Khabiya
3个回答

5

可以通过自定义验证器来实现。在验证失败时,无法将文档保存到数据库中。

var peopleSchema = new mongoose.Schema({
        name: String,
        age: Number
    });
var People = mongoose.model('People', peopleSchema);

peopleSchema.path('name').validate(function(n) {
    return !!n && n.length >= 3 && n.length < 25;
}, 'Invalid Name');

function savePeople() {
    var p = new People({
        name: 'you',
        age: 3
    });

    p.save(function(err){
        if (err) {
             console.log(err);           
         }
        else
            console.log('save people successfully.');
    });
}

或者,您可以通过使用与您定义的相同模式的validate()来完成此操作。

var p = new People({
    name: 'you',
    age: 3
});

p.validate(function(err) {
    if (err)
        console.log(err);
    else
        console.log('pass validate');
});

我说“不保存”,所以第一个选项不行,除非你假设数据无效,但我们并不知道(这就是验证的作用..)。第二个选项看起来更合适。我会试一下! - Justin
我可以让验证工作,但是它不仅仅返回false,而是抛出一个错误:Unhandled rejection ValidationError: Person validation failed。我尝试在验证方法上使用try/catch,但似乎这并没有起作用。我认为在Mongoose中有一种处理这种情况的方法。 - Justin
我更新了工单并添加了更多的数据。到目前为止,非常感谢您! - Justin
@Justin,第一个方法中的save()方法在if (err)中捕获验证错误而不保存此记录。我猜这可以满足您的要求。如果我错了,请纠正我。 - zangw
1
zangw - 这里不应该有保存方法,这只是为了验证而已,完全不涉及保存。我需要验证某些对象是否与模型架构兼容(不保存),以便在进行一系列其他查询/保存/更新之前进行验证。一旦这些操作都成功了,我才能保存这些数据。但是,在进行任何其他先决条件的查询/保存之前,我需要根据模式验证数据... 这种验证只是一些其他操作的先决条件,不是为了插入/保存文档 - Justin
@Justin,我已经测试了你的代码,链接在这里,在我的测试中似乎运行良好...异常可以在catch()中捕获... - zangw

4
根据mongoose文档(https://mongoosejs.com/docs/validation.html),你可以使用doc.validate(callback)doc.validateSync() 进行验证。
区别在于,validateSync() 的名称表明无需使用await,如果验证失败则返回错误,否则返回undefined
例如:
const model = new Model({somedata:somedata})

const validatedModel = model.validateSync()
if(!!validatedModel) throw validatedModel

你如何使用这个方法?你在回调函数中放什么?是否有一个例子,如果验证失败,则通过console.log打印错误? - Aaron Franke
1
@AaronFranke说,如果你有一个格式为const model = new Model({some data})的模型,你可以使用const validator = model.validateSync()来验证创建的模型。如果它无效,它将返回一个错误类型,如果它是有效的模型,则返回未定义。所以基本上你可以这样使用它: if(!!validator) throw validator - fafa.mnzm

1
我编写了下面的函数,它完全不需要模型,你只需要传递一个对象和Mongoose架构,无论是作为文档还是子文档。子文档也会被递归检查:
const validateObjectAgainstMongooseSchema = ({checkObject, mongooseSchema, currentPath = "object", checkDate} = {}) => {
    if (!checkDate) throw new Error("checkDate has to be provided");

    const errors = [];
    const schemaKeys = Object.keys(mongooseSchema.obj);

    // Check type of provided values
    for (const key of schemaKeys) {
        const checkObjectType = Array.isArray(checkObject[key]) ? "array" : typeof checkObject[key];

        const mongoosePath = mongooseSchema.path(key);

        // If path doesn't exist in schema, jump it
        if (!mongoosePath) continue;

        const mongooseType = mongoosePath.instance.toLowerCase();
        const mongooseRequired = mongoosePath.isRequired;

        let valid = mongooseType === checkObjectType;

        if ((checkObject[key] === undefined || checkObject[key] === null) && !mongooseRequired) {
            // If value undefined and path not required, skip validation
            continue;
        } else if (!checkObject[key] && mongooseRequired) {
            // If value undefined and path required, save error
            errors.push(`${currentPath}.${key} is required but got ${checkObject[key]}`);
            continue;
        } else if ((checkObjectType === "string" || checkObject[key]?.toISOString) && mongooseType === "date") {
            // Check if value is a date disguised as a string
            if (checkDate(checkObject[key])) valid = true;
        } else if (checkObjectType === "object") {
            // If we still have an object, we must have a subschema
            errors.push(
                ...validateObjectAgainstMongooseSchema({
                    checkObject: checkObject[key],
                    mongooseSchema: mongooseSchema.path(key).schema,
                    currentPath: `${currentPath}.${key}`,
                    checkDate: checkDate
                })
            );
            continue;
        }

        if (!valid) {
            errors.push(`${currentPath}.${key} should be of type ${mongooseType} but got ${checkObjectType}`);
        }
    }

    return errors;
};

使用以下模式时:


const schema = new mongoose.Schema({
        stringType: {
            type: String
        },
        numberType: {
            type: Number
        },
        dateType: {
            type: Date
        },
        boolType: {
            type: Boolean
        },
        arrayType: {
            type: Array
        },
        schemaType: {
            type: new mongoose.Schema({
                embeddedDate: {
                    type: Date
                },
                embeddedBool: {
                    type: Boolean
                }
            })
        }
    });

以下代码会返回一个空数组。
const errors = schemaUtils.helpers.validateObjectAgainstMongooseSchema({
            checkObject: {
                stringType: "test",
                numberType: 2,
                dateType: new Date("2020-01-01"),
                boolType: true,
                arrayType: ["test", "it"],
                schemaType: {embeddedDate: new Date("2020-01-02"), embeddedBool: true}
            },
            mongooseSchema: schema
        });

和这个

const errors = schemaUtils.helpers.validateObjectAgainstMongooseSchema({
            checkObject: {
                stringType: 1,
                numberType: "1",
                dateType: 1,
                boolType: 1,
                arrayType: 1,
                schemaType: {embeddedDate: 1, embeddedBool: 1}
            },
            mongooseSchema: schema
        });

产生:

[
      'object.stringType should be of type string but got number',
      'object.numberType should be of type number but got string',
      'object.dateType should be of type date but got number',
      'object.boolType should be of type boolean but got number',
      'object.arrayType should be of type array but got number',
      'object.schemaType.embeddedDate should be of type date but got number',
      'object.schemaType.embeddedBool should be of type boolean but got number'
    ]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接