diff --git a/Cargo.lock b/Cargo.lock index cf1b8b278b..2ea53b2c97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,7 +280,7 @@ dependencies = [ "proc-macro2", "quote", "strum", - "syn 2.0.70", + "syn 2.0.69", "thiserror", ] @@ -434,7 +434,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -502,7 +502,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -519,7 +519,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -563,7 +563,7 @@ dependencies = [ "futures-util", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.30", + "hyper 0.14.29", "itoa", "matchit", "memchr", @@ -907,9 +907,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.9" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" +checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" dependencies = [ "clap_builder", "clap_derive", @@ -917,9 +917,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.9" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" +checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" dependencies = [ "anstream", "anstyle", @@ -936,7 +936,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -1216,7 +1216,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -1238,7 +1238,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core 0.20.9", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -1276,7 +1276,7 @@ checksum = "0a6433aac097572ea8ccc60b3f2e756c661c9aeed9225cdd4d0cb119cb7ff6ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -1320,7 +1320,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -1332,7 +1332,7 @@ dependencies = [ "darling 0.20.9", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -1703,7 +1703,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -2265,7 +2265,7 @@ dependencies = [ "crossbeam-utils", "form_urlencoded", "futures-util", - "hyper 0.14.30", + "hyper 0.14.29", "lazy_static", "levenshtein", "log", @@ -2280,9 +2280,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.30" +version = "0.14.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" dependencies = [ "bytes", "futures-channel", @@ -2295,7 +2295,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -2329,7 +2329,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.29", "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", @@ -2343,7 +2343,7 @@ checksum = "399c78f9338483cb7e630c8474b07268983c6bd5acee012e4211f9f7bb21b070" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.29", "log", "rustls 0.22.4", "rustls-native-certs", @@ -2358,7 +2358,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.30", + "hyper 0.14.29", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -2550,6 +2550,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -2838,7 +2847,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.8.4", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -2986,7 +2995,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -2997,7 +3006,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -3570,7 +3579,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -3605,14 +3614,14 @@ dependencies = [ [[package]] name = "phonenumber" -version = "0.3.6+8.13.36" +version = "0.3.5+8.13.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11756237b57b8cc5e97dc8b1e70ea436324d30e7075de63b14fd15073a8f692a" +checksum = "f174c8db59b620032bd52b655fc97000458850fec0db35fcd4e802b668517ec0" dependencies = [ "bincode", "either", "fnv", - "itertools 0.10.5", + "itertools 0.12.1", "lazy_static", "nom", "quick-xml", @@ -3647,7 +3656,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -3767,7 +3776,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -3855,7 +3864,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", "heck", - "itertools 0.10.5", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -3864,7 +3873,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.70", + "syn 2.0.69", "tempfile", ] @@ -3875,10 +3884,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -4274,7 +4283,7 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.30", + "hyper 0.14.29", "hyper-rustls 0.24.2", "ipnet", "js-sys", @@ -4379,7 +4388,7 @@ dependencies = [ "proc-macro2", "quote", "rquickjs-core", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -4454,20 +4463,20 @@ dependencies = [ "log", "ring", "rustls-pki-types", - "rustls-webpki 0.102.5", + "rustls-webpki 0.102.4", "subtle", "zeroize", ] [[package]] name = "rustls" -version = "0.23.11" +version = "0.23.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" dependencies = [ "once_cell", "rustls-pki-types", - "rustls-webpki 0.102.5", + "rustls-webpki 0.102.4", "subtle", "zeroize", ] @@ -4522,9 +4531,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.5" +version = "0.102.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" dependencies = [ "ring", "rustls-pki-types", @@ -4582,7 +4591,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -4694,7 +4703,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -4705,7 +4714,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -5028,7 +5037,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -5050,9 +5059,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.70" +version = "2.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" +checksum = "201fcda3845c23e8212cd466bfebf0bd20694490fc0356ae8e428e0824a915a6" dependencies = [ "proc-macro2", "quote", @@ -5160,7 +5169,7 @@ dependencies = [ "http-cache-reqwest", "http-cache-semantics", "httpmock", - "hyper 0.14.30", + "hyper 0.14.29", "hyper-rustls 0.25.0", "indenter", "indexmap 2.2.6", @@ -5202,7 +5211,7 @@ dependencies = [ "reqwest-middleware", "resource", "rquickjs", - "rustls 0.23.11", + "rustls 0.23.10", "rustls-pemfile 1.0.4", "rustls-pki-types", "schemars", @@ -5248,7 +5257,7 @@ dependencies = [ "async-graphql-value", "async-trait", "dotenvy", - "hyper 0.14.30", + "hyper 0.14.29", "lambda_http", "lambda_runtime", "reqwest", @@ -5267,7 +5276,7 @@ dependencies = [ "async-std", "async-trait", "console_error_panic_hook", - "hyper 0.14.30", + "hyper 0.14.29", "lazy_static", "protox", "reqwest", @@ -5319,7 +5328,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -5378,7 +5387,7 @@ version = "0.1.0" dependencies = [ "anyhow", "http-body-util", - "hyper 0.14.30", + "hyper 0.14.29", "hyper-util", "once_cell", "opentelemetry 0.23.0", @@ -5474,7 +5483,7 @@ checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -5494,7 +5503,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -5618,7 +5627,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -5721,7 +5730,7 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.30", + "hyper 0.14.29", "hyper-timeout", "percent-encoding", "pin-project", @@ -5748,7 +5757,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -5761,7 +5770,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -5787,7 +5796,7 @@ dependencies = [ "futures-util", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.30", + "hyper 0.14.29", "opentelemetry 0.23.0", "pin-project-lite", "tonic", @@ -5859,7 +5868,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -6174,7 +6183,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", "wasm-bindgen-shared", ] @@ -6208,7 +6217,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6395,7 +6404,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -6417,7 +6426,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -6672,7 +6681,7 @@ dependencies = [ "async-trait", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-macro-support", @@ -6700,7 +6709,7 @@ dependencies = [ "darling 0.20.9", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] @@ -6732,7 +6741,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.69", ] [[package]] diff --git a/src/core/jit/builder.rs b/src/core/jit/builder.rs index 016e49e642..ce1b413ddc 100644 --- a/src/core/jit/builder.rs +++ b/src/core/jit/builder.rs @@ -1,16 +1,49 @@ use std::collections::HashMap; +use std::ops::Deref; use async_graphql::parser::types::{ - DocumentOperations, ExecutableDocument, FragmentDefinition, OperationType, Selection, - SelectionSet, + Directive, DocumentOperations, ExecutableDocument, FragmentDefinition, OperationType, + Selection, SelectionSet, }; use async_graphql::Positioned; +use async_graphql_value::Value; use super::model::*; use crate::core::blueprint::{Blueprint, Index, QueryField}; use crate::core::counter::{Count, Counter}; use crate::core::merge_right::MergeRight; +#[derive(PartialEq)] +enum Condition { + True, + False, + Variable(Variable), +} + +struct Conditions { + skip: Option, + include: Option, +} + +impl Conditions { + /// Checks if the field should be skipped always + fn is_const_skip(&self) -> bool { + matches!(self.skip, Some(Condition::True)) ^ matches!(self.include, Some(Condition::True)) + } + + fn into_variable_tuple(self) -> (Option, Option) { + let comp = |condition| match condition? { + Condition::Variable(var) => Some(var), + _ => None, + }; + + let include = comp(self.include); + let skip = comp(self.skip); + + (include, skip) + } +} + pub struct Builder { pub index: Index, pub arg_id: Counter, @@ -29,7 +62,48 @@ impl Builder { } } + #[inline(always)] + fn include( + &self, + directives: &[Positioned], + ) -> Conditions { + fn get_condition(dir: &Directive) -> Option { + let arg = dir.get_argument("if").map(|pos| &pos.node); + let is_include = dir.name.node.as_str() == "include"; + match arg { + None => None, + Some(value) => match value { + Value::Boolean(bool) => { + let condition = if is_include ^ bool { + Condition::True + } else { + Condition::False + }; + Some(condition) + } + Value::Variable(var) => { + Some(Condition::Variable(Variable::new(var.deref().to_owned()))) + } + _ => None, + }, + } + } + Conditions { + skip: directives + .iter() + .find(|d| d.node.name.node.as_str() == "skip") + .map(|d| &d.node) + .and_then(get_condition), + include: directives + .iter() + .find(|d| d.node.name.node.as_str() == "include") + .map(|d| &d.node) + .and_then(get_condition), + } + } + #[allow(clippy::too_many_arguments)] + #[inline(always)] fn iter( &self, selection: &SelectionSet, @@ -41,6 +115,16 @@ impl Builder { for selection in &selection.items { match &selection.node { Selection::Field(Positioned { node: gql_field, .. }) => { + let conditions = self.include(&gql_field.directives); + + // if include is always false xor skip is always true, + // then we can skip the field from the plan + if conditions.is_const_skip() { + continue; + } + + let (include, skip) = conditions.into_variable_tuple(); + let field_name = gql_field.name.node.as_str(); let field_args = gql_field .arguments @@ -91,6 +175,8 @@ impl Builder { name, ir, type_of, + skip, + include, args, extensions: exts.clone(), }); @@ -116,6 +202,7 @@ impl Builder { fields } + #[inline(always)] fn get_type(&self, ty: OperationType) -> Option<&str> { match ty { OperationType::Query => Some(self.index.get_query()), @@ -124,6 +211,7 @@ impl Builder { } } + #[inline(always)] pub fn build(&self) -> Result { let mut fields = Vec::new(); let mut fragments: HashMap<&str, &FragmentDefinition> = HashMap::new(); @@ -176,7 +264,6 @@ mod tests { let config = Config::from_sdl(CONFIG).to_result().unwrap(); let blueprint = Blueprint::try_from(&config.into()).unwrap(); let document = async_graphql::parser::parse_query(query).unwrap(); - Builder::new(&blueprint, document).build().unwrap() } diff --git a/src/core/jit/common/json_placeholder.rs b/src/core/jit/common/json_placeholder.rs index c832bda7bb..e2f0a1de9b 100644 --- a/src/core/jit/common/json_placeholder.rs +++ b/src/core/jit/common/json_placeholder.rs @@ -7,6 +7,7 @@ use crate::core::config::{Config, ConfigModule}; use crate::core::jit::builder::Builder; use crate::core::jit::store::{Data, Store}; use crate::core::jit::synth::Synth; +use crate::core::jit::Variables; use crate::core::json::JsonLike; use crate::core::valid::Validator; @@ -82,6 +83,7 @@ impl JsonPlaceholder { store }); - Synth::new(plan, store) + let vars = Variables::new(); + Synth::new(plan, store, vars) } } diff --git a/src/core/jit/exec.rs b/src/core/jit/exec.rs index ae12be86ee..1d1f8911cb 100644 --- a/src/core/jit/exec.rs +++ b/src/core/jit/exec.rs @@ -20,10 +20,10 @@ pub struct Executor { exec: IRExec, } -impl Executor +impl Executor where Output: JsonLike + Debug, - Synth: Synthesizer>, + Synth: Synthesizer, Variable = Input>, Exec: IRExecutor, { pub fn new(plan: ExecutionPlan, synth: Synth, exec: Exec) -> Self { @@ -40,8 +40,9 @@ where } pub async fn execute(self, request: Request) -> Response { + let vars = request.variables.clone(); let store = self.execute_inner(request).await; - Response::new(self.synth.synthesize(store)) + Response::new(self.synth.synthesize(store, vars)) } } diff --git a/src/core/jit/model.rs b/src/core/jit/model.rs index 1329b0f2f7..3cd0234b68 100644 --- a/src/core/jit/model.rs +++ b/src/core/jit/model.rs @@ -1,7 +1,37 @@ +use std::collections::HashMap; use std::fmt::{Debug, Formatter}; +use serde::Deserialize; + use crate::core::ir::model::IR; +#[derive(Debug, Deserialize, Clone)] +pub struct Variables(HashMap); + +impl Default for Variables { + fn default() -> Self { + Self::new() + } +} + +impl Variables { + pub fn new() -> Self { + Self(HashMap::new()) + } + pub fn get(&self, key: &str) -> Option<&Value> { + self.0.get(key) + } + pub fn insert(&mut self, key: String, value: Value) { + self.0.insert(key, value); + } +} + +impl FromIterator<(String, V)> for Variables { + fn from_iter>(iter: T) -> Self { + Self(iter.into_iter().collect()) + } +} + #[derive(Debug, Clone)] pub struct Arg { pub id: ArgId, @@ -50,9 +80,40 @@ pub struct Field { pub name: String, pub ir: Option, pub type_of: crate::core::blueprint::Type, + pub skip: Option, + pub include: Option, pub args: Vec, pub extensions: Option, } +#[derive(Clone, Debug, PartialEq)] +pub struct Variable(String); + +impl Variable { + pub fn new(name: String) -> Self { + Variable(name) + } +} + +impl Field { + #[inline(always)] + pub fn skip(&self, variables: &Variables) -> bool { + let eval = |variable_option: Option<&Variable>, + variables: &Variables, + default: bool| { + match variable_option { + Some(Variable(name)) => variables.get(name).map_or(default, |value| match value { + async_graphql_value::ConstValue::Boolean(b) => *b, + _ => default, + }), + None => default, + } + }; + let skip = eval(self.skip.as_ref(), variables, false); + let include = eval(self.include.as_ref(), variables, true); + + skip == include + } +} const EMPTY_VEC: &Vec> = &Vec::new(); impl Field { @@ -90,6 +151,8 @@ impl Field { name: self.name, ir: self.ir, type_of: self.type_of, + skip: self.skip, + include: self.include, args: self.args, extensions, } diff --git a/src/core/jit/request.rs b/src/core/jit/request.rs index d79b4adef1..ce7cb9a091 100644 --- a/src/core/jit/request.rs +++ b/src/core/jit/request.rs @@ -5,15 +5,16 @@ use serde::Deserialize; use super::{Builder, Error, ExecutionPlan, Result}; use crate::core::blueprint::Blueprint; +use crate::core::jit::model::Variables; -#[derive(Debug, Deserialize, Setters)] +#[derive(Debug, Deserialize, Setters, Clone)] pub struct Request { #[serde(default)] pub query: String, #[serde(default, rename = "operationName")] pub operation_name: Option, #[serde(default)] - pub variables: HashMap, + pub variables: Variables, #[serde(default)] pub extensions: HashMap, } @@ -25,9 +26,9 @@ impl From for Request { operation_name: value.operation_name, variables: match value.variables.into_value() { async_graphql_value::ConstValue::Object(val) => { - HashMap::from_iter(val.into_iter().map(|(k, v)| (k.to_string(), v))) + Variables::from_iter(val.into_iter().map(|(name, val)| (name.to_string(), val))) } - _ => HashMap::new(), + _ => Variables::default(), }, extensions: value.extensions, } @@ -47,7 +48,7 @@ impl Request { Self { query: query.to_string(), operation_name: None, - variables: HashMap::new(), + variables: Variables::default(), extensions: HashMap::new(), } } diff --git a/src/core/jit/synth/mod.rs b/src/core/jit/synth/mod.rs index 8edb154176..534e3f5aa5 100644 --- a/src/core/jit/synth/mod.rs +++ b/src/core/jit/synth/mod.rs @@ -4,8 +4,14 @@ mod synth_const; // pub use synth_borrow::SynthBorrow; pub use synth_const::{Synth, SynthConst}; -use super::Store; +use super::{Store, Variables}; +use crate::core::json::JsonLike; pub trait Synthesizer { type Value; - fn synthesize(self, store: Store) -> Self::Value; + type Variable: JsonLike; + fn synthesize( + self, + store: Store, + variables: Variables, + ) -> Self::Value; } diff --git a/src/core/jit/synth/synth_borrow.rs b/src/core/jit/synth/synth_borrow.rs index 9c995fae4d..243593e54f 100644 --- a/src/core/jit/synth/synth_borrow.rs +++ b/src/core/jit/synth/synth_borrow.rs @@ -131,6 +131,7 @@ mod tests { use crate::core::jit::model::FieldId; use crate::core::jit::store::{Data, Store}; use crate::core::jit::synth::SynthBorrow; + use crate::core::jit::Variables; use crate::core::valid::Validator; const POSTS: &str = r#" @@ -207,7 +208,11 @@ mod tests { let config = Config::from_sdl(CONFIG).to_result().unwrap(); let config = ConfigModule::from(config); - let builder = Builder::new(&Blueprint::try_from(&config).unwrap(), doc); + let builder = Builder::new( + &Blueprint::try_from(&config).unwrap(), + doc, + Variables::new(), + ); let plan = builder.build().unwrap(); let store = store diff --git a/src/core/jit/synth/synth_const.rs b/src/core/jit/synth/synth_const.rs index 387b7cecc5..ffd86afa99 100644 --- a/src/core/jit/synth/synth_const.rs +++ b/src/core/jit/synth/synth_const.rs @@ -5,23 +5,36 @@ use super::super::Result; use super::Synthesizer; use crate::core::jit::model::{Field, Nested}; use crate::core::jit::store::{Data, Store}; -use crate::core::jit::{DataPath, ExecutionPlan}; +use crate::core::jit::{DataPath, ExecutionPlan, Variables}; use crate::core::json::JsonLike; pub struct Synth { selection: Vec>, store: Store>, + variables: Variables, } impl Synth { - pub fn new(plan: ExecutionPlan, store: Store>) -> Self { - Self { selection: plan.into_nested(), store } + pub fn new( + plan: ExecutionPlan, + store: Store>, + variables: Variables, + ) -> Self { + Self { selection: plan.into_nested(), store, variables } + } + + #[inline(always)] + fn include(&self, field: &Field) -> bool { + !field.skip(&self.variables) } pub fn synthesize(&self) -> Result { let mut data = IndexMap::default(); for child in self.selection.iter() { + if !self.include(child) { + continue; + } let val = self.iter(child, None, &DataPath::new())?; data.insert(Name::new(child.name.as_str()), val); } @@ -90,32 +103,40 @@ impl Synth { parent: &'b Value, data_path: &'b DataPath, ) -> Result { + let include = self.include(node); + match parent { Value::Object(obj) => { let mut ans = IndexMap::default(); let children = node.nested(); - - if children.is_empty() { - let val = obj.get(node.name.as_str()); - // if it's a leaf node, then push the value - if let Some(val) = val { - ans.insert(Name::new(node.name.as_str()), val.to_owned()); - } else { - return Ok(Value::Null); - } - } else { - for child in children { - let val = obj.get(child.name.as_str()); + if include { + if children.is_empty() { + let val = obj.get(node.name.as_str()); + // if it's a leaf node, then push the value if let Some(val) = val { - ans.insert( - Name::new(child.name.as_str()), - self.iter_inner(child, val, data_path)?, - ); + ans.insert(Name::new(node.name.as_str()), val.to_owned()); } else { - ans.insert( - Name::new(child.name.as_str()), - self.iter(child, None, data_path)?, - ); + return Ok(Value::Null); + } + } else { + for child in children { + // all checks for skip must occur in `iter_inner` + // and include be checked before calling `iter` or recursing. + let include = self.include(child); + if include { + let val = obj.get(child.name.as_str()); + if let Some(val) = val { + ans.insert( + Name::new(child.name.as_str()), + self.iter_inner(child, val, data_path)?, + ); + } else { + ans.insert( + Name::new(child.name.as_str()), + self.iter(child, None, data_path)?, + ); + } + } } } } @@ -123,9 +144,11 @@ impl Synth { } Value::List(arr) => { let mut ans = vec![]; - for (i, val) in arr.iter().enumerate() { - let val = self.iter_inner(node, val, &data_path.clone().with_index(i))?; - ans.push(val) + if include { + for (i, val) in arr.iter().enumerate() { + let val = self.iter_inner(node, val, &data_path.clone().with_index(i))?; + ans.push(val) + } } Ok(Value::List(ans)) } @@ -146,15 +169,19 @@ impl SynthConst { impl Synthesizer for SynthConst { type Value = Result; - - fn synthesize(self, store: Store) -> Self::Value { - Synth::new(self.plan, store).synthesize() + type Variable = Value; + + fn synthesize( + self, + store: Store, + variables: Variables, + ) -> Self::Value { + Synth::new(self.plan, store, variables).synthesize() } } #[cfg(test)] mod tests { - use async_graphql::Value; use super::Synth; @@ -164,6 +191,7 @@ mod tests { use crate::core::jit::common::JsonPlaceholder; use crate::core::jit::model::FieldId; use crate::core::jit::store::{Data, Store}; + use crate::core::jit::Variables; use crate::core::valid::Validator; const POSTS: &str = r#" @@ -249,8 +277,8 @@ mod tests { store.set_data(id, data.map(Ok)); store }); - - let synth = Synth::new(plan, store); + let vars = Variables::new(); + let synth = Synth::new(plan, store, vars); let val = synth.synthesize().unwrap(); serde_json::to_string_pretty(&val).unwrap() diff --git a/tests/jit_spec.rs b/tests/jit_spec.rs index d8b24f405f..8969bf3380 100644 --- a/tests/jit_spec.rs +++ b/tests/jit_spec.rs @@ -109,6 +109,32 @@ mod tests { let response = executor.execute(request).await; let data = response.data; + insta::assert_json_snapshot!(data); + } + #[tokio::test] + async fn test_skip() { + // NOTE: This test makes a real HTTP call + let mut request = Request::new( + r#" + query ($TRUE: Boolean!){ + users { + id @skip(if: true) + name @skip(if: $TRUE) + email @include(if: $TRUE) + username @include(if: false) + phone @skip(if: false) @include(if: true) + } + } + "#, + ); + request + .variables + .insert("TRUE".to_string(), Value::Boolean(true)); + + let executor = new_executor(&request).await.unwrap(); + let response = executor.execute(request).await; + let data = response.data; + insta::assert_json_snapshot!(data); } } diff --git a/tests/snapshots/jit_spec__tests__skip.snap b/tests/snapshots/jit_spec__tests__skip.snap new file mode 100644 index 0000000000..b60906f839 --- /dev/null +++ b/tests/snapshots/jit_spec__tests__skip.snap @@ -0,0 +1,48 @@ +--- +source: tests/jit_spec.rs +expression: data +--- +{ + "users": [ + { + "email": "Sincere@april.biz", + "phone": "1-770-736-8031 x56442" + }, + { + "email": "Shanna@melissa.tv", + "phone": "010-692-6593 x09125" + }, + { + "email": "Nathan@yesenia.net", + "phone": "1-463-123-4447" + }, + { + "email": "Julianne.OConner@kory.org", + "phone": "493-170-9623 x156" + }, + { + "email": "Lucio_Hettinger@annie.ca", + "phone": "(254)954-1289" + }, + { + "email": "Karley_Dach@jasper.info", + "phone": "1-477-935-8478 x6430" + }, + { + "email": "Telly.Hoeger@billy.biz", + "phone": "210.067.6132" + }, + { + "email": "Sherwood@rosamond.me", + "phone": "586.493.6943 x140" + }, + { + "email": "Chaim_McDermott@dana.io", + "phone": "(775)976-6794 x41206" + }, + { + "email": "Rey.Padberg@karina.biz", + "phone": "024-648-3804" + } + ] +}