diff --git a/Cargo.toml b/Cargo.toml index 103c9d47..68c0ab03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ pickup = { path = "./crates/web-plugins/didcomm-messaging/protocols/pickup", ver forward = { path = "./crates/web-plugins/didcomm-messaging/protocols/forward", version = "0.1.0" } trust-ping = { path = "./crates/web-plugins/didcomm-messaging/protocols/trust-ping", version = "0.1.0" } basic-message = { path = "./crates/web-plugins/didcomm-messaging/protocols/basic-message", version = "0.1.0" } +discover-features = { path = "./crates/web-plugins/didcomm-messaging/protocols/discover-features", version = "0.1.0" } mediator-coordination = { path = "./crates/web-plugins/didcomm-messaging/protocols/mediator-coordination", version = "0.1.0" } # Other common dependencies @@ -63,6 +64,8 @@ url = "2.4.1" num-bigint = "0.4.4" base64 = "0.13.0" hex = "0.4.3" +eyre = "0.6" +anyhow = "1" subtle = "2.5.0" regex = "1.10.2" mongodb = "2.7.1" @@ -94,6 +97,8 @@ plugin-api.workspace = true axum.workspace = true dotenv-flow.workspace = true +eyre.workspace = true +thiserror.workspace = true tracing.workspace = true lazy_static.workspace = true serde_json.workspace = true diff --git a/crates/filesystem/src/lib.rs b/crates/filesystem/src/lib.rs index f208acc9..a8f04e71 100644 --- a/crates/filesystem/src/lib.rs +++ b/crates/filesystem/src/lib.rs @@ -61,10 +61,17 @@ impl FileSystem for StdFileSystem { flock(file.as_raw_fd(), FlockArg::LockExclusive) .map_err(|_| IoError::new(ErrorKind::Other, "Error acquiring file lock"))?; - std::fs::write(path, &content).expect("Error saving base64-encoded image to file"); + std::fs::write(path, &content).map_err(|_| { + IoError::new( + ErrorKind::Other, + "Error saving base64-encoded image to file", + ) + })?; // Release the lock after writing to the file - flock(file.as_raw_fd(), FlockArg::Unlock).expect("Error releasing file lock"); + flock(file.as_raw_fd(), FlockArg::Unlock) + .map_err(|_| IoError::new(ErrorKind::Other, "Error releasing file lock"))?; + Ok(()) } diff --git a/crates/plugin-api/Cargo.toml b/crates/plugin-api/Cargo.toml index 0c8d7556..a7b55592 100644 --- a/crates/plugin-api/Cargo.toml +++ b/crates/plugin-api/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" [dependencies] axum.workspace = true +thiserror.workspace = true diff --git a/crates/plugin-api/src/lib.rs b/crates/plugin-api/src/lib.rs index 726ef506..f4ce0538 100644 --- a/crates/plugin-api/src/lib.rs +++ b/crates/plugin-api/src/lib.rs @@ -2,12 +2,16 @@ use std::{ fmt::Debug, hash::{Hash, Hasher}, }; +use thiserror::Error; use axum::Router; -#[derive(Debug, PartialEq)] +#[derive(Debug, Error, PartialEq)] pub enum PluginError { - InitError, + #[error("{0}")] + InitError(String), + #[error("{0}")] + Other(String), } pub trait Plugin: Sync + Send { @@ -21,7 +25,7 @@ pub trait Plugin: Sync + Send { fn unmount(&self) -> Result<(), PluginError>; /// Export managed endpoints - fn routes(&self) -> Router; + fn routes(&self) -> Result; } impl Eq for dyn Plugin {} diff --git a/crates/web-plugins/did-endpoint/src/plugin.rs b/crates/web-plugins/did-endpoint/src/plugin.rs index 50d93b6d..da364eff 100644 --- a/crates/web-plugins/did-endpoint/src/plugin.rs +++ b/crates/web-plugins/did-endpoint/src/plugin.rs @@ -24,14 +24,11 @@ pub(crate) struct DidEndPointState { } fn get_env() -> Result { - let storage_dirpath = std::env::var("STORAGE_DIRPATH").map_err(|_| { - tracing::error!("STORAGE_DIRPATH env variable required"); - PluginError::InitError - })?; + let storage_dirpath = std::env::var("STORAGE_DIRPATH") + .map_err(|_| PluginError::InitError("STORAGE_DIRPATH env variable required".to_owned()))?; let server_public_domain = std::env::var("SERVER_PUBLIC_DOMAIN").map_err(|_| { - tracing::error!("SERVER_PUBLIC_DOMAIN env variable required"); - PluginError::InitError + PluginError::InitError("SERVER_PUBLIC_DOMAIN env variable required".to_owned()) })?; Ok(DidEndpointEnv { @@ -62,8 +59,9 @@ impl Plugin for DidEndpoint { &mut filesystem, ) .map_err(|_| { - tracing::error!("failed to generate an initial keystore and its DID document"); - PluginError::InitError + PluginError::InitError( + "failed to generate an initial keystore and its DID document".to_owned(), + ) })?; }; @@ -80,8 +78,10 @@ impl Plugin for DidEndpoint { Ok(()) } - fn routes(&self) -> Router { - let state = self.state.as_ref().expect("Plugin not mounted"); - web::routes(Arc::new(state.clone())) + fn routes(&self) -> Result { + let state = self.state.as_ref().ok_or(PluginError::Other( + "missing state, plugin not mounted".to_owned(), + ))?; + Ok(web::routes(Arc::new(state.clone()))) } } diff --git a/crates/web-plugins/did-endpoint/src/web.rs b/crates/web-plugins/did-endpoint/src/web.rs index ac914a18..88fb7f34 100644 --- a/crates/web-plugins/did-endpoint/src/web.rs +++ b/crates/web-plugins/did-endpoint/src/web.rs @@ -36,7 +36,10 @@ async fn diddoc(State(state): State>) -> Result Ok(Json(serde_json::from_str(&content).unwrap())), + Ok(content) => Ok(Json(serde_json::from_str(&content).map_err(|_| { + tracing::error!("Unparseable did.json"); + StatusCode::NOT_FOUND + })?)), Err(_) => Err(StatusCode::NOT_FOUND), } } diff --git a/crates/web-plugins/didcomm-messaging/Cargo.toml b/crates/web-plugins/didcomm-messaging/Cargo.toml index 8da6d9bf..4ccc5698 100644 --- a/crates/web-plugins/didcomm-messaging/Cargo.toml +++ b/crates/web-plugins/didcomm-messaging/Cargo.toml @@ -14,6 +14,7 @@ filesystem.workspace = true forward.workspace = true pickup.workspace = true trust-ping.workspace = true +discover-features.workspace = true mediator-coordination.workspace = true mongodb.workspace = true diff --git a/crates/web-plugins/didcomm-messaging/protocols/basic-message/src/handler.rs b/crates/web-plugins/didcomm-messaging/protocols/basic-message/src/handler.rs index 44aa155e..42a51ded 100644 --- a/crates/web-plugins/didcomm-messaging/protocols/basic-message/src/handler.rs +++ b/crates/web-plugins/didcomm-messaging/protocols/basic-message/src/handler.rs @@ -91,12 +91,8 @@ mod tests { message_repository: Arc::new(MockMessagesRepository::from(vec![])), keystore: Arc::new(MockKeyStore::new(vec![])), }; - let state = Arc::new(AppState::from( - public_domain, - diddoc, - None, - Some(repository), - )); + let state = + Arc::new(AppState::from(public_domain, diddoc, None, Some(repository)).unwrap()); let message = Message::build( "id_alice".to_owned(), diff --git a/crates/web-plugins/didcomm-messaging/protocols/discover-features/src/handler.rs b/crates/web-plugins/didcomm-messaging/protocols/discover-features/src/handler.rs index f8c6f885..092f725d 100644 --- a/crates/web-plugins/didcomm-messaging/protocols/discover-features/src/handler.rs +++ b/crates/web-plugins/didcomm-messaging/protocols/discover-features/src/handler.rs @@ -1,22 +1,19 @@ -use std::{collections::HashSet, sync::Arc}; - -use axum::response::{IntoResponse, Response}; +use crate::{ + errors::DiscoveryError, + model::{Disclosures, DisclosuresContent}, +}; use didcomm::Message; use serde_json::json; use shared::{constants::DISCOVER_FEATURE, state::AppState}; +use std::{collections::HashSet, sync::Arc}; use uuid::Uuid; -use super::{ - errors::DiscoveryError, - model::{Disclosures, DisclosuresContent}, -}; - // handle discover feature request // https://didcomm.org/discover-features/2.0/ -pub fn handle_query_request( - message: Message, +pub async fn handle_query_request( state: Arc, -) -> Result, Response> { + message: Message, +) -> Result, DiscoveryError> { let mut disclosed_protocols: HashSet = HashSet::new(); let supported: &Vec; @@ -76,13 +73,13 @@ pub fn handle_query_request( } } - None => return Err(DiscoveryError::MalformedBody.into_response()), + None => return Err(DiscoveryError::MalformedBody), } } else { - return Err(DiscoveryError::FeatureNOTSupported.into_response()); + return Err(DiscoveryError::FeatureNOTSupported); } } - None => return Err(DiscoveryError::MalformedBody.into_response()), + None => return Err(DiscoveryError::MalformedBody), } } @@ -90,13 +87,14 @@ pub fn handle_query_request( let msg = build_response(disclosed_protocols); Ok(Some(msg)) } else { - return Err(DiscoveryError::QueryNotFound.into_response()); + return Err(DiscoveryError::QueryNotFound); } } else { let msg = build_response(disclosed_protocols); Ok(Some(msg)) } } + fn build_response(disclosed_protocols: HashSet) -> Message { let mut body = Disclosures::new(); for id in disclosed_protocols.iter() { @@ -115,6 +113,7 @@ fn build_response(disclosed_protocols: HashSet) -> Message { msg } + #[cfg(test)] mod test { @@ -147,14 +146,17 @@ mod test { keystore: Arc::new(MockKeyStore::new(vec![])), }; - let state = Arc::new(AppState::from( - public_domain, - diddoc, - Some(vec![ - "https://didcomm.org/coordinate-mediation/2.0/mediate-request".to_string(), - ]), - Some(repository), - )); + let state = Arc::new( + AppState::from( + public_domain, + diddoc, + Some(vec![ + "https://didcomm.org/coordinate-mediation/2.0/mediate-request".to_string(), + ]), + Some(repository), + ) + .unwrap(), + ); state } @@ -170,7 +172,7 @@ mod test { let message = Message::build(id, QUERY_FEATURE.to_string(), json!(body)).finalize(); let state = setup(); - match handle_query_request(message, state) { + match handle_query_request(state, message).await { Ok(result) => { assert!(result.clone().unwrap().body.get("disclosures").is_some()); assert!(result @@ -227,7 +229,7 @@ mod test { let message = Message::build(id, QUERY_FEATURE.to_string(), json!(body)).finalize(); let state = setup(); - match handle_query_request(message, state) { + match handle_query_request(state, message).await { Ok(result) => { println!("{:#?}", result.clone().unwrap()); assert!(result.clone().unwrap().body.get("disclosures").is_some()); @@ -284,7 +286,7 @@ mod test { let message = Message::build(id, QUERY_FEATURE.to_string(), json!(body)).finalize(); - match handle_query_request(message, state) { + match handle_query_request(state, message).await { Ok(_) => { panic!("This should'nt occur"); } diff --git a/crates/web-plugins/didcomm-messaging/protocols/discover-features/src/lib.rs b/crates/web-plugins/didcomm-messaging/protocols/discover-features/src/lib.rs index 10f2947e..0fcdc3af 100644 --- a/crates/web-plugins/didcomm-messaging/protocols/discover-features/src/lib.rs +++ b/crates/web-plugins/didcomm-messaging/protocols/discover-features/src/lib.rs @@ -1,3 +1,4 @@ mod errors; -pub mod handler; mod model; + +pub mod handler; diff --git a/crates/web-plugins/didcomm-messaging/protocols/forward/src/web/routing.rs b/crates/web-plugins/didcomm-messaging/protocols/forward/src/handler.rs similarity index 96% rename from crates/web-plugins/didcomm-messaging/protocols/forward/src/web/routing.rs rename to crates/web-plugins/didcomm-messaging/protocols/forward/src/handler.rs index b841b1a0..9738c361 100644 --- a/crates/web-plugins/didcomm-messaging/protocols/forward/src/web/routing.rs +++ b/crates/web-plugins/didcomm-messaging/protocols/forward/src/handler.rs @@ -1,4 +1,4 @@ -use crate::error::ForwardError; +use crate::ForwardError; use database::Repository; use didcomm::{AttachmentData, Message}; use mongodb::bson::doc; @@ -9,30 +9,9 @@ use shared::{ }; use std::sync::Arc; -async fn checks( - message: &Message, - connection_repository: &Arc>, -) -> Result { - let next = message.body.get("next").and_then(Value::as_str); - match next { - Some(next) => next, - None => return Err(ForwardError::MalformedBody), - }; - - // Check if the client's did in mediator's keylist - let _connection = match connection_repository - .find_one_by(doc! {"keylist": doc!{ "$elemMatch": { "$eq": &next}}}) - .await - .map_err(|_| ForwardError::InternalServerError)? - { - Some(connection) => connection, - None => return Err(ForwardError::UncoordinatedSender), - }; - - Ok(next.unwrap().to_string()) -} - -pub(crate) async fn handler( +/// Mediator receives forwarded messages, extract the next field in the message body, and the attachments in the message +/// then stores the attachment with the next field as key for pickup +pub async fn mediator_forward_process( state: Arc, message: Message, ) -> Result, ForwardError> { @@ -69,10 +48,31 @@ pub(crate) async fn handler( Ok(None) } +async fn checks( + message: &Message, + connection_repository: &Arc>, +) -> Result { + let next = message.body.get("next").and_then(Value::as_str); + match next { + Some(next) => next, + None => return Err(ForwardError::MalformedBody), + }; + + // Check if the client's did in mediator's keylist + let _connection = match connection_repository + .find_one_by(doc! {"keylist": doc!{ "$elemMatch": { "$eq": &next}}}) + .await + .map_err(|_| ForwardError::InternalServerError)? + { + Some(connection) => connection, + None => return Err(ForwardError::UncoordinatedSender), + }; + + Ok(next.unwrap().to_string()) +} + #[cfg(test)] mod test { - use crate::web::handler::mediator_forward_process; - use super::*; use did_utils::jwk::Jwk; use didcomm::{ diff --git a/crates/web-plugins/didcomm-messaging/protocols/forward/src/lib.rs b/crates/web-plugins/didcomm-messaging/protocols/forward/src/lib.rs index e17bcec6..15606af3 100644 --- a/crates/web-plugins/didcomm-messaging/protocols/forward/src/lib.rs +++ b/crates/web-plugins/didcomm-messaging/protocols/forward/src/lib.rs @@ -1,5 +1,5 @@ mod error; -pub mod web; +pub mod handler; // Re-exports pub use error::ForwardError; diff --git a/crates/web-plugins/didcomm-messaging/protocols/forward/src/web/handler.rs b/crates/web-plugins/didcomm-messaging/protocols/forward/src/web/handler.rs deleted file mode 100644 index 92413149..00000000 --- a/crates/web-plugins/didcomm-messaging/protocols/forward/src/web/handler.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::{web::routing::handler, ForwardError}; -use didcomm::Message; -use shared::state::AppState; -use std::sync::Arc; - -/// Mediator receives forwarded messages, extract the next field in the message body, and the attachments in the message -/// then stores the attachment with the next field as key for pickup -pub async fn mediator_forward_process( - state: Arc, - payload: Message, -) -> Result, ForwardError> { - let result = handler(state.clone(), payload).await.unwrap(); - Ok(result) -} diff --git a/crates/web-plugins/didcomm-messaging/protocols/forward/src/web/mod.rs b/crates/web-plugins/didcomm-messaging/protocols/forward/src/web/mod.rs deleted file mode 100644 index 5f05e388..00000000 --- a/crates/web-plugins/didcomm-messaging/protocols/forward/src/web/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod handler; -mod routing; diff --git a/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/Cargo.toml b/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/Cargo.toml index 983550f0..caf38de1 100644 --- a/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/Cargo.toml +++ b/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/Cargo.toml @@ -14,6 +14,7 @@ multibase.workspace = true serde.workspace = true serde_json.workspace = true thiserror.workspace = true +tracing.workspace = true json-canon.workspace = true didcomm = { workspace = true, features = ["uniffi"] } tokio = { workspace = true, features = ["full"] } diff --git a/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/errors.rs b/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/errors.rs index ed25318d..b776eea4 100644 --- a/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/errors.rs +++ b/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/errors.rs @@ -13,6 +13,8 @@ pub enum MediationError { UncoordinatedSender, #[error("could not parse into expected message format")] UnexpectedMessageFormat, + #[error("internal server error")] + InternalServerError, } impl IntoResponse for MediationError { @@ -23,6 +25,7 @@ impl IntoResponse for MediationError { } MediationError::UncoordinatedSender => StatusCode::UNAUTHORIZED, MediationError::UnexpectedMessageFormat => StatusCode::BAD_REQUEST, + MediationError::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR, }; let body = Json(serde_json::json!({ diff --git a/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/web/handler/midlw.rs b/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/handler/midlw.rs similarity index 100% rename from crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/web/handler/midlw.rs rename to crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/handler/midlw.rs diff --git a/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/web/handler/mod.rs b/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/handler/mod.rs similarity index 100% rename from crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/web/handler/mod.rs rename to crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/handler/mod.rs diff --git a/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/web/handler/stateful.rs b/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/handler/stateful.rs similarity index 95% rename from crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/web/handler/stateful.rs rename to crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/handler/stateful.rs index b37077cc..45ad4d7b 100644 --- a/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/web/handler/stateful.rs +++ b/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/handler/stateful.rs @@ -1,11 +1,11 @@ use crate::{ errors::MediationError, + handler::midlw::ensure_jwm_type_is_mediation_request, model::stateful::coord::{ Keylist, KeylistBody, KeylistEntry, KeylistUpdateAction, KeylistUpdateBody, KeylistUpdateConfirmation, KeylistUpdateResponseBody, KeylistUpdateResult, MediationDeny, MediationGrant, MediationGrantBody, }, - web::handler::midlw::ensure_jwm_type_is_mediation_request, }; use did_utils::{ crypto::{Ed25519KeyPair, Generate, ToMultikey, X25519KeyPair}, @@ -40,10 +40,7 @@ pub async fn process_mediate_request( let mediator_did = &state.diddoc.id; - let sender_did = plain_message - .from - .as_ref() - .expect("should not panic as anonymous requests are rejected earlier"); + let sender_did = plain_message.from.as_ref().unwrap(); // Retrieve repository to connection entities @@ -53,15 +50,15 @@ pub async fn process_mediate_request( } = state .repository .as_ref() - .expect("missing persistence layer"); + .ok_or(MediationError::InternalServerError)?; // If there is already mediation, send mediate deny if let Some(_connection) = connection_repository .find_one_by(doc! { "client_did": sender_did}) .await - .unwrap() + .map_err(|_| MediationError::InternalServerError)? { - println!("Sending mediate deny."); + tracing::info!("Sending mediate deny."); return Ok(Some( Message::build( format!("urn:uuid:{}", Uuid::new_v4()), @@ -78,7 +75,7 @@ pub async fn process_mediate_request( )); } else { /* Issue mediate grant response */ - println!("Sending mediate grant."); + tracing::info!("Sending mediate grant."); // Create routing, store it and send mediation grant let (routing_did, auth_keys, agreem_keys) = generate_did_peer(state.public_domain.to_string()); @@ -86,16 +83,19 @@ pub async fn process_mediate_request( let AppStateRepository { keystore, .. } = state .repository .as_ref() - .expect("missing persistence layer"); + .ok_or(MediationError::InternalServerError)?; let diddoc = state .did_resolver .resolve(&routing_did) .await - .unwrap() - .expect("Could not resolve DID"); + .map_err(|err| { + tracing::error!("Failed to resolve DID: {:?}", err); + MediationError::InternalServerError + })? + .ok_or(MediationError::InternalServerError)?; - let agreem_keys_jwk: Jwk = agreem_keys.try_into().expect("MediateRequestError"); + let agreem_keys_jwk: Jwk = agreem_keys.try_into().unwrap(); let agreem_keys_secret = Secrets { id: None, @@ -105,12 +105,12 @@ pub async fn process_mediate_request( match keystore.store(agreem_keys_secret).await { Ok(_stored_connection) => { - println!("Successfully stored connection.") + tracing::info!("Successfully stored agreement keys.") } - Err(error) => eprintln!("Error storing connection: {:?}", error), + Err(error) => tracing::debug!("Error storing agreement keys: {:?}", error), } - let auth_keys_jwk: Jwk = auth_keys.try_into().expect("MediateRequestError"); + let auth_keys_jwk: Jwk = auth_keys.try_into().unwrap(); let auth_keys_secret = Secrets { id: None, @@ -120,9 +120,9 @@ pub async fn process_mediate_request( match keystore.store(auth_keys_secret).await { Ok(_stored_connection) => { - println!("Successfully stored connection.") + tracing::info!("Successfully stored authentication keys.") } - Err(error) => eprintln!("Error storing connection: {:?}", error), + Err(error) => tracing::debug!("Error storing authentication keys: {:?}", error), } let mediation_grant = create_mediation_grant(&routing_did); @@ -138,9 +138,9 @@ pub async fn process_mediate_request( // Use store_one to store the sample connection match connection_repository.store(new_connection).await { Ok(_stored_connection) => { - println!("Successfully stored connection: ") + tracing::info!("Successfully stored connection: ") } - Err(error) => eprintln!("Error storing connection: {:?}", error), + Err(error) => tracing::debug!("Error storing connection: {:?}", error), } Ok(Some( @@ -225,7 +225,7 @@ pub async fn process_plain_keylist_update_message( } = state .repository .as_ref() - .expect("missing persistence layer"); + .ok_or(MediationError::InternalServerError)?; // Find connection for this keylist update @@ -345,7 +345,7 @@ pub async fn process_plain_keylist_query_message( } = state .repository .as_ref() - .expect("missing persistence layer"); + .ok_or(MediationError::InternalServerError)?; let connection = connection_repository .find_one_by(doc! { "client_did": &sender }) diff --git a/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/web/handler/stateless.rs b/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/handler/stateless.rs similarity index 100% rename from crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/web/handler/stateless.rs rename to crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/handler/stateless.rs diff --git a/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/lib.rs b/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/lib.rs index 33872a31..ba63c48c 100644 --- a/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/lib.rs +++ b/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/lib.rs @@ -3,7 +3,7 @@ mod jose; mod model; pub mod client; -pub mod web; +pub mod handler; // Re-exports pub use errors::MediationError; diff --git a/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/web/mod.rs b/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/web/mod.rs deleted file mode 100644 index 062ae9d9..00000000 --- a/crates/web-plugins/didcomm-messaging/protocols/mediator-coordination/src/web/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod handler; diff --git a/crates/web-plugins/didcomm-messaging/shared/Cargo.toml b/crates/web-plugins/didcomm-messaging/shared/Cargo.toml index 2efe62ec..b9ad2128 100644 --- a/crates/web-plugins/didcomm-messaging/shared/Cargo.toml +++ b/crates/web-plugins/didcomm-messaging/shared/Cargo.toml @@ -15,6 +15,8 @@ serde.workspace = true serde_json.workspace = true async-trait.workspace = true mongodb.workspace = true +tracing.workspace = true +eyre.workspace = true hyper = { workspace = true, features = ["full"] } tokio = { workspace = true, features = ["full"] } axum = { workspace = true, features = ["macros"] } diff --git a/crates/web-plugins/didcomm-messaging/shared/src/state.rs b/crates/web-plugins/didcomm-messaging/shared/src/state.rs index dbaa1e18..17aa6b02 100644 --- a/crates/web-plugins/didcomm-messaging/shared/src/state.rs +++ b/crates/web-plugins/didcomm-messaging/shared/src/state.rs @@ -40,22 +40,22 @@ impl AppState { diddoc: Document, disclose_protocols: Option>, repository: Option, - ) -> Self { + ) -> eyre::Result { let did_resolver = LocalDIDResolver::new(&diddoc); let keystore = repository .as_ref() - .expect("Missing persistence layer") + .ok_or_else(|| eyre::eyre!("Missing persistence layer"))? .keystore .clone(); let secrets_resolver = LocalSecretsResolver::new(keystore); - Self { + Ok(Self { public_domain, diddoc, did_resolver, secrets_resolver, repository, supported_protocols: disclose_protocols, - } + }) } } diff --git a/crates/web-plugins/didcomm-messaging/shared/src/utils/resolvers.rs b/crates/web-plugins/didcomm-messaging/shared/src/utils/resolvers.rs index 66d9d5aa..5c562a37 100644 --- a/crates/web-plugins/didcomm-messaging/shared/src/utils/resolvers.rs +++ b/crates/web-plugins/didcomm-messaging/shared/src/utils/resolvers.rs @@ -13,7 +13,7 @@ use didcomm::{ use keystore::Secrets; use mongodb::bson::doc; use serde_json::json; -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; #[derive(Clone)] pub struct LocalDIDResolver { @@ -23,8 +23,7 @@ pub struct LocalDIDResolver { impl LocalDIDResolver { pub fn new(server_diddoc: &Document) -> Self { Self { - diddoc: serde_json::from_value(json!(server_diddoc)) - .expect("Should easily convert between documents representations"), + diddoc: serde_json::from_value(json!(server_diddoc)).unwrap_or_default(), } } } @@ -35,22 +34,18 @@ impl DIDResolver for LocalDIDResolver { if did == self.diddoc.id { let mut diddoc = self.diddoc.clone(); prepend_doc_id_to_vm_ids(&mut diddoc); - return Ok(Some(serde_json::from_value(json!(diddoc)).expect( - "Should easily convert between documents representations", - ))); + return Ok(Some(serde_json::from_value(json!(diddoc))?)); } if did.starts_with("did:key") { Ok(DidKey::new_full(true, PublicKeyFormat::Jwk) .expand(did) .map(|doc| { - Some( - serde_json::from_value(json!(Document { - service: Some(vec![]), - ..doc - })) - .expect("Should easily convert between documents representations"), - ) + serde_json::from_value(json!(Document { + service: Some(vec![]), + ..doc + })) + .ok() }) .map_err(|e| Error::new(ErrorKind::DIDNotResolved, e))?) } else if did.starts_with("did:peer") { @@ -58,10 +53,7 @@ impl DIDResolver for LocalDIDResolver { .expand(did) .map(|mut doc| { prepend_doc_id_to_vm_ids(&mut doc); - Some( - serde_json::from_value(json!(doc)) - .expect("Should easily convert between documents representations"), - ) + serde_json::from_value(json!(doc)).ok() }) .map_err(|e| Error::new(ErrorKind::DIDNotResolved, e))?) } else { @@ -128,7 +120,7 @@ impl SecretsResolver for LocalSecretsResolver { } async fn find_secrets<'a>(&self, secret_ids: &'a [&'a str]) -> Result> { - let mut found_secret_ids = Vec::with_capacity(secret_ids.len()); + let mut found_secret_ids = HashSet::with_capacity(secret_ids.len()); for secret_id in secret_ids.iter() { if self @@ -139,11 +131,11 @@ impl SecretsResolver for LocalSecretsResolver { .map_err(|e| Error::new(ErrorKind::IoError, e))? .is_some() { - found_secret_ids.push(*secret_id); + found_secret_ids.insert(*secret_id); } } - Ok(found_secret_ids) + Ok(found_secret_ids.into_iter().collect()) } } diff --git a/crates/web-plugins/didcomm-messaging/shared/src/utils/tests_utils.rs b/crates/web-plugins/didcomm-messaging/shared/src/utils/tests_utils.rs index 5a406359..7ba2b7b5 100644 --- a/crates/web-plugins/didcomm-messaging/shared/src/utils/tests_utils.rs +++ b/crates/web-plugins/didcomm-messaging/shared/src/utils/tests_utils.rs @@ -93,12 +93,8 @@ pub mod tests { keystore: Arc::new(MockKeyStore::new(vec![mediator_secret])), }; - let state = Arc::new(AppState::from( - public_domain, - diddoc, - None, - Some(repository), - )); + let state = + Arc::new(AppState::from(public_domain, diddoc, None, Some(repository)).unwrap()); state } diff --git a/crates/web-plugins/didcomm-messaging/src/did_rotation/did_rotation.rs b/crates/web-plugins/didcomm-messaging/src/did_rotation/did_rotation.rs index 5b1593ab..ad67df48 100644 --- a/crates/web-plugins/didcomm-messaging/src/did_rotation/did_rotation.rs +++ b/crates/web-plugins/didcomm-messaging/src/did_rotation/did_rotation.rs @@ -80,13 +80,10 @@ mod test { use did_utils::{didcore::Document, jwk::Jwk}; use didcomm::secrets::SecretsResolver; - use hyper::{header::CONTENT_TYPE, Body, Method, Request, StatusCode}; use mongodb::bson::doc; - use tower::ServiceExt; use keystore::{tests::MockKeyStore, Secrets}; use shared::{ - constants::DIDCOMM_ENCRYPTED_MIME_TYPE, repository::{ entity::Connection, tests::{MockConnectionRepository, MockMessagesRepository}, @@ -95,28 +92,6 @@ mod test { utils::resolvers::{LocalDIDResolver, LocalSecretsResolver}, }; - pub fn new_secrets_resolver() -> impl SecretsResolver { - let secret_id = "did:key:z6MkqvgpxveKbuygKXnoRcD3jtLTJLgv7g6asLGLsoC4sUEp#z6LSeQmJnBaXhHz81dCGNDeTUUdMcX1a8p5YSVacaZEDdscp"; - let secret_material: Jwk = serde_json::from_str( - r#"{ - "kty": "OKP", - "crv": "X25519", - "d": "EIR1SxQ67uhVaeUd__sJZ_9pLLgtbVTq12Km8FI5TWY", - "x": "KKBfakcXdzmJ3hhL0mVDg8OIwhTr9rPg_gvc-kPQpCU" - }"#, - ) - .unwrap(); - - let secret = Secrets { - id: None, - kid: secret_id.to_string(), - secret_material, - }; - - let keystore = MockKeyStore::new(vec![secret]); - LocalSecretsResolver::new(Arc::new(keystore)) - } - pub fn prev_did() -> String { "did:key:z6MkrQT3VKYGkbPaYuJeBv31gNgpmVtRWP5yTocLDBgPpayM".to_string() } @@ -162,12 +137,17 @@ mod test { message_repository: Arc::new(MockMessagesRepository::from(vec![])), }; +<<<<<<< HEAD let state = Arc::new(AppState::from( public_domain, diddoc, None, Some(repository), )); +======= + let state = + Arc::new(AppState::from(public_domain, diddoc, None, Some(repository)).unwrap()); +>>>>>>> main state } diff --git a/crates/web-plugins/didcomm-messaging/src/plugin.rs b/crates/web-plugins/didcomm-messaging/src/plugin.rs index 5c9a7373..18effe34 100644 --- a/crates/web-plugins/didcomm-messaging/src/plugin.rs +++ b/crates/web-plugins/didcomm-messaging/src/plugin.rs @@ -12,28 +12,25 @@ use std::sync::Arc; #[derive(Default)] pub struct MediatorCoordination { - env: Option, + env: Option, db: Option, } -struct MediatorCoordinationPluginEnv { +struct DidcommMessagingPluginEnv { public_domain: String, storage_dirpath: String, } /// Loads environment variables required for this plugin -fn load_plugin_env() -> Result { +fn load_plugin_env() -> Result { let public_domain = std::env::var("SERVER_PUBLIC_DOMAIN").map_err(|_| { - tracing::error!("SERVER_PUBLIC_DOMAIN env variable required"); - PluginError::InitError + PluginError::InitError("SERVER_PUBLIC_DOMAIN env variable required".to_owned()) })?; - let storage_dirpath = std::env::var("STORAGE_DIRPATH").map_err(|_| { - tracing::error!("STORAGE_DIRPATH env variable required"); - PluginError::InitError - })?; + let storage_dirpath = std::env::var("STORAGE_DIRPATH") + .map_err(|_| PluginError::InitError("STORAGE_DIRPATH env variable required".to_owned()))?; - Ok(MediatorCoordinationPluginEnv { + Ok(DidcommMessagingPluginEnv { public_domain, storage_dirpath, }) @@ -54,8 +51,9 @@ impl Plugin for MediatorCoordination { if did_endpoint::validate_diddoc(env.storage_dirpath.as_ref(), &keystore, &mut filesystem) .is_err() { - tracing::error!("diddoc validation failed; is plugin did-endpoint mounted?"); - return Err(PluginError::InitError); + return Err(PluginError::InitError( + "diddoc validation failed; is plugin did-endpoint mounted?".to_owned(), + )); } // Check connectivity to database @@ -79,16 +77,20 @@ impl Plugin for MediatorCoordination { Ok(()) } - fn routes(&self) -> Router { + fn routes(&self) -> Result { // Ensure the plugin is properly mounted - let env = self.env.as_ref().expect("Plugin not mounted"); - let db = self.db.as_ref().expect("Plugin not mounted"); - - let msg = "This should not occur following successful mounting."; + let env = self.env.as_ref().ok_or(PluginError::Other( + "Failed to get environment variables. Check if the plugin is mounted".to_owned(), + ))?; + let db = self.db.as_ref().ok_or(PluginError::Other( + "Failed to get database handle. Check if the plugin is mounted".to_owned(), + ))?; // Load crypto identity let fs = StdFileSystem; - let diddoc = utils::read_diddoc(&fs, &env.storage_dirpath).expect(msg); + let diddoc = utils::read_diddoc(&fs, &env.storage_dirpath).map_err(|_| { + PluginError::Other("This should not occur following successful mounting.".to_owned()) + })?; // Load persistence layer let repository = AppStateRepository { @@ -98,9 +100,17 @@ impl Plugin for MediatorCoordination { }; // Compile state +<<<<<<< HEAD let state = AppState::from(env.public_domain.clone(), diddoc, None, Some(repository)); +======= + let state = AppState::from(env.public_domain.clone(), diddoc, None, Some(repository)) + .map_err(|err| { + tracing::error!("Failed to load app state: {:?}", err); + PluginError::Other("Failed to load app state".to_owned()) + })?; +>>>>>>> main // Build router - web::routes(Arc::new(state)) + Ok(web::routes(Arc::new(state))) } } diff --git a/crates/web-plugins/didcomm-messaging/src/web/dispatcher.rs b/crates/web-plugins/didcomm-messaging/src/web/dispatcher.rs index 7d89540d..51fdd67b 100644 --- a/crates/web-plugins/didcomm-messaging/src/web/dispatcher.rs +++ b/crates/web-plugins/didcomm-messaging/src/web/dispatcher.rs @@ -5,12 +5,12 @@ use axum::{ }; use didcomm::Message; use hyper::{header::CONTENT_TYPE, StatusCode}; -use mediator_coordination::web; +use mediator_coordination::handler; use shared::{ constants::{ - DELIVERY_REQUEST_3_0, DIDCOMM_ENCRYPTED_MIME_TYPE, KEYLIST_QUERY_2_0, KEYLIST_UPDATE_2_0, - LIVE_MODE_CHANGE_3_0, MEDIATE_FORWARD_2_0, MEDIATE_REQUEST_2_0, MESSAGE_RECEIVED_3_0, - STATUS_REQUEST_3_0, TRUST_PING_2_0, + DELIVERY_REQUEST_3_0, DIDCOMM_ENCRYPTED_MIME_TYPE, DISCOVER_FEATURE, KEYLIST_QUERY_2_0, + KEYLIST_UPDATE_2_0, LIVE_MODE_CHANGE_3_0, MEDIATE_FORWARD_2_0, MEDIATE_REQUEST_2_0, + MESSAGE_RECEIVED_3_0, STATUS_REQUEST_3_0, TRUST_PING_2_0, }, state::AppState, }; @@ -22,27 +22,22 @@ pub(crate) async fn process_didcomm_message( Extension(message): Extension, ) -> Response { let response: Result, Response> = match message.type_.as_str() { - MEDIATE_FORWARD_2_0 => { - forward::web::handler::mediator_forward_process(state.clone(), message) - .await - .map_err(|e| e.into_response()) - } + MEDIATE_FORWARD_2_0 => forward::handler::mediator_forward_process(state.clone(), message) + .await + .map_err(|e| e.into_response()), + + MEDIATE_REQUEST_2_0 => handler::stateful::process_mediate_request(state.clone(), &message) + .await + .map_err(|e| e.into_response()), - MEDIATE_REQUEST_2_0 => { - web::handler::stateful::process_mediate_request(state.clone(), &message) + KEYLIST_UPDATE_2_0 => { + handler::stateful::process_plain_keylist_update_message(Arc::clone(&state), message) .await .map_err(|e| e.into_response()) } - KEYLIST_UPDATE_2_0 => web::handler::stateful::process_plain_keylist_update_message( - Arc::clone(&state), - message, - ) - .await - .map_err(|e| e.into_response()), - KEYLIST_QUERY_2_0 => { - web::handler::stateful::process_plain_keylist_query_message(state.clone(), message) + handler::stateful::process_plain_keylist_query_message(state.clone(), message) .await .map_err(|e| e.into_response()) } @@ -71,6 +66,12 @@ pub(crate) async fn process_didcomm_message( .await .map_err(|e| e.into_response()), + DISCOVER_FEATURE => { + discover_features::handler::handle_query_request(state.clone(), message) + .await + .map_err(|e| e.into_response()) + } + _ => return (StatusCode::BAD_REQUEST, "Unsupported operation".to_string()).into_response(), }; diff --git a/crates/web-plugins/oob-messages/src/models.rs b/crates/web-plugins/oob-messages/src/models.rs index 0cc53873..6dd93a74 100644 --- a/crates/web-plugins/oob-messages/src/models.rs +++ b/crates/web-plugins/oob-messages/src/models.rs @@ -143,14 +143,14 @@ pub(crate) fn retrieve_or_generate_qr_image( // Update the cache with the retrieved data CACHE .lock() - .map_err(|e| format!("Cache error: {}", e))? + .map_err(|e| format!("Cache error: {:?}", e))? .insert(path.clone(), existing_image.clone()); return Ok(existing_image); } // Generate QR code let qr_code = QrCode::new(url.as_bytes()) - .map_err(|error| format!("Failed to generate QR code: {}", error))?; + .map_err(|error| format!("Failed to generate QR code: {:?}", error))?; let image = qr_code.render::>().build(); @@ -166,10 +166,10 @@ pub(crate) fn retrieve_or_generate_qr_image( // Save to file fs.write_with_lock(path.as_ref(), &base64_string) - .map_err(|e| format!("Error writing: {}", e))?; + .map_err(|e| format!("Error writing: {:?}", e))?; CACHE .lock() - .map_err(|e| format!("Cache error: {}", e))? + .map_err(|e| format!("Cache error: {:?}", e))? .insert(path.clone(), base64_string.clone()); Ok(base64_string) @@ -178,7 +178,7 @@ pub(crate) fn retrieve_or_generate_qr_image( fn to_local_storage(fs: &mut dyn FileSystem, oob_url: &str, storage_dirpath: &str) { // Ensure the parent directory ('storage') exists if let Err(e) = fs.create_dir_all(storage_dirpath.as_ref()) { - tracing::error!("Error creating directory: {}", e); + tracing::error!("Error creating directory: {:?}", e); return; } @@ -186,7 +186,7 @@ fn to_local_storage(fs: &mut dyn FileSystem, oob_url: &str, storage_dirpath: &st // Attempt to write the string directly to the file if let Err(e) = fs.write(file_path.as_ref(), oob_url) { - tracing::error!("Error writing to file: {}", e); + tracing::error!("Error writing to file: {:?}", e); } else { tracing::info!("String successfully written to file."); } diff --git a/crates/web-plugins/oob-messages/src/plugin.rs b/crates/web-plugins/oob-messages/src/plugin.rs index 96bf462f..a33d8824 100644 --- a/crates/web-plugins/oob-messages/src/plugin.rs +++ b/crates/web-plugins/oob-messages/src/plugin.rs @@ -18,18 +18,15 @@ impl Plugin for OOBMessages { let mut fs = StdFileSystem; let server_public_domain = std::env::var("SERVER_PUBLIC_DOMAIN").map_err(|_| { - tracing::error!("SERVER_PUBLIC_DOMAIN env variable required"); - PluginError::InitError + PluginError::InitError("SERVER_PUBLIC_DOMAIN env variable required".to_owned()) })?; let server_local_port = std::env::var("SERVER_LOCAL_PORT").map_err(|_| { - tracing::error!("SERVER_LOCAL_PORT env variable required"); - PluginError::InitError + PluginError::InitError("SERVER_LOCAL_PORT env variable required".to_owned()) })?; let storage_dirpath = std::env::var("STORAGE_DIRPATH").map_err(|_| { - tracing::error!("STORAGE_DIRPATH env variable required"); - PluginError::InitError + PluginError::InitError("STORAGE_DIRPATH env variable required".to_owned()) })?; let oob_inv = retrieve_or_generate_oob_inv( @@ -39,16 +36,16 @@ impl Plugin for OOBMessages { &storage_dirpath, ) .map_err(|e| { - tracing::error!("Error retrieving or generating OOB invitation: {}", e); - PluginError::InitError + PluginError::InitError(format!( + "Error retrieving or generating OOB invitation: {e}" + )) })?; tracing::debug!("Out Of Band Invitation: {}", oob_inv); let _ = retrieve_or_generate_qr_image(&mut fs, &storage_dirpath, &oob_inv).map_err(|e| { - println!("Error retrieving or generating QR code image: {}", e); - PluginError::InitError + PluginError::InitError(format!("Error retrieving or generating QR code image: {e}")) })?; Ok(()) @@ -58,7 +55,7 @@ impl Plugin for OOBMessages { Ok(()) } - fn routes(&self) -> Router { - web::routes() + fn routes(&self) -> Result { + Ok(web::routes()) } } diff --git a/src/lib.rs b/src/lib.rs index ebf6d838..f934c616 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,18 @@ pub mod plugins; use axum::Router; +use eyre::{eyre, Result}; use plugins::handler::PluginContainer; use tower_http::{catch_panic::CatchPanicLayer, trace::TraceLayer}; -pub fn app() -> (PluginContainer<'static>, Router) { +pub fn app() -> Result<(PluginContainer<'static>, Router)> { let mut container = PluginContainer::new(); - let _ = container.load(); + container.load().map_err(|e| eyre!(e))?; let router = Router::new() .merge(container.routes().unwrap_or_default()) .layer(TraceLayer::new_for_http()) .layer(CatchPanicLayer::new()); - (container, router) + Ok((container, router)) } diff --git a/src/main.rs b/src/main.rs index 74c71fde..b3f524c6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,10 @@ use axum::Server; use didcomm_mediator::app; +use eyre::{Result, WrapErr}; use std::net::SocketAddr; #[tokio::main] -async fn main() { +async fn main() -> Result<()> { // Load dotenv-flow variables dotenv_flow::dotenv_flow().ok(); @@ -12,23 +13,32 @@ async fn main() { // Start server let port = std::env::var("SERVER_LOCAL_PORT").unwrap_or("3000".to_owned()); - let addr: SocketAddr = format!("127.0.0.1:{port}").parse().unwrap(); + let addr: SocketAddr = format!("0.0.0.0:{port}") + .parse() + .context("failed to parse address")?; - tracing::info!("listening on {addr}"); - generic_server_with_graceful_shutdown(addr).await; + tracing::debug!("listening on {}", addr); + + generic_server_with_graceful_shutdown(addr) + .await + .map_err(|e| { + tracing::error!("{:?}", e); + e + })?; + + Ok(()) } -async fn generic_server_with_graceful_shutdown(addr: SocketAddr) { +async fn generic_server_with_graceful_shutdown(addr: SocketAddr) -> Result<()> { // Load plugins - let (mut plugin_container, router) = app(); + let (mut plugin_container, router) = app()?; // Spawn task for server - tokio::spawn(async move { - Server::bind(&addr) - .serve(router.into_make_service()) - .await - .unwrap(); - }); + + Server::bind(&addr) + .serve(router.into_make_service()) + .await + .context("failed to start server")?; tokio::select! { _ = tokio::signal::ctrl_c() => { @@ -36,9 +46,16 @@ async fn generic_server_with_graceful_shutdown(addr: SocketAddr) { let _ = plugin_container.unload(); } }; + + Ok(()) } fn config_tracing() { + // Enable errors backtrace + if std::env::var("RUST_LIB_BACKTRACE").is_err() { + std::env::set_var("RUST_LIB_BACKTRACE", "1") + } + use tracing::Level; use tracing_subscriber::{filter, layer::SubscriberExt, util::SubscriberInitExt}; diff --git a/src/plugins/handler.rs b/src/plugins/handler.rs index c70a60d2..f4fd74f7 100644 --- a/src/plugins/handler.rs +++ b/src/plugins/handler.rs @@ -1,15 +1,19 @@ use axum::Router; use std::collections::{HashMap, HashSet}; use std::sync::{Arc, Mutex}; +use thiserror::Error; use super::PLUGINS; -use plugin_api::{Plugin, PluginError}; +use plugin_api::Plugin; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Error)] pub enum PluginContainerError { + #[error("duplicate entry in plugin registry")] DuplicateEntry, + #[error("plugin container is unloaded")] Unloaded, - PluginErrorMap(HashMap), + #[error("{0}")] + ContainerError(String), } pub struct PluginContainer<'a> { @@ -71,26 +75,30 @@ impl<'a> PluginContainer<'a> { self.mounted_plugins.clear(); // Mount plugins and collect routes on successful status - let errors: HashMap<_, _> = self - .plugins - .iter() - .filter_map(|plugin| { - let plugin_clone = plugin.clone(); - let mut plugin = plugin.lock().unwrap(); - match plugin.mount() { - Ok(_) => { - tracing::info!("mounted plugin {}", plugin.name()); - self.collected_routes.push(plugin.routes()); - self.mounted_plugins.push(plugin_clone); - None - } - Err(err) => { - tracing::error!("error mounting plugin {}", plugin.name()); - Some((plugin.name().to_string(), err)) - } + let mut errors = HashMap::new(); + self.mounted_plugins.reserve(self.plugins.len()); + self.collected_routes.reserve(self.plugins.len()); + for plugin in self.plugins.iter() { + let plugin_clone = plugin.clone(); + let mut plugin = plugin.lock().unwrap(); + let plugin_name = plugin.name().to_string(); + match plugin.mount() { + Ok(_) => { + tracing::info!("mounted plugin {}", plugin_name); + self.mounted_plugins.push(plugin_clone); + self.collected_routes.push(plugin.routes().map_err(|err| { + PluginContainerError::ContainerError(format!( + "Error collecting routes for plugin {plugin_name}\n{:?}", + err + )) + })?); } - }) - .collect(); + Err(err) => { + tracing::error!("Error mounting plugin {plugin_name}\n{:?}", err); + errors.insert(plugin_name, err); + } + } + } // Flag as loaded self.loaded = true; @@ -100,7 +108,9 @@ impl<'a> PluginContainer<'a> { tracing::debug!("plugin container loaded"); Ok(()) } else { - Err(PluginContainerError::PluginErrorMap(errors)) + Err(PluginContainerError::ContainerError( + "error loading plugin container".to_string(), + )) } } @@ -142,7 +152,9 @@ impl<'a> PluginContainer<'a> { tracing::debug!("plugin container unloaded"); Ok(()) } else { - Err(PluginContainerError::PluginErrorMap(errors)) + Err(PluginContainerError::ContainerError( + "error unloading plugin container".to_string(), + )) } } @@ -163,6 +175,7 @@ impl<'a> PluginContainer<'a> { mod tests { use super::*; use axum::routing::get; + use plugin_api::PluginError; // Define plugin structs for testing struct FirstPlugin; @@ -179,8 +192,8 @@ mod tests { Ok(()) } - fn routes(&self) -> Router { - Router::new().route("/first", get(|| async {})) + fn routes(&self) -> Result { + Ok(Router::new().route("/first", get(|| async {}))) } } @@ -198,8 +211,8 @@ mod tests { Ok(()) } - fn routes(&self) -> Router { - Router::new().route("/second", get(|| async {})) + fn routes(&self) -> Result { + Ok(Router::new().route("/second", get(|| async {}))) } } @@ -217,8 +230,8 @@ mod tests { Ok(()) } - fn routes(&self) -> Router { - Router::new().route("/second", get(|| async {})) + fn routes(&self) -> Result { + Ok(Router::new().route("/second", get(|| async {}))) } } @@ -229,15 +242,15 @@ mod tests { } fn mount(&mut self) -> Result<(), PluginError> { - Err(PluginError::InitError) + Err(PluginError::InitError("failed to mount".to_owned())) } fn unmount(&self) -> Result<(), PluginError> { Ok(()) } - fn routes(&self) -> Router { - Router::new().route("/faulty", get(|| async {})) + fn routes(&self) -> Result { + Ok(Router::new().route("/faulty", get(|| async {}))) } } @@ -348,7 +361,7 @@ mod tests { assert_eq!( err, - PluginContainerError::PluginErrorMap(expected_error_map) + PluginContainerError::ContainerError("error loading plugin container".to_string(),) ); // Verify collected routes diff --git a/src/plugins/index/mod.rs b/src/plugins/index/mod.rs index 81505041..eb42fe1e 100644 --- a/src/plugins/index/mod.rs +++ b/src/plugins/index/mod.rs @@ -20,7 +20,7 @@ impl Plugin for IndexPlugin { Ok(()) } - fn routes(&self) -> Router { - web::routes() + fn routes(&self) -> Result { + Ok(web::routes()) } }