turkerdev / fastify-type-provider-zod

MIT License
411 stars 25 forks source link

How I can create a schema to use an input of file on swagger docs? #82

Closed Gustavo-Murdiga88 closed 6 months ago

Gustavo-Murdiga88 commented 6 months ago

Today I use this approach, but the input does not work on my docs.

schema: { tags: ["Upload"] params: z.object({ filename: z.string(), }), consumes: ["multipart/form-data"], body: z.custom<Multipart>() },

How I can write for the input works on my docs?

This is the output of this code: image

cjvnjde commented 6 months ago

I come up with this solution to tackle the problem, but I'm not entirely satisfied with it and am still seeking a more appropriate way to handle it.

fastify.register(multipart, {
  attachFieldsToBody: "keyValues",
  onFile: async (part) => {
    const buff = await part.toBuffer();
    part.value = {
      filename: part.filename,
      file: buff,
    };
  },
});
fastify.register(swagger, {
...
  transform: (data) => {
    const jsonSchema = jsonSchemaTransform(data);

    normalizeFileFields(jsonSchema.schema);

    return jsonSchema;
  },
});
const normalizeFileFields = (obj: Record<string, any>) => {
  for (let key in obj) {
    if (typeof obj[key] === "object" && obj[key] !== null) {
      if (obj[key].hasOwnProperty("filename") && obj[key].hasOwnProperty("file")) {
        obj.type = "file";
      } else {
        normalizeFileFields(obj[key]);
      }
    }
  }

  return obj;
};
Gustavo-Murdiga88 commented 6 months ago

Dude, I am so glad you helped me, your answer was very good, but it does not work 100%, I had to change some parts of your code.

Then your code in my implementation should be like this.

const normalizeFileFields = (obj: Record<string, any>) => {
  let routeContainsFile = false;

  if (obj.hasOwnProperty("consumes") && obj.consumes.includes("multipart/form-data")) {
    routeContainsFile = true;
  }

  for (let key in obj) {

    if (typeof obj[key] === "object" && obj[key] !== null) {
      if (key === "body" && routeContainsFile) {
        obj[key] = {
          type: "object",
          required: ["file"],
          properties: {
            file: { type: 'file' },
          }
        };
      } else {
        normalizeFileFields(obj[key]);
      }
    }
  }

  return obj;
};

And I need to change my implementation for this.

  app.withTypeProvider<ZodTypeProvider>().route({
    method: "POST",
    url: "/files/:filename",
    schema: {
      tags: ["Upload"],
      summary: 'Insert a file from CloudFlare R2 or AWS',
      params: z.object({
        filename: z.string(),
      }),
      consumes: ["multipart/form-data"],
      body: z.custom<MultipartFile>(),
    },
    handler: async (request, reply) => {
      const data = request.body;

      if (!data.file) {
        return reply.status(400).send({
          message: "Please send a file"
        })
      }

      const buffer = data.file;
      await client.putObject({
        Bucket: process.env.BUCKET_NAME,
        Key: request.params.filename,
        Body: buffer,
      }).promise();

      return reply.status(201).send({ message: "File uploaded" })
    }
  });

Then my end point starts working with the input file correctly. See it here.

image

Thank you so much for your help.