Search code examples
validationmongoosemongoose-schema

Mongoose Validation falling for embedded document


I have this Issues schema:

// Require Mongoose
import mongoose from "mongoose";
import { rolesSchema } from "./roles.js";
import { storyPointSchema } from "./storyPoint.js";


// Define a schema
const Schema = mongoose.Schema;
/**
 * Issues Report Schema
 */
export const issuesSchema = new Schema({
    team: {
        required: true, 
        type: String,
    },
    teamRoles: {
        type: [rolesSchema],
        required: true
    },
    ticketId: {
        type: String,
        required: true
    },
    issueName: {
        type: String,
        required: true,
    },
    description: {
        type: String,
        required: true
    },
    issueType: {
        type: String,
        enum: ['story', 'bug', 'task', 'sub-task', 'epic'],
        default: 'story',
        required: true
    },
    storyPoints: {
        accepted: storyPointSchema,
        committed: storyPointSchema,
        completed: storyPointSchema,
        estimated: storyPointSchema, 
        actual: storyPointSchema
    }
}, {_id: false})

/**
 * Calculate Full Name
 * Virtual for ticket assignee's full name
 */
issuesSchema.virtual('fullname').get(function () {
    let fullName = ""
    if (this.firstName && this.lastName) {
        fullName = `${this.firstName}, ${this.lastName}`
    }
    return fullName
})

export const Issues =  mongoose.model('Issues', issuesSchema)

Here are the two embedded document schemas:

export const rolesSchema = new Schema({
    firstName: {
        type: String,
        required: true
    },
    lastName:{
        type: String,
        required: true
    },
    role: {
        type:String,
        required: true
    }
})

export const Roles =  mongoose.model('Roles', rolesSchema)

// Require Mongoose
import mongoose from "mongoose";

// Define a schema
const Schema = mongoose.Schema;
/**
 * Story Point Report Schema
 */
export const storyPointSchema = new Schema([{
    storyPoint: {
        type: Number,
        required: true,
        enum: [0,1, 2, 3, 5, 8, 13]
    }
}])

In unit testing I can get all errors that the validators throw when required is missing or wrong format data

Here is the validations errors:

