用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 字段,就可以方便地定义相同的字段。非常方便。