用graphql-ruby和graphql-codegen安全处理Date/DateTime类型的字段

背景

graphql-ruby と graphql-codegen を使ってスキーマの型を TypeScript 向けに自動生成している
その中で Date/DateTime 型のフィールドをうまく扱うにはどうしたらいいか調べた

环境

    • graphql-ruby 2.0.11

 

    • @graphql-codegen/cli 2.8.0

 

    • @graphql-codegen/typescript 2.7.1

 

    @graphql-codegen/typescript-urql 3.6.1

题目

如果在 GraphQL-Ruby 中有 Date/DateTime 类型的字段,默认情况下没有类似于 Date 的标量类型,因此可以直接写成以下方式。

module Types
  class UserType < Types::BaseObject
    field :name, String, null: false
    field :created_at, String, null: false
    field :updated_at, String, null: false

    def created_at
      object.created_at.iso8601
    end

    def updated_at
      object.updated_at.iso8601
    end
  end
end

由于需要针对每个字段定义方法才能逐个转换为ISO8601格式,我希望找到一种办法解决这个问题。

使用 GraphQL::Types::ISO8601DateTime 类

当我稍微调查了一下,发现存在一个名为 GraphQL::Types::ISO8601Date / GraphQL::Types::ISO8601DateTime 的类。

这个可以通过指定类型来使用,当 field 中的内容是 DateTime 类型时,它会将其转换为 ISO8601 格式的字符串。

module Types
  class UserType < Types::BaseObject
    field :name, String, null: false
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
  end
end

“Can you recommend a good place to eat in this area?”

query getUser {
  user { name createdAt updatedAt }
}

返回的 JSON 数据:
返回的 JSON:
JSON 响应:

{
  "data": {
    "user": {
      "name": "User Name",
      "createdAt": "2022-10-08T20:25:10Z",
      "updatedAt": "2022-10-08T20:25:10Z"
    }
  }
}

在 graphql-codegen 中将其作为字符串类型处理

虽然到目前为止还不错,但是如果继续使用 graphql-codegen 来生成 TypeScript 的类型,createdAt 和 updatedAt 将变成 any 类型。

export type User = {
  __typename?: 'User';
  name: string;
  createdAt: any;
  updatedAt: any;
};

为了避免错误地将代码误认为是 Date 类型等其他类型,正确地将其定义为 string 类型,我们需要在类型定义中明确指定返回的类型为 string。
因此,我们需要在配置文件 codegen.yml 中添加以下设置。

generates:
  src/generated/graphql.ts:
    config:
      scalars:
        ISO8601Date: string
        ISO8601DateTime: string

当我们在执行graphql-codegen时,将这段代码添加进去,这段代码指示了将ISO8601Date类型视为自定义的Scalar类型,以string类型进行定义。

export type Scalars = {
  ID: string;
  String: string;
  Boolean: boolean;
  Int: number;
  Float: number;
  ISO8601DateTime: string; // Scalars['ISO8601DateTime'] が string に変換される
};

export type User = {
  __typename?: 'User';
  name: string;
  createdAt: Scalars['ISO8601DateTime']; // any ではなく Scalars['ISO8601DateTime'] として定義される
  updatedAt: Scalars['ISO8601DateTime'];
};

通过这种方式,可以将Date/DateTime类型的字段轻松转换为字符串并包含在JSON中,同时在前端中也可以安全地将其视为字符串类型处理。

虽然本次讨论的重点是处理 Date/Datetime 数据类型作为 field 的方法,但 GraphQL::Types::ISO8601DateTime 应该也具备将接收到的字符串参数转换为 DateTime 类型并传递给解析器的功能。
虽然我没有在本次讨论中使用它,也没有进行特别验证,但如果需要从前端发送 Date/DateTime 类型的数据,我会考虑尝试使用它。

附录:将定义timestamp字段的处理通用化

因为在处理Rails时,我认为会处理许多具有created_at/updated_at字段的数据,所以每次都写这样的定义很麻烦,我考虑定义了下面这个模块。

module Types
  module Concerns
    module TimestampFields
      extend ActiveSupport::Concern

      included do
        field :created_at, GraphQL::Types::ISO8601DateTime, null: false
        field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
      end
    end
  end
end

将其在每个资源的类中按照下述方式进行include。

module Types
  class UserType < Types::BaseObject
    include Types::Concerns::TimestampFields

    field :name, String, null: false
  end
end

在中国人的母语中改述以下内容,只需要提供一个选项:
通过包含 TimestampFields 模块,就不需要为每个模型每次都定义 created_at / updated_at 字段,就可以方便地定义相同的字段。非常方便。

广告
将在 10 秒后关闭
bannerAds