use std::marker::PhantomData;
use crate::agent_id::AgentId;
use crate::capability_path::CapabilityPath;
use crate::constants::MAX_URI_LENGTH;
use crate::error::{
AgentIdError, BuilderError, CapabilityPathError, FragmentError, ParseErrorKind, QueryError,
TrustRootError,
};
use crate::fragment::Fragment;
use crate::query::QueryParams;
use crate::trust_root::TrustRoot;
use crate::uri::AgentUri;
#[derive(Debug, Clone, Copy, Default)]
pub struct Empty;
#[derive(Debug, Clone, Copy, Default)]
pub struct HasTrustRoot;
#[derive(Debug, Clone, Copy, Default)]
pub struct HasCapabilityPath;
#[derive(Debug, Clone, Copy, Default)]
pub struct Ready;
#[derive(Debug, Clone)]
pub struct AgentUriBuilder<State = Empty> {
trust_root: Option<TrustRoot>,
capability_path: Option<CapabilityPath>,
agent_id: Option<AgentId>,
query: QueryParams,
fragment: Option<Fragment>,
_state: PhantomData<State>,
}
impl AgentUriBuilder<Empty> {
#[must_use]
pub fn new() -> Self {
Self {
trust_root: None,
capability_path: None,
agent_id: None,
query: QueryParams::new(),
fragment: None,
_state: PhantomData,
}
}
#[must_use]
pub fn trust_root(self, trust_root: TrustRoot) -> AgentUriBuilder<HasTrustRoot> {
AgentUriBuilder {
trust_root: Some(trust_root),
capability_path: self.capability_path,
agent_id: self.agent_id,
query: self.query,
fragment: self.fragment,
_state: PhantomData,
}
}
pub fn try_trust_root(self, s: &str) -> Result<AgentUriBuilder<HasTrustRoot>, TrustRootError> {
let trust_root = TrustRoot::parse(s)?;
Ok(self.trust_root(trust_root))
}
}
impl Default for AgentUriBuilder<Empty> {
fn default() -> Self {
Self::new()
}
}
impl AgentUriBuilder<HasTrustRoot> {
#[must_use]
pub fn capability_path(
self,
capability_path: CapabilityPath,
) -> AgentUriBuilder<HasCapabilityPath> {
AgentUriBuilder {
trust_root: self.trust_root,
capability_path: Some(capability_path),
agent_id: self.agent_id,
query: self.query,
fragment: self.fragment,
_state: PhantomData,
}
}
pub fn try_capability_path(
self,
s: &str,
) -> Result<AgentUriBuilder<HasCapabilityPath>, CapabilityPathError> {
let capability_path = CapabilityPath::parse(s)?;
Ok(self.capability_path(capability_path))
}
}
impl AgentUriBuilder<HasCapabilityPath> {
#[must_use]
pub fn agent_id(self, agent_id: AgentId) -> AgentUriBuilder<Ready> {
AgentUriBuilder {
trust_root: self.trust_root,
capability_path: self.capability_path,
agent_id: Some(agent_id),
query: self.query,
fragment: self.fragment,
_state: PhantomData,
}
}
pub fn try_agent_id(self, s: &str) -> Result<AgentUriBuilder<Ready>, AgentIdError> {
let agent_id = AgentId::parse(s)?;
Ok(self.agent_id(agent_id))
}
}
impl AgentUriBuilder<Ready> {
pub fn build(self) -> Result<AgentUri, BuilderError> {
let trust_root = self
.trust_root
.expect("trust_root set in HasTrustRoot state");
let capability_path = self
.capability_path
.expect("capability_path set in HasCapabilityPath state");
let agent_id = self.agent_id.expect("agent_id set in Ready state");
AgentUri::new(
trust_root,
capability_path,
agent_id,
self.query,
self.fragment,
)
.map_err(|e| match e.kind {
ParseErrorKind::TooLong { max, actual } => BuilderError::UriTooLong { max, actual },
_ => BuilderError::UriTooLong {
max: MAX_URI_LENGTH,
actual: 0,
},
})
}
}
impl<State> AgentUriBuilder<State> {
#[must_use]
pub fn query(mut self, query: QueryParams) -> Self {
self.query = query;
self
}
#[must_use]
pub fn fragment(mut self, fragment: Fragment) -> Self {
self.fragment = Some(fragment);
self
}
pub fn try_query(self, s: &str) -> Result<Self, QueryError> {
let query = QueryParams::parse(s)?;
Ok(self.query(query))
}
pub fn try_fragment(self, s: &str) -> Result<Self, FragmentError> {
let fragment = Fragment::parse(s)?;
Ok(self.fragment(fragment))
}
#[must_use]
pub fn maybe_query(self, query: Option<QueryParams>) -> Self {
match query {
Some(q) => self.query(q),
None => self,
}
}
#[must_use]
pub fn maybe_fragment(self, fragment: Option<Fragment>) -> Self {
match fragment {
Some(f) => self.fragment(f),
None => self,
}
}
pub fn maybe_query_str(self, s: Option<&str>) -> Result<Self, QueryError> {
match s {
Some(s) => self.try_query(s),
None => Ok(self),
}
}
pub fn maybe_fragment_str(self, s: Option<&str>) -> Result<Self, FragmentError> {
match s {
Some(s) => self.try_fragment(s),
None => Ok(self),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_trust_root() -> TrustRoot {
TrustRoot::parse("anthropic.com").unwrap()
}
fn sample_capability_path() -> CapabilityPath {
CapabilityPath::parse("assistant/chat").unwrap()
}
fn sample_agent_id() -> AgentId {
AgentId::new("llm_chat")
}
#[test]
fn new_creates_empty_builder() {
let builder = AgentUriBuilder::new();
assert!(builder.trust_root.is_none());
assert!(builder.capability_path.is_none());
assert!(builder.agent_id.is_none());
}
#[test]
fn trust_root_transitions_to_has_trust_root() {
let builder = AgentUriBuilder::new().trust_root(sample_trust_root());
assert!(builder.trust_root.is_some());
assert!(builder.capability_path.is_none());
}
#[test]
fn capability_path_transitions_to_has_capability_path() {
let builder = AgentUriBuilder::new()
.trust_root(sample_trust_root())
.capability_path(sample_capability_path());
assert!(builder.trust_root.is_some());
assert!(builder.capability_path.is_some());
assert!(builder.agent_id.is_none());
}
#[test]
fn agent_id_transitions_to_ready() {
let builder = AgentUriBuilder::new()
.trust_root(sample_trust_root())
.capability_path(sample_capability_path())
.agent_id(sample_agent_id());
assert!(builder.trust_root.is_some());
assert!(builder.capability_path.is_some());
assert!(builder.agent_id.is_some());
}
#[test]
fn build_creates_valid_uri() {
let uri = AgentUriBuilder::new()
.trust_root(sample_trust_root())
.capability_path(sample_capability_path())
.agent_id(sample_agent_id())
.build()
.unwrap();
assert_eq!(uri.trust_root().host_str(), "anthropic.com");
assert_eq!(uri.capability_path().as_str(), "assistant/chat");
assert_eq!(uri.agent_id().prefix().as_str(), "llm_chat");
}
#[test]
fn build_with_query_includes_query() {
let query = QueryParams::parse("version=2.0&ttl=300").unwrap();
let uri = AgentUriBuilder::new()
.trust_root(sample_trust_root())
.query(query)
.capability_path(sample_capability_path())
.agent_id(sample_agent_id())
.build()
.unwrap();
assert_eq!(uri.query().version(), Some("2.0"));
assert_eq!(uri.query().ttl(), Some(300));
}
#[test]
fn build_with_fragment_includes_fragment() {
let fragment = Fragment::parse("summarization").unwrap();
let uri = AgentUriBuilder::new()
.trust_root(sample_trust_root())
.capability_path(sample_capability_path())
.fragment(fragment)
.agent_id(sample_agent_id())
.build()
.unwrap();
assert_eq!(
uri.fragment().map(Fragment::as_str),
Some("summarization")
);
}
#[test]
fn build_with_all_optionals() {
let query = QueryParams::parse("version=2.0").unwrap();
let fragment = Fragment::parse("test").unwrap();
let uri = AgentUriBuilder::new()
.trust_root(sample_trust_root())
.capability_path(sample_capability_path())
.agent_id(sample_agent_id())
.query(query)
.fragment(fragment)
.build()
.unwrap();
assert_eq!(uri.query().version(), Some("2.0"));
assert_eq!(uri.fragment().map(Fragment::as_str), Some("test"));
}
#[test]
fn build_too_long_returns_error() {
let long_domain = format!("{}.{}.com", "a".repeat(55), "b".repeat(55));
let trust_root = TrustRoot::parse(&long_domain).unwrap();
let long_path = (0..28).map(|_| "abcdefgh").collect::<Vec<_>>().join("/");
let capability_path = CapabilityPath::parse(&long_path).unwrap();
let agent_id = AgentId::new("very_long_prefix_name_for_this_test");
let long_query = format!("custom={}", "x".repeat(70));
let query = QueryParams::parse(&long_query).unwrap();
let result = AgentUriBuilder::new()
.trust_root(trust_root)
.capability_path(capability_path)
.agent_id(agent_id)
.query(query)
.build();
assert!(matches!(result, Err(BuilderError::UriTooLong { .. })));
}
#[test]
fn query_can_be_set_at_any_state() {
let query = QueryParams::parse("version=1.0").unwrap();
let builder1 = AgentUriBuilder::new()
.trust_root(sample_trust_root())
.query(query.clone());
assert!(!builder1.query.is_empty());
let builder2 = AgentUriBuilder::new()
.trust_root(sample_trust_root())
.capability_path(sample_capability_path())
.query(query.clone());
assert!(!builder2.query.is_empty());
let builder3 = AgentUriBuilder::new()
.trust_root(sample_trust_root())
.capability_path(sample_capability_path())
.agent_id(sample_agent_id())
.query(query);
assert!(!builder3.query.is_empty());
}
#[test]
fn fragment_can_be_set_at_any_state() {
let fragment = Fragment::parse("test").unwrap();
let builder1 = AgentUriBuilder::new()
.trust_root(sample_trust_root())
.fragment(fragment.clone());
assert!(builder1.fragment.is_some());
let builder2 = AgentUriBuilder::new()
.trust_root(sample_trust_root())
.capability_path(sample_capability_path())
.fragment(fragment.clone());
assert!(builder2.fragment.is_some());
let builder3 = AgentUriBuilder::new()
.trust_root(sample_trust_root())
.capability_path(sample_capability_path())
.agent_id(sample_agent_id())
.fragment(fragment);
assert!(builder3.fragment.is_some());
}
#[test]
fn default_creates_empty_builder() {
let builder: AgentUriBuilder<Empty> = AgentUriBuilder::default();
assert!(builder.trust_root.is_none());
}
#[test]
fn clone_preserves_state() {
let builder = AgentUriBuilder::new()
.trust_root(sample_trust_root())
.capability_path(sample_capability_path());
let cloned = builder.clone();
assert!(cloned.trust_root.is_some());
assert!(cloned.capability_path.is_some());
}
#[test]
fn debug_output_is_useful() {
let builder = AgentUriBuilder::new().trust_root(sample_trust_root());
let debug_str = format!("{builder:?}");
assert!(debug_str.contains("AgentUriBuilder"));
assert!(debug_str.contains("trust_root"));
}
}