学习GraphQL服务器端(第五部分)-引入自定义标量类型
你好。
第五部分讲述的是关于“引入自定义Scala类型”的内容。
关于这一章
在GraphQL查询中,返回的字段必须全部为标量类型。
query {
user(name: "hsaki") {
id # ID型(スカラ型)
name # 文字列型(スカラ)
projectV2(number: 1) {
title # 文字列型(スカラ)
}
}
}
在Go语言中,内置了五个变量,每个变量都对应着相应的合适数据类型。
-
- Int: 符号あり32bit整数
-
- Float: 符号あり倍精度浮動小数点数
-
- Boolean: trueまたはfalse
String: UTF‐8の文字列
ID: 実態としてはStringですが、unique identifierとしての機能を持ったフィールドであればこのID型にするのが望ましいです。
但是,除了这个之外,还会有其他需要自己定义和准备标量类型的场景存在。
在本章中,我们将解释如何引入这些自定义标量类型。
自定义标量的具体例。
我将从当前使用的GraphQL架构中介绍使用自定义标量的地方。
具体例一:日期时间
在定义了DateTime类型的情况下,它被用于Repository对象的createdAt字段等,表示日期和时间信息。
schema.graphqls的翻译: 模式.graphqls
scalar DateTime
type Repository implements Node {
createdAt: DateTime!
}
如果在 gqlgen上自動生成代碼時並不進行任何設置,對於 DateTime 型別的 Go 結構體字段將變為 string 型別。
type Repository struct {
CreatedAt string `json:"createdAt"`
}
如果能在Go语言中将这个转换为time.Time类型将非常便利。这就是引入自定义标量类型的动机。
具体例2:统一资源标识符(URI)
除了DateTime类型之外,还定义了一种称为URI类型的自定义标量类型,用于表示Issue和PR的URL。
schema.graphqls 的另一种说法是什么?
scalar URI
type Issue implements Node {
url: URI!
}
这种情况下,如果没有特别的设置,这也会在Go端被处理为字符串类型。我希望将其转换为url.URL类型。
type Issue struct {
URL string `json:"url"`
}
自定义Scala的实现
要修改Go类型以与在GraphQL模式中定义的自定义标量类型相对应,有以下三种方法:
-
- サードパーティライブラリに用意されたカスタムスカラ対応ロジックを使う
-
- 独自型にMarshalGQL/UnmarshalGQLメソッドを実装する
MarshalXxx/UnmarshalXxx関数を定義する
选项1 – 使用第三方库中提供的自定义标量兼容类型的方法
实施方式 (shí shī shì)
在 github.com/99designs/gqlgen 上,已经提供了常用的自定义标量的逻辑处理。
包括将自定义定义的标量与 Go 的 time.Time 类型相匹配的逻辑也不例外。
为了将当前模式中的DateTime类型转换为Go的time.Time类型,您可以在gqlgen的配置文件gqlgen.yml中添加以下设置。
models:
ID:
model:
- github.com/99designs/gqlgen/graphql.ID
- github.com/99designs/gqlgen/graphql.Int
- github.com/99designs/gqlgen/graphql.Int64
- github.com/99designs/gqlgen/graphql.Int32
Int:
model:
- github.com/99designs/gqlgen/graphql.Int
- github.com/99designs/gqlgen/graphql.Int64
- github.com/99designs/gqlgen/graphql.Int32s
+ DateTime:
+ model:
+ - github.com/99designs/gqlgen/graphql.Time
在修改了gqlgen.yml之后,执行gqlgen generate命令,应该可以确认生成的模型类型在models_gen.go中已经发生了以下的变化。
type Repository struct {
- CreatedAt string `json:"createdAt"`
+ CreatedAt time.Time `json:"createdAt"`
}
在github.com/99designs/gqlgen上提供的自定义标量支持类型。
类似于 `github.com/99designs/gqlgen/graphql.Time` 的方式,可以通过在 gqlgen.yml 中指定来改变自定义标量的 Go 类型,还有其他的机制存在。
选项1: 实现自定义的MarshalGQL/UnmarshalGQL方法
如果你想让自定义的标量类型适应github.com/99designs/gqlgen没有准备好的类型,比如URI类型,在这种情况下也是存在的。
模式.graphqls
scalar URI
希望使Go语言的结构体与GraphQL的自定义标量类型相对应,必须满足graphql.Marshaler接口和graphql.Unmarshaler接口。
type Marshaler interface {
MarshalGQL(w io.Writer)
}
type Unmarshaler interface {
UnmarshalGQL(v interface{}) error
}
要将自定义标量类型URI与本次定义的MyURL类型对应起来,首先需要在MyURL类型中实现MarshalGQL/UnmarshalGQL方法。
type MyURL struct {
url.URL
}
// MarshalGQL implements the graphql.Marshaler interface
func (u MyURL) MarshalGQL(w io.Writer) {
io.WriteString(w, fmt.Sprintf(`"%s"`, u.URL.String()))
}
// UnmarshalGQL implements the graphql.Unmarshaler interface
func (u *MyURL) UnmarshalGQL(v interface{}) error {
switch v := v.(type) {
case string:
if result, err := url.Parse(v); err != nil {
return err
} else {
u = &MyURL{*result}
}
return nil
case []byte:
result := &url.URL{}
if err := result.UnmarshalBinary(v); err != nil {
return err
}
u = &MyURL{*result}
return nil
default:
return fmt.Errorf("%T is not a url.URL", v)
}
}
在gqlgen.yml文件中配置使用刚刚创建的MyURL类型,并运行gqlgen generate命令重新生成代码,可以确认利用了URI类型的Issue.URL字段的定义已经发生了变化。
models:
+ URI:
+ model:
+ - github.com/saki-engineering/graphql-sample/graph/model.MyURL
type Issue struct {
- URL string `json:"url"`
+ URL MyURL `json:"url"`
}
选项1:第三种方法-自定义MarshalXxx/UnmarshalXxx函数。
对于方法2,由于自定义了名为MyURL的类型来映射自定义标量URI类型,因此可以自由地添加MarshalGQL/UnmarshalGQL方法。
然而,如果想将自定义标量URI类型映射到标准包内的url.URL类型,方法2是无法使用的。
这是因为无法为已经在标准包net/url中定义的现有类型url.URL添加方法实现。
在某些情况下,存在着无法根据自己的判断添加MarshalGQL/UnmarshalGQL方法的现有第三方包型和标准包型的模式。在这种情况下,我们需要准备MarshalXxx/UnmarshalXxx函数。
func MarshalURI(u url.URL) graphql.Marshaler {
return graphql.WriterFunc(func(w io.Writer) {
io.WriteString(w, fmt.Sprintf(`"%s"`, u.String()))
})
}
func UnmarshalURI(v interface{}) (url.URL, error) {
switch v := v.(type) {
case string:
u, err := url.Parse(v)
if err != nil {
return url.URL{}, err
}
return *u, nil
case []byte:
u := &url.URL{}
if err := u.UnmarshalBinary(v); err != nil {
return url.URL{}, err
}
return *u, nil
default:
return url.URL{}, fmt.Errorf("%T is not a url.URL", v)
}
}
在定义了MarshalURI/UnmarshalURI函数后,只需在gqlgen.yml中按照以下设置并重新生成代码,就可以将自定义类型URI与标准包中的url.URL类型成功关联。
models:
+ URI:
+ model:
- - github.com/saki-engineering/graphql-sample/graph/model.MyURL
+ - github.com/saki-engineering/graphql-sample/graph/model.URI
type Issue struct {
- URL string `json:"url"`
+ URL url.URL `json:"url"`
}
下一个部分
下面是有关“解决方案的实施-应用篇”的内容。
今天就到这里吧。
请多关照。