Saltar al contenido principal

Filtros de Entrada

[Traducción Beta No Oficial]

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

En esta sección, continuamos con el ejemplo de GraphQL explicando cómo generar filtros type-safe para GraphQL (es decir, predicados Where) desde nuestro ent/schema, permitiendo a los usuarios mapear consultas GraphQL a consultas Ent de forma transparente. Por ejemplo, la siguiente consulta GraphQL se mapea a la consulta Ent que se muestra:

GraphQL

{
hasParent: true,
hasChildrenWith: {
status: IN_PROGRESS,
}
}

Ent

client.Todo.
Query().
Where(
todo.HasParent(),
todo.HasChildrenWith(
todo.StatusEQ(todo.StatusInProgress),
),
).
All(ctx)

Clonar el código (opcional)

El código de este tutorial está disponible en github.com/a8m/ent-graphql-example, con etiquetas (usando Git) en cada paso. Si deseas saltar la configuración básica y comenzar con la versión inicial del servidor GraphQL, puedes clonar el repositorio y ejecutar el programa así:

git clone git@github.com:a8m/ent-graphql-example.git
cd ent-graphql-example
go run ./cmd/todo/

Configurar Ent

Ve a tu archivo ent/entc.go y añade las 4 líneas resaltadas (opciones de extensión):

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)
}
}

La opción WithWhereInputs habilita la generación de filtros. WithConfigPath configura la ruta al archivo de configuración de gqlgen, permitiendo al extension mapear tipos de GraphQL a Ent con mayor precisión. La última opción WithSchemaPath, configura una ruta para escribir los filtros generados, ya sea en un esquema GraphQL nuevo o existente.

Tras modificar la configuración en entc.go, estamos listos para ejecutar la generación de código:

go generate .

Observa que Ent ha generado <T>WhereInput para cada tipo de tu esquema en el archivo ent/gql_where_input.go. Ent también genera un esquema GraphQL (ent.graphql), así que no necesitas hacer autobind manualmente en gqlgen. Por ejemplo:

ent/gql_where_input.go
// 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 ..
}
ent.graphql
"""
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 ..
}
información

Si tu proyecto contiene más de 1 esquema GraphQL (ej. todo.graphql y ent.graphql), configura el archivo gqlgen.yml así:

schema:
- todo.graphql
# The ent.graphql schema was generated by Ent.
- ent.graphql

Configurar GQL

Tras la generación de código, completamos la integración y exponemos los filtros en GraphQL:

1. Edita el esquema GraphQL para aceptar los nuevos tipos de filtros:

ent.graphql
type Query {
todos(
after: Cursor,
first: Int,
before: Cursor,
last: Int,
orderBy: TodoOrder,
where: TodoWhereInput,
): TodoConnection!
}

2. Utiliza los nuevos tipos de filtros en los resolvers de GraphQL:

ent.resolvers.go
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),
)
}

Ejecutar Consultas

Como mencionamos, con los nuevos tipos de filtro de GraphQL puedes expresar los mismos filtros Ent que usas en tu código Go.

Conjunción, disyunción y negación

Los operadores Not, And y Or pueden añadirse a la cláusula where usando los campos not, and y or. Por ejemplo:

query {
todos(
where: {
or: [
{
status: COMPLETED
},
{
not: {
hasParent: true,
status: IN_PROGRESS
}
}
]
}
) {
edges {
node {
id
text
}
cursor
}
}
}

Cuando se proporcionan múltiples campos de filtro, Ent añade implícitamente el operador And.

{
status: COMPLETED,
textHasPrefix: "GraphQL",
}

La consulta anterior producirá el siguiente query de Ent:

client.Todo.
Query().
Where(
todo.And(
todo.StatusEQ(todo.StatusCompleted),
todo.TextHasPrefix("GraphQL"),
)
).
All(ctx)

Filtros de relaciones (edges)

Los predicados de relaciones (edges) pueden expresarse con la misma sintaxis de Ent:

{
hasParent: true,
hasChildrenWith: {
status: IN_PROGRESS,
}
}

La consulta anterior producirá el siguiente query de Ent:

client.Todo.
Query().
Where(
todo.HasParent(),
todo.HasChildrenWith(
todo.StatusEQ(todo.StatusInProgress),
),
).
All(ctx)

Filtros personalizados

A veces necesitamos añadir condiciones personalizadas a nuestros filtros. Si bien siempre es posible usar Plantillas y SchemaHooks, no siempre es la solución más sencilla, especialmente para condiciones simples.

Afortunadamente, combinando extensiones de tipos de objeto en GraphQL y resolvers personalizados, podemos lograr esta funcionalidad.

Veamos un ejemplo añadiendo un filtro personalizado isCompleted que reciba un valor booleano y filtre todos los TODO que tengan el estado completed.

Comencemos extendiendo TodoWhereInput:

todo.graphql
extend input TodoWhereInput {
isCompleted: Boolean
}

Tras generar el código, veremos un nuevo campo resolver en el archivo todo.resolvers.go:

todo.resolvers.go
func (r *todoWhereInputResolver) IsCompleted(ctx context.Context, obj *ent.TodoWhereInput, data *bool) error {
panic(fmt.Errorf("not implemented"))
}

Ahora podemos usar el método AddPredicates en la struct ent.TodoWhereInput para implementar nuestro filtro:

todo.resolvers.go
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
}

Podemos usar este nuevo filtro como cualquier otro predicado:

{
isCompleted: true,
}
# including the not, and and or fields
{
not: {
isCompleted: true,
}
}

Uso como predicados

La opción Filter nos permite usar los WhereInput generados como predicados regulares en cualquier tipo de consulta:

query := ent.Todo.Query()
query, err := input.Filter(query)
if err != nil {
return nil, err
}
return query.All(ctx)

¡Bien hecho! Como ves, cambiando unas pocas líneas nuestra aplicación ahora expone filtros type-safe para GraphQL que se mapean automáticamente a consultas Ent. ¿Tienes preguntas? ¿Necesitas ayuda para comenzar? Únete a nuestro servidor de Discord o canal de Slack.