Skip to content

Commit

Permalink
chore: impl field method on context (#2444)
Browse files Browse the repository at this point in the history
Co-authored-by: Kiryl Mialeshka <[email protected]>
  • Loading branch information
laststylebender14 and meskill authored Jul 21, 2024
1 parent 341c3c6 commit e8c1404
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 46 deletions.
3 changes: 1 addition & 2 deletions benches/impl_path_string_for_evaluation_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use std::collections::BTreeMap;
use std::sync::Arc;
use std::time::Duration;

use async_graphql::context::SelectionField;
use async_graphql::{Name, Value};
use async_trait::async_trait;
use criterion::{BenchmarkId, Criterion};
Expand All @@ -18,7 +17,7 @@ use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use tailcall::core::blueprint::{Server, Upstream};
use tailcall::core::cache::InMemoryCache;
use tailcall::core::http::{RequestContext, Response};
use tailcall::core::ir::{EvalContext, ResolverContextLike};
use tailcall::core::ir::{EvalContext, ResolverContextLike, SelectionField};
use tailcall::core::path::PathString;
use tailcall::core::runtime::TargetRuntime;
use tailcall::core::{EnvIO, FileIO, HttpIO};
Expand Down
34 changes: 12 additions & 22 deletions src/core/ir/eval_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ use std::borrow::Cow;
use std::collections::BTreeMap;
use std::sync::Arc;

use async_graphql::{SelectionField, ServerError, Value};
use async_graphql::{ServerError, Value};
use reqwest::header::HeaderMap;

use super::discriminator::TypeName;
use super::{GraphQLOperationContext, RelatedFields, ResolverContextLike};
use super::{GraphQLOperationContext, RelatedFields, ResolverContextLike, SelectionField};
use crate::core::document::print_directives;
use crate::core::http::RequestContext;

Expand Down Expand Up @@ -122,23 +122,21 @@ impl<'a, Ctx: ResolverContextLike> EvalContext<'a, Ctx> {

impl<'a, Ctx: ResolverContextLike> GraphQLOperationContext for EvalContext<'a, Ctx> {
fn directives(&self) -> Option<String> {
let field = self.graphql_ctx.field()?;

field
let selection_field = self.graphql_ctx.field()?;
selection_field
.directives()
.ok()
.as_ref()
.map(|directives| print_directives(directives.iter()))
}

fn selection_set(&self, related_fields: &RelatedFields) -> Option<String> {
let field = self.graphql_ctx.field()?;

format_selection_set(field.selection_set(), related_fields)
let selection_field = self.graphql_ctx.field()?;
format_selection_set(selection_field.selection_set(), related_fields)
}
}

fn format_selection_set<'a>(
selection_set: impl Iterator<Item = SelectionField<'a>>,
selection_set: impl Iterator<Item = &'a SelectionField>,
related_fields: &RelatedFields,
) -> Option<String> {
let set = selection_set
Expand All @@ -157,14 +155,14 @@ fn format_selection_set<'a>(
Some(format!("{{ {} }}", set.join(" ")))
}

fn format_selection_field(field: SelectionField, related_fields: &RelatedFields) -> String {
fn format_selection_field(field: &SelectionField, related_fields: &RelatedFields) -> String {
let name = field.name();
let arguments = format_selection_field_arguments(field);
let selection_set = format_selection_set(field.selection_set(), related_fields);

let mut output = format!("{}{}", name, arguments);

if let Ok(directives) = field.directives() {
if let Some(directives) = field.directives() {
let directives = print_directives(directives.iter());

if !directives.is_empty() {
Expand All @@ -181,16 +179,8 @@ fn format_selection_field(field: SelectionField, related_fields: &RelatedFields)
output
}

fn format_selection_field_arguments(field: SelectionField) -> Cow<'static, str> {
let name = field.name();
let arguments = field
.arguments()
.map_err(|error| {
tracing::warn!("Failed to resolve arguments for field {name}, due to error: {error}");

error
})
.unwrap_or_default();
fn format_selection_field_arguments(field: &SelectionField) -> Cow<'static, str> {
let arguments = field.arguments();

if arguments.is_empty() {
return Cow::Borrowed("");
Expand Down
4 changes: 3 additions & 1 deletion src/core/ir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ use std::ops::Deref;
pub use discriminator::*;
pub use error::*;
pub use eval_context::EvalContext;
pub use resolver_context_like::{EmptyResolverContext, ResolverContext, ResolverContextLike};
pub use resolver_context_like::{
EmptyResolverContext, ResolverContext, ResolverContextLike, SelectionField,
};

/// Contains all the nested fields that are resolved with current parent
/// resolver i.e. fields that don't have their own resolver and are resolved by
Expand Down
86 changes: 83 additions & 3 deletions src/core/ir/resolver_context_like.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::sync::Arc;

use async_graphql::context::SelectionField;
use async_graphql::parser::types::OperationType;
use async_graphql::parser::types::{ConstDirective, OperationType};
use async_graphql::{Name, ServerError, Value};
use indexmap::IndexMap;

use crate::core::jit::Nested;

pub trait ResolverContextLike: Clone {
fn value(&self) -> Option<&Value>;
fn args(&self) -> Option<&IndexMap<Name, Value>>;
Expand Down Expand Up @@ -57,7 +58,7 @@ impl<'a> ResolverContextLike for ResolverContext<'a> {
}

fn field(&self) -> Option<SelectionField> {
Some(self.inner.ctx.field())
Some(SelectionField::from(self.inner.ctx.field()))
}

fn is_query(&self) -> bool {
Expand All @@ -68,3 +69,82 @@ impl<'a> ResolverContextLike for ResolverContext<'a> {
self.inner.ctx.add_error(error)
}
}

#[derive(Debug)]
pub struct SelectionField {
name: String,
args: Vec<(String, String)>,
directives: Option<Vec<ConstDirective>>,
selection_set: Vec<SelectionField>,
}

impl From<async_graphql::SelectionField<'_>> for SelectionField {
fn from(value: async_graphql::SelectionField) -> Self {
Self::from_async_selection_field(value)
}
}

impl<'a, Input: ToString> From<&'a crate::core::jit::Field<Nested<Input>, Input>>
for SelectionField
{
fn from(value: &'a crate::core::jit::Field<Nested<Input>, Input>) -> Self {
Self::from_jit_field(value)
}
}

impl SelectionField {
fn from_jit_field<Input: ToString>(
field: &crate::core::jit::Field<Nested<Input>, Input>,
) -> SelectionField {
let name = field.name.clone();
let selection_set = field.nested_iter().map(Self::from_jit_field).collect();
let args = field
.args
.iter()
.filter_map(|a| a.value.as_ref().map(|v| (a.name.to_owned(), v.to_string())))
.collect::<Vec<_>>();

// TODO: add support for directives.
SelectionField { name, args, directives: None, selection_set }
}

fn from_async_selection_field(field: async_graphql::SelectionField) -> SelectionField {
let name = field.name().to_owned();
let args = field
.arguments()
.map_err(|err| {
tracing::warn!("Failed to resolve arguments for field {name}, due to error: {err}");
err
})
.unwrap_or_default()
.into_iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect::<Vec<_>>();

let directives = field.directives().ok();
let selection_set = field
.selection_set()
.map(Self::from_async_selection_field)
.collect();

Self { name, args, selection_set, directives }
}

pub fn directives(&self) -> &Option<Vec<ConstDirective>> {
&self.directives
}

pub fn arguments(&self) -> &[(String, String)] {
&self.args
}

pub fn name(&self) -> &str {
&self.name
}

/// Returns an iterator over the `selection_set` that yields
/// `SelectionField` instances.
pub fn selection_set(&self) -> std::slice::Iter<SelectionField> {
self.selection_set.iter()
}
}
66 changes: 55 additions & 11 deletions src/core/jit/context.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,40 @@
use async_graphql::{Name, SelectionField, ServerError};
use async_graphql::{Name, ServerError};
use async_graphql_value::ConstValue;
use derive_getters::Getters;

use super::Request;
use crate::core::ir::ResolverContextLike;
use super::{Field, Nested, Request};
use crate::core::ir::{ResolverContextLike, SelectionField};

/// Rust representation of the GraphQL context available in the DSL
#[derive(Getters, Debug, Clone)]
#[derive(Debug, Clone)]
pub struct Context<'a, Input, Output> {
request: &'a Request<Input>,
value: Option<&'a Output>,
args: Option<indexmap::IndexMap<Name, Input>>,
// TODO: remove the args, since they're already present inside the fields and support for
// default values.
field: &'a Field<Nested<Input>, Input>,
is_query: bool,
}

impl<'a, Input, Output> Context<'a, Input, Output> {
pub fn new(request: &'a Request<Input>, is_query: bool) -> Self {
Self { request, value: None, args: None, is_query }
pub fn new(
request: &'a Request<Input>,
is_query: bool,
field: &'a Field<Nested<Input>, Input>,
) -> Self {
Self { request, value: None, args: None, is_query, field }
}

pub fn with_value(&self, value: &'a Output) -> Self {
pub fn with_value_and_field(
&self,
value: &'a Output,
field: &'a Field<Nested<Input>, Input>,
) -> Self {
Self {
request: self.request,
value: Some(value),
args: None,
is_query: self.is_query,
value: Some(value),
field,
}
}

Expand All @@ -38,6 +48,7 @@ impl<'a, Input, Output> Context<'a, Input, Output> {
value: self.value,
args: Some(map),
is_query: self.is_query,
field: self.field,
}
}
}
Expand All @@ -53,7 +64,7 @@ impl<'a> ResolverContextLike for Context<'a, ConstValue, ConstValue> {
}

fn field(&self) -> Option<SelectionField> {
todo!()
Some(SelectionField::from(self.field))
}

fn is_query(&self) -> bool {
Expand All @@ -64,3 +75,36 @@ impl<'a> ResolverContextLike for Context<'a, ConstValue, ConstValue> {
todo!()
}
}

#[cfg(test)]
mod test {
use super::Context;
use crate::core::blueprint::Blueprint;
use crate::core::config::{Config, ConfigModule};
use crate::core::ir::ResolverContextLike;
use crate::core::jit::{OperationPlan, Request};
use crate::core::valid::Validator;

fn setup(
query: &str,
) -> (
OperationPlan<async_graphql::Value>,
Request<async_graphql::Value>,
) {
let sdl = std::fs::read_to_string(tailcall_fixtures::configs::JSONPLACEHOLDER).unwrap();
let config = Config::from_sdl(&sdl).to_result().unwrap();
let blueprint = Blueprint::try_from(&ConfigModule::from(config)).unwrap();
let request = Request::new(query);
let plan = request.clone().create_plan(&blueprint).unwrap();
(plan, request)
}

#[test]
fn test_field() {
let (plan, req) = setup("query {posts {id title}}");
let field = plan.as_nested();
let ctx: Context<async_graphql::Value, async_graphql::Value> =
Context::new(&req, false, &field[0]);
insta::assert_debug_snapshot!(ctx.field().unwrap());
}
}
7 changes: 3 additions & 4 deletions src/core/jit/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ where
}

async fn init(&mut self) {
let ctx = Context::new(&self.request, self.plan.is_query());
join_all(self.plan.as_nested().iter().map(|field| async {
let mut arg_map = indexmap::IndexMap::new();
for arg in field.args.iter() {
Expand All @@ -92,7 +91,7 @@ where
todo!()
}
}
let ctx = ctx.with_args(arg_map);
let ctx = Context::new(&self.request, self.plan.is_query(), field).with_args(arg_map);
self.execute(field, &ctx, DataPath::new()).await
}))
.await;
Expand All @@ -115,7 +114,7 @@ where
if let Some(array) = value.as_array() {
join_all(field.nested_iter().map(|field| {
join_all(array.iter().enumerate().map(|(index, value)| {
let ctx = ctx.with_value(value); // Output::JsonArray::Value
let ctx = ctx.with_value_and_field(value, field); // Output::JsonArray::Value
let data_path = data_path.clone().with_index(index);
async move { self.execute(field, &ctx, data_path).await }
}))
Expand All @@ -130,7 +129,7 @@ where
// Has to be an Object, we don't do anything while executing if its a Scalar
else {
join_all(field.nested_iter().map(|child| {
let ctx = ctx.with_value(value);
let ctx = ctx.with_value_and_field(value, field);
let data_path = data_path.clone();
async move { self.execute(child, &ctx, data_path).await }
}))
Expand Down
2 changes: 1 addition & 1 deletion src/core/jit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ mod store;
mod synth;

use builder::*;
use model::*;
use store::*;
mod context;
mod error;
Expand All @@ -22,5 +21,6 @@ mod graphql_executor;
pub use error::*;
pub use exec_const::*;
pub use graphql_executor::*;
pub use model::*;
pub use request::*;
pub use response::*;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: src/core/jit/context.rs
expression: ctx.field().unwrap()
---
SelectionField {
name: "posts",
args: [],
directives: None,
selection_set: [
SelectionField {
name: "id",
args: [],
directives: None,
selection_set: [],
},
SelectionField {
name: "title",
args: [],
directives: None,
selection_set: [],
},
],
}
Loading

0 comments on commit e8c1404

Please sign in to comment.