Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The coerceTypes: 'array' doesn't coerce one parameter to a single element in array #318

Open
2 tasks done
meotimdihia opened this issue Jan 17, 2022 · 9 comments
Open
2 tasks done
Labels
documentation Improvements or additions to documentation good first issue Good for newcomers

Comments

@meotimdihia
Copy link

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

3.x.x

Plugin version

5.x.x

Node.js version

14.x

Operating system

Windows

Operating system version (i.e. 20.04, 11.3, 10)

10

Description

I have got this error when send a POST request.

{statusCode: 400, error: "Bad Request", message: "body.authors should be array"}
error: "Bad Request"
message: "body.authors should be array"
statusCode: 400

My schema

  const body = {
    type: "object",
    properties: {
      authors: {
        type: "array",
        items: {
          type: "object"
        }
      }
   }

Fastify configuration

server.register(fastifyMultipart, {
 attachFieldsToBody: true,
 sharedSchemaId: "#mySharedSchema",
})
const ajv = new Ajv({
 removeAdditional: true,
 useDefaults: true,
 coerceTypes: "array",
 nullable: true
})
fastify.setValidatorCompiler(({ schema }) => {
 return ajv.compile(schema)
})

Steps to Reproduce

It is straightforward.

Expected Behavior

No response

@mcollina
Copy link
Member

Can you please include a reproducible example? It might be straightforward but it will greatly simplify fixing it. You could also send a PR, it would be highly welcomed.

@meotimdihia
Copy link
Author

meotimdihia commented Jan 18, 2022

Hi, I dunno how to create a reproducible example that is easy for everyone to see.

But I wrote a test, could you copy it to the test directory of fastify-multipart to check this problem:
https://gist.github.com/meotimdihia/53299a9501843987907a369f445898ac

If I uncomment line 79 then the test will be passed.

@andrewwood2
Copy link

andrewwood2 commented Mar 21, 2022

I've taken a look at this and I think without a reproducible example it's not going to be possible to definitively conclude on this issue. To me it looks as though it could be to do with the way you are registering routes and the validation on your fastify server, as in the below example where username gets coerced into an array.

import Fastify from 'fastify'
import Ajv from 'ajv'
import fastifyMultipart from 'fastify-multipart'

function buildServer() {
  const fastify = Fastify({
    logger: {
      prettyPrint: true,
    },
  })

  const ajv = new Ajv({
    removeAdditional: true,
    useDefaults: true,
    coerceTypes: 'array',
  })

  const opts = {
    attachFieldsToBody: true,
    sharedSchemaId: '#sharedLoginSchema',
  }
  fastify.register(fastifyMultipart, opts)

  fastify.setValidatorCompiler(({ schema }) => ajv.compile(schema))

// works
  fastify.register(async function (fs) {
    fs.post(
      '/login',
      {
        schema: {
          body: {
            type: 'object',
            properties: {
              username: {
                type: 'array',
              },
              password: {
                type: 'string',
              },
            },
          },
        },
      },
      async req => {
        const { username, password } = req.body
        return { username, password }
      }
    )
  })

// doesn't work
  fastify.post(
    '/login2',
    {
      schema: { body: fastify.getSchema('login') },
      schema: {
        body: {
          type: 'object',
          properties: {
            username: {
              type: 'array',
            },
            password: {
              type: 'string',
            },
          },
        },
      },
    },
    async req => {
      const { username, password } = req.body
      return { username, password }
    }
  )

  fastify.log.info('Fastify is starting up!')

  return fastify
}

const fastify = buildServer()

const start = async function () {
  try {
    await fastify.listen(3000)
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}

start()

If this is the cause of the confusion (and I agree it is not obvious that you might register the route like this) then perhaps the solution is simply to update the docs.

Also, from what I can tell, this isn't to do with fastify-multipart as the same behaviour is seen on all fastify route validation.

@mcollina
Copy link
Member

@andrewwood2 could you send a PR to update the docs?

@rubenferreira97
Copy link

I am having a similar (or same problem), where I define a property of my schema as a { type: 'array', items: fastify.getSchema('mySharedSchema')}'. When I send only one file, it says that field expect an array. I am using "coerceTypes": Array, so I expected that it would work without any workaround.
As a temporary fix I am doing a anyOf schema with a single File and a array FileArray.

@stale
Copy link

stale bot commented Apr 28, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Apr 28, 2022
@meotimdihia
Copy link
Author

meotimdihia commented Sep 11, 2022

Hi, I added the example:
https://github.com/meotimdihia/fastify-multipart-bug-report-2

you'll get this error:

Untitled 20

P/S: Please also re-open this issue: #313

@stale stale bot removed the stale label Sep 11, 2022
@gurgunday gurgunday added good first issue Good for newcomers documentation Improvements or additions to documentation labels Jan 28, 2024
@Titou325
Copy link

Titou325 commented Mar 27, 2024

Also bumped into this issue today trying to get a 1 or more files field to work. It seems that ajv will only coerce scalar types as defined in https://github.com/ajv-validator/ajv/blob/master/lib/compile/validate/dataType.ts#L126

For multipart file uploads, dataType is object and thus not automatically coerced.

A potential fix would be to change the filesFromFields function to somehow detect if the underlying schema wants array types. Replacing the schema with oneOf hinders the swagger ui for file upload.

@Titou325
Copy link

I worked around this by creating a custom ajv plugin

import type Ajv from "ajv";
import { DataValidateFunction } from "ajv/dist/types";

export const ajvArrayCoercion = (ajv: Ajv) => {
  ajv.addKeyword({
    keyword: "coerceObjectArray",
    modifying: true,
    compile: (schema, parent) => {
      delete parent.coerceObjectArray;

      const validator: DataValidateFunction = (data, dataCxt) => {
        if (!Array.isArray(data) && typeof data === "object") {
          // @ts-expect-error No typings for package ajv
          dataCxt.parentData[dataCxt.parentDataProperty] = [data];
        }

        return true;
      };

      return validator;
    },
    before: "array",
  });
  return ajv;
};

Which I then use as

multiFileField: {
  allOf: [
    {
      coerceObjectArray: true,
    },
    {
      type: "array",
      items: {
        isFile: true,
      },
    },
  ],
} as unknown as {
  type: "array";
  items: {
    isFile: true;
  };
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

6 participants