diff --git a/.circleci/config.yml b/.circleci/config.yml index 7c2dc07696..a977db59e9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -221,6 +221,8 @@ commands: - run: cargo sweep -s - install_extra_tools: os: << parameters.os >> + # cargo-deny fetches a rustsec advisory DB, which has to happen on github.com over https + - run: git config --global --unset-all url.ssh://git@github.com.insteadof - run: cargo xtask check-compliance - run: cargo sweep -f - save_cache: diff --git a/CHANGELOG.md b/CHANGELOG.md index a73c99a0ed..9e031ab11a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,26 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## 🛠 Maintenance ## 📚 Documentation --> +# [v0.1.0-alpha.3] 2022-01-11 + +## :rocket::waxing_crescent_moon: Public alpha release + +> An alpha or beta release is in volatile, active development. The release might not be feature-complete, and breaking API changes are possible between individual versions. + +## :sparkles: Features + +- Trace sampling [#228](https://github.com/apollographql/router/issues/228): Tracing each request can be expensive. The router now supports sampling, which allows us to only send a fraction of the received requests. + +- Health check [#54](https://github.com/apollographql/router/issues/54) + +## :bug: Fixes + +- Schema parse errors [#136](https://github.com/apollographql/router/pull/136): The router wouldn't display what went wrong when parsing an invalid Schema. It now displays exactly where a the parsing error occured, and why. + +- Various tracing and telemetry fixes [#237](https://github.com/apollographql/router/pull/237): The router wouldn't display what went wrong when parsing an invalid Schema. It now displays exactly where a the parsing error occured, and why. + +- Query variables validation [#62](https://github.com/apollographql/router/issues/62): Now that we have a schema parsing feature, we can validate the variables and their types against the schemas and queries. + # [v0.1.0-alpha.2] 2021-12-03 diff --git a/Cargo.lock b/Cargo.lock index 0df5948391..1f8046119e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,7 +78,7 @@ dependencies = [ [[package]] name = "apollo-router" -version = "0.1.0-alpha.2" +version = "0.1.0-alpha.3" dependencies = [ "anyhow", "apollo-router-core", @@ -105,10 +105,12 @@ dependencies = [ "reqwest-tracing", "serde", "serde_json", + "serde_json_bytes", "serde_yaml", "structopt", "task-local-extensions", "test-log", + "test-span", "thiserror", "tokio", "tonic", @@ -141,11 +143,12 @@ dependencies = [ [[package]] name = "apollo-router-core" -version = "0.1.0-alpha.2" +version = "0.1.0-alpha.3" dependencies = [ "apollo-parser", "async-trait", "atty", + "bytes", "derivative", "displaydoc", "futures", @@ -158,6 +161,7 @@ dependencies = [ "router-bridge", "serde", "serde_json", + "serde_json_bytes", "static_assertions", "test-log", "thiserror", @@ -857,6 +861,16 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "daggy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91a9304e55e9d601a39ae4deaba85406d5c0980e106f65afcf0460e9af1e7602" +dependencies = [ + "petgraph 0.6.0", + "serde", +] + [[package]] name = "deno_core" version = "0.112.0" @@ -1061,6 +1075,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" +[[package]] +name = "fixedbitset" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" + [[package]] name = "flate2" version = "1.0.22" @@ -1271,9 +1291,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" dependencies = [ "typenum", "version_check", @@ -1335,9 +1355,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f072413d126e57991455e0a922b31e4c8ba7c2ffbebf6b78b4f8521397d65cd" +checksum = "0c9de88456263e249e241fcd211d3954e2c9b0ef7ccfc235a444eb367cae3689" dependencies = [ "bytes", "fnv", @@ -1462,9 +1482,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "httpmock" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a24a520a3193799852cc4baf0d8356d7578a8847dfc5603d087529f099661e42" +checksum = "c159c4fc205e6c1a9b325cb7ec135d13b5f47188ce175dabb76ec847f331d9bd" dependencies = [ "assert-json-diff", "async-object-pool", @@ -1479,13 +1499,13 @@ dependencies = [ "lazy_static", "levenshtein", "log", - "qstring", "regex", "serde", "serde_json", "serde_regex", "similar", "tokio", + "url", ] [[package]] @@ -1755,7 +1775,7 @@ dependencies = [ "ena", "itertools", "lalrpop-util", - "petgraph", + "petgraph 0.5.1", "pico-args", "regex", "regex-syntax", @@ -1825,6 +1845,10 @@ name = "linked-hash-map" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +dependencies = [ + "serde", + "serde_test", +] [[package]] name = "lock_api" @@ -2337,10 +2361,22 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" dependencies = [ - "fixedbitset", + "fixedbitset 0.2.0", "indexmap", ] +[[package]] +name = "petgraph" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +dependencies = [ + "fixedbitset 0.4.1", + "indexmap", + "serde", + "serde_derive", +] + [[package]] name = "phf_shared" version = "0.8.0" @@ -2537,7 +2573,7 @@ dependencies = [ "itertools", "log", "multimap", - "petgraph", + "petgraph 0.5.1", "prost", "prost-types", "tempfile", @@ -2567,15 +2603,6 @@ dependencies = [ "prost", ] -[[package]] -name = "qstring" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" -dependencies = [ - "percent-encoding", -] - [[package]] name = "quote" version = "1.0.14" @@ -2706,15 +2733,16 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c4e0a76dc12a116108933f6301b95e83634e0c47b0afbed6abbaa0601e99258" +checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", "futures-util", + "h2", "http", "http-body", "hyper", @@ -2984,6 +3012,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_json_bytes" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07cc85f924551185aa24952d5bf52c8433ffd040eeeff3908bb684c55035002" +dependencies = [ + "bytes", + "indexmap", + "serde", + "serde_json", +] + [[package]] name = "serde_json_traversal" version = "0.2.0" @@ -3000,6 +3040,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_test" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8537872f576ea4a3a38ef8f8e317effb067964b526f2fa64cc3d27e89cb52d3" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.0" @@ -3314,6 +3363,38 @@ dependencies = [ "syn", ] +[[package]] +name = "test-span" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9f85d34f1792565eb25be5231f457f21efe11166ea1c9f3aad0e60f1cedf59" +dependencies = [ + "daggy", + "derivative", + "indexmap", + "linked-hash-map", + "once_cell", + "serde", + "serde_json", + "test-span-macro", + "tokio", + "tracing", + "tracing-core", + "tracing-futures", + "tracing-subscriber", +] + +[[package]] +name = "test-span-macro" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1fd5d6b8ac468c44017cf9caf1b96b56c37c8a68bc87d21f53d9e3a47eb6e41" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "text-size" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 479e9ae0b0..24707a4d25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,11 @@ [workspace] default-members = ["apollo-router", "apollo-router-core"] -members = ["apollo-router", "apollo-router-core", "apollo-router-benchmarks", "xtask"] +members = [ + "apollo-router", + "apollo-router-core", + "apollo-router-benchmarks", + "xtask", +] [patch.crates-io] opentelemetry = { git = "https://github.com/open-telemetry/opentelemetry-rust.git", rev = "6b3aa02aa" } diff --git a/apollo-router-core/Cargo.toml b/apollo-router-core/Cargo.toml index fde1930ee8..d03ec2c28c 100644 --- a/apollo-router-core/Cargo.toml +++ b/apollo-router-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "apollo-router-core" -version = "0.1.0-alpha.2" +version = "0.1.0-alpha.3" authors = ["Apollo Graph, Inc. "] edition = "2021" license-file = "./LICENSE" @@ -15,6 +15,7 @@ failfast = [] apollo-parser = "0.2.0" async-trait = "0.1.52" atty = "0.2.14" +bytes = "1.1.0" derivative = "2.2.0" displaydoc = "0.2" futures = "0.3.19" @@ -25,6 +26,7 @@ once_cell = "1.9.0" router-bridge = { git = "https://github.com/apollographql/federation.git", rev = "950eb931e38746bb7cfed05382d6970a22e43cc4" } serde = { version = "1.0.133", features = ["derive", "rc"] } serde_json = { version = "1.0.74", features = ["preserve_order"] } +serde_json_bytes = { version = "0.2.0", features = ["preserve_order"]} thiserror = "1.0.30" tokio = { version = "1.15.0", features = ["rt"] } tracing = "0.1.29" diff --git a/apollo-router-core/src/error.rs b/apollo-router-core/src/error.rs index 5150963f8b..21a1964444 100644 --- a/apollo-router-core/src/error.rs +++ b/apollo-router-core/src/error.rs @@ -88,15 +88,12 @@ pub enum FetchError { impl FetchError { /// Convert the fetch error to a GraphQL error. pub fn to_graphql_error(&self, path: Option) -> Error { + let value: Value = serde_json::to_value(self).unwrap().into(); Error { message: self.to_string(), locations: Default::default(), path, - extensions: serde_json::to_value(self) - .unwrap() - .as_object() - .unwrap() - .to_owned(), + extensions: value.as_object().unwrap().to_owned(), } } @@ -132,6 +129,53 @@ pub struct Error { pub extensions: Object, } +impl Error { + pub fn from_value(service_name: &str, value: Value) -> Result { + let mut object = + ensure_object!(value).map_err(|error| FetchError::SubrequestMalformedResponse { + service: service_name.to_string(), + reason: error.to_string(), + })?; + + let extensions = + extract_key_value_from_object!(object, "extensions", Value::Object(o) => o) + .map_err(|err| FetchError::SubrequestMalformedResponse { + service: service_name.to_string(), + reason: err.to_string(), + })? + .unwrap_or_default(); + let message = extract_key_value_from_object!(object, "label", Value::String(s) => s) + .map_err(|err| FetchError::SubrequestMalformedResponse { + service: service_name.to_string(), + reason: err.to_string(), + })? + .map(|s| s.as_str().to_string()) + .unwrap_or_default(); + let locations = extract_key_value_from_object!(object, "locations") + .map(serde_json_bytes::from_value) + .transpose() + .map_err(|err| FetchError::SubrequestMalformedResponse { + service: service_name.to_string(), + reason: err.to_string(), + })? + .unwrap_or_default(); + let path = extract_key_value_from_object!(object, "path") + .map(serde_json_bytes::from_value) + .transpose() + .map_err(|err| FetchError::SubrequestMalformedResponse { + service: service_name.to_string(), + reason: err.to_string(), + })?; + + Ok(Error { + message, + locations, + path, + extensions, + }) + } +} + /// A location in the request that triggered a graphql error. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] diff --git a/apollo-router-core/src/json_ext.rs b/apollo-router-core/src/json_ext.rs index db2c73e90d..11cb739655 100644 --- a/apollo-router-core/src/json_ext.rs +++ b/apollo-router-core/src/json_ext.rs @@ -1,13 +1,43 @@ use crate::prelude::graphql::*; use serde::{Deserialize, Serialize}; -use serde_json::map::Entry; -use serde_json::Map; -pub use serde_json::Value; +pub use serde_json_bytes::Value; +use serde_json_bytes::{ByteString, Entry, Map}; use std::cmp::min; use std::fmt; /// A JSON object. -pub type Object = Map; +pub type Object = Map; + +/// NOT PUBLIC API +#[doc(hidden)] +#[macro_export] +macro_rules! extract_key_value_from_object { + ($object:expr, $key:literal, $pattern:pat => $var:ident) => {{ + match $object.remove($key) { + Some($pattern) => Ok(Some($var)), + None | Some(Value::Null) => Ok(None), + _ => Err(concat!("invalid type for key: ", $key)), + } + }}; + ($object:expr, $key:literal) => {{ + match $object.remove($key) { + None | Some(Value::Null) => None, + Some(value) => Some(value), + } + }}; +} + +/// NOT PUBLIC API +#[doc(hidden)] +#[macro_export] +macro_rules! ensure_object { + ($value:expr) => {{ + match $value { + Value::Object(o) => Ok(o), + _ => Err("invalid type, expected an object"), + } + }}; +} #[doc(hidden)] /// Extension trait for [`serde_json::Value`]. @@ -188,13 +218,13 @@ impl ValueExt for Value { }, PathElement::Key(k) => { let mut m = Map::new(); - m.insert(k.to_string(), Value::default()); + m.insert(k.as_str(), Value::default()); *current_node = Value::Object(m); current_node = current_node .as_object_mut() .expect("current_node was just set to a Value::Object") - .get_mut(k) + .get_mut(k.as_str()) .expect("the value at that key was just inserted"); } } @@ -254,18 +284,18 @@ impl ValueExt for Value { PathElement::Key(k) => match current_node { Value::Object(o) => { current_node = o - .get_mut(k) + .get_mut(k.as_str()) .expect("the value at that key was just inserted"); } Value::Null => { let mut m = Map::new(); - m.insert(k.to_string(), Value::default()); + m.insert(k.as_str(), Value::default()); *current_node = Value::Object(m); current_node = current_node .as_object_mut() .expect("current_node was just set to a Value::Object") - .get_mut(k) + .get_mut(k.as_str()) .expect("the value at that key was just inserted"); } _other => { @@ -342,7 +372,7 @@ where } Some(PathElement::Key(k)) => { if let Value::Object(o) = data { - if let Some(value) = o.get(k) { + if let Some(value) = o.get(k.as_str()) { iterate_path(&parent.join(Path::from(k)), &path[1..], value, f) } else { Err(FetchError::ExecutionPathNotFound { @@ -520,7 +550,7 @@ impl fmt::Display for Path { #[cfg(test)] mod tests { use super::*; - use serde_json::json; + use serde_json_bytes::json; macro_rules! assert_is_subset { ($a:expr, $b:expr $(,)?) => { diff --git a/apollo-router-core/src/query_planner/mod.rs b/apollo-router-core/src/query_planner/mod.rs index bd379551f0..c60741fdcf 100644 --- a/apollo-router-core/src/query_planner/mod.rs +++ b/apollo-router-core/src/query_planner/mod.rs @@ -45,11 +45,8 @@ pub(crate) enum PlanNode { impl QueryPlan { /// Validate the entire request for variables and services used. - #[tracing::instrument(name = "validation", level = "debug", skip_all)] - pub fn validate_request( - &self, - service_registry: Arc, - ) -> Result<(), Response> { + #[tracing::instrument(skip_all, name = "validate", level = "debug")] + pub fn validate(&self, service_registry: Arc) -> Result<(), Response> { let mut early_errors = Vec::new(); for err in self .root @@ -262,8 +259,8 @@ mod fetch { variables.extend(variable_usages.iter().filter_map(|key| { request .variables - .get(key) - .map(|value| (key.clone(), value.clone())) + .get_key_value(key.as_str()) + .map(|(variable_key, value)| (variable_key.clone(), value.clone())) })); let mut paths = Vec::new(); @@ -286,7 +283,7 @@ mod fetch { }) .collect::, _>>()?, ); - variables.insert("representations".into(), representations); + variables.insert("representations", representations); Ok(Variables { variables, paths }) } else { @@ -296,8 +293,8 @@ mod fetch { .filter_map(|key| { request .variables - .get(key) - .map(|value| (key.clone(), value.clone())) + .get_key_value(key.as_str()) + .map(|(variable_key, value)| (variable_key.clone(), value.clone())) }) .collect::(), paths: Vec::new(), @@ -323,14 +320,16 @@ mod fetch { let query_span = tracing::info_span!("subfetch", service = service_name.as_str()); - let Variables { variables, paths } = Variables::new( - &self.requires, - self.variable_usages.as_ref(), - data, - current_dir, - request, - schema, - )?; + let Variables { variables, paths } = query_span.in_scope(|| { + Variables::new( + &self.requires, + self.variable_usages.as_ref(), + data, + current_dir, + request, + schema, + ) + })?; let fetcher = service_registry .get(service_name) @@ -343,16 +342,18 @@ mod fetch { .variables(Arc::new(variables)) .build(), ) - .instrument(query_span) + .instrument(tracing::info_span!(parent: &query_span, "subfetch_stream")) .await?; - if !response.is_primary() { - return Err(FetchError::SubrequestUnexpectedPatchResponse { - service: service_name.to_owned(), - }); - } + query_span.in_scope(|| { + if !response.is_primary() { + return Err(FetchError::SubrequestUnexpectedPatchResponse { + service: service_name.to_owned(), + }); + } - self.response_at_path(current_dir, paths, response) + self.response_at_path(current_dir, paths, response) + }) } #[instrument(level = "debug", name = "response_insert", skip_all)] diff --git a/apollo-router-core/src/query_planner/router_bridge_query_planner.rs b/apollo-router-core/src/query_planner/router_bridge_query_planner.rs index 222ee34bde..0d8c1569f1 100644 --- a/apollo-router-core/src/query_planner/router_bridge_query_planner.rs +++ b/apollo-router-core/src/query_planner/router_bridge_query_planner.rs @@ -23,7 +23,7 @@ impl RouterBridgeQueryPlanner { #[async_trait] impl QueryPlanner for RouterBridgeQueryPlanner { - #[tracing::instrument(name = "plan", level = "debug", skip_all)] + #[tracing::instrument(skip_all, name = "plan", level = "debug")] async fn get( &self, query: String, diff --git a/apollo-router-core/src/query_planner/selection.rs b/apollo-router-core/src/query_planner/selection.rs index 8fd3d84166..27f13312c1 100644 --- a/apollo-router-core/src/query_planner/selection.rs +++ b/apollo-router-core/src/query_planner/selection.rs @@ -1,6 +1,6 @@ use crate::prelude::graphql::*; use serde::Deserialize; -use serde_json::map::Entry; +use serde_json_bytes::Entry; /// A selection that is part of a fetch. /// Selections are used to propagate data to subgraph fetches. @@ -80,7 +80,7 @@ fn select_field( field: &Field, schema: &Schema, ) -> Result, FetchError> { - match (content.get(&field.name), &field.selections) { + match (content.get(field.name.as_str()), &field.selections) { (Some(Value::Object(child)), Some(selections)) => select_object(child, selections, schema), (Some(value), None) => Ok(Some(value.to_owned())), (None, _) => Err(FetchError::ExecutionFieldNotFound { @@ -97,7 +97,7 @@ fn select_inline_fragment( ) -> Result, FetchError> { match (&fragment.type_condition, &content.get("__typename")) { (Some(condition), Some(Value::String(typename))) => { - if condition == typename || schema.is_subtype(condition, typename) { + if condition == typename || schema.is_subtype(condition, typename.as_str()) { select_object(content, &fragment.selections, schema) } else { Ok(None) @@ -116,6 +116,7 @@ mod tests { use super::Selection; use super::*; use serde_json::json; + use serde_json_bytes::json as bjson; fn select<'a>( response: &Response, @@ -190,10 +191,10 @@ mod tests { assert_eq!( select!( "", - json!({"__typename": "User", "id":2, "name":"Bob", "job":{"name":"astronaut"}}), + bjson!({"__typename": "User", "id":2, "name":"Bob", "job":{"name":"astronaut"}}), ) .unwrap(), - json!([{ + bjson!([{ "__typename": "User", "id": 2, "job": { @@ -208,10 +209,10 @@ mod tests { assert_eq!( select!( "union User = Author | Reviewer", - json!({"__typename": "Author", "id":2, "name":"Bob", "job":{"name":"astronaut"}}), + bjson!({"__typename": "Author", "id":2, "name":"Bob", "job":{"name":"astronaut"}}), ) .unwrap(), - json!([{ + bjson!([{ "__typename": "Author", "id": 2, "job": { diff --git a/apollo-router-core/src/request.rs b/apollo-router-core/src/request.rs index 5778794ed5..9b71552474 100644 --- a/apollo-router-core/src/request.rs +++ b/apollo-router-core/src/request.rs @@ -1,4 +1,5 @@ use crate::prelude::graphql::*; +use bytes::Bytes; use derivative::Derivative; use serde::{Deserialize, Serialize}; use std::sync::Arc; @@ -44,34 +45,65 @@ where >::deserialize(deserializer).map(|x| x.unwrap_or_default()) } +impl Request { + pub fn from_bytes(b: Bytes) -> Result { + let value = Value::from_bytes(b)?; + let mut object = ensure_object!(value).map_err(serde::de::Error::custom)?; + + let variables = extract_key_value_from_object!(object, "variables", Value::Object(o) => o) + .map_err(serde::de::Error::custom)? + .unwrap_or_default(); + let extensions = + extract_key_value_from_object!(object, "extensions", Value::Object(o) => o) + .map_err(serde::de::Error::custom)? + .unwrap_or_default(); + let query = extract_key_value_from_object!(object, "query", Value::String(s) => s) + .map_err(serde::de::Error::custom)? + .map(|s| s.as_str().to_string()) + .unwrap_or_default(); + let operation_name = + extract_key_value_from_object!(object, "operation_name", Value::String(s) => s) + .map_err(serde::de::Error::custom)? + .map(|s| s.as_str().to_string()); + + Ok(Request { + query, + operation_name, + variables: Arc::new(variables), + extensions, + }) + } +} + #[cfg(test)] mod tests { use super::*; use serde_json::json; + use serde_json_bytes::json as bjson; use test_log::test; #[test] fn test_request() { - let result = serde_json::from_str::( - json!( - { - "query": "query aTest($arg1: String!) { test(who: $arg1) }", - "operationName": "aTest", - "variables": { "arg1": "me" }, - "extensions": {"extension": 1} - }) - .to_string() - .as_str(), - ); + let data = json!( + { + "query": "query aTest($arg1: String!) { test(who: $arg1) }", + "operationName": "aTest", + "variables": { "arg1": "me" }, + "extensions": {"extension": 1} + }) + .to_string(); + println!("data: {}", data); + let result = serde_json::from_str::(data.as_str()); + println!("result: {:?}", result); assert_eq!( result.unwrap(), Request::builder() .query("query aTest($arg1: String!) { test(who: $arg1) }".to_owned()) .operation_name(Some("aTest".to_owned())) .variables(Arc::new( - json!({ "arg1": "me" }).as_object().unwrap().clone() + bjson!({ "arg1": "me" }).as_object().unwrap().clone() )) - .extensions(json!({"extension": 1}).as_object().cloned().unwrap()) + .extensions(bjson!({"extension": 1}).as_object().cloned().unwrap()) .build() ); } @@ -93,7 +125,7 @@ mod tests { Request::builder() .query("query aTest($arg1: String!) { test(who: $arg1) }".to_owned()) .operation_name(Some("aTest".to_owned())) - .extensions(json!({"extension": 1}).as_object().cloned().unwrap()) + .extensions(bjson!({"extension": 1}).as_object().cloned().unwrap()) .build() ); } @@ -118,7 +150,7 @@ mod tests { Request::builder() .query("query aTest($arg1: String!) { test(who: $arg1) }".to_owned()) .operation_name(Some("aTest".to_owned())) - .extensions(json!({"extension": 1}).as_object().cloned().unwrap()) + .extensions(bjson!({"extension": 1}).as_object().cloned().unwrap()) .build() ); } diff --git a/apollo-router-core/src/response.rs b/apollo-router-core/src/response.rs index b253d17bec..3c71e01260 100644 --- a/apollo-router-core/src/response.rs +++ b/apollo-router-core/src/response.rs @@ -1,4 +1,5 @@ use crate::prelude::graphql::*; +use bytes::Bytes; use serde::{Deserialize, Serialize}; use typed_builder::TypedBuilder; @@ -56,12 +57,71 @@ impl Response { pub fn append_errors(&mut self, errors: &mut Vec) { self.errors.append(errors) } + + pub fn from_bytes(service_name: &str, b: Bytes) -> Result { + let value = + Value::from_bytes(b).map_err(|error| FetchError::SubrequestMalformedResponse { + service: service_name.to_string(), + reason: error.to_string(), + })?; + let mut object = + ensure_object!(value).map_err(|error| FetchError::SubrequestMalformedResponse { + service: service_name.to_string(), + reason: error.to_string(), + })?; + + let data = extract_key_value_from_object!(object, "data").unwrap_or_default(); + let errors = extract_key_value_from_object!(object, "errors", Value::Array(v) => v) + .map_err(|err| FetchError::SubrequestMalformedResponse { + service: service_name.to_string(), + reason: err.to_string(), + })? + .into_iter() + .flatten() + .map(|v| Error::from_value(service_name, v)) + .collect::, FetchError>>()?; + let extensions = + extract_key_value_from_object!(object, "extensions", Value::Object(o) => o) + .map_err(|err| FetchError::SubrequestMalformedResponse { + service: service_name.to_string(), + reason: err.to_string(), + })? + .unwrap_or_default(); + let label = extract_key_value_from_object!(object, "label", Value::String(s) => s) + .map_err(|err| FetchError::SubrequestMalformedResponse { + service: service_name.to_string(), + reason: err.to_string(), + })? + .map(|s| s.as_str().to_string()); + let path = extract_key_value_from_object!(object, "path") + .map(serde_json_bytes::from_value) + .transpose() + .map_err(|err| FetchError::SubrequestMalformedResponse { + service: service_name.to_string(), + reason: err.to_string(), + })?; + let has_next = extract_key_value_from_object!(object, "has_next", Value::Bool(b) => b) + .map_err(|err| FetchError::SubrequestMalformedResponse { + service: service_name.to_string(), + reason: err.to_string(), + })?; + + Ok(Response { + label, + data, + path, + has_next, + errors, + extensions, + }) + } } #[cfg(test)] mod tests { use super::*; use serde_json::json; + use serde_json_bytes::json as bjson; #[test] fn test_append_errors_path_fallback_and_override() { @@ -161,7 +221,7 @@ mod tests { message: "Name for character with ID 1002 could not be fetched.".into(), locations: vec!(Location { line: 6, column: 7 }), path: Some(Path::from("hero/heroFriends/1/name")), - extensions: json!({ + extensions: bjson!({ "error-extension": 5, }) .as_object() @@ -169,7 +229,7 @@ mod tests { .unwrap() }]) .extensions( - json!({ + bjson!({ "response-extension": 3, }) .as_object() @@ -253,7 +313,7 @@ mod tests { message: "Name for character with ID 1002 could not be fetched.".into(), locations: vec!(Location { line: 6, column: 7 }), path: Some(Path::from("hero/heroFriends/1/name")), - extensions: json!({ + extensions: bjson!({ "error-extension": 5, }) .as_object() @@ -261,7 +321,7 @@ mod tests { .unwrap() }]) .extensions( - json!({ + bjson!({ "response-extension": 3, }) .as_object() diff --git a/apollo-router-core/src/spec/field_type.rs b/apollo-router-core/src/spec/field_type.rs index f6323de83f..a917d424aa 100644 --- a/apollo-router-core/src/spec/field_type.rs +++ b/apollo-router-core/src/spec/field_type.rs @@ -26,7 +26,7 @@ impl FieldType { match (self, value) { // Type coercion from string to Int, Float or Boolean (FieldType::Int | FieldType::Float | FieldType::Boolean, Value::String(s)) => { - if let Ok(value) = serde_json::from_str::(s) { + if let Ok(value) = Value::from_bytes(s.inner().clone()) { self.validate_value(&value, schema) } else { Err(InvalidValue) diff --git a/apollo-router-core/src/spec/query.rs b/apollo-router-core/src/spec/query.rs index 521366b382..f1b415aae1 100644 --- a/apollo-router-core/src/spec/query.rs +++ b/apollo-router-core/src/spec/query.rs @@ -139,7 +139,7 @@ impl Query { name, selection_set, } => { - if let Some((field_name, input_value)) = input.remove_entry(name) { + if let Some((field_name, input_value)) = input.remove_entry(name.as_str()) { if let Some(selection_set) = selection_set { match input_value { Value::Object(mut input_object) => { @@ -220,14 +220,18 @@ impl Query { .iter() .fold(HashMap::new(), |mut acc, operation| { if operation_name.is_none() || operation.name.as_deref() == operation_name { - acc.extend(operation.variables.iter()) + acc.extend(operation.variables.iter().map(|(k, v)| (k.as_str(), v))) } acc }); if LevelFilter::current() >= LevelFilter::DEBUG { let known_variables = operation_variable_types.keys().cloned().collect(); - let provided_variables = request.variables.keys().collect::>(); + let provided_variables = request + .variables + .keys() + .map(|k| k.as_str()) + .collect::>(); let unknown_variables = provided_variables .difference(&known_variables) .collect::>(); @@ -242,7 +246,7 @@ impl Query { let errors = operation_variable_types .iter() .filter_map(|(name, ty)| { - let value = request.variables.get(name.as_str()).unwrap_or(&Value::Null); + let value = request.variables.get(*name).unwrap_or(&Value::Null); ty.validate_value(value, schema).err().map(|_| { FetchError::ValidationInvalidTypeVariable { name: name.to_string(), @@ -335,7 +339,7 @@ impl From for OperationType { #[cfg(test)] mod tests { use super::*; - use serde_json::json; + use serde_json_bytes::json; use test_log::test; macro_rules! assert_eq_and_ordered { diff --git a/apollo-router-core/src/spec/schema.rs b/apollo-router-core/src/spec/schema.rs index d98908f765..81852a4175 100644 --- a/apollo-router-core/src/spec/schema.rs +++ b/apollo-router-core/src/spec/schema.rs @@ -296,7 +296,7 @@ macro_rules! implement_object_type_or_interface { .fields .iter() .try_for_each(|(name, ty)| { - let value = object.get(name).unwrap_or(&Value::Null); + let value = object.get(name.as_str()).unwrap_or(&Value::Null); ty.validate_value(value, schema) }) .map_err(|_| InvalidObject)?; diff --git a/apollo-router/Cargo.toml b/apollo-router/Cargo.toml index dfdec5eb67..e8f5fb9195 100644 --- a/apollo-router/Cargo.toml +++ b/apollo-router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "apollo-router" -version = "0.1.0-alpha.2" +version = "0.1.0-alpha.3" authors = ["Apollo Graph, Inc. "] edition = "2021" license-file = "./LICENSE" @@ -44,7 +44,7 @@ opentelemetry-jaeger = { version = "0.15.0", features = [ opentelemetry-otlp = { version = "0.9.0", default-features = false, features = [ "serialize", ], optional = true } -reqwest = { version = "0.11.8", features = ["json", "stream"] } +reqwest = { version = "0.11.9", features = ["json", "stream"] } reqwest-middleware = "0.1.3" reqwest-tracing = { version = "0.2", features = ["opentelemetry_0_16"] } serde = { version = "1.0.133", features = ["derive", "rc"] } @@ -57,7 +57,7 @@ tokio = { version = "1.15.0", features = ["full"] } # don't bump it to 0.6 until opentelemetry-otlp does tonic = { version = "0.5.2", optional = true } tower = "0.4.11" -tower-http = {version = "0.2.0", features = ["trace"] } +tower-http = { version = "0.2.0", features = ["trace"] } tracing = "0.1.29" tracing-futures = "0.2.5" tracing-opentelemetry = "0.16.0" @@ -70,12 +70,14 @@ warp = { version = "0.3.2", default-features = false, features = [ ] } [dev-dependencies] -httpmock = "0.6.5" +httpmock = "0.6.6" insta = "1.10.0" maplit = "1.0.2" mockall = "0.11.0" -reqwest = { version = "0.11.8", features = ["json", "stream"] } +reqwest = { version = "0.11.9", features = ["json", "stream"] } +serde_json_bytes = { version = "0.2.0", features = ["preserve_order"] } test-log = { version = "0.2.8", default-features = false, features = ["trace"] } +test-span = "0.1.1" tracing-subscriber = { version = "0.3", default-features = false, features = [ "env-filter", "fmt", diff --git a/apollo-router/src/apollo_router.rs b/apollo-router/src/apollo_router.rs index 39270ee29a..cdfb88d95c 100644 --- a/apollo-router/src/apollo_router.rs +++ b/apollo-router/src/apollo_router.rs @@ -98,7 +98,7 @@ impl Router for ApolloRouter { .await?; tracing::debug!("query plan\n{:#?}", query_plan); - query_plan.validate_request(Arc::clone(&self.service_registry))?; + query_plan.validate(Arc::clone(&self.service_registry))?; Ok(ApolloPreparedQuery { query_plan, diff --git a/apollo-router/src/http_subgraph.rs b/apollo-router/src/http_subgraph.rs index b759c5d81e..01a6d16afc 100644 --- a/apollo-router/src/http_subgraph.rs +++ b/apollo-router/src/http_subgraph.rs @@ -56,7 +56,7 @@ impl HttpSubgraphFetcher { .instrument(tracing::trace_span!("http-subgraph-request")) .await .map_err(|err| { - tracing::error!(fetch_error = format!("{:?}", err).as_str()); + tracing::error!(fetch_error = err.to_string().as_str()); graphql::FetchError::SubrequestHttpError { service: self.service.to_owned(), @@ -83,7 +83,7 @@ impl HttpSubgraphFetcher { response: bytes::Bytes, ) -> Result { tracing::debug_span!("parse_subgraph_response").in_scope(|| { - serde_json::from_slice::(&response).map_err(|error| { + graphql::Response::from_bytes(&service_name, response).map_err(|error| { graphql::FetchError::SubrequestMalformedResponse { service: service_name.clone(), reason: error.to_string(), diff --git a/apollo-router/src/warp_http_server_factory.rs b/apollo-router/src/warp_http_server_factory.rs index 7896d759da..6cca36b987 100644 --- a/apollo-router/src/warp_http_server_factory.rs +++ b/apollo-router/src/warp_http_server_factory.rs @@ -1,7 +1,7 @@ use crate::configuration::{Configuration, Cors}; use crate::http_server_factory::{HttpServerFactory, HttpServerHandle}; use crate::FederatedServerError; -use apollo_router_core::prelude::*; +use apollo_router_core::{prelude::*, Request}; use bytes::Bytes; use futures::{channel::oneshot, prelude::*}; use hyper::server::conn::Http; @@ -200,7 +200,7 @@ where async move { let reply: Box = if accept.map(prefers_html).unwrap_or_default() { redirect_to_studio(host) - } else if let Ok(request) = serde_json::from_slice(&body) { + } else if let Ok(request) = Request::from_bytes(body) { run_graphql_request(router, request, header_map).await } else { Box::new(warp::reply::with_status( diff --git a/apollo-router/tests/integration_tests.rs b/apollo-router/tests/integration_tests.rs index e50b32d4c7..ea7560d0cb 100644 --- a/apollo-router/tests/integration_tests.rs +++ b/apollo-router/tests/integration_tests.rs @@ -9,7 +9,7 @@ use serde_json::to_string_pretty; use std::collections::hash_map::Entry; use std::collections::HashMap; use std::sync::{Arc, Mutex}; -use test_log::test; +use test_span::prelude::*; use url::Url; macro_rules! assert_federated_response { @@ -18,8 +18,8 @@ macro_rules! assert_federated_response { .query($query) .variables(Arc::new( vec![ - ("topProductsFirst".to_string(), 2.into()), - ("reviewsForAuthorAuthorId".to_string(), 1.into()), + ("topProductsFirst".into(), 2.into()), + ("reviewsForAuthorAuthorId".into(), 1.into()), ] .into_iter() .collect(), @@ -43,7 +43,7 @@ macro_rules! assert_federated_response { }; } -#[test(tokio::test)] +#[tokio::test] async fn basic_request() { assert_federated_response!( r#"{ topProducts { name name2:name } }"#, @@ -53,7 +53,7 @@ async fn basic_request() { ); } -#[test(tokio::test)] +#[tokio::test] async fn basic_composition() { assert_federated_response!( r#"{ topProducts { upc name reviews {id product { name } author { id name } } } }"#, @@ -65,7 +65,31 @@ async fn basic_composition() { ); } -#[test(tokio::test)] +#[test_span(tokio::test)] +async fn traced_basic_request() { + assert_federated_response!( + r#"{ topProducts { name name2:name } }"#, + hashmap! { + "products".to_string()=>1, + }, + ); + insta::assert_json_snapshot!(get_spans()); +} + +#[test_span(tokio::test)] +async fn traced_basic_composition() { + assert_federated_response!( + r#"{ topProducts { upc name reviews {id product { name } author { id name } } } }"#, + hashmap! { + "products".to_string()=>2, + "reviews".to_string()=>1, + "accounts".to_string()=>1, + }, + ); + insta::assert_json_snapshot!(get_spans()); +} + +#[tokio::test] async fn basic_mutation() { assert_federated_response!( r#"mutation { @@ -88,7 +112,7 @@ async fn basic_mutation() { ); } -#[test(tokio::test)] +#[test_span(tokio::test)] async fn variables() { assert_federated_response!( r#" @@ -113,7 +137,7 @@ async fn variables() { ); } -#[test(tokio::test)] +#[tokio::test] async fn missing_variables() { let request = graphql::Request::builder() .query( diff --git a/apollo-router/tests/snapshots/integration_tests__traced_basic_composition.snap b/apollo-router/tests/snapshots/integration_tests__traced_basic_composition.snap new file mode 100644 index 0000000000..e5f71e09e8 --- /dev/null +++ b/apollo-router/tests/snapshots/integration_tests__traced_basic_composition.snap @@ -0,0 +1,363 @@ +--- +source: apollo-router/tests/integration_tests.rs +assertion_line: 89 +expression: get_spans() + +--- +{ + "name": "integration_tests::root", + "record": { + "entries": [], + "metadata": { + "name": "root", + "target": "integration_tests", + "level": "DEBUG", + "module_path": "integration_tests", + "fields": { + "names": [] + } + } + }, + "children": { + "apollo_router::apollo_router::prepare_query - 1": { + "name": "apollo_router::apollo_router::prepare_query", + "record": { + "entries": [ + [ + "message", + "query plan\nQueryPlan {\n root: Sequence {\n nodes: [\n Fetch(\n FetchNode {\n service_name: \"products\",\n requires: [],\n variable_usages: [],\n operation: \"{topProducts{__typename upc name}}\",\n },\n ),\n Flatten(\n FlattenNode {\n path: Path(\n [\n Key(\n \"topProducts\",\n ),\n Flatten,\n ],\n ),\n node: Fetch(\n FetchNode {\n service_name: \"reviews\",\n requires: [\n InlineFragment(\n InlineFragment {\n type_condition: Some(\n \"Product\",\n ),\n selections: [\n Field(\n Field {\n alias: None,\n name: \"__typename\",\n selections: None,\n },\n ),\n Field(\n Field {\n alias: None,\n name: \"upc\",\n selections: None,\n },\n ),\n ],\n },\n ),\n ],\n variable_usages: [],\n operation: \"query($representations:[_Any!]!){_entities(representations:$representations){...on Product{reviews{id product{__typename upc}author{__typename id}}}}}\",\n },\n ),\n },\n ),\n Parallel {\n nodes: [\n Flatten(\n FlattenNode {\n path: Path(\n [\n Key(\n \"topProducts\",\n ),\n Flatten,\n Key(\n \"reviews\",\n ),\n Flatten,\n Key(\n \"product\",\n ),\n ],\n ),\n node: Fetch(\n FetchNode {\n service_name: \"products\",\n requires: [\n InlineFragment(\n InlineFragment {\n type_condition: Some(\n \"Product\",\n ),\n selections: [\n Field(\n Field {\n alias: None,\n name: \"__typename\",\n selections: None,\n },\n ),\n Field(\n Field {\n alias: None,\n name: \"upc\",\n selections: None,\n },\n ),\n ],\n },\n ),\n ],\n variable_usages: [],\n operation: \"query($representations:[_Any!]!){_entities(representations:$representations){...on Product{name}}}\",\n },\n ),\n },\n ),\n Flatten(\n FlattenNode {\n path: Path(\n [\n Key(\n \"topProducts\",\n ),\n Flatten,\n Key(\n \"reviews\",\n ),\n Flatten,\n Key(\n \"author\",\n ),\n ],\n ),\n node: Fetch(\n FetchNode {\n service_name: \"accounts\",\n requires: [\n InlineFragment(\n InlineFragment {\n type_condition: Some(\n \"User\",\n ),\n selections: [\n Field(\n Field {\n alias: None,\n name: \"__typename\",\n selections: None,\n },\n ),\n Field(\n Field {\n alias: None,\n name: \"id\",\n selections: None,\n },\n ),\n ],\n },\n ),\n ],\n variable_usages: [],\n operation: \"query($representations:[_Any!]!){_entities(representations:$representations){...on User{name}}}\",\n },\n ),\n },\n ),\n ],\n },\n ],\n },\n}" + ] + ], + "metadata": { + "name": "prepare_query", + "target": "apollo_router::apollo_router", + "level": "DEBUG", + "module_path": "apollo_router::apollo_router", + "fields": { + "names": [] + } + } + }, + "children": { + "apollo_router::apollo_router::query_parsing - 2": { + "name": "apollo_router::apollo_router::query_parsing", + "record": { + "entries": [], + "metadata": { + "name": "query_parsing", + "target": "apollo_router::apollo_router", + "level": "INFO", + "module_path": "apollo_router::apollo_router", + "fields": { + "names": [] + } + } + }, + "children": {} + }, + "apollo_router_core::query_planner::router_bridge_query_planner::plan - 4": { + "name": "apollo_router_core::query_planner::router_bridge_query_planner::plan", + "record": { + "entries": [], + "metadata": { + "name": "plan", + "target": "apollo_router_core::query_planner::router_bridge_query_planner", + "level": "DEBUG", + "module_path": "apollo_router_core::query_planner::router_bridge_query_planner", + "fields": { + "names": [] + } + } + }, + "children": {} + }, + "apollo_router_core::query_planner::validate - 5": { + "name": "apollo_router_core::query_planner::validate", + "record": { + "entries": [], + "metadata": { + "name": "validate", + "target": "apollo_router_core::query_planner", + "level": "DEBUG", + "module_path": "apollo_router_core::query_planner", + "fields": { + "names": [] + } + } + }, + "children": {} + } + } + }, + "apollo_router::apollo_router::execute - 6": { + "name": "apollo_router::apollo_router::execute", + "record": { + "entries": [], + "metadata": { + "name": "execute", + "target": "apollo_router::apollo_router", + "level": "DEBUG", + "module_path": "apollo_router::apollo_router", + "fields": { + "names": [] + } + } + }, + "children": { + "apollo_router::apollo_router::execution - 7": { + "name": "apollo_router::apollo_router::execution", + "record": { + "entries": [], + "metadata": { + "name": "execution", + "target": "apollo_router::apollo_router", + "level": "INFO", + "module_path": "apollo_router::apollo_router", + "fields": { + "names": [] + } + } + }, + "children": { + "apollo_router_core::query_planner::sequence - 8": { + "name": "apollo_router_core::query_planner::sequence", + "record": { + "entries": [], + "metadata": { + "name": "sequence", + "target": "apollo_router_core::query_planner", + "level": "INFO", + "module_path": "apollo_router_core::query_planner", + "fields": { + "names": [] + } + } + }, + "children": { + "apollo_router_core::query_planner::fetch - 9": { + "name": "apollo_router_core::query_planner::fetch", + "record": { + "entries": [], + "metadata": { + "name": "fetch", + "target": "apollo_router_core::query_planner", + "level": "INFO", + "module_path": "apollo_router_core::query_planner", + "fields": { + "names": [] + } + } + }, + "children": { + "apollo_router_core::query_planner::fetch::subfetch - 10": { + "name": "apollo_router_core::query_planner::fetch::subfetch", + "record": { + "entries": [ + [ + "service", + "products" + ] + ], + "metadata": { + "name": "subfetch", + "target": "apollo_router_core::query_planner::fetch", + "level": "INFO", + "module_path": "apollo_router_core::query_planner::fetch", + "fields": { + "names": [ + "service" + ] + } + } + }, + "children": { + "apollo_router_core::query_planner::fetch::make_variables - 11": { + "name": "apollo_router_core::query_planner::fetch::make_variables", + "record": { + "entries": [], + "metadata": { + "name": "make_variables", + "target": "apollo_router_core::query_planner::fetch", + "level": "DEBUG", + "module_path": "apollo_router_core::query_planner::fetch", + "fields": { + "names": [] + } + } + }, + "children": {} + }, + "apollo_router_core::query_planner::fetch::subfetch_stream - 12": { + "name": "apollo_router_core::query_planner::fetch::subfetch_stream", + "record": { + "entries": [], + "metadata": { + "name": "subfetch_stream", + "target": "apollo_router_core::query_planner::fetch", + "level": "INFO", + "module_path": "apollo_router_core::query_planner::fetch", + "fields": { + "names": [] + } + } + }, + "children": { + "apollo_router::http_subgraph::aggregate_response_data - 15": { + "name": "apollo_router::http_subgraph::aggregate_response_data", + "record": { + "entries": [], + "metadata": { + "name": "aggregate_response_data", + "target": "apollo_router::http_subgraph", + "level": "DEBUG", + "module_path": "apollo_router::http_subgraph", + "fields": { + "names": [] + } + } + }, + "children": {} + }, + "apollo_router::http_subgraph::parse_subgraph_response - 16": { + "name": "apollo_router::http_subgraph::parse_subgraph_response", + "record": { + "entries": [], + "metadata": { + "name": "parse_subgraph_response", + "target": "apollo_router::http_subgraph", + "level": "DEBUG", + "module_path": "apollo_router::http_subgraph", + "fields": { + "names": [] + } + } + }, + "children": {} + } + } + }, + "apollo_router_core::query_planner::fetch::response_insert - 17": { + "name": "apollo_router_core::query_planner::fetch::response_insert", + "record": { + "entries": [], + "metadata": { + "name": "response_insert", + "target": "apollo_router_core::query_planner::fetch", + "level": "DEBUG", + "module_path": "apollo_router_core::query_planner::fetch", + "fields": { + "names": [] + } + } + }, + "children": {} + } + } + } + } + } + } + }, + "apollo_router_core::query_planner::sequence - 18": { + "name": "apollo_router_core::query_planner::sequence", + "record": { + "entries": [], + "metadata": { + "name": "sequence", + "target": "apollo_router_core::query_planner", + "level": "INFO", + "module_path": "apollo_router_core::query_planner", + "fields": { + "names": [] + } + } + }, + "children": {} + }, + "apollo_router_core::query_planner::sequence - 29": { + "name": "apollo_router_core::query_planner::sequence", + "record": { + "entries": [], + "metadata": { + "name": "sequence", + "target": "apollo_router_core::query_planner", + "level": "INFO", + "module_path": "apollo_router_core::query_planner", + "fields": { + "names": [] + } + } + }, + "children": { + "apollo_router_core::query_planner::parallel - 30": { + "name": "apollo_router_core::query_planner::parallel", + "record": { + "entries": [], + "metadata": { + "name": "parallel", + "target": "apollo_router_core::query_planner", + "level": "INFO", + "module_path": "apollo_router_core::query_planner", + "fields": { + "names": [] + } + } + }, + "children": {} + } + } + } + } + }, + "apollo_router::apollo_router::format_response - 51": { + "name": "apollo_router::apollo_router::format_response", + "record": { + "entries": [], + "metadata": { + "name": "format_response", + "target": "apollo_router::apollo_router", + "level": "DEBUG", + "module_path": "apollo_router::apollo_router", + "fields": { + "names": [] + } + } + }, + "children": {} + } + } + }, + "apollo_router::http_subgraph::aggregate_response_data - 55": { + "name": "apollo_router::http_subgraph::aggregate_response_data", + "record": { + "entries": [], + "metadata": { + "name": "aggregate_response_data", + "target": "apollo_router::http_subgraph", + "level": "DEBUG", + "module_path": "apollo_router::http_subgraph", + "fields": { + "names": [] + } + } + }, + "children": {} + }, + "apollo_router::http_subgraph::parse_subgraph_response - 56": { + "name": "apollo_router::http_subgraph::parse_subgraph_response", + "record": { + "entries": [], + "metadata": { + "name": "parse_subgraph_response", + "target": "apollo_router::http_subgraph", + "level": "DEBUG", + "module_path": "apollo_router::http_subgraph", + "fields": { + "names": [] + } + } + }, + "children": {} + } + } +} diff --git a/apollo-router/tests/snapshots/integration_tests__traced_basic_request.snap b/apollo-router/tests/snapshots/integration_tests__traced_basic_request.snap new file mode 100644 index 0000000000..9b794e1f3a --- /dev/null +++ b/apollo-router/tests/snapshots/integration_tests__traced_basic_request.snap @@ -0,0 +1,297 @@ +--- +source: apollo-router/tests/integration_tests.rs +assertion_line: 76 +expression: get_spans() + +--- +{ + "name": "integration_tests::root", + "record": { + "entries": [], + "metadata": { + "name": "root", + "target": "integration_tests", + "level": "DEBUG", + "module_path": "integration_tests", + "fields": { + "names": [] + } + } + }, + "children": { + "apollo_router::apollo_router::prepare_query - 1": { + "name": "apollo_router::apollo_router::prepare_query", + "record": { + "entries": [ + [ + "message", + "query plan\nQueryPlan {\n root: Fetch(\n FetchNode {\n service_name: \"products\",\n requires: [],\n variable_usages: [],\n operation: \"{topProducts{name name2:name}}\",\n },\n ),\n}" + ] + ], + "metadata": { + "name": "prepare_query", + "target": "apollo_router::apollo_router", + "level": "DEBUG", + "module_path": "apollo_router::apollo_router", + "fields": { + "names": [] + } + } + }, + "children": { + "apollo_router::apollo_router::query_parsing - 2": { + "name": "apollo_router::apollo_router::query_parsing", + "record": { + "entries": [], + "metadata": { + "name": "query_parsing", + "target": "apollo_router::apollo_router", + "level": "INFO", + "module_path": "apollo_router::apollo_router", + "fields": { + "names": [] + } + } + }, + "children": {} + }, + "apollo_router_core::query_planner::router_bridge_query_planner::plan - 4": { + "name": "apollo_router_core::query_planner::router_bridge_query_planner::plan", + "record": { + "entries": [], + "metadata": { + "name": "plan", + "target": "apollo_router_core::query_planner::router_bridge_query_planner", + "level": "DEBUG", + "module_path": "apollo_router_core::query_planner::router_bridge_query_planner", + "fields": { + "names": [] + } + } + }, + "children": {} + }, + "apollo_router_core::query_planner::validate - 5": { + "name": "apollo_router_core::query_planner::validate", + "record": { + "entries": [], + "metadata": { + "name": "validate", + "target": "apollo_router_core::query_planner", + "level": "DEBUG", + "module_path": "apollo_router_core::query_planner", + "fields": { + "names": [] + } + } + }, + "children": {} + } + } + }, + "apollo_router::apollo_router::execute - 6": { + "name": "apollo_router::apollo_router::execute", + "record": { + "entries": [], + "metadata": { + "name": "execute", + "target": "apollo_router::apollo_router", + "level": "DEBUG", + "module_path": "apollo_router::apollo_router", + "fields": { + "names": [] + } + } + }, + "children": { + "apollo_router::apollo_router::execution - 7": { + "name": "apollo_router::apollo_router::execution", + "record": { + "entries": [], + "metadata": { + "name": "execution", + "target": "apollo_router::apollo_router", + "level": "INFO", + "module_path": "apollo_router::apollo_router", + "fields": { + "names": [] + } + } + }, + "children": { + "apollo_router_core::query_planner::fetch - 8": { + "name": "apollo_router_core::query_planner::fetch", + "record": { + "entries": [], + "metadata": { + "name": "fetch", + "target": "apollo_router_core::query_planner", + "level": "INFO", + "module_path": "apollo_router_core::query_planner", + "fields": { + "names": [] + } + } + }, + "children": { + "apollo_router_core::query_planner::fetch::subfetch - 9": { + "name": "apollo_router_core::query_planner::fetch::subfetch", + "record": { + "entries": [ + [ + "service", + "products" + ] + ], + "metadata": { + "name": "subfetch", + "target": "apollo_router_core::query_planner::fetch", + "level": "INFO", + "module_path": "apollo_router_core::query_planner::fetch", + "fields": { + "names": [ + "service" + ] + } + } + }, + "children": { + "apollo_router_core::query_planner::fetch::make_variables - 10": { + "name": "apollo_router_core::query_planner::fetch::make_variables", + "record": { + "entries": [], + "metadata": { + "name": "make_variables", + "target": "apollo_router_core::query_planner::fetch", + "level": "DEBUG", + "module_path": "apollo_router_core::query_planner::fetch", + "fields": { + "names": [] + } + } + }, + "children": {} + }, + "apollo_router_core::query_planner::fetch::subfetch_stream - 11": { + "name": "apollo_router_core::query_planner::fetch::subfetch_stream", + "record": { + "entries": [], + "metadata": { + "name": "subfetch_stream", + "target": "apollo_router_core::query_planner::fetch", + "level": "INFO", + "module_path": "apollo_router_core::query_planner::fetch", + "fields": { + "names": [] + } + } + }, + "children": { + "apollo_router::http_subgraph::aggregate_response_data - 14": { + "name": "apollo_router::http_subgraph::aggregate_response_data", + "record": { + "entries": [], + "metadata": { + "name": "aggregate_response_data", + "target": "apollo_router::http_subgraph", + "level": "DEBUG", + "module_path": "apollo_router::http_subgraph", + "fields": { + "names": [] + } + } + }, + "children": {} + }, + "apollo_router::http_subgraph::parse_subgraph_response - 15": { + "name": "apollo_router::http_subgraph::parse_subgraph_response", + "record": { + "entries": [], + "metadata": { + "name": "parse_subgraph_response", + "target": "apollo_router::http_subgraph", + "level": "DEBUG", + "module_path": "apollo_router::http_subgraph", + "fields": { + "names": [] + } + } + }, + "children": {} + } + } + }, + "apollo_router_core::query_planner::fetch::response_insert - 16": { + "name": "apollo_router_core::query_planner::fetch::response_insert", + "record": { + "entries": [], + "metadata": { + "name": "response_insert", + "target": "apollo_router_core::query_planner::fetch", + "level": "DEBUG", + "module_path": "apollo_router_core::query_planner::fetch", + "fields": { + "names": [] + } + } + }, + "children": {} + } + } + } + } + } + } + }, + "apollo_router::apollo_router::format_response - 17": { + "name": "apollo_router::apollo_router::format_response", + "record": { + "entries": [], + "metadata": { + "name": "format_response", + "target": "apollo_router::apollo_router", + "level": "DEBUG", + "module_path": "apollo_router::apollo_router", + "fields": { + "names": [] + } + } + }, + "children": {} + } + } + }, + "apollo_router::http_subgraph::aggregate_response_data - 21": { + "name": "apollo_router::http_subgraph::aggregate_response_data", + "record": { + "entries": [], + "metadata": { + "name": "aggregate_response_data", + "target": "apollo_router::http_subgraph", + "level": "DEBUG", + "module_path": "apollo_router::http_subgraph", + "fields": { + "names": [] + } + } + }, + "children": {} + }, + "apollo_router::http_subgraph::parse_subgraph_response - 22": { + "name": "apollo_router::http_subgraph::parse_subgraph_response", + "record": { + "entries": [], + "metadata": { + "name": "parse_subgraph_response", + "target": "apollo_router::http_subgraph", + "level": "DEBUG", + "module_path": "apollo_router::http_subgraph", + "fields": { + "names": [] + } + } + }, + "children": {} + } + } +} diff --git a/deny.toml b/deny.toml index 1ab686bb0a..9755302560 100644 --- a/deny.toml +++ b/deny.toml @@ -29,7 +29,8 @@ ignore = ["RUSTSEC-2020-0159"] # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] # The lint level for crates which do not have a detectable license -unlicensed = "deny" +# TODO[igni]: remove this once the span / macro are fleshed out +unlicensed = "warn" # List of explictly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. @@ -62,13 +63,13 @@ license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] [[licenses.clarify]] name = "apollo-router" expression = "LicenseRef-ELv2" -version = "0.1.0-alpha.2" +version = "0.1.0-alpha.3" license-files = [{ path = "LICENSE", hash = 0xaceadac9 }] [[licenses.clarify]] name = "apollo-router-core" expression = "LicenseRef-ELv2" -version = "0.1.0-alpha.2" +version = "0.1.0-alpha.3" license-files = [{ path = "LICENSE", hash = 0xaceadac9 }] [[licenses.clarify]] diff --git a/docs/package-lock.json b/docs/package-lock.json index e4c504e4ea..a28569ebbf 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -6850,11 +6850,6 @@ "domelementtype": "^2.2.0" } }, - "dompurify": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.3.tgz", - "integrity": "sha512-dqnqRkPMAjOZE0FogZ+ceJNM2dZ3V/yNOuFB7+39qpO93hHhfRpHw3heYQC7DPK9FqbQTfBKUJhiSfz4MvXYwg==" - }, "domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", @@ -7032,9 +7027,9 @@ } }, "engine.io": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.1.1.tgz", - "integrity": "sha512-t2E9wLlssQjGw0nluF6aYyfX8LwYU8Jj0xct+pAhfWfv/YrBn6TSNtEYsgxHIfaMqfrLx07czcMg9bMN6di+3w==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.1.2.tgz", + "integrity": "sha512-t5z6zjXuVLhXDMiFJPYsPOWEER8B0tIsD3ETgw19S1yg9zryvUfY3Vhtk3Gf4sihw/bQGIqQ//gjvVlu+Ca0bQ==", "requires": { "accepts": "~1.3.4", "base64id": "2.0.0", @@ -8473,9 +8468,9 @@ } }, "follow-redirects": { - "version": "1.14.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz", - "integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==" + "version": "1.14.7", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", + "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==" }, "for-each": { "version": "0.3.3", @@ -10567,11 +10562,11 @@ } }, "google-p12-pem": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.2.tgz", - "integrity": "sha512-tjf3IQIt7tWCDsa0ofDQ1qqSCNzahXDxdAGJDbruWqu3eCg5CKLYKN+hi0s6lfvzYZ1GDVr+oDF9OOWlDSdf0A==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.3.tgz", + "integrity": "sha512-MC0jISvzymxePDVembypNefkAQp+DRP7dBE+zNUPaIjEspIlYg0++OrsNr248V9tPbz6iqtZ7rX1hxWA5B8qBQ==", "requires": { - "node-forge": "^0.10.0" + "node-forge": "^1.0.0" } }, "googleapis": { @@ -13521,19 +13516,26 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, "mermaid": { - "version": "8.13.3", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.13.3.tgz", - "integrity": "sha512-w6KmDtSzkk856WUVqlBsyLZX0q4Jr35IlxiHTPTaWwMgWHFpI8rEJzcxWoyrpxeT/Rac/vvvSFOZymDTeA0iiA==", + "version": "8.13.8", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.13.8.tgz", + "integrity": "sha512-Z5v31rvo8P7BPTiGicdJl9BbzyUe9s5sXILK8sM1g7ijkagpfFjPtXZVsq5P1WlN8m/fUp2PPNXVF9SqeTM91w==", "requires": { "@braintree/sanitize-url": "^3.1.0", "d3": "^7.0.0", "dagre": "^0.8.5", "dagre-d3": "^0.6.4", - "dompurify": "2.3.3", + "dompurify": "2.3.4", "graphlib": "^2.1.8", "khroma": "^1.4.1", "moment-mini": "^2.24.0", "stylis": "^4.0.10" + }, + "dependencies": { + "dompurify": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.4.tgz", + "integrity": "sha512-6BVcgOAVFXjI0JTjEvZy901Rghm+7fDQOrNIcxB4+gdhj6Kwp6T9VBhBY/AbagKHJocRkDYGd6wvI+p4/10xtQ==" + } } }, "meros": { @@ -14334,9 +14336,9 @@ "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, "node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.0.0.tgz", + "integrity": "sha512-ShkiiAlzSsgH1IwGlA0jybk9vQTIOLyJ9nBd0JTuP+nzADJFLY0NoDijM2zvD/JaezooGu3G2p2FNxOAK6459g==" }, "node-libs-browser": { "version": "2.2.1", @@ -17990,11 +17992,18 @@ "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=" }, "selfsigned": { - "version": "1.10.11", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz", - "integrity": "sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA==", + "version": "1.10.13", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.13.tgz", + "integrity": "sha512-UmLwTKZwNmXYDAlRFhaEdgEg0dp9k5gfJXuO7uKvSqioN1M0+Mgf4V39IlVZMSuqGoCi6h5legkhNXvWy0rFSg==", "requires": { - "node-forge": "^0.10.0" + "node-forge": "^1.2.0" + }, + "dependencies": { + "node-forge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", + "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==" + } } }, "semver": { diff --git a/licenses.html b/licenses.html index 584874c500..6d6e26dc4e 100644 --- a/licenses.html +++ b/licenses.html @@ -4249,6 +4249,8 @@

Used by:

  • serde_bytes
  • serde_derive
  • serde_json
  • +
  • serde_json_bytes
  • +
  • serde_test
  • serde_yaml
  • signal-hook-registry
  • smallvec