Skip to content

Commit

Permalink
Allow TryIntoStruct to unpack EdgeParameters as well. (#350)
Browse files Browse the repository at this point in the history
Edges defined in Trustfall schemas may take parameters, for example:

```graphql
type NewsWebsite {
    latest_stories(count: Int!): [Story!]!
}
```

This trait can be used to deserialize [`&EdgeParameters`](crate::ir::EdgeParameters)
into a struct specific to the parameters of that edge:
```rust
struct LatestStoriesParameters {
    count: usize
}
```

For example:
```rust
use trustfall_core::TryIntoStruct;

fn resolve_latest_stories(contexts: ContextIterator<Vertex>, parameters: &EdgeParameters) {
    let parameters: LatestStoriesParameters = parameters
        .try_into_struct()
        .expect("edge parameters did not match struct definition");
    let count = parameters.count;
    // then resolve the edge with the given count
}
```

Resolves #343.
  • Loading branch information
obi1kenobi authored Jul 7, 2023
1 parent 269ae7d commit bbade61
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 13 deletions.
2 changes: 1 addition & 1 deletion trustfall_core/src/ir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl Eid {
/// [`Adapter::resolve_neighbors`]: crate::interpreter::Adapter::resolve_neighbors
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct EdgeParameters {
contents: Arc<BTreeMap<Arc<str>, FieldValue>>,
pub(crate) contents: Arc<BTreeMap<Arc<str>, FieldValue>>,
}

impl EdgeParameters {
Expand Down
2 changes: 1 addition & 1 deletion trustfall_core/src/schema/adapter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use super::Schema;
///
/// // Create an adapter that queries
/// // the schema in the local `schema.graphql` file.
/// # [allow(unused_variables)]
/// # #[allow(unused_variables)]
/// let adapter = SchemaAdapter::new(&schema);
///
/// // Run queries using the adapter, etc.
Expand Down
106 changes: 95 additions & 11 deletions trustfall_core/src/serialization/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,40 @@ mod deserializers;
#[cfg(test)]
mod tests;

/// Deserialize Trustfall query results into a Rust struct.
/// Deserialize Trustfall query results or edge parameters into a Rust struct.
///
/// # Use with query results
///
/// Running a Trustfall query produces an iterator of `BTreeMap<Arc<str>, FieldValue>` outputs
/// representing the query results. These maps all have a common "shape" — the same keys and
/// the same value types — as determined by the query and schema.
///
/// This trait allows deserializing those query result maps into a dedicated struct,
/// to get you easy access to strongly-typed data instead of [`FieldValue`] enums.
///
/// ## Example
///
/// Say we ran a query like:
/// ```graphql
/// query {
/// Order {
/// item_name @output
/// quantity @output
/// }
/// }
/// ```
///
/// Each of this query's outputs contain a string named `item_name` and an integer named `quantity`.
/// This trait allows us to define an output struct type:
/// ```rust
/// #[derive(Debug, PartialEq, Eq, serde::Deserialize)]
/// struct Output {
/// item_name: String,
/// quantity: i64,
/// }
/// ```
///
/// We can then unpack the query results into an iterator of such structs:
/// ```rust
/// # use std::{collections::BTreeMap, sync::Arc};
/// # use maplit::btreemap;
Expand All @@ -19,20 +51,20 @@ mod tests;
/// # fn run_query() -> Result<Box<dyn Iterator<Item = BTreeMap<Arc<str>, FieldValue>>>, ()> {
/// # Ok(Box::new(vec![
/// # btreemap! {
/// # Arc::from("number") => FieldValue::Int64(42),
/// # Arc::from("text") => FieldValue::String("the answer to everything".to_string()),
/// # Arc::from("item_name") => FieldValue::String("widget".to_string()),
/// # Arc::from("quantity") => FieldValue::Int64(42),
/// # }
/// # ].into_iter()))
/// # }
/// #
/// # #[derive(Debug, PartialEq, Eq, serde::Deserialize)]
/// # struct Output {
/// # item_name: String,
/// # quantity: i64,
/// # }
///
/// use trustfall_core::TryIntoStruct;
///
/// #[derive(Debug, PartialEq, Eq, serde::Deserialize)]
/// struct Output {
/// number: i64,
/// text: String,
/// }
///
/// let results: Vec<_> = run_query()
/// .expect("bad query arguments")
/// .map(|v| v.try_into_struct().expect("struct definition did not match query result shape"))
Expand All @@ -41,13 +73,55 @@ mod tests;
/// assert_eq!(
/// vec![
/// Output {
/// number: 42,
/// text: "the answer to everything".to_string(),
/// item_name: "widget".to_string(),
/// quantity: 42,
/// },
/// ],
/// results,
/// );
/// ```
///
/// # Use with edge parameters
///
/// Edges defined in Trustfall schemas may take parameters, for example:
/// ```graphql
/// type NewsWebsite {
/// latest_stories(count: Int!): [Story!]!
/// }
/// ```
///
/// This trait can be used to deserialize [`&EdgeParameters`](crate::ir::EdgeParameters)
/// into a struct specific to the parameters of that edge:
/// ```rust
/// #[derive(Debug, PartialEq, Eq, serde::Deserialize)]
/// struct LatestStoriesParameters {
/// count: usize
/// }
/// ```
///
/// For example:
/// ```rust
/// # use trustfall_core::{ir::EdgeParameters, interpreter::ContextIterator};
/// #
/// # #[derive(Debug, Clone)]
/// # struct Vertex;
/// #
/// # #[derive(Debug, PartialEq, Eq, serde::Deserialize)]
/// # struct LatestStoriesParameters {
/// # count: usize
/// # }
///
/// use trustfall_core::TryIntoStruct;
///
/// fn resolve_latest_stories(contexts: ContextIterator<Vertex>, parameters: &EdgeParameters) {
/// let parameters: LatestStoriesParameters = parameters
/// .try_into_struct()
/// .expect("edge parameters did not match struct definition");
/// let count = parameters.count;
///
/// // then resolve the edge with the given count
/// }
/// ```
pub trait TryIntoStruct {
type Error;

Expand All @@ -62,3 +136,13 @@ impl TryIntoStruct for BTreeMap<Arc<str>, FieldValue> {
S::deserialize(deserializer)
}
}

impl<'a> TryIntoStruct for &'a crate::ir::EdgeParameters {
type Error = deserializers::Error;

fn try_into_struct<S: DeserializeOwned>(self) -> Result<S, deserializers::Error> {
let data = (*self.contents).clone();
let deserializer = deserializers::QueryResultDeserializer::new(data);
S::deserialize(deserializer)
}
}

0 comments on commit bbade61

Please sign in to comment.