{
  "errors": {
    "description": {
      "name": "ValidatorError",
      "message": "Path `description` is required.",
      "properties": {
        "message": "Path `description` is required.",
        "type": "required",
        "path": "description"
      },
      "kind": "required",
      "path": "description"
    },
    "issueName": {
      "name": "ValidatorError",
      "message": "Path `issueName` is required.",
      "properties": {
        "message": "Path `issueName` is required.",
        "type": "required",
        "path": "issueName"
      },
      "kind": "required",
      "path": "issueName"
    },
    "ticketId": {
      "name": "ValidatorError",
      "message": "Path `ticketId` is required.",
      "properties": {
        "message": "Path `ticketId` is required.",
        "type": "required",
        "path": "ticketId"
      },
      "kind": "required",
      "path": "ticketId"
    },
    "team": {
      "name": "ValidatorError",
      "message": "Path `team` is required.",
      "properties": {
        "message": "Path `team` is required.",
        "type": "required",
        "path": "team"
      },
      "kind": "required",
      "path": "team"
    },
    "issueType": {
      "name": "ValidatorError",
      "message": "`foo` is not a valid enum value for path `issueType`.",
      "properties": {
        "message": "`foo` is not a valid enum value for path `issueType`.",
        "type": "enum",
        "enumValues": [
          "story",
          "bug",
          "task",
          "sub-task",
          "epic"
        ],
        "path": "issueType",
        "value": "foo"
      },
      "kind": "enum",
      "path": "issueType",
      "value": "foo"
    }
  },
  "_message": "Issues validation failed",
  "name": "ValidationError",
  "message": "Issues validation failed: description: Path `description` is required., issueName: Path `issueName` is required., ticketId: Path `ticketId` is required., team: Path `team` is required., issueType: `foo` is not a valid enum value for path `issueType`."

Can you see any issues with these schemas to see why both embedded documents fail to register any errors relevant to roles or story points?


Solution

  • To get default or any validation working for the Story Points I had to flatten this schema ( As per Mongoose's documentation where it says embedded sub documents can be tricky as they don't actually belong to document so validation won't work.

    Here is the updated storyPointSchema:

    // Require Mongoose
    import mongoose from "mongoose";
    import { ActoValidator } from "../services/validators/ActoValidator.js";
    const Validator = new ActoValidator();
       
    
    // Define a schema
    const Schema = mongoose.Schema;
    /**
     * Story Point Report Schema
     */
    export const storyPointSchema = new Schema({
        accepted:{
            type: Number,
            enum: [0,1, 2, 3, 5, 8, 13],
        },
        committed: {
            type: Number,
            enum: [0,1, 2, 3, 5, 8, 13],
        },
        completed: {
            type: Number,
            enum: [0,1, 2, 3, 5, 8, 13],
        },
        estimated: {
            type: Number,
            enum: [0,1, 2, 3, 5, 8, 13],
    
        },
        actual: {
            type: Number,
            enum: [0,1, 2, 3, 5, 8, 13],
        },  
    })
    

    And then the issue schema is:

    // Require Mongoose
    import mongoose from "mongoose";
    import { rolesSchema } from "./roles.js";
    import { storyPointSchema } from "./storyPoint.js";
    
    // Define a schema
    const Schema = mongoose.Schema;
    /**
     * Issues Report Schema
     */
    export const issuesSchema = new Schema({
        team: {
            required: true, 
            type: String
        },
        teamRoles: {
            type: [rolesSchema],
            required: true
        },
        ticketId: {
            type: String,
            required: true
        },
        issueName: {
            type: String,
            required: true,
        },
        description: {
            type: String,
            required: true
        },
        issueType: {
            type: String,
            enum: ['story', 'bug', 'task', 'sub-task', 'epic'],
            default: 'story',
            required: true
        },
        storyPoints: {
            type: storyPointSchema,
            required: [true,  "`the estimated story point field` is required, either 0, 1, 2, 3, 5, 8, or 13"],
        }
    }, {_id: false})
    
    /**
     * Calculate Full Name
     * Virtual for ticket assignee's full name
     */
    issuesSchema.virtual('fullname').get(function () {
        let fullName = ""
        if (this.firstName && this.lastName) {
            fullName = `${this.firstName}, ${this.lastName}`
        }
        return fullName
    })
    /**
     * Team roles validation for the array items
     */
    issuesSchema.path('teamRoles').validate(function(teamRoles){
        if(!teamRoles){return false}
        else if(teamRoles.length === 0){return false}
        return true;
    }, '`issueRole` is required. There must be at least one role added to each issue');
    
    
    export const Issues =  mongoose.model('Issues', issuesSchema)
    

    This now has the appropriate errors:

    {
      "errors": {
        "storyPoints": {
          "name": "ValidatorError",
          "message": "`the estimated story point field` is required, either 0, 1, 2, 3, 5, 8, or 13",
          "properties": {
            "message": "`the estimated story point field` is required, either 0, 1, 2, 3, 5, 8, or 13",
            "type": "required",
            "path": "storyPoints"
          },
          "kind": "required",
          "path": "storyPoints"
        },
        "description": {
          "name": "ValidatorError",
          "message": "Path `description` is required.",
          "properties": {
            "message": "Path `description` is required.",
            "type": "required",
            "path": "description"
          },
          "kind": "required",
          "path": "description"
        },
        "issueName": {
          "name": "ValidatorError",
          "message": "Path `issueName` is required.",
          "properties": {
            "message": "Path `issueName` is required.",
            "type": "required",
            "path": "issueName"
          },
          "kind": "required",
          "path": "issueName"
        },
        "ticketId": {
          "name": "ValidatorError",
          "message": "Path `ticketId` is required.",
          "properties": {
            "message": "Path `ticketId` is required.",
            "type": "required",
            "path": "ticketId"
          },
          "kind": "required",
          "path": "ticketId"
        },
        "team": {
          "name": "ValidatorError",
          "message": "Path `team` is required.",
          "properties": {
            "message": "Path `team` is required.",
            "type": "required",
            "path": "team"
          },
          "kind": "required",
          "path": "team"
        },
        "teamRoles": {
          "name": "ValidatorError",
          "message": "`issueRole` is required. There must be at least one role added to each issue",
          "properties": {
            "message": "`issueRole` is required. There must be at least one role added to each issue",
            "type": "user defined",
            "path": "teamRoles",
            "value": []
          },
          "kind": "user defined",
          "path": "teamRoles",
          "value": []
        }
      },
      "_message": "Issues validation failed",
      "name": "ValidationError",
      "message": "Issues validation failed: storyPoints: `the estimated story point field` is required, either 0, 1, 2, 3, 5, 8, or 13, description: Path `description` is required., issueName: Path `issueName` is required., ticketId: Path `ticketId` is required., team: Path `team` is required., teamRoles: `issueRole` is required. There must be at least one role added to each issue"
    

    I hope this helps someone else :shrug"