Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions crates/rmcp/src/transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Cow<'static, str>>,
transport_type_id: std::any::TypeId,
error: Box<dyn std::error::Error + Send + Sync>,
) -> Self {
Self {
transport_name: transport_name.into(),
transport_type_id,
error,
}
}

pub fn downcast<T: Transport<R> + 'static, R: ServiceRole>(self) -> Result<T::Error, Self> {
if !self.is::<T, R>() {
Err(self)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,21 +284,28 @@ impl StreamableHttpClientTransport<reqwest::Client> {
#[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);
}
Expand Down
17 changes: 17 additions & 0 deletions crates/rmcp/src/transport/streamable_http_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -37,6 +46,14 @@ pub struct InsufficientScopeError {
}

impl InsufficientScopeError {
/// Create a new `InsufficientScopeError` instance.
pub fn new(www_authenticate_header: String, required_scope: Option<String>) -> 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()
Expand Down
Loading