diff --git a/Cargo.lock b/Cargo.lock index 98908d6a..9a805c4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,7 +38,9 @@ version = "0.6.1" dependencies = [ "assert_fs", "camino", + "lazy_static", "log", + "regex", "semver", "serde", "serde_json", diff --git a/apollo-federation-types/Cargo.toml b/apollo-federation-types/Cargo.toml index c41272ce..322e9160 100644 --- a/apollo-federation-types/Cargo.toml +++ b/apollo-federation-types/Cargo.toml @@ -19,6 +19,8 @@ config = ["camino", "log", "thiserror", "serde_yaml", "url", "serde_with"] [dependencies] # config and build dependencies serde = { version = "1", features = ["derive"] } +regex = "1" +lazy_static = "1.4.0" # config-only dependencies camino = { version = "1", features = [ "serde1" ], optional = true } @@ -34,4 +36,4 @@ serde_json = { version = "1", optional = true } [dev-dependencies] assert_fs = "1" -serde_json = "1" \ No newline at end of file +serde_json = "1" diff --git a/apollo-federation-types/src/build/hint.rs b/apollo-federation-types/src/build/hint.rs index eb4a1e2c..ddb1c502 100644 --- a/apollo-federation-types/src/build/hint.rs +++ b/apollo-federation-types/src/build/hint.rs @@ -1,23 +1,76 @@ use serde::{Deserialize, Serialize}; +use regex::Regex; +use lazy_static::lazy_static; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct BuildHintLevel { + /// Value of the hint level. Higher values correspond to more "important" hints. + pub value: u16, + + /// Readable name of the hint level + pub name: String, +} + +impl BuildHintLevel { + pub fn warn() -> Self { Self { value: 60, name: String::from("WARN") } } + pub fn info() -> Self { Self { value: 40, name: String::from("INFO") } } + pub fn debug() -> Self { Self { value: 20, name: String::from("DEBUG") } } +} + /// BuildHint contains helpful information that pertains to a build #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct BuildHint { /// The message of the hint + /// This will usually be formatted as "[] " and the + /// `BuildHint::extract_code_and_message` method can be used to extract the components of this + /// message. pub message: String, + /// The level of the hint + // We should always get a level out of recent harmonizer, but older one will not have it and we + // default to "INFO". + #[serde(default="BuildHintLevel::info")] + pub level: BuildHintLevel, + /// Other untyped JSON included in the build hint. #[serde(flatten)] pub other: crate::UncaughtJson, } impl BuildHint { - pub fn new(message: String) -> Self { + pub fn new(message: String, level: BuildHintLevel) -> Self { Self { message, + level, other: crate::UncaughtJson::new(), } } + + pub fn debug(message: String) -> Self { + Self::new(message, BuildHintLevel::debug()) + } + + pub fn info(message: String) -> Self { + Self::new(message, BuildHintLevel::info()) + } + + pub fn warn(message: String) -> Self { + Self::new(message, BuildHintLevel::warn()) + } + + /// Extracts the underlying code and "raw" message of the hint. + pub fn extract_code_and_message(&self) -> (String, String) { + lazy_static! { + static ref RE: Regex = Regex::new(r"^\[(\w+)\] (.+)").unwrap(); + } + let maybe_captures = RE.captures(&self.message); + if let Some(captures) = maybe_captures { + (captures.get(1).unwrap().as_str().to_string(), captures.get(2).unwrap().as_str().to_string()) + } else { + (String::from("UNKNOWN"), self.message.clone()) + } + } } #[cfg(test)] @@ -29,16 +82,24 @@ mod tests { #[test] fn it_can_serialize() { let msg = "hint".to_string(); - let expected_json = json!({ "message": &msg }); - let actual_json = serde_json::to_value(&BuildHint::new(msg)).unwrap(); + let expected_json = json!({"level": { "value": 40, "name": "INFO"}, "message": &msg }); + let actual_json = serde_json::to_value(&BuildHint::info(msg)).unwrap(); assert_eq!(expected_json, actual_json) } #[test] fn it_can_deserialize() { + let msg = "hint".to_string(); + let actual_struct = serde_json::from_str(&json!({"level": { "value": 20, "name": "DEBUG"}, "message": &msg }).to_string()).unwrap(); + let expected_struct = BuildHint::debug(msg); + assert_eq!(expected_struct, actual_struct); + } + + #[test] + fn it_can_deserialize_without_levels() { let msg = "hint".to_string(); let actual_struct = serde_json::from_str(&json!({ "message": &msg }).to_string()).unwrap(); - let expected_struct = BuildHint::new(msg); + let expected_struct = BuildHint::info(msg); assert_eq!(expected_struct, actual_struct); } @@ -51,10 +112,30 @@ mod tests { &json!({ "message": &msg, &unexpected_key: &unexpected_value }).to_string(), ) .unwrap(); - let mut expected_struct = BuildHint::new(msg); + let mut expected_struct = BuildHint::info(msg); expected_struct .other .insert(unexpected_key, Value::String(unexpected_value)); assert_eq!(expected_struct, actual_struct); } + + #[test] + fn it_extracts_code_and_message() { + let hint = BuildHint::info("[MY_CODE] Some message".to_string()); + let (actual_code, actual_message) = hint.extract_code_and_message(); + let expected_code = "MY_CODE".to_string(); + let expected_message = "Some message".to_string(); + assert_eq!(expected_code, actual_code); + assert_eq!(expected_message, actual_message); + } + + #[test] + fn it_handle_extracting_code_and_message_with_unknown_code() { + let hint = BuildHint::info("Some message without code".to_string()); + let (actual_code, actual_message) = hint.extract_code_and_message(); + let expected_code = "UNKNOWN".to_string(); + let expected_message = "Some message without code".to_string(); + assert_eq!(expected_code, actual_code); + assert_eq!(expected_message, actual_message); + } } diff --git a/apollo-federation-types/src/build/output.rs b/apollo-federation-types/src/build/output.rs index e6c99d8a..1bdf450d 100644 --- a/apollo-federation-types/src/build/output.rs +++ b/apollo-federation-types/src/build/output.rs @@ -52,10 +52,10 @@ mod tests { let sdl = "my-sdl".to_string(); let hint_one = "hint-one".to_string(); let hint_two = "hint-two".to_string(); - let expected_json = json!({"supergraphSdl": &sdl, "hints": [{"message": &hint_one}, {"message": &hint_two}]}); + let expected_json = json!({"supergraphSdl": &sdl, "hints": [{"level": { "value": 40, "name": "INFO"}, "message": &hint_one}, {"level": { "value": 60, "name": "WARN"}, "message": &hint_two}]}); let actual_json = serde_json::to_value(&BuildOutput::new_with_hints( sdl.to_string(), - vec![BuildHint::new(hint_one), BuildHint::new(hint_two)], + vec![BuildHint::info(hint_one), BuildHint::warn(hint_two)], )) .unwrap(); assert_eq!(expected_json, actual_json) @@ -81,7 +81,7 @@ mod tests { .unwrap(); let expected_struct = BuildOutput::new_with_hints( sdl, - vec![BuildHint::new(hint_one), BuildHint::new(hint_two)], + vec![BuildHint::info(hint_one), BuildHint::info(hint_two)], ); assert_eq!(expected_struct, actual_struct) diff --git a/federation-2/Cargo.lock b/federation-2/Cargo.lock index 4a5bafa9..7662739b 100644 --- a/federation-2/Cargo.lock +++ b/federation-2/Cargo.lock @@ -31,7 +31,9 @@ name = "apollo-federation-types" version = "0.6.1" dependencies = [ "camino", + "lazy_static", "log", + "regex", "semver 1.0.12", "serde", "serde_json", diff --git a/federation-2/harmonizer/deno/do_compose.js b/federation-2/harmonizer/deno/do_compose.js index 4c50ee76..003ed543 100644 --- a/federation-2/harmonizer/deno/do_compose.js +++ b/federation-2/harmonizer/deno/do_compose.js @@ -56,7 +56,10 @@ try { let hints = []; if (composed.hints) { composed.hints.map((composed_hint) => { - hints.push({ message: composed_hint.toString() }); + hints.push({ + message: composed_hint.toString(), + level: composed_hint.definition.level, + }); }); } done(