206 lines
6.2 KiB
Rust
206 lines
6.2 KiB
Rust
|
mod connection;
|
||
|
mod edge;
|
||
|
mod page_info;
|
||
|
mod slice;
|
||
|
|
||
|
use crate::{Context, ObjectType, QueryError, Result};
|
||
|
|
||
|
pub use connection::Connection;
|
||
|
|
||
|
/// Connection query operation.
|
||
|
pub enum QueryOperation<'a> {
|
||
|
Forward {
|
||
|
after: Option<&'a str>,
|
||
|
limit: usize,
|
||
|
},
|
||
|
Backward {
|
||
|
before: Option<&'a str>,
|
||
|
limit: usize,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
/// Empty edge extension object.
|
||
|
pub struct EmptyEdgeFields;
|
||
|
|
||
|
#[async_graphql_derive::Object(internal)]
|
||
|
impl EmptyEdgeFields {}
|
||
|
|
||
|
/// Data source of GraphQL Cursor Connections type.
|
||
|
///
|
||
|
/// `Edge` is an extension object type that extends the edge fields, If you don't need it, you can use `EmptyEdgeFields`.
|
||
|
///
|
||
|
/// # References
|
||
|
/// (GraphQL Cursor Connections Specification)[https://facebook.github.io/relay/graphql/connections.htm]
|
||
|
///
|
||
|
/// # Examples
|
||
|
///
|
||
|
/// ```rust
|
||
|
/// use async_graphql::*;
|
||
|
/// use byteorder::{ReadBytesExt, BE};
|
||
|
///
|
||
|
/// struct QueryRoot;
|
||
|
///
|
||
|
/// struct DiffFields(i32);
|
||
|
///
|
||
|
/// #[Object]
|
||
|
/// impl DiffFields {
|
||
|
/// #[field]
|
||
|
/// async fn diff(&self) -> i32 {
|
||
|
/// self.0
|
||
|
/// }
|
||
|
/// }
|
||
|
///
|
||
|
/// struct Numbers;
|
||
|
///
|
||
|
/// #[async_trait::async_trait]
|
||
|
/// impl DataSource for Numbers {
|
||
|
/// type Element = i32;
|
||
|
/// type Edge = DiffFields;
|
||
|
///
|
||
|
/// async fn query_operation(&self, operation: &QueryOperation<'_>) -> Result<Connection<Self::Element, Self::Edge>> {
|
||
|
/// let (start, end) = match operation {
|
||
|
/// QueryOperation::Forward {after, limit} => {
|
||
|
/// let start = after.and_then(|after| base64::decode(after).ok())
|
||
|
/// .and_then(|data| data.as_slice().read_i32::<BE>().ok())
|
||
|
/// .map(|idx| idx + 1)
|
||
|
/// .unwrap_or(0);
|
||
|
/// let end = start + *limit as i32;
|
||
|
/// (start, end)
|
||
|
/// }
|
||
|
/// QueryOperation::Backward {before, limit} => {
|
||
|
/// let end = before.and_then(|before| base64::decode(before).ok())
|
||
|
/// .and_then(|data| data.as_slice().read_i32::<BE>().ok())
|
||
|
/// .unwrap_or(0);
|
||
|
/// let start = end - *limit as i32;
|
||
|
/// (start, end)
|
||
|
/// }
|
||
|
/// };
|
||
|
///
|
||
|
/// let nodes = (start..end).into_iter().map(|n| (base64::encode(n.to_be_bytes()), DiffFields(n - 1000), n)).collect();
|
||
|
/// Ok(Connection::new(None, true, true, nodes))
|
||
|
/// }
|
||
|
/// }
|
||
|
///
|
||
|
/// #[Object]
|
||
|
/// impl QueryRoot {
|
||
|
/// #[field]
|
||
|
/// async fn numbers(&self, ctx: &Context<'_>,
|
||
|
/// after: Option<String>,
|
||
|
/// before: Option<String>,
|
||
|
/// first: Option<i32>,
|
||
|
/// last: Option<i32>
|
||
|
/// ) -> Result<Connection<i32, DiffFields>> {
|
||
|
/// Numbers.query(ctx, after, before, first, last).await
|
||
|
/// }
|
||
|
/// }
|
||
|
///
|
||
|
/// #[async_std::main]
|
||
|
/// async fn main() {
|
||
|
/// let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);
|
||
|
///
|
||
|
/// assert_eq!(schema.query("{ numbers(first: 2) { edges { node } } }").execute().await.unwrap(), serde_json::json!({
|
||
|
/// "numbers": {
|
||
|
/// "edges": [
|
||
|
/// {"node": 0},
|
||
|
/// {"node": 1}
|
||
|
/// ]
|
||
|
/// },
|
||
|
/// }));
|
||
|
///
|
||
|
/// assert_eq!(schema.query("{ numbers(last: 2) { edges { node diff } } }").execute().await.unwrap(), serde_json::json!({
|
||
|
/// "numbers": {
|
||
|
/// "edges": [
|
||
|
/// {"node": -2, "diff": -1002},
|
||
|
/// {"node": -1, "diff": -1001}
|
||
|
/// ]
|
||
|
/// },
|
||
|
/// }));
|
||
|
/// }
|
||
|
/// ```
|
||
|
#[async_trait::async_trait]
|
||
|
pub trait DataSource: Sync + Send {
|
||
|
type Element;
|
||
|
type Edge: ObjectType + Send + Sync;
|
||
|
|
||
|
async fn query(
|
||
|
&self,
|
||
|
ctx: &Context<'_>,
|
||
|
after: Option<String>,
|
||
|
before: Option<String>,
|
||
|
first: Option<i32>,
|
||
|
last: Option<i32>,
|
||
|
) -> Result<Connection<Self::Element, Self::Edge>> {
|
||
|
let operation = if let Some(after) = &after {
|
||
|
QueryOperation::Forward {
|
||
|
after: Some(after),
|
||
|
limit: match first {
|
||
|
Some(value) => {
|
||
|
if value < 0 {
|
||
|
return Err(QueryError::ArgumentMustBeNonNegative {
|
||
|
field_name: ctx.name.clone(),
|
||
|
}
|
||
|
.into());
|
||
|
} else {
|
||
|
value as usize
|
||
|
}
|
||
|
}
|
||
|
None => 10,
|
||
|
},
|
||
|
}
|
||
|
} else if let Some(before) = &before {
|
||
|
QueryOperation::Backward {
|
||
|
before: Some(before),
|
||
|
limit: match last {
|
||
|
Some(value) => {
|
||
|
if value < 0 {
|
||
|
return Err(QueryError::ArgumentMustBeNonNegative {
|
||
|
field_name: ctx.name.clone(),
|
||
|
}
|
||
|
.into());
|
||
|
} else {
|
||
|
value as usize
|
||
|
}
|
||
|
}
|
||
|
None => 10,
|
||
|
},
|
||
|
}
|
||
|
} else if let Some(first) = first {
|
||
|
QueryOperation::Forward {
|
||
|
after: None,
|
||
|
limit: if first < 0 {
|
||
|
return Err(QueryError::ArgumentMustBeNonNegative {
|
||
|
field_name: ctx.name.clone(),
|
||
|
}
|
||
|
.into());
|
||
|
} else {
|
||
|
first as usize
|
||
|
},
|
||
|
}
|
||
|
} else if let Some(last) = last {
|
||
|
QueryOperation::Backward {
|
||
|
before: None,
|
||
|
limit: if last < 0 {
|
||
|
return Err(QueryError::ArgumentMustBeNonNegative {
|
||
|
field_name: ctx.name.clone(),
|
||
|
}
|
||
|
.into());
|
||
|
} else {
|
||
|
last as usize
|
||
|
},
|
||
|
}
|
||
|
} else {
|
||
|
QueryOperation::Forward {
|
||
|
after: None,
|
||
|
limit: 10,
|
||
|
}
|
||
|
};
|
||
|
|
||
|
self.query_operation(&operation).await
|
||
|
}
|
||
|
|
||
|
async fn query_operation(
|
||
|
&self,
|
||
|
operation: &QueryOperation<'_>,
|
||
|
) -> Result<Connection<Self::Element, Self::Edge>>;
|
||
|
}
|