diegohaz/bodymen

Error with nested properties

chemitaxis opened this issue · 14 comments

Hi, I'm trying to use bodymen with nested properties like this:

{
  'Name': 'Marc',
  'Address' : {
          'Street' : 'My Street',
          'City' : 'My City'      
   }
}

But I have an error in the middleware:

ValidationError: Address: Cast to Object failed for value "[object Object]" at path "Address"

Any ideas? Thanks.

Hi @diegohaz , the problem is in the validator method, BUT the problem is not in Bodymen, is your other module rich-param, it can't work fine with objects.

If I change in the validator method this:

/**
   * Validate values of the schema params.
   * @param {Object} [values] - Object with {param: value} pairs to validate.
   * @param {Function} [next] - Callback to be called with error
   * @return {boolean} Result of the validation.
   */
  validate (values = {}, next = error => !error) {
    let error
    if (_.isFunction(values)) {
      next = values
      values = {}
    }

    _.forIn(this.params, param => {
      const value = values[this._getBodyParamName(param.name)]
     
      if (!_.isNil(value)) {
        // THIS LINE ADDED TO CHECK IF OBJECT
        if (typeof value === 'object') {
          param._value = value
        } else {
          param.value(value)
        }
      }
    })

    for (let i in this.params) {
      if (error) break
      const param = this.params[i]
      param.validate(err => {
        error = err
      })
    }

    return next(error)
  }

What is the best way to solve this problem? Thanks

