From ea3532247eda0d28a0fa94a3d61da09d7b6a1d43 Mon Sep 17 00:00:00 2001 From: Sylvain Lebresne Date: Fri, 7 Oct 2022 18:07:58 +0200 Subject: [PATCH] Expose hint levels Composition hints in federation 2 are generated with a "level" (which can currently be one of of "DEBUG", "INFO" or "WARN"). This commit modify harmonizer to include those levels in the output and the type definitions to decode them, and include them in the `supergraph` output for future use by `rover`. Fixes #102 --- Cargo.lock | 2 + apollo-federation-types/Cargo.toml | 4 +- apollo-federation-types/src/build/hint.rs | 91 +++++++++++++++++++-- apollo-federation-types/src/build/output.rs | 6 +- federation-2/Cargo.lock | 2 + federation-2/harmonizer/deno/do_compose.js | 5 +- 6 files changed, 100 insertions(+), 10 deletions(-) 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(