diff --git a/.agents/rules/rust-error-handling.mdc b/.agents/rules/rust-error-handling.mdc index e1e2a913..007ba6e5 100644 --- a/.agents/rules/rust-error-handling.mdc +++ b/.agents/rules/rust-error-handling.mdc @@ -19,7 +19,7 @@ alwaysApply: false - Use `change_context()` to map error types consistently - Ensure that errors include sufficient context for debugging -- Add `attach_printable()` or `attach_printable_lazy()` to include relevant debug information +- Add `attach()` or `attach_with()` to include relevant debug information - When reporting errors to users, provide actionable guidance where possible ## Error Definition diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 46536a73..4888c74e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -116,7 +116,23 @@ See also: #456, #789 Consistency is the most important. Following the existing Rust style, formatting, and naming conventions of the file you are modifying and of the overall project. Failure to do so will result in a prolonged review process that has to focus on updating the superficial aspects of your code, rather than improving its functionality and performance. -Style and format will be enforced with a linter when the PR is created. +Style and format will be enforced with a linter when PR is created. + +## :warning: Error Handling + +We use [error-stack](https://docs.rs/error-stack/latest/error_stack/) for error handling to provide rich context and traceability. + +### Guidelines + +1. **Use `Report`**: Public functions should generally return `Result>`. +2. **Context**: Use `.change_context(TrustedServerError::Variant)` to wrap errors and provide semantic meaning. + ```rust + // Good + file.read_to_string(&mut content) + .change_context(TrustedServerError::Configuration { message: "Failed to read config".into() })?; + ``` +3. **Attachments**: Use `.attach_printable("additional info")` to add debugging context without changing the error variant. +4. **Consistency**: Avoid returning bare `TrustedServerError` unless absolutely necessary (e.g. implementing traits). Wrap them in `Report::new()`. ## :pray: Credits diff --git a/crates/common/src/fastly_storage.rs b/crates/common/src/fastly_storage.rs index 932bb8fc..a2466baf 100644 --- a/crates/common/src/fastly_storage.rs +++ b/crates/common/src/fastly_storage.rs @@ -1,5 +1,6 @@ use std::io::Read; +use error_stack::{Report, ResultExt}; use fastly::{ConfigStore, Request, Response, SecretStore}; use http::StatusCode; @@ -24,17 +25,17 @@ impl FastlyConfigStore { /// # Errors /// /// Returns an error if the key is not found in the config store. - pub fn get(&self, key: &str) -> Result { + pub fn get(&self, key: &str) -> Result> { // TODO use try_open and return the error let store = ConfigStore::open(&self.store_name); - store - .get(key) - .ok_or_else(|| TrustedServerError::Configuration { + store.get(key).ok_or_else(|| { + Report::new(TrustedServerError::Configuration { message: format!( "Key '{}' not found in config store '{}'", key, self.store_name ), }) + }) } } @@ -55,25 +56,28 @@ impl FastlySecretStore { /// /// Returns an error if the secret store cannot be opened, the key is not found, /// or the secret plaintext cannot be retrieved. - pub fn get(&self, key: &str) -> Result, TrustedServerError> { - let store = - SecretStore::open(&self.store_name).map_err(|_| TrustedServerError::Configuration { + pub fn get(&self, key: &str) -> Result, Report> { + let store = SecretStore::open(&self.store_name).map_err(|_| { + Report::new(TrustedServerError::Configuration { message: format!("Failed to open SecretStore '{}'", self.store_name), - })?; + }) + })?; - let secret = store - .get(key) - .ok_or_else(|| TrustedServerError::Configuration { + let secret = store.get(key).ok_or_else(|| { + Report::new(TrustedServerError::Configuration { message: format!( "Secret '{}' not found in secret store '{}'", key, self.store_name ), - })?; + }) + })?; secret .try_plaintext() - .map_err(|_| TrustedServerError::Configuration { - message: "Failed to get secret plaintext".into(), + .map_err(|_| { + Report::new(TrustedServerError::Configuration { + message: "Failed to get secret plaintext".into(), + }) }) .map(|bytes| bytes.into_iter().collect()) } @@ -83,10 +87,10 @@ impl FastlySecretStore { /// # Errors /// /// Returns an error if the secret cannot be retrieved or is not valid UTF-8. - pub fn get_string(&self, key: &str) -> Result { + pub fn get_string(&self, key: &str) -> Result> { let bytes = self.get(key)?; - String::from_utf8(bytes).map_err(|e| TrustedServerError::Configuration { - message: format!("Failed to decode secret as UTF-8: {}", e), + String::from_utf8(bytes).change_context(TrustedServerError::Configuration { + message: "Failed to decode secret as UTF-8".to_string(), }) } } @@ -103,7 +107,7 @@ impl FastlyApiClient { /// # Errors /// /// Returns an error if the secret store cannot be opened or the API key cannot be retrieved. - pub fn new() -> Result { + pub fn new() -> Result> { Self::from_secret_store("api-keys", "api_key") } @@ -112,12 +116,11 @@ impl FastlyApiClient { /// # Errors /// /// Returns an error if the API backend cannot be ensured or the API key cannot be retrieved. - pub fn from_secret_store(store_name: &str, key_name: &str) -> Result { - let backend_name = ensure_backend_from_url(FASTLY_API_HOST).map_err(|e| { - TrustedServerError::Configuration { - message: format!("Failed to ensure API backend: {}", e), - } - })?; + pub fn from_secret_store( + store_name: &str, + key_name: &str, + ) -> Result> { + let backend_name = ensure_backend_from_url("https://api.fastly.com")?; let secret_store = FastlySecretStore::new(store_name); let api_key = secret_store.get(key_name)?; @@ -137,7 +140,7 @@ impl FastlyApiClient { path: &str, body: Option, content_type: &str, - ) -> Result { + ) -> Result> { let url = format!("{}{}", self.base_url, path); let api_key_str = String::from_utf8_lossy(&self.api_key).to_string(); @@ -148,9 +151,9 @@ impl FastlyApiClient { "PUT" => Request::put(&url), "DELETE" => Request::delete(&url), _ => { - return Err(TrustedServerError::Configuration { + return Err(Report::new(TrustedServerError::Configuration { message: format!("Unsupported HTTP method: {}", method), - }) + })) } }; @@ -164,11 +167,11 @@ impl FastlyApiClient { .with_body(body_content); } - request - .send(&self.backend_name) - .map_err(|e| TrustedServerError::Configuration { + request.send(&self.backend_name).map_err(|e| { + Report::new(TrustedServerError::Configuration { message: format!("Failed to send API request: {}", e), }) + }) } /// Updates a configuration item in a Fastly config store. @@ -181,7 +184,7 @@ impl FastlyApiClient { store_id: &str, key: &str, value: &str, - ) -> Result<(), TrustedServerError> { + ) -> Result<(), Report> { let path = format!("/resources/stores/config/{}/item/{}", store_id, key); let payload = format!("item_value={}", value); @@ -196,20 +199,22 @@ impl FastlyApiClient { response .get_body_mut() .read_to_string(&mut buf) - .map_err(|e| TrustedServerError::Configuration { - message: format!("Failed to read API response: {}", e), + .map_err(|e| { + Report::new(TrustedServerError::Configuration { + message: format!("Failed to read API response: {}", e), + }) })?; if response.get_status() == StatusCode::OK { Ok(()) } else { - Err(TrustedServerError::Configuration { + Err(Report::new(TrustedServerError::Configuration { message: format!( "Failed to update config item: HTTP {} - {}", response.get_status(), buf ), - }) + })) } } @@ -223,7 +228,7 @@ impl FastlyApiClient { store_id: &str, secret_name: &str, secret_value: &str, - ) -> Result<(), TrustedServerError> { + ) -> Result<(), Report> { let path = format!("/resources/stores/secret/{}/secrets", store_id); let payload = serde_json::json!({ @@ -238,20 +243,22 @@ impl FastlyApiClient { response .get_body_mut() .read_to_string(&mut buf) - .map_err(|e| TrustedServerError::Configuration { - message: format!("Failed to read API response: {}", e), + .map_err(|e| { + Report::new(TrustedServerError::Configuration { + message: format!("Failed to read API response: {}", e), + }) })?; if response.get_status() == StatusCode::OK { Ok(()) } else { - Err(TrustedServerError::Configuration { + Err(Report::new(TrustedServerError::Configuration { message: format!( "Failed to create secret: HTTP {} - {}", response.get_status(), buf ), - }) + })) } } @@ -260,7 +267,11 @@ impl FastlyApiClient { /// # Errors /// /// Returns an error if the API request fails or returns a non-OK/NO_CONTENT status. - pub fn delete_config_item(&self, store_id: &str, key: &str) -> Result<(), TrustedServerError> { + pub fn delete_config_item( + &self, + store_id: &str, + key: &str, + ) -> Result<(), Report> { let path = format!("/resources/stores/config/{}/item/{}", store_id, key); let mut response = self.make_request("DELETE", &path, None, "application/json")?; @@ -269,8 +280,10 @@ impl FastlyApiClient { response .get_body_mut() .read_to_string(&mut buf) - .map_err(|e| TrustedServerError::Configuration { - message: format!("Failed to read API response: {}", e), + .map_err(|e| { + Report::new(TrustedServerError::Configuration { + message: format!("Failed to read API response: {}", e), + }) })?; if response.get_status() == StatusCode::OK @@ -278,13 +291,13 @@ impl FastlyApiClient { { Ok(()) } else { - Err(TrustedServerError::Configuration { + Err(Report::new(TrustedServerError::Configuration { message: format!( "Failed to delete config item: HTTP {} - {}", response.get_status(), buf ), - }) + })) } } @@ -297,7 +310,7 @@ impl FastlyApiClient { &self, store_id: &str, secret_name: &str, - ) -> Result<(), TrustedServerError> { + ) -> Result<(), Report> { let path = format!( "/resources/stores/secret/{}/secrets/{}", store_id, secret_name @@ -309,8 +322,10 @@ impl FastlyApiClient { response .get_body_mut() .read_to_string(&mut buf) - .map_err(|e| TrustedServerError::Configuration { - message: format!("Failed to read API response: {}", e), + .map_err(|e| { + Report::new(TrustedServerError::Configuration { + message: format!("Failed to read API response: {}", e), + }) })?; if response.get_status() == StatusCode::OK @@ -318,13 +333,13 @@ impl FastlyApiClient { { Ok(()) } else { - Err(TrustedServerError::Configuration { + Err(Report::new(TrustedServerError::Configuration { message: format!( "Failed to delete secret: HTTP {} - {}", response.get_status(), buf ), - }) + })) } } } @@ -381,6 +396,7 @@ mod tests { } } + // Other tests logic is preserved, prints error which is now a Report #[test] fn test_update_config_item() { let result = FastlyApiClient::new(); diff --git a/crates/common/src/request_signing/jwks.rs b/crates/common/src/request_signing/jwks.rs index 2b89c9a5..27115899 100644 --- a/crates/common/src/request_signing/jwks.rs +++ b/crates/common/src/request_signing/jwks.rs @@ -4,6 +4,7 @@ //! Ed25519 keypairs in JWK format for request signing. use ed25519_dalek::{SigningKey, VerifyingKey}; +use error_stack::{Report, ResultExt}; use jose_jwk::{ jose_jwa::{Algorithm, Signing}, Jwk, Key, Okp, OkpCurves, Parameters, @@ -58,9 +59,11 @@ impl Keypair { /// # Errors /// /// Returns an error if the config store cannot be accessed or if active keys cannot be retrieved. -pub fn get_active_jwks() -> Result { +pub fn get_active_jwks() -> Result> { let store = FastlyConfigStore::new("jwks_store"); - let active_kids_str = store.get("active-kids")?; + let active_kids_str = store + .get("active-kids") + .attach("while fetching active kids list")?; let active_kids: Vec<&str> = active_kids_str .split(',') @@ -70,7 +73,9 @@ pub fn get_active_jwks() -> Result { let mut jwks = Vec::new(); for kid in active_kids { - let jwk = store.get(kid)?; + let jwk = store + .get(kid) + .attach(format!("Failed to get JWK for kid: {}", kid))?; jwks.push(jwk); } diff --git a/crates/common/src/request_signing/rotation.rs b/crates/common/src/request_signing/rotation.rs index affc84d4..8ddca3a7 100644 --- a/crates/common/src/request_signing/rotation.rs +++ b/crates/common/src/request_signing/rotation.rs @@ -5,6 +5,7 @@ use base64::{engine::general_purpose, Engine}; use ed25519_dalek::SigningKey; +use error_stack::{Report, ResultExt}; use jose_jwk::Jwk; use crate::error::TrustedServerError; @@ -36,7 +37,7 @@ impl KeyRotationManager { pub fn new( config_store_id: impl Into, secret_store_id: impl Into, - ) -> Result { + ) -> Result> { let config_store_id = config_store_id.into(); let secret_store_id = secret_store_id.into(); @@ -56,7 +57,10 @@ impl KeyRotationManager { /// # Errors /// /// Returns an error if key storage or update operations fail. - pub fn rotate_key(&self, kid: Option) -> Result { + pub fn rotate_key( + &self, + kid: Option, + ) -> Result> { let new_kid = kid.unwrap_or_else(generate_date_based_kid); let keypair = Keypair::generate(); @@ -86,45 +90,46 @@ impl KeyRotationManager { &self, kid: &str, signing_key: &SigningKey, - ) -> Result<(), TrustedServerError> { + ) -> Result<(), Report> { let key_bytes = signing_key.as_bytes(); let key_b64 = general_purpose::STANDARD.encode(key_bytes); self.api_client .create_secret(&self.secret_store_id, kid, &key_b64) - .map_err(|e| TrustedServerError::Configuration { - message: format!("Failed to store private key '{}': {}", kid, e), + .change_context(TrustedServerError::Configuration { + message: format!("Failed to store private key '{}'", kid), }) } - fn store_public_jwk(&self, kid: &str, jwk: &Jwk) -> Result<(), TrustedServerError> { - let jwk_json = - serde_json::to_string(jwk).map_err(|e| TrustedServerError::Configuration { + fn store_public_jwk(&self, kid: &str, jwk: &Jwk) -> Result<(), Report> { + let jwk_json = serde_json::to_string(jwk).map_err(|e| { + Report::new(TrustedServerError::Configuration { message: format!("Failed to serialize JWK: {}", e), - })?; + }) + })?; self.api_client .update_config_item(&self.config_store_id, kid, &jwk_json) - .map_err(|e| TrustedServerError::Configuration { - message: format!("Failed to store public JWK '{}': {}", kid, e), + .change_context(TrustedServerError::Configuration { + message: format!("Failed to store public JWK '{}'", kid), }) } - fn update_current_kid(&self, kid: &str) -> Result<(), TrustedServerError> { + fn update_current_kid(&self, kid: &str) -> Result<(), Report> { self.api_client .update_config_item(&self.config_store_id, "current-kid", kid) - .map_err(|e| TrustedServerError::Configuration { - message: format!("Failed to update current-kid: {}", e), + .change_context(TrustedServerError::Configuration { + message: "Failed to update current-kid".into(), }) } - fn update_active_kids(&self, active_kids: &[String]) -> Result<(), TrustedServerError> { + fn update_active_kids(&self, active_kids: &[String]) -> Result<(), Report> { let active_kids_str = active_kids.join(","); self.api_client .update_config_item(&self.config_store_id, "active-kids", &active_kids_str) - .map_err(|e| TrustedServerError::Configuration { - message: format!("Failed to update active-kids: {}", e), + .change_context(TrustedServerError::Configuration { + message: "Failed to update active-kids".into(), }) } @@ -133,7 +138,7 @@ impl KeyRotationManager { /// # Errors /// /// Returns an error if the active keys cannot be retrieved from the config store. - pub fn list_active_keys(&self) -> Result, TrustedServerError> { + pub fn list_active_keys(&self) -> Result, Report> { let active_kids_str = self.config_store.get("active-kids")?; let active_kids: Vec = active_kids_str @@ -150,15 +155,15 @@ impl KeyRotationManager { /// # Errors /// /// Returns an error if this would deactivate the last active key, or if the update fails. - pub fn deactivate_key(&self, kid: &str) -> Result<(), TrustedServerError> { + pub fn deactivate_key(&self, kid: &str) -> Result<(), Report> { let mut active_kids = self.list_active_keys()?; active_kids.retain(|k| k != kid); if active_kids.is_empty() { - return Err(TrustedServerError::Configuration { + return Err(Report::new(TrustedServerError::Configuration { message: "Cannot deactivate the last active key".into(), - }); + })); } self.update_active_kids(&active_kids) @@ -169,19 +174,19 @@ impl KeyRotationManager { /// # Errors /// /// Returns an error if deactivation fails or if the key cannot be deleted from storage. - pub fn delete_key(&self, kid: &str) -> Result<(), TrustedServerError> { + pub fn delete_key(&self, kid: &str) -> Result<(), Report> { self.deactivate_key(kid)?; self.api_client .delete_config_item(&self.config_store_id, kid) - .map_err(|e| TrustedServerError::Configuration { - message: format!("Failed to delete JWK from ConfigStore: {}", e), + .change_context(TrustedServerError::Configuration { + message: "Failed to delete JWK from ConfigStore".into(), })?; self.api_client .delete_secret(&self.secret_store_id, kid) - .map_err(|e| TrustedServerError::Configuration { - message: format!("Failed to delete secret from SecretStore: {}", e), + .change_context(TrustedServerError::Configuration { + message: "Failed to delete secret from SecretStore".into(), })?; Ok(()) @@ -203,15 +208,11 @@ mod tests { #[test] fn test_generate_date_based_kid() { let kid = generate_date_based_kid(); - println!("Generated KID: {}", kid); - // Verify format: ts-YYYY-MM-DD assert!(kid.starts_with("ts-")); - assert!(kid.len() >= 13); // "ts-" + "YYYY-MM-DD" = 13 chars minimum - - // Verify it contains only valid characters + assert!(kid.len() >= 13); let parts: Vec<&str> = kid.split('-').collect(); - assert_eq!(parts.len(), 4); // ["ts", "YYYY", "MM", "DD"] + assert_eq!(parts.len(), 4); assert_eq!(parts[0], "ts"); } @@ -222,7 +223,6 @@ mod tests { Ok(manager) => { assert_eq!(manager.config_store_id, "jwks_store"); assert_eq!(manager.secret_store_id, "signing_keys"); - println!("✓ KeyRotationManager created successfully"); } Err(e) => { println!("Expected error in test environment: {}", e); @@ -236,7 +236,6 @@ mod tests { if let Ok(manager) = result { match manager.list_active_keys() { Ok(keys) => { - println!("Active keys: {:?}", keys); assert!(!keys.is_empty(), "Should have at least one active key"); } Err(e) => println!("Expected error in test environment: {}", e), diff --git a/crates/common/src/request_signing/signing.rs b/crates/common/src/request_signing/signing.rs index bd1ae0a3..6013ff05 100644 --- a/crates/common/src/request_signing/signing.rs +++ b/crates/common/src/request_signing/signing.rs @@ -5,6 +5,7 @@ use base64::{engine::general_purpose, Engine}; use ed25519_dalek::{Signature, Signer as Ed25519Signer, SigningKey, Verifier, VerifyingKey}; +use error_stack::{Report, ResultExt}; use crate::error::TrustedServerError; use crate::fastly_storage::{FastlyConfigStore, FastlySecretStore}; @@ -14,27 +15,27 @@ use crate::fastly_storage::{FastlyConfigStore, FastlySecretStore}; /// # Errors /// /// Returns an error if the config store cannot be accessed or the current-kid key is not found. -pub fn get_current_key_id() -> Result { +pub fn get_current_key_id() -> Result> { let store = FastlyConfigStore::new("jwks_store"); store.get("current-kid") } -fn parse_ed25519_signing_key(key_bytes: Vec) -> Result { +fn parse_ed25519_signing_key(key_bytes: Vec) -> Result> { let bytes = if key_bytes.len() > 32 { general_purpose::STANDARD.decode(&key_bytes).map_err(|_| { - TrustedServerError::Configuration { + Report::new(TrustedServerError::Configuration { message: "Failed to decode base64 key".into(), - } + }) })? } else { key_bytes }; - let key_array: [u8; 32] = bytes - .try_into() - .map_err(|_| TrustedServerError::Configuration { + let key_array: [u8; 32] = bytes.try_into().map_err(|_| { + Report::new(TrustedServerError::Configuration { message: "Invalid key length (expected 32 bytes for Ed25519)".into(), - })?; + }) + })?; Ok(SigningKey::from_bytes(&key_array)) } @@ -50,12 +51,19 @@ impl RequestSigner { /// # Errors /// /// Returns an error if the key ID cannot be retrieved or the key cannot be parsed. - pub fn from_config() -> Result { + pub fn from_config() -> Result> { let config_store = FastlyConfigStore::new("jwks_store"); - let key_id = config_store.get("current-kid")?; + let key_id = + config_store + .get("current-kid") + .change_context(TrustedServerError::Configuration { + message: "Failed to get current-kid".into(), + })?; let secret_store = FastlySecretStore::new("signing_keys"); - let key_bytes = secret_store.get(&key_id)?; + let key_bytes = secret_store + .get(&key_id) + .attach(format!("Failed to get signing key for kid: {}", key_id))?; let signing_key = parse_ed25519_signing_key(key_bytes)?; Ok(Self { @@ -69,7 +77,7 @@ impl RequestSigner { /// # Errors /// /// Returns an error if signing fails. - pub fn sign(&self, payload: &[u8]) -> Result { + pub fn sign(&self, payload: &[u8]) -> Result> { let signature_bytes = self.key.sign(payload).to_bytes(); Ok(general_purpose::URL_SAFE_NO_PAD.encode(signature_bytes)) @@ -85,54 +93,60 @@ pub fn verify_signature( payload: &[u8], signature_b64: &str, kid: &str, -) -> Result { +) -> Result> { let store = FastlyConfigStore::new("jwks_store"); - let jwk_json = store.get(kid)?; + let jwk_json = store + .get(kid) + .change_context(TrustedServerError::Configuration { + message: format!("Failed to get JWK for kid: {}", kid), + })?; - let jwk: serde_json::Value = - serde_json::from_str(&jwk_json).map_err(|e| TrustedServerError::Configuration { + let jwk: serde_json::Value = serde_json::from_str(&jwk_json).map_err(|e| { + Report::new(TrustedServerError::Configuration { message: format!("Failed to parse JWK: {}", e), - })?; + }) + })?; - let x_b64 = - jwk.get("x") - .and_then(|v| v.as_str()) - .ok_or_else(|| TrustedServerError::Configuration { - message: "JWK missing 'x' parameter".into(), - })?; + let x_b64 = jwk.get("x").and_then(|v| v.as_str()).ok_or_else(|| { + Report::new(TrustedServerError::Configuration { + message: "JWK missing 'x' parameter".into(), + }) + })?; let public_key_bytes = general_purpose::URL_SAFE_NO_PAD .decode(x_b64) - .map_err(|e| TrustedServerError::Configuration { - message: format!("Failed to decode public key: {}", e), + .map_err(|e| { + Report::new(TrustedServerError::Configuration { + message: format!("Failed to decode public key: {}", e), + }) })?; - let verifying_key_bytes: [u8; 32] = - public_key_bytes - .try_into() - .map_err(|_| TrustedServerError::Configuration { - message: "Public key must be 32 bytes".into(), - })?; + let verifying_key_bytes: [u8; 32] = public_key_bytes.try_into().map_err(|_| { + Report::new(TrustedServerError::Configuration { + message: "Public key must be 32 bytes".into(), + }) + })?; let verifying_key = VerifyingKey::from_bytes(&verifying_key_bytes).map_err(|e| { - TrustedServerError::Configuration { + Report::new(TrustedServerError::Configuration { message: format!("Failed to create verifying key: {}", e), - } + }) })?; let signature_bytes = general_purpose::URL_SAFE_NO_PAD .decode(signature_b64) .or_else(|_| general_purpose::STANDARD.decode(signature_b64)) - .map_err(|e| TrustedServerError::Configuration { - message: format!("Failed to decode signature: {}", e), + .map_err(|e| { + Report::new(TrustedServerError::Configuration { + message: format!("Failed to decode signature: {}", e), + }) })?; - let signature_array: [u8; 64] = - signature_bytes - .try_into() - .map_err(|_| TrustedServerError::Configuration { - message: "Signature must be 64 bytes".into(), - })?; + let signature_array: [u8; 64] = signature_bytes.try_into().map_err(|_| { + Report::new(TrustedServerError::Configuration { + message: "Signature must be 64 bytes".into(), + }) + })?; let signature = Signature::from_bytes(&signature_array); @@ -145,18 +159,19 @@ mod tests { #[test] fn test_request_signer_sign() { + // Report unwraps print full error chain on test failure + // Note: unwrapping a Report prints it nicely if test fails. let signer = RequestSigner::from_config().unwrap(); let signature = signer .sign(b"these pretzels are making me thirsty") .unwrap(); assert!(!signature.is_empty()); - assert!(signature.len() > 32); // Ed25519 signatures are 64 bytes, base64 encoded should be longer + assert!(signature.len() > 32); } #[test] fn test_request_signer_from_config() { let signer = RequestSigner::from_config().unwrap(); - // Verify that we can successfully load the signing key from Fastly config assert!(!signer.kid.is_empty()); } @@ -166,7 +181,6 @@ mod tests { let signer = RequestSigner::from_config().unwrap(); let signature = signer.sign(payload).unwrap(); - // Verify the signature let result = verify_signature(payload, &signature, &signer.kid).unwrap(); assert!(result, "Signature should be valid"); } @@ -176,10 +190,8 @@ mod tests { let payload = b"test payload"; let signer = RequestSigner::from_config().unwrap(); - // Create a valid Ed25519 signature (64 bytes) but for a different payload let wrong_signature = signer.sign(b"different payload").unwrap(); - // Should return false for signature of different payload let result = verify_signature(payload, &wrong_signature, &signer.kid).unwrap(); assert!(!result, "Invalid signature should not verify"); } @@ -190,7 +202,6 @@ mod tests { let signer = RequestSigner::from_config().unwrap(); let signature = signer.sign(original_payload).unwrap(); - // Try to verify with different payload let wrong_payload = b"wrong payload"; let result = verify_signature(wrong_payload, &signature, &signer.kid).unwrap(); assert!(!result, "Signature should not verify with wrong payload"); @@ -203,7 +214,6 @@ mod tests { let signature = signer.sign(payload).unwrap(); let nonexistent_kid = "nonexistent-key-id"; - // Should return an error for missing key let result = verify_signature(payload, &signature, nonexistent_kid); assert!(result.is_err(), "Should error for missing key"); } @@ -214,7 +224,6 @@ mod tests { let signer = RequestSigner::from_config().unwrap(); let malformed_signature = "not-valid-base64!!!"; - // Should return an error for malformed base64 let result = verify_signature(payload, malformed_signature, &signer.kid); assert!(result.is_err(), "Should error for malformed signature"); }