Say I have a javascript object (the data) and I want to check to see if it conforms to a given Schema that I've defined.
Is there a way to do this without turning the schema into a model, creating an instance of that model populated with the data, and running mymodel.validate()?
I'd love to have a Schema(definition).validate(data, callback), but the validate function is defined on the Document class, from what I could tell.
2021 update
Mongoose added that functionality as Model.validate(...) back in 2019 (v5.8.0):
You can:
try {
await Model.validate({ name: 'Hafez', age: 26 });
} catch (err) {
err instanceof mongoose.Error.ValidationError; // true
}
One way is to perform that with the help of custom validators. When the validation declined, it failed to save the document into the database.
Or another way to do that through validate() function provided by MongoDB with the same schema as you defined.
You can validate your schema on the Mongo side link
For example:
db.createCollection("students", {
validator: {
$jsonSchema: {
bsonType: "object",
required: [ "name" ],
properties: {
name: {
bsonType: "string",
description: "must be a string and is required"
},
year: {
bsonType: "int",
minimum: 2017,
maximum: 3017,
description: "must be an integer in [ 2017, 3017 ] and is required"
}
}
}
}
})
Related
I am using migrate-mongo for managing my database migration and I am trying to create a new migration that create a collection with a validator and insert values in it. I want to use a UUID for the _id property and I am using the uuid-mongodb library to generate it. My problem is that I am not able to set the bsonType of my _id in the validator without causing the data insertion failure. Is there any way to make sure that the id of the documents inserted in the collection is a UUID? I know that mongoose could help me to solve this issue, but I would like the validation to be done at the database level. Also, when I do not specify the _id's bsonType in the validator, the insertion works, it fails validation only when I specify it.
Here is my migration code
const MUUID = require("uuid-mongodb");
module.exports = {
async up(db) {
//Will use https://docs.mongodb.com/manual/tutorial/model-tree-structures-with-materialized-paths/
await db.createCollection("itemCategories", {
validator: {
$jsonSchema: {
required: ["name"],
bsonType: "object",
properties: {
_id: {"object"}, //I also tried with binData
name: {
bsonType: "string",
maxLength: 50,
},
path: {
bsonType: ["string", "null"],
pattern: "^,([^,]+,)+$"
}
},
additionalProperties: false,
}
},
});
await db.collection("itemCategories").createIndex({"name": 1}, {unique: true});
await db.collection("itemCategories").insertMany([
{_id: MUUID.v4(), name: "Sport", path: null},
{_id: MUUID.v4(), name: "Tool", path: null},
{_id: MUUID.v4(), name: "Entertainment", path: null}
]);
},
async down(db) {
await db.dropCollection("itemCategories");
}
};
And here is the error I get when running it
ERROR: Could not migrate up 20210627041314-create-categories.js: Document failed validation BulkWriteError: Document failed validation
at OrderedBulkOperation.handleWriteError (C:\Users\username\projectDirectory\node_modules\mongodb\lib\bulk\common.js:1352:9)
at resultHandler (C:\Users\username\projectDirectory\node_modules\mongodb\lib\bulk\common.js:579:23)
at handler (C:\Users\username\projectDirectory\node_modules\mongodb\lib\core\sdam\topology.js:943:24)
at C:\Users\username\projectDirectory\node_modules\mongodb\lib\cmap\connection_pool.js:350:13
at handleOperationResult (C:\Users\username\projectDirectory\node_modules\mongodb\lib\core\sdam\server.js:558:5)
at MessageStream.messageHandler (C:\Users\username\projectDirectory\node_modules\mongodb\lib\cmap\connection.js:281:5)
at MessageStream.emit (events.js:321:20)
at processIncomingData (C:\Users\username\projectDirectory\node_modules\mongodb\lib\cmap\message_stream.js:144:12)
at MessageStream._write (C:\Users\username\projectDirectory\node_modules\mongodb\lib\cmap\message_stream.js:42:5)
at doWrite (_stream_writable.js:441:12)
Assuming collection name user_demo and having 2 fields only ( _id, name )
Create collection Schema Validator
db.createCollection("user_demo", {
validator: {
$jsonSchema: {
bsonType: "object",
title: "User Object Validation",
required: [ "_id","name"],
properties: {
_id: {
bsonType: "binData",
description: "Unique identifier,I am using it instead of objectId for portibility",
pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
},
name: {
bsonType: "string",
description: "'name' must be a string and is required",
maxLength: 50,
minLength: 1
}
}
}
}
} )
Insert data in collection
a) If you already have a uuid4
db.user_demo.insertOne({_id: UUID("a5750db3-1616-45a4-bf92-6a44c3e67342"), name:"shiva"})
b) If you want random uuid4
db.user_demo.insertOne({_id: UUID(), name:"explore"})
Tested with mongo version 6.0.3
I use Mongo DB and node.js. Thanks for any help :).
I want to do the following:
Search for a user in the db using the googleId.
Then if the user does not have a description field yet I want to create it. Within the description field, I want to have several objects that all have the coinId as an index.
If the coinId already exists it should overwrite the content of that particular object with the variables I pass. If it does not already exist it should add the new object to the description field.
The Mongo db document should look like this in the end. Note that 1027 or 123 are the values of the coinIds.
googleId: "PyovWaX8HERRACmeg4IzYCaMK833"
description:
1027:
_id: "ckpi7q8c60002qe9h0e4wgh3r"
coinId: 1027
description: "test1"
date: 2021-06-04T10:56:52.662+00:00
123:
_id: "woienfeiowfnaoewinfefneo"
coinId: 123
description: "test2"
date: 2021-06-04T10:56:52.662+00:00
I already tried the following:
const { result } = await db.collection('users').updateOne(
{ googleId },
{
$set: {
description: { [coinId]: { _id, coinId, description, date } },
},
},
{
returnOriginal: false,
}
);
The problem here is that the entry is always overwritten which I only want to happen if the coinId is the same.
I also tried to do it with $addToSet but then it never overwrites the object if the coinId's match.
How about findAndModify?
findAndModify
In your case, something like this:
db.collection('users').findAndModify({
query: { _id: googleId },
update: {
$setOnInsert: {
description: { [coinId]: { _id, coinId, description, date } },
}
},
new: true, // return new doc if one is upserted
upsert: true // insert the document if it does not exist
})
db.collection.findAndModify({
query: { _id: "some potentially existing id" },
update: {
$setOnInsert: { foo: "bar" }
},
new: true, // return new doc if one is upserted
upsert: true // insert the document if it does not exist
})
I wrote the below schema. But while running it gives me an error -- throw new TypeError(Invalid schema configuration: \${name}` is not ` + --- can someone help me why does this error comes?. Below shown is my schema, can someone figure out if my schema have some mistakes.
$
jsonSchema: {
bsonType: "object",
properties: {
name: {
bsonType: "string",
description: "must be a string"
},
teacherId: {
bsonType: "objectId",
description: "must be a Object ID"
}
}
}
You are using the MongoDB native syntax to define your schema. If you are using mongoose, mongoose has its own syntax. Please refer to mongoose documentation. For your schema it would be:
var studentSchema = new mongoose.Schema({
name: { type: String },
teacherId: { type: mongoose.Schema.Types.ObjectId }
})
Mmm how I resolved this was to use the Types defined within the Schema import. For some reason importing the Types module directly didn't work -- well for Map at least.
import { Document, Schema, model, Query, Model } from "mongoose";
{
...
metadata: { type: Schema.Types.Map, of: String, required: false },
}
"name" is not a valid attribute anymore in mongoose schemas. Replace it with "type" it will work.
Oh man this is related to wrong name of the schema fields in my case:
participants: [
{
typeof: Types.ObjectId,
ref: "User",
},
]
and I got same error as you. I was so struggled and this typeof - type f* VS code I'm using it at the moment cause this computer can't deal with jet brains, :(
I have following mutation on serverside (nodeJS) (RequiredDataType is imported):
mutationA: {
type: MutationResponseType,
args: {
id: {
type: new GraphQLNonNull(GraphQLString)
},
name: {
type: new GraphQLNonNull(GraphQLString)
},
requiredData: {
type: new GraphQLNonNull(new GraphQLList(RequiredDataType))
}
},
async resolve(parentValue, {
id,
name,
requiredData
}, req) {
// Some Magic Code
}
},
The RequiredDataType is coded as follow (All GraphQL things are imported :)):
const RequiredDataType = new GraphQLObjectType({
name: 'RequiredDataType',
fields: {
name: {
type: GraphQLString
},
value: {
type: GraphQLString
},
required: {
type: GraphQLBoolean
}
}
});
module.exports = RequiredDataType;
When I use this code I get the following error: "module initialization error: Error"
If I change the RequiredDataType in the mutation to GraphQLString it works without any error but I can't use the object which I need :)
At the end I will send and process following data structure:
{
"name": "Hallo"
"id": "a54de3d0-a0a6-11e7-bf70-7b64ae72d2b6",
"requiredData": [
{
"name": "givenName",
"value": null,
"required": true
},
{
"name": "familyName",
"value": null,
"required": false
}
]
}
On the client (reactJS with apollo-client) I use the following gql-tag code:
export default gql`
mutation MutationA($id: String!, $name: String!, $requiredData: [RequiredDataType]!){
mutationA(id: $id, name: $name, requiredData: $requiredData) {
id,
somethingElse
}
}
`;
But in the first place it crashes on the mutation declaration on the server. So is it not possible to use and GQLObject as an argument at an mutation or where is my error in the code?
Thank you for your help!
Best,
Fabian
Unfortunately, a type cannot be used in place of an input, and an input cannot be used in place of a type. This is by design. From the official specification:
Fields can define arguments that the client passes up with the query,
to configure their behavior. These inputs can be Strings or Enums, but
they sometimes need to be more complex than this.
The Object type defined above is inappropriate for re‐use here,
because Objects can contain fields that express circular references or
references to interfaces and unions, neither of which is appropriate
for use as an input argument. For this reason, input objects have a
separate type in the system.
You can check this answer for more details as to the why
You'll need to define RequiredDataType as a GraphQLInputObjectType, not a GraphQLObjectType, to get your mutation working. If you need it as a GraphQLObjectType too, you'll need to declare them as two separate types -- something like RequiredDataType and RequiredDataInput.
I want to have two types of users on my site - normal users and web designers.
How do I specify in the mongoose schema a type that can be two values - "normal" or "designer"
The "choice" between two types depends entirely on what you mean.
In the simple case where you just want multiple values to be valid for a property from a list of choices then you can use enum to make the only values one of the possibilities:
var userSchema = new Schema({
"type": { "type": String, "enum": ["normal", "developer"] }
});
That allows basic validation that the property must contain one of those values and only one of those values. Trying to set to another value or omitting the value will result in a validation error.
If however, you want something more advanced such as essentially different objects with extended fields for each "type", then "discriminators" are likely for you.
This allows you to have different properties between a normal "user" and a "developer", such as a list of the projects for the developer. They will all be saved in the same collection, but they are first-class objects with all their own schema definition and rules:
var util = require('util'),
async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
function BaseSchema() {
Schema.apply(this,arguments);
this.add({
name: String,
createdAt: { type: Date, default: Date.now }
});
}
util.inherits(BaseSchema,Schema);
var userSchema = new BaseSchema();
var developerSchema = new BaseSchema({
projects: [String]
});
var User = mongoose.model('User',userSchema),
Developer = User.discriminator( 'Developer', developerSchema );
async.series(
[
function(callback) {
User.remove({},callback);
},
function(callback) {
async.eachSeries(
[
{ "role": "User", "name": "Bill" },
{ "role": "Developer", "name": "Ted", "projects": [
"apples", "oranges" ] }
],
function(item,callback) {
mongoose.model(item.role).create(item,callback);
},
callback
);
},
function(callback) {
User.find().exec(function(err,docs) {
console.log(docs)
callback(err);
});
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
For which the output shows the two objects in the same collection:
[ { _id: 55ff7b22f1ff34f915cc8312,
name: 'Bill',
__v: 0,
createdAt: Mon Sep 21 2015 13:36:02 GMT+1000 (AEST) },
{ _id: 55ff7b22f1ff34f915cc8313,
name: 'Ted',
__v: 0,
__t: 'Developer',
createdAt: Mon Sep 21 2015 13:36:02 GMT+1000 (AEST),
projects: [ 'apples', 'oranges' ] } ]
Note that "Ted" the "Developer" has another field stored as __t which holds the discriminator value for the model. This allows mongoose to apply magic when it is read that casts to the correct object.
It also means that each model can be used independently, with operations like:
Developer.find().exec(function(err,developers) { });
Developer.create(newDev,function(err,developer) { });
Which respectively "magically insert" the "type" so that only matching __t: 'Developer' objects are either found or created by each operation.
So enum will give you a restriction on possible values and discriminator() allows you to set up completely independent definitions for objects as a whole. It depends on which form you mean, but both are nice to use.