diff --git a/trustfall_core/src/frontend/mod.rs b/trustfall_core/src/frontend/mod.rs index 3e1d6fbf..52f63000 100644 --- a/trustfall_core/src/frontend/mod.rs +++ b/trustfall_core/src/frontend/mod.rs @@ -1,3 +1,4 @@ +//! Frontend for Trustfall, containing parsers for queries using schemas #![allow(dead_code, unused_variables, unused_mut)] use std::{ collections::BTreeMap, convert::TryFrom, iter::successors, num::NonZeroUsize, sync::Arc, @@ -41,6 +42,9 @@ mod tags; mod util; mod validation; +/// Parses a query string to the Trustfall IR using a provided +/// [Schema](crate::schema::Schema). May fail if [parse_to_ir](parse_to_ir) +/// fails for the provided schema and query. pub fn parse(schema: &Schema, query: impl AsRef) -> Result, FrontendError> { let ir_query = parse_to_ir(schema, query)?; @@ -53,6 +57,7 @@ pub fn parse(schema: &Schema, query: impl AsRef) -> Result>(schema: &Schema, query: T) -> Result { let document = async_graphql_parser::parse_query(query)?; let q = parse_document(&document)?; diff --git a/trustfall_core/src/frontend/util.rs b/trustfall_core/src/frontend/util.rs index 976d3bd1..e955aee3 100644 --- a/trustfall_core/src/frontend/util.rs +++ b/trustfall_core/src/frontend/util.rs @@ -5,6 +5,8 @@ use async_graphql_value::Name; use crate::ir::Vid; +/// Retrieves the underlying GraphQL named type by looping through any [list +/// types](BaseType::List) until a [named type](Type) is found pub(super) fn get_underlying_named_type(t: &Type) -> &Name { let mut base_type = &t.base; loop { @@ -36,6 +38,9 @@ impl ComponentPath { self.path.push(component_start_vid); } + /// Pops the current path. + /// + /// Will panic if the popped value is not the provided `component_start_vid` pub(super) fn pop(&mut self, component_start_vid: Vid) { let popped_vid = self.path.pop().unwrap(); assert_eq!(popped_vid, component_start_vid); diff --git a/trustfall_core/src/graphql_query/directives.rs b/trustfall_core/src/graphql_query/directives.rs index b7fcc7cc..ea42ac89 100644 --- a/trustfall_core/src/graphql_query/directives.rs +++ b/trustfall_core/src/graphql_query/directives.rs @@ -1,3 +1,5 @@ +//! Directives in GraphQL can be identified by staring with `@`. While +//! `trustfall_core` doesn't support all GraphQL directives, some are available. use std::{collections::HashSet, convert::TryFrom, num::NonZeroUsize, sync::Arc}; use async_graphql_parser::{types::Directive, Positioned}; @@ -9,14 +11,38 @@ use crate::ir::{Operation, TransformationKind}; use super::error::ParseError; +/// An argument as passed to the `value` array, for example for a `@filter` +/// directive (see [FilterDirective]). #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum OperatorArgument { + /// Reference to a variable provided to the query. Variable names are always + /// prefixed with `$`. VariableRef(Arc), + + /// Reference to a `@tag`ed value encountered elsewhere + /// in the query. Tag names are always prefixed with `%`. TagRef(Arc), } +/// A GraphQL `@filter` directive. +/// +/// The following GraphQL filter directive and Rust instance would be +/// equivalent: +/// +/// ```graphql +/// @filter(op: ">=", value: ["$some_value"]) +/// ``` +/// +/// and +/// +/// ``` +/// FilterDirective { +/// operation: Operation::GreaterThanOrEqual(VariableRef(Arc::new("$some_value"))) +/// } +/// ``` #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub(crate) struct FilterDirective { + /// Describes which operation should be made by the filter pub operation: Operation<(), OperatorArgument>, } @@ -146,8 +172,21 @@ impl TryFrom<&Positioned> for FilterDirective { } } +/// A GraphQL `@output` directive. +/// +/// For example, the following GraphQL and Rust would be equivalent: +/// ```graphql +/// @output(name: "betterName") +/// ``` +/// +/// and +/// +/// ``` +/// OutputDirective { name: Some(Arc::new("betterName"))} +/// ``` #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub(crate) struct OutputDirective { + /// The name that should be used for this field when it is given as output #[serde(default, skip_serializing_if = "Option::is_none")] pub name: Option>, } @@ -208,8 +247,21 @@ impl TryFrom<&Positioned> for OutputDirective { } } +/// A GraphQL `@transform` directive. +/// +/// For example, the following GraphQL and Rust would be equivalent: +/// ```graphql +/// @transform(op: "count") +/// ``` +/// +/// and +/// +/// ``` +/// TransformDirective { kind: TransformKind::Count } +/// ``` #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub(crate) struct TransformDirective { + /// The `op` in a GraphQL `@transform` pub kind: TransformationKind, } @@ -271,6 +323,18 @@ impl TryFrom<&Positioned> for TransformDirective { } } +/// A GraphQL `@tag` directive. +/// +/// For example, the following GraphQL and Rust would be equivalent: +/// ```graphql +/// @tag(name: "%tag_name") +/// ``` +/// +/// and +/// +/// ``` +/// TagDirective { name: Some(Arc::new("%tag_name"))} +/// ``` #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub(crate) struct TagDirective { #[serde(default, skip_serializing_if = "Option::is_none")] @@ -331,6 +395,7 @@ impl TryFrom<&Positioned> for TagDirective { } } +/// A GraphQL `@optional` directive. #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub(crate) struct OptionalDirective {} @@ -351,6 +416,7 @@ impl TryFrom<&Positioned> for OptionalDirective { } } +/// A GraphQL `@fold` directive. #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub(crate) struct FoldDirective {} @@ -371,6 +437,18 @@ impl TryFrom<&Positioned> for FoldDirective { } } +/// A GraphQL `@recurse` directive. +/// +/// For example, the following GraphQL and Rust would be equivalent: +/// ```graphql +/// @recurse(depth: 1) +/// ``` +/// +/// and +/// +/// ``` +/// RecurseDirective { depth: NonZeroUsize::new(1usize)} +/// ``` #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub(crate) struct RecurseDirective { pub depth: NonZeroUsize, diff --git a/trustfall_core/src/graphql_query/mod.rs b/trustfall_core/src/graphql_query/mod.rs index 7d65045d..8a559bc9 100644 --- a/trustfall_core/src/graphql_query/mod.rs +++ b/trustfall_core/src/graphql_query/mod.rs @@ -1,3 +1,4 @@ +//! Translating GraphQL queries to Trustfall IR pub(crate) mod directives; pub mod error; pub(crate) mod query; diff --git a/trustfall_core/src/graphql_query/query.rs b/trustfall_core/src/graphql_query/query.rs index cef4b755..eb474bc7 100644 --- a/trustfall_core/src/graphql_query/query.rs +++ b/trustfall_core/src/graphql_query/query.rs @@ -116,6 +116,10 @@ impl ParsedDirective { } } +/// Attempts to extract the query root from an [ExecutableDocument] +/// +/// May return [ParseError] if the query is empty, there is no query root, or +/// the query root is not formatted properly fn try_get_query_root(document: &ExecutableDocument) -> Result<&Positioned, ParseError> { if let Some(v) = document.fragments.values().next() { return Err(ParseError::DocumentContainsNonInlineFragments(v.pos)); @@ -507,6 +511,7 @@ fn make_transform_group( }) } +/// Parses a query document. May fail if a query root is missing (see [try_get_query_root](try_get_query_root)) pub(crate) fn parse_document(document: &ExecutableDocument) -> Result { let query_root = try_get_query_root(document)?; diff --git a/trustfall_core/src/ir/mod.rs b/trustfall_core/src/ir/mod.rs index 3448798c..53742341 100644 --- a/trustfall_core/src/ir/mod.rs +++ b/trustfall_core/src/ir/mod.rs @@ -1,3 +1,4 @@ +//! Trustfall internal representation (IR) #![allow(dead_code)] pub mod indexed; @@ -26,8 +27,10 @@ lazy_static! { pub(crate) static ref TYPENAME_META_FIELD_ARC: Arc = Arc::from(TYPENAME_META_FIELD); } +/// Vertex ID +#[doc(alias("vertex", "node"))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] -pub struct Vid(pub(crate) NonZeroUsize); // vertex ID +pub struct Vid(pub(crate) NonZeroUsize); impl Vid { pub fn new(id: NonZeroUsize) -> Vid { @@ -35,8 +38,10 @@ impl Vid { } } +/// Edge ID +#[doc(alias = "edge")] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] -pub struct Eid(pub(crate) NonZeroUsize); // edge ID +pub struct Eid(pub(crate) NonZeroUsize); impl Eid { pub fn new(id: NonZeroUsize) -> Eid { @@ -49,8 +54,12 @@ pub struct EdgeParameters( #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub BTreeMap, FieldValue>, ); +/// IR of components of a query, containing information about the vertex ID +/// of the root of the query, as well as well as maps of all vertices, edges, +/// folds, and outputs of the query. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct IRQueryComponent { + /// The [Vid] of the root, or entry point, of the query. pub root: Vid, #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] @@ -66,6 +75,7 @@ pub struct IRQueryComponent { pub outputs: BTreeMap, ContextField>, } +/// Intermediate representation of a query #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct IRQuery { pub root_name: Arc, @@ -94,6 +104,9 @@ pub struct IREdge { #[serde(default, skip_serializing_if = "Option::is_none")] pub parameters: Option>, + /// Indicating if this edge is optional. + /// + /// This would correspond to `@optional` in GraphQL. #[serde(default = "default_optional", skip_serializing_if = "is_false")] pub optional: bool, @@ -123,9 +136,13 @@ impl Recursive { } } +/// Representation of a vertex (node) in the Trustfall intermediate +/// representation (IR). #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct IRVertex { pub vid: Vid, + + /// The name of the type of the vertex as a string. pub type_name: Arc, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -282,6 +299,15 @@ impl Argument { } } +/// Operations that can be made in the graph. +/// +/// In GraphQL, this can correspond to the `op` argument in `@filter`, +/// for example in the following: +/// ```graphql +/// query Student { +/// name @filter(op: "has_substring", values: ["John"]) +/// } +/// ``` #[non_exhaustive] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Operation @@ -365,6 +391,10 @@ where } } + /// The operation name as a `str` + /// + /// Note that these are the same as would be given to a GraphQL `op` + /// argumetn. pub(crate) fn operation_name(&self) -> &'static str { match self { Operation::IsNull(..) => "is_null", diff --git a/trustfall_core/src/ir/value.rs b/trustfall_core/src/ir/value.rs index ec5c4917..3d035faf 100644 --- a/trustfall_core/src/ir/value.rs +++ b/trustfall_core/src/ir/value.rs @@ -1,7 +1,12 @@ +/// IR of the values of GraphQL fields. + use async_graphql_value::{ConstValue, Number, Value}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +/// Values of fields in GraphQL types. +/// +/// For version that is serialized as an untagged enum, see [TransparentValue]. #[derive(Debug, Clone, Serialize, Deserialize)] pub enum FieldValue { // Order may matter here! Deserialization, if ever configured for untagged serialization, @@ -10,9 +15,11 @@ pub enum FieldValue { // This is because we want to prioritize the standard Integer GraphQL type over our custom u64, // and prioritize exact integers over lossy floats. Null, - Int64(i64), // AKA Integer + /// AKA integer + Int64(i64), Uint64(u64), - Float64(f64), // AKA Float, and also not allowed to be NaN + /// AKA Float, and also not allowed to be NaN + Float64(f64), String(String), Boolean(bool), DateTimeUtc(DateTime), @@ -20,7 +27,9 @@ pub enum FieldValue { List(Vec), } -/// Same as FieldValue, but serialized as an untagged enum, +/// Values of fields in GraphQL types. +/// +/// Same as [FieldValue], but serialized as an untagged enum, /// which may be more suitable e.g. when serializing to JSON. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -190,6 +199,7 @@ impl From for FieldValue { } } +/// Represents a finite (non-infinite, not-NaN) [f64] value pub struct FiniteF64(f64); impl From for FieldValue { fn from(f: FiniteF64) -> FieldValue { @@ -320,6 +330,7 @@ impl> From<&[T]> for FieldValue { } } +/// Converts a JSON number to a [FieldValue] fn convert_number_to_field_value(n: &Number) -> Result { // The order here matters! // Int64 must be before Uint64, which must be before Float64. diff --git a/trustfall_core/src/schema/mod.rs b/trustfall_core/src/schema/mod.rs index 9b9a5b99..1b3fe418 100644 --- a/trustfall_core/src/schema/mod.rs +++ b/trustfall_core/src/schema/mod.rs @@ -83,6 +83,7 @@ lazy_static! { const RESERVED_PREFIX: &str = "__"; impl Schema { + /// All GraphQL directives supported by Trustfall, in GraphQL format pub const ALL_DIRECTIVE_DEFINITIONS: &'static str = " directive @filter(op: String!, value: [String!]) on FIELD | INLINE_FRAGMENT directive @tag(name: String) on FIELD @@ -228,7 +229,7 @@ directive @transform(op: String!) on FIELD } /// If the named type is defined, iterate through the names of its subtypes including itself. - /// Otherwise, return None. + /// Otherwise, return `None`. pub fn subtypes<'slf, 'a: 'slf>( &'slf self, type_name: &'a str,