From 54d04f4b121a7d256dde07f9002a0e7aeb05a75b Mon Sep 17 00:00:00 2001 From: Emil Jonathan Eriksson Date: Fri, 10 Feb 2023 13:32:31 +0100 Subject: [PATCH 1/5] Add documentation for get_starting_tokens and Adapter trait --- trustfall_core/src/interpreter/mod.rs | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/trustfall_core/src/interpreter/mod.rs b/trustfall_core/src/interpreter/mod.rs index ba1a9f34..741e1369 100644 --- a/trustfall_core/src/interpreter/mod.rs +++ b/trustfall_core/src/interpreter/mod.rs @@ -347,9 +347,47 @@ fn validate_argument_type( } } +/// An `Adapter` is an entry point between the Trustfall query engine and an +/// external API, file, database or whatever. +/// +/// By providing Trustfall with ways of finding and relating the `DataToken`s +/// for your particular data source, it can be queried together with other data +/// sources. pub trait Adapter<'token> { type DataToken: Clone + Debug + 'token; + /// Retrieves an iterator of `DataToken` from an entry point for this + /// adapter based on the name of the entry point and which parameters is to + /// passed to it. + /// + /// Arguments: + /// * `edge`: The name of the query field as a string + /// * `parameters`: Arguments passed to the field + /// * `query_hint`: An optional already interpreted and indexed query for + /// some arguments to speed up the query + /// * `vertex_hint`: The Vertex ID + /// + /// In GraphQL, this would be correspond to all fields the _Root_ type or + /// the _Query_ type. In the following GraphQL schema, `student` is a field + /// of the Query type, but `homework` and `name` are not. This means that + /// while `student` is a starting token, `homework` and `name` are not. + /// ```graphql + /// type Query { + /// student(name: String!): Student! + /// } + /// + /// type Student { + /// name: String! + /// homework: [Homework]! + /// } + /// + /// // ... + /// ``` + /// + /// In this example, `edge` would be `"student"`, `parameters` would be be a + /// `BTreeMap` containing a mapping `name` to some [FieldValue::String] + /// value. The returned would be an iterator over a single `Student`-like + /// `DataToken`. fn get_starting_tokens( &mut self, edge: Arc, From 6c5c3ed109b8bad64ecc0d265bf41a6af792b6dc Mon Sep 17 00:00:00 2001 From: Emil Jonathan Eriksson Date: Fri, 10 Feb 2023 13:41:16 +0100 Subject: [PATCH 2/5] Run format --- trustfall_core/src/interpreter/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/trustfall_core/src/interpreter/mod.rs b/trustfall_core/src/interpreter/mod.rs index 741e1369..ddc8c8e7 100644 --- a/trustfall_core/src/interpreter/mod.rs +++ b/trustfall_core/src/interpreter/mod.rs @@ -349,7 +349,7 @@ fn validate_argument_type( /// An `Adapter` is an entry point between the Trustfall query engine and an /// external API, file, database or whatever. -/// +/// /// By providing Trustfall with ways of finding and relating the `DataToken`s /// for your particular data source, it can be queried together with other data /// sources. @@ -359,7 +359,7 @@ pub trait Adapter<'token> { /// Retrieves an iterator of `DataToken` from an entry point for this /// adapter based on the name of the entry point and which parameters is to /// passed to it. - /// + /// /// Arguments: /// * `edge`: The name of the query field as a string /// * `parameters`: Arguments passed to the field @@ -383,7 +383,7 @@ pub trait Adapter<'token> { /// /// // ... /// ``` - /// + /// /// In this example, `edge` would be `"student"`, `parameters` would be be a /// `BTreeMap` containing a mapping `name` to some [FieldValue::String] /// value. The returned would be an iterator over a single `Student`-like From ff4b37281baaa89d3197f5b2606f0a8dea0d34b9 Mon Sep 17 00:00:00 2001 From: Emil Jonathan Eriksson Date: Fri, 10 Feb 2023 15:08:01 +0100 Subject: [PATCH 3/5] Add docs to project_property and some examples to adaptor trait --- trustfall_core/src/interpreter/mod.rs | 99 ++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/trustfall_core/src/interpreter/mod.rs b/trustfall_core/src/interpreter/mod.rs index ddc8c8e7..afe31f6f 100644 --- a/trustfall_core/src/interpreter/mod.rs +++ b/trustfall_core/src/interpreter/mod.rs @@ -353,6 +353,22 @@ fn validate_argument_type( /// By providing Trustfall with ways of finding and relating the `DataToken`s /// for your particular data source, it can be queried together with other data /// sources. +/// +/// Although `DataToken` may be anything within the constraints, normally it +/// would be on the form +/// +/// ``` +/// # use std::{rc::Rc, }; +/// struct Student { +/// name: String, +/// homework: Vec, +/// }; +/// struct Homework; +/// enum DataToken { +/// StudentToken(Rc), +/// HomeworkToken(Rc), +/// } +/// ``` pub trait Adapter<'token> { type DataToken: Clone + Debug + 'token; @@ -365,7 +381,7 @@ pub trait Adapter<'token> { /// * `parameters`: Arguments passed to the field /// * `query_hint`: An optional already interpreted and indexed query for /// some arguments to speed up the query - /// * `vertex_hint`: The Vertex ID + /// * `vertex_hint`: /// /// In GraphQL, this would be correspond to all fields the _Root_ type or /// the _Query_ type. In the following GraphQL schema, `student` is a field @@ -396,6 +412,87 @@ pub trait Adapter<'token> { vertex_hint: Vid, ) -> Box + 'token>; + /// Implement a property on all `DataTokens` in an iterator of contexts, + /// returning an iterator over tuples of context-value pairs. + /// + /// Arguments: + /// * `data_contexts`: Tokens and their contexts, such as other known tokens + /// * `current_type_name`: The name of the current type + /// * `field_name`: The name of the field having this property + /// * `query_hint` + /// * `vertex_hint` + /// + /// In GraphQL this would correspond to retrieving a field from another + /// field. + /// ```graphql + /// type Student { + /// name: String! + /// homework: [Homework]! + /// } + /// ``` + /// + /// Using the schema above, to retrieve the name of a `"student"` + /// `DataToken` would require a call like the following, assuming `ctx` + /// contains a `DataToken`. + /// + /// ```ignore + /// project_property(ctx, "Student", "name", query_hint, vertex_hint) + /// ``` + /// + /// Normally implementing this requires a lot of repetition as all + /// properties are added to the types that contains them (like all GraphQL + /// fields with a property called `"name"` of type `String`). This can be be + /// made easier by using declarative macros, for example like so + /// + /// ```ignore + /// fn project_property( + /// data_contexts: Box> + 'token>, + /// current_type_name: Arc, + /// field_name: Arc, + /// query_hint: InterpretedQuery, + /// vertex_hint: Vid, + /// ) { + /// match ((¤t_type_name).as_ref(), &field_name.as_ref()) => { + /// ("Student", "name") => impl_property!(data_contexts, as_student, name), + /// // ... + /// } + /// } + /// ``` + /// + /// where `impl_property!` simply maps over all contexts, creating a new + /// iterator of ([DataContext], [FieldValue]) by converting to a student + /// using a `as_student` method implemented on `DataToken` and then + /// retrieving the `name` attribute of it. + /// + /// This relies on that the `DataToken` type all implement `as_student`, + /// returning an `Option` if the conversion succeeded, thus ignoring all + /// tokens that can not be converted to a student (in this example, probably + /// a `Homework` `DataToken`) and setting the value of their `name` as a + /// [FieldValue::Null]. + /// + /// A simple example of this is the following taken from the `hackernews` + /// demo in the `trustfall` repository: + /// + /// ``` + /// macro_rules! impl_property { + /// ($data_contexts:ident, $conversion:ident, $attr:ident) => { + /// Box::new($data_contexts.map(|ctx| { + /// let token = ctx + /// .current_token + /// .as_ref() + /// .map(|token| token.$conversion().unwrap()); + /// let value = match token { + /// None => FieldValue::Null, + /// Some(t) => (&t.$attr).into(), + /// #[allow(unreachable_patterns)] + /// _ => unreachable!(), + /// }; + /// + /// (ctx, value) + /// })) + /// }; + /// } + /// ``` #[allow(clippy::type_complexity)] fn project_property( &mut self, From 299b513773fa871d1cf30c5b049bf076da5ceead Mon Sep 17 00:00:00 2001 From: Emil Jonathan Eriksson Date: Fri, 10 Feb 2023 15:59:01 +0100 Subject: [PATCH 4/5] Add more examples to project_property --- demo-hackernews/src/adapter.rs | 24 ++++++++++ trustfall_core/src/interpreter/mod.rs | 63 +++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/demo-hackernews/src/adapter.rs b/demo-hackernews/src/adapter.rs index 7dc5b20c..00f6847a 100644 --- a/demo-hackernews/src/adapter.rs +++ b/demo-hackernews/src/adapter.rs @@ -146,6 +146,30 @@ macro_rules! impl_property { }; } +fn sketchy_example( + ctx: Box>>, +) -> Box, FieldValue)>> { + Box::new(ctx.map(|c| { + let opt_user: Option<&hn_api::types::User> = c + .current_token // Option + .as_ref() // Option<&Token> + .map(|t: &Token| { + t // &Token + .as_user() // Option<&User> + .unwrap() // Remove inner Option, Option> => Option + }); + let value: FieldValue = match opt_user { + None => FieldValue::Null, + Some(t) => (&t.id).into(), + + #[allow(unreachable_patterns)] + _ => unreachable!(), + }; + + (c, value) + })) +} + impl Adapter<'static> for HackerNewsAdapter { type DataToken = Token; diff --git a/trustfall_core/src/interpreter/mod.rs b/trustfall_core/src/interpreter/mod.rs index afe31f6f..7444cfe1 100644 --- a/trustfall_core/src/interpreter/mod.rs +++ b/trustfall_core/src/interpreter/mod.rs @@ -358,12 +358,17 @@ fn validate_argument_type( /// would be on the form /// /// ``` -/// # use std::{rc::Rc, }; +/// # use std::rc::Rc; +/// #[derive(Debug, Clone)] /// struct Student { /// name: String, /// homework: Vec, /// }; +/// +/// #[derive(Debug, Clone)] /// struct Homework; +/// +/// #[derive(Debug, Clone)] /// enum DataToken { /// StudentToken(Rc), /// HomeworkToken(Rc), @@ -372,8 +377,8 @@ fn validate_argument_type( pub trait Adapter<'token> { type DataToken: Clone + Debug + 'token; - /// Retrieves an iterator of `DataToken` from an entry point for this - /// adapter based on the name of the entry point and which parameters is to + /// Retrieves an iterator of `DataToken`s from an entry point for this + /// adapter based on the name of the entry point and which parameters is /// passed to it. /// /// Arguments: @@ -402,7 +407,7 @@ pub trait Adapter<'token> { /// /// In this example, `edge` would be `"student"`, `parameters` would be be a /// `BTreeMap` containing a mapping `name` to some [FieldValue::String] - /// value. The returned would be an iterator over a single `Student`-like + /// value. The returned value would be an iterator over a single `Student`-like /// `DataToken`. fn get_starting_tokens( &mut self, @@ -493,6 +498,56 @@ pub trait Adapter<'token> { /// }; /// } /// ``` + /// + /// which in our case would be expanded to (here with type annotations) + /// ``` + /// # use trustfall_core::{interpreter::DataContext, ir::FieldValue}; + /// # use std::rc::Rc; + /// # #[derive(Debug, Clone)] + /// # struct Student { + /// # name: String, + /// # homework: Vec, + /// # }; + /// # #[derive(Debug, Clone)] + /// # struct Homework; + /// # #[derive(Debug, Clone)] + /// # enum DataToken { + /// # StudentToken(Rc), + /// # HomeworkToken(Rc), + /// # } + /// + /// impl DataToken { + /// pub fn as_student(&self) -> Option<&Student> { + /// match self { + /// DataToken::StudentToken(s) => Some(s.as_ref()), + /// _ => None, + /// } + /// } + /// } + /// + /// // ... + /// + /// # fn expanded(data_contexts: Box>>) + /// # -> Box, FieldValue)>> { + /// Box::new(data_contexts.map(|ctx| { + /// let stud: Option<&Student> = (&ctx + /// .current_token) // Option + /// .as_ref() // Option<&Token> + /// .map(|t: &DataToken| { + /// t // &Token + /// .as_student() // Option<&Student> + /// .unwrap() // Option => Option<&Student> + /// }); + /// + /// let value: FieldValue = match stud { + /// None => FieldValue::Null, + /// Some(s) => (&s.name).into(), + /// }; + /// + /// (ctx, value) + /// })) + /// # } + /// ``` #[allow(clippy::type_complexity)] fn project_property( &mut self, From e74c33b742cc36aa45b215c28f87ba0f564b877f Mon Sep 17 00:00:00 2001 From: Emil Jonathan Eriksson Date: Fri, 10 Feb 2023 16:01:09 +0100 Subject: [PATCH 5/5] Run cargo fmt --- trustfall_core/src/interpreter/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/trustfall_core/src/interpreter/mod.rs b/trustfall_core/src/interpreter/mod.rs index 7444cfe1..405df4ef 100644 --- a/trustfall_core/src/interpreter/mod.rs +++ b/trustfall_core/src/interpreter/mod.rs @@ -364,10 +364,10 @@ fn validate_argument_type( /// name: String, /// homework: Vec, /// }; -/// +/// /// #[derive(Debug, Clone)] /// struct Homework; -/// +/// /// #[derive(Debug, Clone)] /// enum DataToken { /// StudentToken(Rc), @@ -515,7 +515,7 @@ pub trait Adapter<'token> { /// # StudentToken(Rc), /// # HomeworkToken(Rc), /// # } - /// + /// /// impl DataToken { /// pub fn as_student(&self) -> Option<&Student> { /// match self { @@ -524,9 +524,9 @@ pub trait Adapter<'token> { /// } /// } /// } - /// + /// /// // ... - /// + /// /// # fn expanded(data_contexts: Box>>) /// # -> Box, FieldValue)>> { /// Box::new(data_contexts.map(|ctx| { @@ -543,7 +543,7 @@ pub trait Adapter<'token> { /// None => FieldValue::Null, /// Some(s) => (&s.name).into(), /// }; - /// + /// /// (ctx, value) /// })) /// # }