But... with this, the nested property is not checked... :(

Ok, more information...

If I configure my schema like this in the body:

{
  'Name': String,
  'Address' : {
          type: Object 
   }
}

It works... but the validation of Address properties is not triggered... thanks.

@chemitaxis Thank you for reporting. This is a known problem, but not very easy to fix.

FYI, I'm working on a new library to replace querymen, bodymen and rich-param. That solves issues like diegohaz/querymen#30 and this one.

https://github.com/diegohaz/schm

Currently, it lacks validation (diegohaz/schm#1 - almost done on dev branch), express and mongoose support, but these probably will be implemented in the next few weeks. Any help is welcome.

Hi @diegohaz, perfect and thanks. I will check this new repo, and I will try to contribute. My workaround is validate with express-validator. One question, I did not found problems with the issue that you mentioned, diegohaz/querymen#30, but can you please confirm me if I need to be worried about it? ;)

I have check the code, it would be easier to contribute if you document a litte the code ;) For example, how walk method works in utils? Thank you so much!

Hi I'm using the @diegohaz's rest project generator.
I think my problem is the similar to @chemitaxis's problem.
I have this Schema :

const contextSchema = new Schema({
  user: {
    type: Schema.ObjectId,
    ref: 'User',
    required: true
  },
  rootview: {
    type: String
  },
  directoryview: {
    type: String
  },
  mimetypes: [ { mime: String, viewer: String } ]
}, {
  timestamps: true
});

The index.test.js of the api is sends this:

test('POST /contexts 201 (user)', async () => {
  const { status, body } = await request(app())
    .post('/')
    .send({
      access_token: userSession,
      rootview: 'UvCardRootView',
      directoryview: 'UvDiffusionDirectoryView',
      mimetypes: [
        { mime: 'image/jpeg', viewer: 'UvDiffusionImageViewer' },
        { mime: 'application/pdf', viewer: 'UvEmbeddedImagePdfViewer' },
        { mime: 'video/mp4', viewer: 'UvBrowserVideoViewer' }
      ]
    });

On the api side it seems that bodymen cannot parse the mimetypes field.
Indeed the create method of my api controller receives this body object:

body = Object
   directoryview = "UvDiffusionDirectoryView"
   mimetypes = "[object Object],[object Object],[object Object]"
   rootview = "UvCardRootView"

So body.mimetypes is not an array but a toString of it. I debugged in bodymen and indeed the following line in the middleware function doesn't give me the source req.body.mimetypes array but an "[object Object],[object Object],[object Object]":
req.bodymen = { body: _schema.parse(), schema: _schema };

I'm really new to all this validation, mongoose, schema, nested schema ... I did read Mongoose doc, took the time to understand what I could in @diegohaz's work but I don't know how to workaround this.
Can you help me ? How I could at least disable the bodymen validation for this api? And so forth directly send the json parsed req.body to the create method of my api controller?
I mean what I should doin from the context/index.js of my api? here:

import { Router } from 'express';
import { middleware as query } from 'querymen';
import { middleware as body } from 'bodymen';
import { token } from '../../services/passport';
import { create, index, show, update, destroy } from './controller';
import { schema } from './model';
export Context, { schema } from './model';

const router = new Router();
const { rootview, directoryview, mimetypes } = schema.tree;

router.post('/',
  token({ required: true }),
  body({ rootview, directoryview, mimetypes }),
  create);

Hi @khelkun, my workaround is something like this:

const contextSchema = new Schema({
  user: {
    type: Schema.ObjectId,
    ref: 'User',
    required: true
  },
  rootview: {
    type: String
  },
  directoryview: {
    type: String
  },
  mimetypes: { type: Object }
}, {
  timestamps: true
});

I have changed mimetypes to Object, you can validate after, what you want.

Thanks!

@chemitaxis thanks it actually works!
it also works with this schema:

const contextSchema = new Schema({
  user: {
    type: Schema.ObjectId,
    ref: 'User',
    required: true
  },
  rootview: {
    type: String
  },
  directoryview: {
    type: String
  },
  mimetypes: [ { type: Object } ]
}, {
  timestamps: true
});

Which is probably a bit more accurate since mimetypes field is supposed to be an Array of Objects.
Still I wish I could have specified in the schema that each Object in the mimetypes Array has 2 String fields: mime and viewer. But apparently I'll not be able to validate this.

Also note that bodymen succeeds to validate a body containing [ { mime: "test", viewer: "test"} ] for a schema definition like this:
mimetypes: { type: Object, required:true }

But bodymen fails to validate the same body with a schema definition like this:
mimetypes: [ { type: Object, required:true } ]

Interesting... you can validate an array ob objects?

My bad it's not bodymen issue. The validation error comes from mongoose:

Failed: Context validation failed: mimetypes: Cast to Array failed for value "[ { mime: 'image/jpeg', viewer: 'UvMessidorImageViewer' },
  { mime: 'application/pdf', viewer: 'UvEmbeddedImagePdfViewer' } ]" at path "mimetypes"
ValidationError: Context validation failed: mimetypes: Cast to Array failed for value "[ { mime: 'image/jpeg', viewer: 'UvMessidorImageViewer' },
  { mime: 'application/pdf', viewer: 'UvEmbeddedImagePdfViewer' } ]" at path "mimetypes"
    at MongooseError.ValidationError (/home/me/myprojectnode_modules/mongoose/lib/error/validation.js:28:11)
    at model.Object.<anonymous>.Document.invalidate (/home/me/myprojectnode_modules/mongoose/lib/document.js:1612:32)
    at model.Object.<anonymous>.Document.set (/home/me/myprojectnode_modules/mongoose/lib/document.js:761:10)
    at model._handleIndex (/home/me/myprojectnode_modules/mongoose/lib/document.js:594:14)
    at model.Object.<anonymous>.Document.set (/home/me/myprojectnode_modules/mongoose/lib/document.js:554:24)
    at model.Document (/home/me/myprojectnode_modules/mongoose/lib/document.js:73:12)
    at model.Model (/home/me/myprojectnode_modules/mongoose/lib/model.js:47:12)
    at new model (/home/me/myprojectnode_modules/mongoose/lib/model.js:3688:13)
    at /home/me/myprojectnode_modules/mongoose/lib/model.js:1942:51
    at /home/me/myprojectnode_modules/async/internal/parallel.js:27:9
    at eachOfArrayLike (/home/me/myprojectnode_modules/async/eachOf.js:57:9)
    at Object.<anonymous>.exports.default (/home/me/myprojectnode_modules/async/eachOf.js:9:5)
    at _parallel (/home/me/myprojectnode_modules/async/internal/parallel.js:26:5)
    at parallelLimit (/home/me/myprojectnode_modules/async/parallel.js:85:26)
    at /home/me/myprojectnode_modules/mongoose/lib/model.js:1963:5
    at Promise._execute (/home/me/myprojectnode_modules/bluebird/js/release/debuggability.js:300:9)
    at Promise._resolveFromExecutor (/home/me/myprojectnode_modules/bluebird/js/release/promise.js:483:18)
    at new Promise (/home/me/myprojectnode_modules/bluebird/js/release/promise.js:79:10)
    at Function.create (/home/me/myprojectnode_modules/mongoose/lib/model.js:1926:17)
    at _callee$ (/home/me/myprojectsrc/api/context/index.test.js:16:49)
    at tryCatch (/home/me/myprojectnode_modules/regenerator-runtime/runtime.js:65:40)
    at GeneratorFunctionPrototype.invoke [as _invoke] (/home/me/myprojectnode_modules/regenerator-runtime/runtime.js:299:22)

So I guess mimetypes: [ { type: Object, required:true } ] is not a valid schema definition... To be honnest I barely know what I'm doing

Sorry I deleted the message cause the change I was talking about actually breaks bodymen.
It works for my schema but the tests fails on the schemas of the others api (auth, password, users).