pub struct ResourceUri(/* private fields */);Expand description
The canonical resource identifier for an MCP server: the resource field,
and the audience a token must carry.
§Canonicalisation
Two rules, both from RFC 9728 §2 and RFC 8707 §2, and both of which a hand-rolled string comparison would get wrong:
- No trailing slash.
https://example.com/mcpandhttps://example.com/mcp/are the same resource to a human and two different strings to anaudcomparison, so the slash is dropped once. A bare origin (https://example.com) keeps none. - No default port.
https://example.com:443/mcpandhttps://example.com/mcpare the same resource, so443and80are dropped for their schemes.
Scheme and host are not case-folded beyond parsing the scheme, because the host part is not something this crate can safely normalise: IDNA punycode and case-sensitive comparison are a much larger problem than authorization needs, and a wrong fold is a wrong audience. What is folded is the scheme, which RFC 3986 §3.1 defines as case-insensitive.
§Examples
use protocol_droid::domain::mcp::ResourceUri;
// The trailing slash is dropped, so both spellings name one resource.
assert_eq!(
ResourceUri::new("https://example.com/mcp/").unwrap().as_str(),
"https://example.com/mcp",
);
assert_eq!(
ResourceUri::new("https://example.com/mcp").unwrap(),
ResourceUri::new("https://example.com/mcp/").unwrap(),
);
// A default port is dropped too.
assert_eq!(
ResourceUri::new("HTTPS://example.com:443/mcp").unwrap().as_str(),
"https://example.com/mcp",
);
// A non-default port is part of the resource and is kept.
assert_eq!(
ResourceUri::new("https://example.com:8443/mcp").unwrap().as_str(),
"https://example.com:8443/mcp",
);Implementations§
Source§impl ResourceUri
impl ResourceUri
Sourcepub fn new(value: &str) -> Result<Self, ResourceUriError>
pub fn new(value: &str) -> Result<Self, ResourceUriError>
Canonicalises a resource identifier.
The inherent twin of FromStr, so a caller reading this module does
not have to know that the parsing rule lives in a trait impl. Both do
the same thing; new exists because the parse in a doctest is noise
when the value came from a config file rather than from user input.
§Errors
The same as ResourceUri::from_str.
§Examples
use protocol_droid::domain::mcp::ResourceUri;
assert_eq!(
ResourceUri::new("https://example.com/mcp/")?.as_str(),
"https://example.com/mcp",
);Sourcepub fn as_str(&self) -> &str
pub fn as_str(&self) -> &str
The canonical string, which is also the resource field and the
expected aud.
Sourcepub fn from_parts(origin: &str, path: &str) -> Result<Self, ResourceUriError>
pub fn from_parts(origin: &str, path: &str) -> Result<Self, ResourceUriError>
Builds a canonical identifier from a base origin and a mounted path.
The join is done here rather than by the caller because the two halves
each arrive from somewhere that gets slash handling wrong: a config file
writes a trailing slash on the origin, and the mount point is an
Endpoint that already requires a leading one.
§Errors
Whatever Self::new returns for the joined string.
§Examples
use protocol_droid::domain::mcp::ResourceUri;
let uri = ResourceUri::from_parts("https://example.com/", "/mcp")?;
assert_eq!(uri.as_str(), "https://example.com/mcp");
// An origin with no path still produces a usable identifier.
let root = ResourceUri::from_parts("https://example.com", "")?;
assert_eq!(root.as_str(), "https://example.com");Sourcepub fn metadata_path(&self) -> String
pub fn metadata_path(&self) -> String
The RFC 9728 path this resource’s metadata is published at.
The resource’s own path appended to WELL_KNOWN, which is the
path-inserted form from RFC 9728 §3.1. A resource at the origin root
publishes at exactly WELL_KNOWN.
§Examples
use protocol_droid::domain::mcp::ResourceUri;
assert_eq!(
ResourceUri::new("https://example.com/mcp").unwrap().metadata_path(),
"/.well-known/oauth-protected-resource/mcp",
);
assert_eq!(
ResourceUri::new("https://example.com").unwrap().metadata_path(),
"/.well-known/oauth-protected-resource",
);Sourcepub fn metadata_url(&self) -> Result<String, ResourceUriError>
pub fn metadata_url(&self) -> Result<String, ResourceUriError>
The absolute URL a WWW-Authenticate challenge points at.
§Errors
ResourceUriError::NoOrigin when this identifier has no origin, which
Self::new rules out — so this cannot fire from a ResourceUri that
was built through the constructor, and exists only so the return type is
honest about the fallback rather than panicking.
§Examples
use protocol_droid::domain::mcp::ResourceUri;
let uri = ResourceUri::new("https://example.com/mcp")?;
assert_eq!(
uri.metadata_url()?,
"https://example.com/.well-known/oauth-protected-resource/mcp",
);Trait Implementations§
Source§impl Clone for ResourceUri
impl Clone for ResourceUri
Source§impl Debug for ResourceUri
impl Debug for ResourceUri
Source§impl Display for ResourceUri
impl Display for ResourceUri
impl Eq for ResourceUri
Source§impl From<&ResourceUri> for Audience
impl From<&ResourceUri> for Audience
Source§fn from(value: &ResourceUri) -> Self
fn from(value: &ResourceUri) -> Self
The canonical form, so a resource identifier and the audience a token carries for it compare equal however the token spelled it.
Source§impl From<ResourceUri> for Audience
impl From<ResourceUri> for Audience
Source§fn from(value: ResourceUri) -> Self
fn from(value: ResourceUri) -> Self
Source§impl FromStr for ResourceUri
impl FromStr for ResourceUri
Source§fn from_str(value: &str) -> Result<Self, Self::Err>
fn from_str(value: &str) -> Result<Self, Self::Err>
§Errors
ResourceUriError::Empty for a blank value, and
ResourceUriError::NotAbsolute when there is no scheme://. RFC 9728
§2 requires resource to be an absolute URI, so a relative one is not
a resource identifier and cannot be compared against an aud.
A trailing slash and a default port are canonicalised rather than refused — see the type docs for why that is the right call.
§Examples
use protocol_droid::domain::mcp::ResourceUri;
assert!(ResourceUri::new("example.com/mcp").is_err());
assert!(ResourceUri::new(" ").is_err());
assert!(ResourceUri::new("https://example.com/mcp").is_ok());Source§type Err = ResourceUriError
type Err = ResourceUriError
Source§impl Hash for ResourceUri
impl Hash for ResourceUri
Source§impl Ord for ResourceUri
impl Ord for ResourceUri
1.21.0 (const: unstable) · Source§fn max(self, other: Self) -> Selfwhere
Self: Sized,
fn max(self, other: Self) -> Selfwhere
Self: Sized,
1.21.0 (const: unstable) · Source§fn min(self, other: Self) -> Selfwhere
Self: Sized,
fn min(self, other: Self) -> Selfwhere
Self: Sized,
Source§impl PartialEq for ResourceUri
impl PartialEq for ResourceUri
Source§impl PartialOrd for ResourceUri
impl PartialOrd for ResourceUri
Source§impl Serialize for ResourceUri
impl Serialize for ResourceUri
Source§fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>
Serialises as the canonical string, which is the RFC 9728 resource
value verbatim.
Hand-written rather than derived because the inner Arc<str> is a
newtype’s implementation detail — a derived impl would make the
canonical form load-bearing in a second place, and the whole point of
the type is that there is exactly one spelling of a resource.