Skip to content

Commit

Permalink
Merge branch 'main' into feat/link-custom-headers
Browse files Browse the repository at this point in the history
  • Loading branch information
beelchester authored Sep 18, 2024
2 parents aa454d3 + 5ce342c commit 361135d
Show file tree
Hide file tree
Showing 43 changed files with 805 additions and 505 deletions.
270 changes: 135 additions & 135 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ path = "src/main.rs"

[workspace.dependencies]
anyhow = "1.0.82"
async-graphql = { version = "7.0.3" }
async-graphql = { version = "7.0.9" }
futures-util = { version = "0.3.30" }
indexmap = "2.2.6"
insta = { version = "1.38.0", features = ["json"] }
Expand Down Expand Up @@ -141,7 +141,7 @@ headers = "0.3.9" # previous version until hyper is updated to 1+
mime = "0.3.17"
htpasswd-verify = { version = "0.3.0", git = "https://github.com/twistedfall/htpasswd-verify", rev = "ff14703083cbd639f7d05622b398926f3e718d61" } # fork version that is wasm compatible
jsonwebtoken = "9.3.0"
async-graphql-value = "7.0.3"
async-graphql-value = "7.0.9"
async-graphql = { workspace = true, features = [
"dynamic-schema",
"dataloader",
Expand Down
4 changes: 4 additions & 0 deletions generated/.tailcallrc.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ directive @link(
"""
id: String
"""
Additional metadata pertaining to the linked resource.
"""
meta: JSON
"""
The source of the link. It can be a URL or a path to a file. If a path is provided,
it is relative to the file that imports the link.
"""
Expand Down
3 changes: 3 additions & 0 deletions generated/.tailcallrc.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,9 @@
"null"
]
},
"meta": {
"description": "Additional metadata pertaining to the linked resource."
},
"src": {
"description": "The source of the link. It can be a URL or a path to a file. If a path is provided, it is relative to the file that imports the link.",
"type": "string"
Expand Down
5 changes: 4 additions & 1 deletion src/core/config/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ pub struct Link {
/// The type of the link. It can be `Config`, or `Protobuf`.
#[serde(default, skip_serializing_if = "is_default", rename = "type")]
pub type_of: LinkType,

///

Check failure on line 61 in src/core/config/link.rs

View workflow job for this annotation

GitHub Actions / Run Formatter and Lint Check

empty doc comment
#[serde(default, skip_serializing_if = "is_default")]
pub headers: Option<Vec<KeyValue>>,
/// Additional metadata pertaining to the linked resource.
#[serde(default, skip_serializing_if = "is_default")]
pub meta: Option<serde_json::Value>,
}
1 change: 1 addition & 0 deletions src/core/generator/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ impl Generator {
src: metadata.path.to_owned(),
type_of: LinkType::Protobuf,
headers: None,
meta: None,
});
Ok(config)
}
Expand Down
1 change: 1 addition & 0 deletions src/core/grpc/data_loader_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ mod tests {
src: test_file.to_string(),
type_of: LinkType::Protobuf,
headers: None,
meta: None,
}]);
let method = GrpcMethod {
package: "greetings".to_string(),
Expand Down
1 change: 1 addition & 0 deletions src/core/grpc/protobuf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ pub mod tests {
src: path.to_string(),
type_of: LinkType::Protobuf,
headers: None,
meta: None,
}]);

let method = GrpcMethod { package: id, service: "a".to_owned(), name: "b".to_owned() };
Expand Down
1 change: 1 addition & 0 deletions src/core/grpc/request_template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ mod tests {
src: test_file.to_string(),
type_of: LinkType::Protobuf,
headers: None,
meta: None,
}]);
let method = GrpcMethod {
package: id.to_string(),
Expand Down
2 changes: 1 addition & 1 deletion src/core/ir/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ impl IR {
second.eval(ctx).await
}
IR::Discriminate(discriminator, expr) => expr.eval(ctx).await.and_then(|value| {
let value = value.map(|mut value| {
let value = value.map(&mut |mut value| {
let type_name = discriminator.resolve_type(&value)?;

value.set_type_name(type_name.to_string())?;
Expand Down
3 changes: 2 additions & 1 deletion src/core/ir/resolver_context_like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ impl SelectionField {
let name = field.output_name.to_string();
let type_name = field.type_of.name();
let selection_set = field
.iter_only(|field| match &field.type_condition {
.iter()
.filter(|field| match &field.type_condition {
Some(type_condition) => type_condition == type_name,
None => true,
})
Expand Down
33 changes: 14 additions & 19 deletions src/core/jit/common/jp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use serde::Deserialize;
use crate::core::blueprint::Blueprint;
use crate::core::config::{Config, ConfigModule};
use crate::core::jit::builder::Builder;
use crate::core::jit::store::{Data, Store};
use crate::core::jit::store::Store;
use crate::core::jit::synth::Synth;
use crate::core::jit::{self, OperationPlan, Positioned, Variables};
use crate::core::jit::{OperationPlan, Variables};
use crate::core::json::{JsonLike, JsonObjectLike};
use crate::core::valid::Validator;

Expand All @@ -25,11 +25,9 @@ struct TestData<Value> {
users: Vec<Value>,
}

type Entry<Value> = Data<Result<Value, Positioned<jit::Error>>>;

struct ProcessedTestData<Value> {
posts: Value,
users: HashMap<usize, Entry<Value>>,
users: Value,
}

impl<'a, Value: JsonLike<'a> + Deserialize<'a> + Clone + 'a> TestData<Value> {
Expand All @@ -56,7 +54,7 @@ impl<'a, Value: JsonLike<'a> + Deserialize<'a> + Clone + 'a> TestData<Value> {
map
});

let users: HashMap<_, _> = posts
let users: Vec<_> = posts
.iter()
.map(|post| {
let user_id = post
Expand All @@ -74,12 +72,12 @@ impl<'a, Value: JsonLike<'a> + Deserialize<'a> + Clone + 'a> TestData<Value> {
Value::null()
}
})
.map(Ok)
.map(Data::Single)
.enumerate()
.collect();

ProcessedTestData { posts: Value::array(posts.clone()), users }
ProcessedTestData {
posts: Value::array(posts.clone()),
users: Value::array(users),
}
}
}

Expand Down Expand Up @@ -120,15 +118,12 @@ impl<'a, Value: Deserialize<'a> + Clone + 'a + JsonLike<'a>> JP<Value> {
.id
.to_owned();

let store = [
(posts_id, Data::Single(Ok(posts))),
(users_id, Data::Multiple(users)),
]
.into_iter()
.fold(Store::new(), |mut store, (id, data)| {
store.set_data(id, data);
store
});
let store = [(posts_id, Ok(posts)), (users_id, Ok(users))]
.into_iter()
.fold(Store::new(), |mut store, (id, data)| {
store.set_data(id, data);
store
});

Synth::new(plan, store, vars)
}
Expand Down
10 changes: 10 additions & 0 deletions src/core/jit/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ impl<'a, Input: Clone, Output> Context<'a, Input, Output> {
Self { request, value: None, args: Self::build_args(field), field }
}

pub fn with_value(&self, value: &'a Output) -> Self {
Self {
request: self.request,
// TODO: no need to build again?
args: Self::build_args(self.field),
value: Some(value),
field: self.field,
}
}

pub fn with_value_and_field(
&self,
value: &'a Output,
Expand Down
100 changes: 33 additions & 67 deletions src/core/jit/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ use derive_getters::Getters;
use futures_util::future::join_all;

use super::context::{Context, RequestContext};
use super::{DataPath, OperationPlan, Positioned, Response, Store};
use super::{OperationPlan, Positioned, Response, Store};
use crate::core::ir::model::IR;
use crate::core::ir::TypedValue;
use crate::core::jit;
use crate::core::jit::synth::Synth;
use crate::core::json::{JsonLike, JsonObjectLike};
use crate::core::json::{JsonLike, JsonLikeList};

type SharedStore<Output, Error> = Arc<Mutex<Store<Result<Output, Positioned<Error>>>>>;

Expand Down Expand Up @@ -58,7 +58,7 @@ struct ExecutorInner<'a, Input, Output, Error, Exec> {

impl<'a, Input, Output, Error, Exec> ExecutorInner<'a, Input, Output, Error, Exec>
where
for<'i> Output: JsonLike<'i> + TypedValue<'i> + Debug,
for<'i> Output: JsonLike<'i> + JsonLikeList<'i> + TypedValue<'i> + Debug + Clone,
Input: Clone + Debug,
Exec: IRExecutor<Input = Input, Output = Output, Error = Error>,
{
Expand All @@ -75,97 +75,63 @@ where
let ctx = Context::new(field, self.request);
// TODO: with_args should be called on inside iter_field on any level, not only
// for root fields
self.execute(&ctx, DataPath::new()).await
self.execute(&ctx).await
}))
.await;
}

async fn iter_field<'b>(
&'b self,
ctx: &'b Context<'b, Input, Output>,
data_path: &DataPath,
value: &'b Output,
) -> Result<(), Error> {
let field = ctx.field();
// Array
// Check if the field expects a list
if field.type_of.is_list() {
// Check if the value is an array
if let Some(array) = value.as_array() {
join_all(array.iter().enumerate().map(|(index, value)| {
join_all(
self.request
.plan()
.field_iter_only(field, value)
.map(|field| {
let ctx = ctx.with_value_and_field(value, field);
let data_path = data_path.clone().with_index(index);
async move { self.execute(&ctx, data_path).await }
}),
)
}))
.await;
}
// TODO: We should throw an error stating that we expected
// a list type here but because the `Error` is a
// type-parameter, its not possible
}
// TODO: Validate if the value is an Object
// Has to be an Object, we don't do anything while executing if its a Scalar
else {
join_all(
self.request
.plan()
.field_iter_only(field, value)
.map(|child| {
let ctx = ctx.with_value_and_field(value, child);
let data_path = data_path.clone();
async move { self.execute(&ctx, data_path).await }
}),
)
.await;
}
join_all(field.iter().map(|child| {
let ctx = ctx.with_value_and_field(value, child);
async move { self.execute(&ctx).await }
}))
.await;

Ok(())
}

async fn execute<'b>(
&'b self,
ctx: &'b Context<'b, Input, Output>,
data_path: DataPath,
) -> Result<(), Error> {
async fn execute<'b>(&'b self, ctx: &'b Context<'b, Input, Output>) -> Result<(), Error> {
let field = ctx.field();

if let Some(ir) = &field.ir {
let result = self.ir_exec.execute(ir, ctx).await;

if let Ok(value) = &result {
self.iter_field(ctx, &data_path, value).await?;
self.iter_field(ctx, value).await?;
}

let mut store = self.store.lock().unwrap();

store.set(
&field.id,
&data_path,
result.map_err(|e| Positioned::new(e, field.pos)),
);
store.set(&field.id, result.map_err(|e| Positioned::new(e, field.pos)));
} else {
// if the present field doesn't have IR, still go through it's extensions to see
// if they've IR.
let default_obj = Output::object(Output::JsonObject::new());
let value = ctx
.value()
.and_then(|v| v.get_key(&field.output_name))
// in case there is no value we still put some dumb empty value anyway
// to force execution of the nested fields even when parent object is not present.
// For async_graphql it's done by `fix_dangling_resolvers` fn that basically creates
// fake IR that resolves to empty object. The `fix_dangling_resolvers` is also
// working here, but eventually it can be replaced by this logic
// here without doing the "fix"
.unwrap_or(&default_obj);

self.iter_field(ctx, &data_path, value).await?;
let value = match ctx.value() {
Some(value) => value.map_ref(&mut |value| {
Ok(value
.get_key(&field.output_name)
.cloned()
// in case there is no value we still put some dumb empty value anyway
// to force execution of the nested fields even when parent object is not
// present. For async_graphql it's done by
// `fix_dangling_resolvers` fn that basically creates
// fake IR that resolves to empty object. The `fix_dangling_resolvers` is
// also working here, but eventually it can be
// replaced by this logic here without doing the
// "fix"
.unwrap_or(Output::null()))
})?,
// if the present field doesn't have IR, still go through nested fields to check
// if they've IR.
None => Output::null(),
};

self.iter_field(ctx, &value).await?;
}

Ok(())
Expand Down
Loading

0 comments on commit 361135d

Please sign in to comment.