Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add more documentation to trustfall_core #136

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions trustfall_core/src/frontend/mod.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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<str>) -> Result<Arc<IndexedQuery>, FrontendError> {
let ir_query = parse_to_ir(schema, query)?;

Expand All @@ -53,6 +57,7 @@ pub fn parse(schema: &Schema, query: impl AsRef<str>) -> Result<Arc<IndexedQuery
Ok(Arc::from(indexed_query))
}

/// Parses a query string to IR using a GraphQL [Schema](crate::schema::Schema)
pub fn parse_to_ir<T: AsRef<str>>(schema: &Schema, query: T) -> Result<IRQuery, FrontendError> {
let document = async_graphql_parser::parse_query(query)?;
let q = parse_document(&document)?;
Expand Down
5 changes: 5 additions & 0 deletions trustfall_core/src/frontend/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
78 changes: 78 additions & 0 deletions trustfall_core/src/graphql_query/directives.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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<str>),

/// Reference to a `@tag`ed value encountered elsewhere
/// in the query. Tag names are always prefixed with `%`.
TagRef(Arc<str>),
}

/// 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>,
}

Expand Down Expand Up @@ -146,8 +172,21 @@ impl TryFrom<&Positioned<Directive>> 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<Arc<str>>,
}
Expand Down Expand Up @@ -208,8 +247,21 @@ impl TryFrom<&Positioned<Directive>> 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,
}

Expand Down Expand Up @@ -271,6 +323,18 @@ impl TryFrom<&Positioned<Directive>> 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")]
Expand Down Expand Up @@ -331,6 +395,7 @@ impl TryFrom<&Positioned<Directive>> for TagDirective {
}
}

/// A GraphQL `@optional` directive.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub(crate) struct OptionalDirective {}

Expand All @@ -351,6 +416,7 @@ impl TryFrom<&Positioned<Directive>> for OptionalDirective {
}
}

/// A GraphQL `@fold` directive.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub(crate) struct FoldDirective {}

Expand All @@ -371,6 +437,18 @@ impl TryFrom<&Positioned<Directive>> 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,
Expand Down
1 change: 1 addition & 0 deletions trustfall_core/src/graphql_query/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Translating GraphQL queries to Trustfall IR
pub(crate) mod directives;
pub mod error;
pub(crate) mod query;
5 changes: 5 additions & 0 deletions trustfall_core/src/graphql_query/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Field>, ParseError> {
if let Some(v) = document.fragments.values().next() {
return Err(ParseError::DocumentContainsNonInlineFragments(v.pos));
Expand Down Expand Up @@ -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<Query, ParseError> {
let query_root = try_get_query_root(document)?;

Expand Down
34 changes: 32 additions & 2 deletions trustfall_core/src/ir/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Trustfall internal representation (IR)
#![allow(dead_code)]

pub mod indexed;
Expand Down Expand Up @@ -26,17 +27,21 @@ lazy_static! {
pub(crate) static ref TYPENAME_META_FIELD_ARC: Arc<str> = 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 {
Vid(id)
}
}

/// 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 {
Expand All @@ -49,8 +54,12 @@ pub struct EdgeParameters(
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub BTreeMap<Arc<str>, 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")]
Expand All @@ -66,6 +75,7 @@ pub struct IRQueryComponent {
pub outputs: BTreeMap<Arc<str>, ContextField>,
}

/// Intermediate representation of a query
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IRQuery {
pub root_name: Arc<str>,
Expand Down Expand Up @@ -94,6 +104,9 @@ pub struct IREdge {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parameters: Option<Arc<EdgeParameters>>,

/// 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,

Expand Down Expand Up @@ -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<str>,

#[serde(default, skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -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<LeftT, RightT>
Expand Down Expand Up @@ -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",
Expand Down
17 changes: 14 additions & 3 deletions trustfall_core/src/ir/value.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -10,17 +15,21 @@ 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<Utc>),
Enum(String),
List(Vec<FieldValue>),
}

/// 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)]
Expand Down Expand Up @@ -190,6 +199,7 @@ impl From<bool> for FieldValue {
}
}

/// Represents a finite (non-infinite, not-NaN) [f64] value
pub struct FiniteF64(f64);
impl From<FiniteF64> for FieldValue {
fn from(f: FiniteF64) -> FieldValue {
Expand Down Expand Up @@ -320,6 +330,7 @@ impl<T: Clone + Into<FieldValue>> From<&[T]> for FieldValue {
}
}

/// Converts a JSON number to a [FieldValue]
fn convert_number_to_field_value(n: &Number) -> Result<FieldValue, String> {
// The order here matters!
// Int64 must be before Uint64, which must be before Float64.
Expand Down
3 changes: 2 additions & 1 deletion trustfall_core/src/schema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down