【问题标题】:Graphql Apollo upload in Nestjs returns invalid value {}Nestjs 中的 Graphql Apollo 上传返回无效值 {}
【发布时间】:2020-05-20 10:21:42
【问题描述】:

我尝试使用 graphql-uploadGraphQLUpload 标量向 GraphQL 端点添加上传参数:

import { FileUpload, GraphQLUpload } from 'graphql-upload'

@Mutation(() => Image, { nullable: true })
async addImage(@Args({name: 'image', type: () => GraphQLUpload}) image: FileUpload): Promise<Image | undefined> {
// do stuff...
}

这最初是有效的。然而,运行几次后,它开始返回以下错误:

"Variable \"$image\" got invalid value {}; Expected type Upload. Upload value invalid."

尝试使用 Insomnia 客户端和 curl 进行测试:

curl localhost:8000/graphql \
  -F operations='{ "query": "mutation ($image: Upload!) { addImage(image: $image) { id } }", "variables": { "image": null } }'
  -F map='{ "0": ["variables.image"] }'
  -F 0=@/path/to/image

【问题讨论】:

  • @xadm 感谢您的回复。这里的规范:github.com/jaydenseric/graphql-multipart-request-spec 提到这些应该为空,不是这样吗?
  • 它工作... node.js 错误/警告?重启?
  • @xadm GraphQLError: Upload value invalid. at GraphQLScalarType.parseValue (/path/to/project/nest/node_modules/graphql-upload/lib/GraphQLUpload.js:66:11)。它最初对我有用(大约一个小时),然后出现错误,我不确定发生了什么变化。

标签: graphql nestjs apollo-server


【解决方案1】:

使用import {GraphQLUpload} from "apollo-server-express"

从'graphql-upload'导入GraphQLUpload

【讨论】:

  • 为什么这个答案不在顶部?!
  • 我在 docker 上遇到了问题,这对我有用。你来自未来
【解决方案2】:

根据@willsquire 的回答,我意识到由于某种原因,Scalar 装饰器对我不起作用,因此我最终将 graphql-upload 替换为下一个 sn-p

import * as FileType from 'file-type'
import { GraphQLError, GraphQLScalarType } from 'graphql'
import { Readable } from 'stream'

export interface FileUpload {
  filename: string
  mimetype: string
  encoding: string
  createReadStream: () => Readable
}

export const GraphQLUpload = new GraphQLScalarType({
  name: 'Upload',
  description: 'The `Upload` scalar type represents a file upload.',
  async parseValue(value: Promise<FileUpload>): Promise<FileUpload> {
    const upload = await value
    const stream = upload.createReadStream()
    const fileType = await FileType.fromStream(stream)

    if (fileType?.mime !== upload.mimetype)
      throw new GraphQLError('Mime type does not match file content.')

    return upload
  },
  parseLiteral(ast): void {
    throw new GraphQLError('Upload literal unsupported.', ast)
  },
  serialize(): void {
    throw new GraphQLError('Upload serialization unsupported.')
  },
})

【讨论】:

  • 抱歉回复晚了@Mutation(() =&gt; File) async uploadFile( @Args({ name: 'input', type: () =&gt; GraphQLUpload }) fileInput: FileUpload, ): Promise&lt;File&gt; { const url = await this.filesService.create(fileInput) return { url, success: true } } GraphQLUpload 和 FileUpload 是从我之前的代码中导入的,文件类型只是您要返回的架构
  • 上传文件时出现此错误:RangeError: Maximum call stack size exceeded at ReadStream.open"
【解决方案3】:

对我来说,@Yhozen 和@Willsquire 在这里提出的解决方案是一种解决方法,但不是问题的真正答案。

就我而言,真正的问题来自 graphql-upload 我的依赖项中有它,它正在创建此堆栈中描述的错误。

通过消除依赖,它解决了问题。正如@willsquire 评论的那样,graphql-upload 已经在 apollo-server 包中,无需将其导入包中。

【讨论】:

  • 它现在可能已经暴露了,但当时类型还没有暴露(即FileUpload )。因此依赖和导入:)
  • 当然@willsquire。你是 2 月 12 日的评论指导我。谢谢你。在尝试解决问题几个小时后,您给了我权利提示!
【解决方案4】:

经过一番挖掘,apollo-server-core 似乎自动解析中间件中的文件上传,graphql-upload 基于请求是多部分形式,而不是通过标量类型名称确定。所以 graphql-upload 不一定需要,因为它已经集成,但它对于获取解析的类型很有用:

