Skip to main content
Version: 2.0.x

Custom Query

Define a new struct and apply the CustomFields macro.

queries.rs
use async_graphql::Context;
use seaography::{CustomFields};

pub struct Operations;

#[CustomFields]
impl Operations {
// this will become the name of the field
async my_query(
ctx: &Context<'_>, // GraphQL context
// parameters can be Seaography structs or structs derived by CustomInputType
) -> async_graphql::Result<()>
// return value must be async_graphql::Result<T>
// can return SeaORM Models or structs derived by CustomOutputType
{
// function body
Ok(())
}
}

Custom query with Custom Ouput

You can define nested data structures as custom output objects:

#[derive(Clone, CustomOutputType)]
pub struct PurchaseOrder {
pub po_number: String,
pub lineitems: Vec<Lineitem>, // ⬅ nested list
}

#[derive(Clone, CustomOutputType)]
pub struct Lineitem {
pub product: String,
pub quantity: Decimal, // ⬅ non-built-in type
pub size: Option<ProductSize>, // ⬅ nested object
}

#[derive(Clone, CustomOutputType)]
pub struct ProductSize {
pub size: i32,
}

You can use them in custom query endpoints:

#[CustomFields]
impl Operations {
async fn purchase_order(
_ctx: &Context<'_>,
po_number: String, // ⬅ named parameter
) -> async_graphql::Result<PurchaseOrder> {
// here we simply hard code the object, but you can totally make a REST API call
// and deserialize the JSON result with serde!

Ok(PurchaseOrder {
po_number: "AB1234".into(),
lineitems: vec![
Lineitem {
product: "Towel".into(),
quantity: "2".parse().unwrap(),
size: Some(ProductSize { size: 4 }),
},
Lineitem {
product: "Soap".into(),
quantity: "2.5".parse().unwrap(),
size: None,
},
],
})
}
}

Custom query with SeaORM Models

#[CustomFields]
impl Operations {
async fn find_film_by(
ctx: &Context<'_>,
title: String, // ⬅ named parameter
) -> async_graphql::Result<Vec<film::Model>> {
let db = ctx.data::<DatabaseConnection>()?;
// ⬆ this is a normal SeaORM db connection

// ⬇ your business logic, of course will be more complicated
let models = film::Entity::find()
.filter(film::Column::Title.contains(title))
.all(db)
.await?;

Ok(models)
}
}

Custom query with pagination

Let's say we have a Customer entity. We want to create a custom endpoint, like the one Seaography already provides, but with an additional requirement: only return customers of the current store from which the user makes request from.

customer.rs
//! This is an entity from the sakila schema, generated by sea-orm-cli
use sea_orm::entity::prelude::*;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "customer")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub customer_id: i32,
pub store_id: i32,
pub first_name: String,
pub last_name: String,
..
}
queries.rs
use seaography::{apply_pagination, Connection, CustomFields, PaginationInput};

#[CustomFields]
impl Operations {
async fn customer_of_current_store(
ctx: &Context<'_>,
pagination: PaginationInput,
// ⬆ this input struct is provided by Seaography
) -> async_graphql::Result<Connection<customer::Entity>> {
// this output struct ⬆ is provided by Seaography
let db = ctx.data::<DatabaseConnection>()?;
// ⬆ this is a normal SeaORM db connection
let session = ctx.data::<Session>()?;
// ⬆ this session is inject by the HTTP handler
let query = customer::Entity::find()
// ⬆ this is the same old SeaORM API
.filter(customer::Column::StoreId.eq(session.store_id));
// ⬆ here we implement our custom logic
// note that here, we haven't execute the query yet ..
// instead, we pass it to Seaography to handle the rest!
let connection = apply_pagination(&CONTEXT, db, query, pagination).await?;
// now the query executes ⬆

Ok(connection)
}
}

This would expose the following query endpoint:

customer_of_current_store(
pagination: PaginationInput
): CustomerConnection!

Query it like the following:

{
customer_of_current_store(pagination: { page: { page: 0, limit: 10 } }) {
nodes {
storeId
customerId
firstName
lastName
email
}
paginationInfo {
pages
current
}
}
}