Nestjs 中的 Graphql Apollo 上传返回无效值

Posted

技术标签:

【中文标题】Nestjs 中的 Graphql Apollo 上传返回无效值【英文标题】:Graphql Apollo upload in Nestjs returns invalid value 【发布时间】: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)。它最初对我有用(大约一个小时),然后出现错误,我不确定发生了什么变化。 【参考方案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://***.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'

您必须收到完整的文件。

【讨论】:

以上是关于Nestjs 中的 Graphql Apollo 上传返回无效值的主要内容,如果未能解决你的问题,请参考以下文章

NestJS GraphQL 数据源

通过 Apollo Server (NestJS) 处理异常

elastic APM 和 NestJS 不区分 graphql 查询

在 NestJS + GraphQL 中实现“命名空间类型”解析器

基于typescript 强大的 nestjs 框架试用

使用 Graphql 在 NestJS 上订阅时,“不能为不可为空的字段返回 null”