import { Scalar } from '@nestjs/graphql'
import FileType from 'file-type'
import { GraphQLError } from 'graphql'
import { FileUpload } from 'graphql-upload'
import { isUndefined } from 'lodash'

@Scalar('Upload')
export class Upload {
  description = 'File upload scalar type'

  async parseValue(value: Promise<FileUpload>) {
    const upload = await value
    const stream = upload.createReadStream()
    const fileType = await FileType.fromStream(stream)

    if (isUndefined(fileType)) throw new GraphQLError('Mime type is unknown.')

    if (fileType?.mime !== upload.mimetype)
      throw new GraphQLError('Mime type does not match file content.')

    return upload
  }
}

2021 年 1 月 2 日更新

今天仍在为此苦苦挣扎。这里有一些很好的答案,但它们不再对我有用。问题是如果我在parseValue 中抛出错误,它会“挂起”。以下解决方案通过解决“挂起”问题并仍然推送实际文件以供使用(用例是.csv 文件)对我来说效果最好:

import { UnsupportedMediaTypeException } from '@nestjs/common'
import { Scalar } from '@nestjs/graphql'
import { ValueNode } from 'graphql'
import { FileUpload, GraphQLUpload } from 'graphql-upload'

export type CSVParseProps = {
  file: FileUpload
  promise: Promise<FileUpload>
}

export type CSVUpload = Promise<FileUpload | Error>
export type CSVFile = FileUpload

@Scalar('CSV', () => CSV)
export class CSV {
  description = 'CSV upload type.'
  supportedFormats = ['text/csv']

  parseLiteral(arg: ValueNode) {
    const file = GraphQLUpload.parseLiteral(arg, (arg as any).value)

    if (
      file.kind === 'ObjectValue' &&
      typeof file.filename === 'string' &&
      typeof file.mimetype === 'string' &&
      typeof file.encoding === 'string' &&
      typeof file.createReadStream === 'function'
    )
      return Promise.resolve(file)

    return null
  }

  // If this is `async` then any error thrown
  // hangs and doesn't return to the user. However,
  // if a non-promise is returned it fails reading the
  // stream later. We can't evaluate the `sync`
  // version of the file either as there's a data race (it's not
  // always there). So we return the `Promise` version
  // for usage that gets parsed after return...
  parseValue(value: CSVParseProps) {
    return value.promise.then((file) => {
      if (!this.supportedFormats.includes(file.mimetype))
        return new UnsupportedMediaTypeException(
          `Unsupported file format. Supports: ${this.supportedFormats.join(
            ' '
          )}.`
        )

      return file
    })
  }

  serialize(value: unknown) {
    return GraphQLUpload.serialize(value)
  }
}

这个在ArgsType:

@Field(() => CSV)
file!: CSVUpload

解析器中的这个:

// returns either the file or error to throw
const fileRes = await file

if (isError(fileRes)) throw fileRes

【讨论】:

  • 您能否进一步解释一下您是如何使您的突变发挥作用的?我遇到了同样的错误
  • 你的apollo-server-core 包最晚了吗?如果是这样,它的依赖项中包含graphql-upload,并且已经有中间件处理文件上传。
【解决方案5】:

如果你想使用 graphql-upload 包,那么你必须应用他们的 Express 中间件,并禁用 apollo 服务器的内部上传模块。

看到这个答案: https://stackoverflow.com/a/64659256/10404270

之后,像这样的突变对我有用:

import { GraphQLUpload, FileUpload } from "graphql-upload";

  @Mutation(() => Boolean)
  async docUpload(
    @Arg('userID') userid: number,
    @Arg('file', () => GraphQLUpload)
    file: FileUpload
  ) {
    const { filename, createReadStream } = file;
    console.log(userid, file, filename, createReadStream);
    return true
  }

【讨论】:

    【解决方案6】:

    最佳答案在这里: https://dilushadasanayaka.medium.com/nestjs-file-upload-with-grapql-18289b9e32a2

    注意:要测试文件上传,您必须发送正确格式的文件。 Graphql 不支持文件路径。如:

    {
        file: '/user/mim/desktop/t.txt'
    }
    

    您必须收到完整的文件。

    【讨论】:

      猜你喜欢
      • 2019-09-29
      • 2020-04-03
      • 2018-12-16
      • 2021-04-25
      • 2019-01-04
      • 2017-12-09
      • 2021-12-10
      • 2020-07-01
      • 2018-08-08
      相关资源
      最近更新 更多