diff --git a/crates/rmcp/src/transport.rs b/crates/rmcp/src/transport.rs index 8969f194..89568b3d 100644 --- a/crates/rmcp/src/transport.rs +++ b/crates/rmcp/src/transport.rs @@ -252,6 +252,24 @@ impl DynamicTransportError { error: Box::new(e), } } + + /// Create a `DynamicTransportError` from raw parts. + /// + /// Unlike [`new`](Self::new), this does not require a concrete [`Transport`] type, + /// making it usable in test fixtures and other contexts where a real transport + /// implementation is not available. + pub fn from_parts( + transport_name: impl Into>, + transport_type_id: std::any::TypeId, + error: Box, + ) -> Self { + Self { + transport_name: transport_name.into(), + transport_type_id, + error, + } + } + pub fn downcast + 'static, R: ServiceRole>(self) -> Result { if !self.is::() { Err(self) diff --git a/crates/rmcp/src/transport/common/reqwest/streamable_http_client.rs b/crates/rmcp/src/transport/common/reqwest/streamable_http_client.rs index dea98c7b..b72617bf 100644 --- a/crates/rmcp/src/transport/common/reqwest/streamable_http_client.rs +++ b/crates/rmcp/src/transport/common/reqwest/streamable_http_client.rs @@ -284,21 +284,28 @@ impl StreamableHttpClientTransport { #[cfg(test)] mod tests { use super::parse_json_rpc_error; - use crate::{model::JsonRpcMessage, transport::streamable_http_client::InsufficientScopeError}; + use crate::{ + model::JsonRpcMessage, + transport::streamable_http_client::{AuthRequiredError, InsufficientScopeError}, + }; + + #[test] + fn auth_required_error_new() { + let err = AuthRequiredError::new("Bearer realm=\"test\"".to_string()); + assert_eq!(err.www_authenticate_header, "Bearer realm=\"test\""); + } #[test] fn insufficient_scope_error_can_upgrade() { - let with_scope = InsufficientScopeError { - www_authenticate_header: "Bearer scope=\"admin\"".to_string(), - required_scope: Some("admin".to_string()), - }; + let with_scope = InsufficientScopeError::new( + "Bearer scope=\"admin\"".to_string(), + Some("admin".to_string()), + ); assert!(with_scope.can_upgrade()); assert_eq!(with_scope.get_required_scope(), Some("admin")); - let without_scope = InsufficientScopeError { - www_authenticate_header: "Bearer error=\"insufficient_scope\"".to_string(), - required_scope: None, - }; + let without_scope = + InsufficientScopeError::new("Bearer error=\"insufficient_scope\"".to_string(), None); assert!(!without_scope.can_upgrade()); assert_eq!(without_scope.get_required_scope(), None); } diff --git a/crates/rmcp/src/transport/streamable_http_client.rs b/crates/rmcp/src/transport/streamable_http_client.rs index 980e63db..53794ce5 100644 --- a/crates/rmcp/src/transport/streamable_http_client.rs +++ b/crates/rmcp/src/transport/streamable_http_client.rs @@ -29,6 +29,15 @@ pub struct AuthRequiredError { pub www_authenticate_header: String, } +impl AuthRequiredError { + /// Create a new `AuthRequiredError` instance. + pub fn new(www_authenticate_header: String) -> Self { + Self { + www_authenticate_header, + } + } +} + #[derive(Debug)] #[non_exhaustive] pub struct InsufficientScopeError { @@ -37,6 +46,14 @@ pub struct InsufficientScopeError { } impl InsufficientScopeError { + /// Create a new `InsufficientScopeError` instance. + pub fn new(www_authenticate_header: String, required_scope: Option) -> Self { + Self { + www_authenticate_header, + required_scope, + } + } + /// check if scope upgrade is possible (i.e., we know what scope is required) pub fn can_upgrade(&self) -> bool { self.required_scope.is_some()