Custom Queries and Operations
Relevant source files
The following files were used as context for generating this wiki page:
Seaography provides a robust mechanism for extending the automatically generated GraphQL schema with custom business logic. While the framework primarily focuses on reflecting SeaORM entities into a GraphQL API, the "Custom Queries and Operations" feature allows developers to define specialized resolvers, mutations, and complex types that are not directly mapped to a single database table.
This system leverages Rust proc-macros to reduce boilerplate when defining custom input/output types and implementation blocks for GraphQL fields. By integrating these custom components into the BuilderContext and Builder, developers can seamlessly mix hand-written logic with automated entity-based queries.
Sources: src/lib.rs:43-58, CHANGELOG.md:38-55
Core Architecture and Traits
The extensibility of Seaography relies on a set of traits and macros that bridge standard Rust structures with the async-graphql dynamic schema.
The CustomFields Trait
The CustomFields trait is the primary interface for adding new fields to the GraphQL schema. It is typically implemented via the #[CustomFields] macro on a struct (often named Operations or Queries).
This diagram illustrates how the #[CustomFields] macro transforms an implementation block into a collection of GraphQL fields ready for registration.
Sources: src/custom/types.rs:45-47, examples/sea-draw/src/schema.rs:252-260
Custom Type Traits
To support complex inputs and outputs in custom operations, Seaography defines several specialized traits:
| Trait | Purpose |
|---|---|
CustomFields | Converts struct methods into GraphQL fields for Queries, Mutations, or Subscriptions. |
CustomOutputType | Defines how a Rust type is represented as a GraphQL output type. |
CustomOutputObject | Specific for Rust structs that should be converted into GraphQL Objects. |
CustomEnum | Maps a Rust enum to a GraphQL Enum. |
CustomUnion | Maps a Rust enum with data-carrying variants to a GraphQL Union. |
Sources: src/custom/types.rs:45-66, macros/src/custom_output_type.rs:47-60, macros/src/custom_output_type.rs:149-160
Implementation of Custom Operations
Custom operations are defined by applying the #[CustomFields] macro to an impl block. Each async function within the block is converted into a GraphQL field.
Data Flow in Custom Resolvers
When a custom operation is executed, the framework manages the transition from GraphQL arguments to Rust types and handles the execution within the provided context.
The sequence shows how the custom resolver interacts with the database via the shared context provided by Seaography.
Sources: CHANGELOG.md:41-55, generator/src/writer.rs:86-105
Code Example: Custom Query Implementation
#[CustomFields]
impl Operations {
async fn login(ctx: &Context<'_>) -> async_graphql::Result<customer::Model> {
let db = ctx.data::<DatabaseConnection>().unwrap();
Ok(customer::Entity::find()
.one(db)
.await?
.ok_or_else(|| DbErr::RecordNotFound("Customer not found".to_owned()))?)
}
}
Sources: CHANGELOG.md:48-55
Type Mapping and Macros
The framework uses macros like #[derive(CustomOutputType)] and #[derive(CustomInputType)] to automate the metadata generation required by the dynamic schema builder.
Output Type Generation
The CustomOutputType macro handles both named structs and enums. For structs, it iterates through fields and maps them to their corresponding GraphQL types using gql_output_type_ref.
- Structs: Fields are mapped to
async_graphql::dynamic::Field. - Unit Enums: Mapped to
async_graphql::dynamic::Enum. - Data Enums: Mapped to
async_graphql::dynamic::Unionwhere each variant becomes a possible type.
Sources: macros/src/custom_output_type.rs:25-45, macros/src/custom_output_type.rs:136-160
Handling SeaORM Models as Output
Seaography can bridge custom types and SeaORM models. The GqlModelType trait allows SeaORM models to be used as return types in custom operations, maintaining consistency with the automatically generated entity types.
Sources: src/custom/types.rs:114-135
Registration in Schema Builder
Custom operations and types must be registered with the seaography::Builder to be included in the final schema.
| Registration Method | Argument Type | Source |
|---|---|---|
builder.queries.extend() | Vec<Field> via to_fields() | examples/sea-draw/src/schema.rs:256-258 |
builder.mutations.extend() | Vec<Field> via to_fields() | examples/sea-draw/src/schema.rs:252-254 |
builder.register_custom_output::<T>() | Type T implementing CustomOutput | examples/sea-draw/src/schema.rs:271-283 |
builder.register_custom_input::<T>() | Type T implementing CustomInput | examples/sea-draw/src/schema.rs:262-269 |
The query_root.rs file generated by the CLI serves as the entry point where these manual registrations are integrated alongside the register_entity_modules call.
Sources: generator/src/writer.rs:86-105, examples/sea-draw/src/schema.rs:240-290
Summary
Custom Queries and Operations in Seaography allow developers to bypass the strict entity-to-schema mapping when necessary. By using the CustomFields trait and associated derive macros, developers can add complex business logic, aggregate queries, and custom mutations while retaining access to the underlying SeaORM database connection and the type safety of the async-graphql ecosystem.