过滤器输入
在本部分我们继续 GraphQL 示例 来解释如何为 ent/schema 生成类型安全的 GraphQL 过滤器,并允许用户无缝地将 GraphQL 查询映射为 Ent 查询。例如下面的 GraphQL 映射为了 Ent 查询:
GraphQL
{
hasParent: true,
hasChildrenWith: {
status: IN_PROGRESS,
}
}
Ent
client.Todo.
Query().
Where(
todo.HasParent(),
todo.HasChildrenWith(
todo.StatusEQ(todo.StatusInProgress),
),
).
All(ctx)
克隆代码(可选)
本教程代码可以在 github.com/a8m/ent-graphql-example 找到, 并在每一步都打了标签(使用 Git)。 如果你想要跳过基础安装并使用 GraphQL 最初的版本,你可以像如下克隆代码仓库:
git clone git@github.com:a8m/ent-graphql-example.git
cd ent-graphql-example
go run ./cmd/todo/
配置 Ent
找到你的 ent/entc.go 文件,添加以下四行高亮的行(扩展选项):
func main() {
ex, err := entgql.NewExtension(
entgql.WithSchemaGenerator(),
entgql.WithWhereInputs(true),
entgql.WithConfigPath("gqlgen.yml"),
entgql.WithSchemaPath("ent.graphql"),
)
if err != nil {
log.Fatalf("creating entgql extension: %v", err)
}
opts := []entc.Option{
entc.Extensions(ex),
entc.TemplateDir("./template"),
}
if err := entc.Generate("./schema", &gen.Config{}, opts...); err != nil {
log.Fatalf("running ent codegen: %v", err)
}
}
WithWhereInputs 启用过滤器生成,WithConfigPath 设置 gqlgen 的配置文件路径,
此配置文件会使 GraphQL 到 Ent 类型映射的扩展更加精准。
最后的选项 WithSchemaPath 配置将生成后的过滤器写入到一个新的或已存在的 GraphQL 模式中。
修改完 entc.go 配置后,我们可以像如下执行代码生成:
go generate .
可以看到 Ent 已经在一个名为 ent/gql_where_input.go 的文件中为每个类型生成了 <T>WhereInput。
Ent 也生成了一个 GraphQL 模式(ent.graphql),所以你不必手动地 autobind 他们到 gqlgen。
例如:
// TodoWhereInput represents a where input for filtering Todo queries.
type TodoWhereInput struct {
Not *TodoWhereInput `json:"not,omitempty"`
Or []*TodoWhereInput `json:"or,omitempty"`
And []*TodoWhereInput `json:"and,omitempty"`
// "created_at" field predicates.
CreatedAt *time.Time `json:"createdAt,omitempty"`
CreatedAtNEQ *time.Time `json:"createdAtNEQ,omitempty"`
CreatedAtIn []time.Time `json:"createdAtIn,omitempty"`
CreatedAtNotIn []time.Time `json:"createdAtNotIn,omitempty"`
CreatedAtGT *time.Time `json:"createdAtGT,omitempty"`
CreatedAtGTE *time.Time `json:"createdAtGTE,omitempty"`
CreatedAtLT *time.Time `json:"createdAtLT,omitempty"`
CreatedAtLTE *time.Time `json:"createdAtLTE,omitempty"`
// "status" field predicates.
Status *todo.Status `json:"status,omitempty"`
StatusNEQ *todo.Status `json:"statusNEQ,omitempty"`
StatusIn []todo.Status `json:"statusIn,omitempty"`
StatusNotIn []todo.Status `json:"statusNotIn,omitempty"`
// .. truncated ..
}
"""
TodoWhereInput is used for filtering Todo objects.
Input was generated by ent.
"""
input TodoWhereInput {
not: TodoWhereInput
and: [TodoWhereInput!]
or: [TodoWhereInput!]
"""created_at field predicates"""
createdAt: Time
createdAtNEQ: Time
createdAtIn: [Time!]
createdAtNotIn: [Time!]
createdAtGT: Time
createdAtGTE: Time
createdAtLT: Time
createdAtLTE: Time
"""status field predicates"""
status: Status
statusNEQ: Status
statusIn: [Status!]
statusNotIn: [Status!]
# .. truncated ..
}
如果你的项目包括一个以上 GraphQL 模式(例如 todo.graphql 和 ent.graphql),你应该像如下配置 gqlgen.yml 文件:
schema:
- todo.graphql
# The ent.graphql schema was generated by Ent.
- ent.graphql
配置 GQL
运行完代码生成后,我们可以在 GraphQL 中完成集成并暴露过滤能力:
1. 编辑 GraphQL 模式以接收新的过滤器类型:
type Query {
todos(
after: Cursor,
first: Int,
before: Cursor,
last: Int,
orderBy: TodoOrder,
where: TodoWhereInput,
): TodoConnection!
}
2. 在 GraphQL 解析器中使用新的过滤器类型:
func (r *queryResolver) Todos(ctx context.Context, after *ent.Cursor, first *int, before *ent.Cursor, last *int, orderBy *ent.TodoOrder, where *ent.TodoWhereInput) (*ent.TodoConnection, error) {
return r.client.Todo.Query().
Paginate(ctx, after, first, before, last,
ent.WithTodoOrder(orderBy),
ent.WithTodoFilter(where.Filter),
)
}
执行查询
如上所述,借助新的 GraphQL 过滤器类型,你可以像在 Go 语言代码中一样表达 Ent 过滤器。
联合、析取和否定(与、或、否)
通过使用 not、 and 和 or 字段,Not、And 和 Or 操作可以被添加到 where 语句中。例如:
query {
todos(
where: {
or: [
{
status: COMPLETED
},
{
not: {
hasParent: true,
status: IN_PROGRESS
}
}
]
}
) {
edges {
node {
id
text
}
cursor
}
}
}
当提供了多个过滤器字段,Ent 隐式地添加 And 操作符。
{
status: COMPLETED,
textHasPrefix: "GraphQL",
}
以上查询会执行下面的 Ent 查询:
client.Todo.
Query().
Where(
todo.And(
todo.StatusEQ(todo.StatusCompleted),
todo.TextHasPrefix("GraphQL"),
)
).
All(ctx)
边/关系过滤器
边 (关系) 谓词 可以在 Ent 语法中以相同方式表达:
{
hasParent: true,
hasChildrenWith: {
status: IN_PROGRESS,
}
}
以上查询会执行下面的 Ent 查询:
client.Todo.
Query().
Where(
todo.HasParent(),
todo.HasChildrenWith(
todo.StatusEQ(todo.StatusInProgress),
),
).
All(ctx)
自定义过滤器
有时我们向过滤器需要添加自定义条件,但是使用 模板(Templates) 和 模式钩子(SchemaHooks) 并不总是最简单的解决方法, 特别是我们只想添加一个简单的条件。
幸运的是我们可以使用 GraphQL 对象类型扩展 与自定义解析器的组合获取这个功能。
让我们看一个添加自定义 isCompleted 过滤器的示例,这个过滤器会接收布尔值并过滤所有待办事项是否是 completed 状态。
我们先扩展 TodoWhereInput:
extend input TodoWhereInput {
isCompleted: Boolean
}
运行代码生成后我们应该可以看到在 todo.resolvers.go 生成了一个新的字段过滤器:
func (r *todoWhereInputResolver) IsCompleted(ctx context.Context, obj *ent.TodoWhereInput, data *bool) error {
panic(fmt.Errorf("not implemented"))
}
我们现在可以使用 ent.TodoWhereInput 结构体中的 AddPredicates 方法来实现自定义过滤:
func (r *todoWhereInputResolver) IsCompleted(ctx context.Context, obj *ent.TodoWhereInput, data *bool) error {
if obj == nil || data == nil {
return nil
}
if *data {
obj.AddPredicates(todo.StatusEQ(todo.StatusCompleted))
} else {
obj.AddPredicates(todo.StatusNEQ(todo.StatusCompleted))
}
return nil
}
我们可以像其他谓词一样使用新的过滤器:
{
isCompleted: true,
}
# including the not, and and or fields
{
not: {
isCompleted: true,
}
}
像谓词一样使用过滤器
Filter 选项让我们在任何类型的查询中像普通谓词一样使用生成的 WhereInput:
query := ent.Todo.Query()
query, err := input.Filter(query)
if err != nil {
return nil, err
}
return query.All(ctx)
做得很好!正如你所见,通过修改应用程序的几行代码就可以暴露类型安全的 GraphQL 过滤器来自动映射为 Ent 查询。 有任何问题吗?需要帮助吗?加入我们的 Discord 服务器 或 Slack 频道。