Skip to main content

agent_uri/
builder.rs

1//! Typestate builder for constructing [`AgentUri`] instances.
2//!
3//! This module provides a builder that uses phantom types to enforce
4//! at compile-time that components are added in the correct order.
5
6use std::marker::PhantomData;
7
8use crate::agent_id::AgentId;
9use crate::capability_path::CapabilityPath;
10use crate::error::{
11    AgentIdError, BuilderError, CapabilityPathError, FragmentError, ParseErrorKind, QueryError,
12    TrustRootError,
13};
14use crate::fragment::Fragment;
15use crate::query::QueryParams;
16use crate::trust_root::TrustRoot;
17use crate::uri::AgentUri;
18
19/// Marker: No components set yet.
20#[derive(Debug, Clone, Copy, Default)]
21pub struct Empty;
22
23/// Marker: Trust root has been set.
24#[derive(Debug, Clone, Copy, Default)]
25pub struct HasTrustRoot;
26
27/// Marker: Trust root and capability path have been set.
28#[derive(Debug, Clone, Copy, Default)]
29pub struct HasCapabilityPath;
30
31/// Marker: All required components are set, ready to build.
32#[derive(Debug, Clone, Copy, Default)]
33pub struct Ready;
34
35/// A typestate builder for constructing [`AgentUri`] instances.
36///
37/// This builder enforces at compile-time that components are added
38/// in the correct order: trust root, then capability path, then agent ID.
39/// Query and fragment are optional and can be added at any point.
40///
41/// # Type State
42///
43/// The builder uses phantom types to track which components have been set:
44/// - [`Empty`]: Initial state, no components set
45/// - [`HasTrustRoot`]: Trust root has been set
46/// - [`HasCapabilityPath`]: Trust root and capability path have been set
47/// - [`Ready`]: All required components set, can call `build()`
48///
49/// # Examples
50///
51/// ```
52/// use agent_uri::{AgentUriBuilder, TrustRoot, CapabilityPath, AgentId};
53///
54/// let uri = AgentUriBuilder::new()
55///     .trust_root(TrustRoot::parse("anthropic.com").unwrap())
56///     .capability_path(CapabilityPath::parse("assistant/chat").unwrap())
57///     .agent_id(AgentId::new("llm_chat"))
58///     .build()
59///     .unwrap();
60///
61/// assert_eq!(uri.trust_root().host_str(), "anthropic.com");
62/// ```
63///
64/// # Compile-Time Safety
65///
66/// Attempting to call methods out of order results in a compile error:
67///
68/// ```compile_fail
69/// use agent_uri::{AgentUriBuilder, CapabilityPath};
70///
71/// // Error: cannot call capability_path() before trust_root()
72/// let path = CapabilityPath::parse("chat").unwrap();
73/// let builder = AgentUriBuilder::new()
74///     .capability_path(path);  // Compile error!
75/// ```
76///
77/// ```compile_fail
78/// use agent_uri::{AgentUriBuilder, TrustRoot};
79///
80/// // Error: cannot call build() without all required components
81/// let root = TrustRoot::parse("example.com").unwrap();
82/// let uri = AgentUriBuilder::new()
83///     .trust_root(root)
84///     .build();  // Compile error!
85/// ```
86#[derive(Debug, Clone)]
87pub struct AgentUriBuilder<State = Empty> {
88    trust_root: Option<TrustRoot>,
89    capability_path: Option<CapabilityPath>,
90    agent_id: Option<AgentId>,
91    query: QueryParams,
92    fragment: Option<Fragment>,
93    _state: PhantomData<State>,
94}
95
96impl AgentUriBuilder<Empty> {
97    /// Creates a new builder in the initial state.
98    ///
99    /// # Examples
100    ///
101    /// ```
102    /// use agent_uri::AgentUriBuilder;
103    ///
104    /// let builder = AgentUriBuilder::new();
105    /// ```
106    #[must_use]
107    pub fn new() -> Self {
108        Self {
109            trust_root: None,
110            capability_path: None,
111            agent_id: None,
112            query: QueryParams::new(),
113            fragment: None,
114            _state: PhantomData,
115        }
116    }
117
118    /// Sets the trust root and advances to the [`HasTrustRoot`] state.
119    ///
120    /// This must be called first before setting other required components.
121    ///
122    /// # Arguments
123    ///
124    /// * `trust_root` - The validated trust root (authority) for the URI
125    ///
126    /// # Examples
127    ///
128    /// ```
129    /// use agent_uri::{AgentUriBuilder, TrustRoot};
130    ///
131    /// let builder = AgentUriBuilder::new()
132    ///     .trust_root(TrustRoot::parse("anthropic.com").unwrap());
133    /// ```
134    #[must_use]
135    pub fn trust_root(self, trust_root: TrustRoot) -> AgentUriBuilder<HasTrustRoot> {
136        AgentUriBuilder {
137            trust_root: Some(trust_root),
138            capability_path: self.capability_path,
139            agent_id: self.agent_id,
140            query: self.query,
141            fragment: self.fragment,
142            _state: PhantomData,
143        }
144    }
145
146    /// Parses and sets the trust root from a string.
147    ///
148    /// This is a convenience method that combines parsing and setting the trust root.
149    ///
150    /// # Errors
151    ///
152    /// Returns [`TrustRootError`] if the string is not a valid trust root.
153    ///
154    /// # Examples
155    ///
156    /// ```
157    /// use agent_uri::AgentUriBuilder;
158    ///
159    /// let builder = AgentUriBuilder::new()
160    ///     .try_trust_root("anthropic.com")?;
161    /// # Ok::<(), agent_uri::TrustRootError>(())
162    /// ```
163    pub fn try_trust_root(self, s: &str) -> Result<AgentUriBuilder<HasTrustRoot>, TrustRootError> {
164        let trust_root = TrustRoot::parse(s)?;
165        Ok(self.trust_root(trust_root))
166    }
167}
168
169impl Default for AgentUriBuilder<Empty> {
170    fn default() -> Self {
171        Self::new()
172    }
173}
174
175impl AgentUriBuilder<HasTrustRoot> {
176    /// Sets the capability path and advances to the [`HasCapabilityPath`] state.
177    ///
178    /// This must be called after `trust_root()` and before `agent_id()`.
179    ///
180    /// # Arguments
181    ///
182    /// * `capability_path` - The validated capability path describing the agent's function
183    ///
184    /// # Examples
185    ///
186    /// ```
187    /// use agent_uri::{AgentUriBuilder, TrustRoot, CapabilityPath};
188    ///
189    /// let builder = AgentUriBuilder::new()
190    ///     .trust_root(TrustRoot::parse("anthropic.com").unwrap())
191    ///     .capability_path(CapabilityPath::parse("assistant/chat").unwrap());
192    /// ```
193    #[must_use]
194    pub fn capability_path(
195        self,
196        capability_path: CapabilityPath,
197    ) -> AgentUriBuilder<HasCapabilityPath> {
198        AgentUriBuilder {
199            trust_root: self.trust_root,
200            capability_path: Some(capability_path),
201            agent_id: self.agent_id,
202            query: self.query,
203            fragment: self.fragment,
204            _state: PhantomData,
205        }
206    }
207
208    /// Parses and sets the capability path from a string.
209    ///
210    /// This is a convenience method that combines parsing and setting the capability path.
211    ///
212    /// # Errors
213    ///
214    /// Returns [`CapabilityPathError`] if the string is not a valid capability path.
215    ///
216    /// # Examples
217    ///
218    /// ```
219    /// use agent_uri::AgentUriBuilder;
220    ///
221    /// let builder = AgentUriBuilder::new()
222    ///     .try_trust_root("anthropic.com")?
223    ///     .try_capability_path("assistant/chat")?;
224    /// # Ok::<(), Box<dyn std::error::Error>>(())
225    /// ```
226    pub fn try_capability_path(
227        self,
228        s: &str,
229    ) -> Result<AgentUriBuilder<HasCapabilityPath>, CapabilityPathError> {
230        let capability_path = CapabilityPath::parse(s)?;
231        Ok(self.capability_path(capability_path))
232    }
233}
234
235impl AgentUriBuilder<HasCapabilityPath> {
236    /// Sets the agent ID and advances to the [`Ready`] state.
237    ///
238    /// After calling this, the builder is ready to call `build()`.
239    ///
240    /// # Arguments
241    ///
242    /// * `agent_id` - The validated agent identifier
243    ///
244    /// # Examples
245    ///
246    /// ```
247    /// use agent_uri::{AgentUriBuilder, TrustRoot, CapabilityPath, AgentId};
248    ///
249    /// let builder = AgentUriBuilder::new()
250    ///     .trust_root(TrustRoot::parse("anthropic.com").unwrap())
251    ///     .capability_path(CapabilityPath::parse("assistant/chat").unwrap())
252    ///     .agent_id(AgentId::new("llm_chat"));
253    /// // Builder is now in Ready state and can call build()
254    /// ```
255    #[must_use]
256    pub fn agent_id(self, agent_id: AgentId) -> AgentUriBuilder<Ready> {
257        AgentUriBuilder {
258            trust_root: self.trust_root,
259            capability_path: self.capability_path,
260            agent_id: Some(agent_id),
261            query: self.query,
262            fragment: self.fragment,
263            _state: PhantomData,
264        }
265    }
266
267    /// Parses and sets the agent ID from a string.
268    ///
269    /// This is a convenience method that combines parsing and setting the agent ID.
270    ///
271    /// # Errors
272    ///
273    /// Returns [`AgentIdError`] if the string is not a valid agent ID.
274    ///
275    /// # Examples
276    ///
277    /// ```
278    /// use agent_uri::AgentUriBuilder;
279    ///
280    /// let builder = AgentUriBuilder::new()
281    ///     .try_trust_root("anthropic.com")?
282    ///     .try_capability_path("assistant/chat")?
283    ///     .try_agent_id("llm_chat_01h455vb4pex5vsknk084sn02q")?;
284    /// # Ok::<(), Box<dyn std::error::Error>>(())
285    /// ```
286    pub fn try_agent_id(self, s: &str) -> Result<AgentUriBuilder<Ready>, AgentIdError> {
287        let agent_id = AgentId::parse(s)?;
288        Ok(self.agent_id(agent_id))
289    }
290}
291
292impl AgentUriBuilder<Ready> {
293    /// Builds the final [`AgentUri`].
294    ///
295    /// This method is only available when the builder is in the [`Ready`] state,
296    /// meaning all required components (trust root, capability path, agent ID)
297    /// have been set.
298    ///
299    /// # Errors
300    ///
301    /// Returns [`BuilderError::UriTooLong`] if the resulting URI would exceed
302    /// the maximum allowed length of 512 characters. Returns
303    /// [`BuilderError::InvalidUri`] with the underlying parse error if the
304    /// components cannot be assembled for any other reason.
305    ///
306    /// # Panics
307    ///
308    /// This method will not panic in practice because the typestate pattern
309    /// guarantees all required fields are set before `build()` can be called.
310    /// The internal `expect()` calls are for defense-in-depth only.
311    ///
312    /// # Examples
313    ///
314    /// ```
315    /// use agent_uri::{AgentUriBuilder, TrustRoot, CapabilityPath, AgentId};
316    ///
317    /// let uri = AgentUriBuilder::new()
318    ///     .trust_root(TrustRoot::parse("anthropic.com").unwrap())
319    ///     .capability_path(CapabilityPath::parse("assistant/chat").unwrap())
320    ///     .agent_id(AgentId::new("llm_chat"))
321    ///     .build()
322    ///     .unwrap();
323    ///
324    /// assert_eq!(uri.trust_root().host_str(), "anthropic.com");
325    /// ```
326    pub fn build(self) -> Result<AgentUri, BuilderError> {
327        // SAFETY: These are guaranteed to be Some because the typestate
328        // ensures all required fields are set when we reach the Ready state.
329        // The only way to reach Ready is through the proper state transitions.
330        let trust_root = self
331            .trust_root
332            .expect("trust_root set in HasTrustRoot state");
333        let capability_path = self
334            .capability_path
335            .expect("capability_path set in HasCapabilityPath state");
336        let agent_id = self.agent_id.expect("agent_id set in Ready state");
337
338        AgentUri::new(
339            trust_root,
340            capability_path,
341            agent_id,
342            self.query,
343            self.fragment,
344        )
345        // `InvalidUri` is currently unreachable because `AgentUri::new` only returns `TooLong`;
346        // it preserves future failure modes instead of masking them as length errors.
347        .map_err(|e| match &e.kind {
348            ParseErrorKind::TooLong { max, actual } => BuilderError::UriTooLong {
349                max: *max,
350                actual: *actual,
351            },
352            _ => BuilderError::InvalidUri(e),
353        })
354    }
355}
356
357/// Methods available in all states after Empty for optional components.
358impl<State> AgentUriBuilder<State> {
359    /// Sets optional query parameters.
360    ///
361    /// This can be called at any point in the builder chain.
362    /// If called multiple times, the last value wins.
363    ///
364    /// # Arguments
365    ///
366    /// * `query` - The query parameters to include in the URI
367    ///
368    /// # Examples
369    ///
370    /// ```
371    /// use agent_uri::{AgentUriBuilder, TrustRoot, CapabilityPath, AgentId, QueryParams};
372    ///
373    /// let uri = AgentUriBuilder::new()
374    ///     .trust_root(TrustRoot::parse("anthropic.com").unwrap())
375    ///     .query(QueryParams::parse("version=2.0").unwrap())
376    ///     .capability_path(CapabilityPath::parse("chat").unwrap())
377    ///     .agent_id(AgentId::new("llm"))
378    ///     .build()
379    ///     .unwrap();
380    ///
381    /// assert_eq!(uri.query().version(), Some("2.0"));
382    /// ```
383    #[must_use]
384    pub fn query(mut self, query: QueryParams) -> Self {
385        self.query = query;
386        self
387    }
388
389    /// Sets the optional fragment.
390    ///
391    /// This can be called at any point in the builder chain.
392    /// If called multiple times, the last value wins.
393    ///
394    /// # Arguments
395    ///
396    /// * `fragment` - The fragment to include in the URI
397    ///
398    /// # Examples
399    ///
400    /// ```
401    /// use agent_uri::{AgentUriBuilder, TrustRoot, CapabilityPath, AgentId, Fragment};
402    ///
403    /// let uri = AgentUriBuilder::new()
404    ///     .trust_root(TrustRoot::parse("anthropic.com").unwrap())
405    ///     .capability_path(CapabilityPath::parse("chat").unwrap())
406    ///     .fragment(Fragment::parse("summarization").unwrap())
407    ///     .agent_id(AgentId::new("llm"))
408    ///     .build()
409    ///     .unwrap();
410    ///
411    /// assert_eq!(uri.fragment().map(|f| f.as_str()), Some("summarization"));
412    /// ```
413    #[must_use]
414    pub fn fragment(mut self, fragment: Fragment) -> Self {
415        self.fragment = Some(fragment);
416        self
417    }
418
419    /// Parses and sets the query parameters from a string.
420    ///
421    /// This is a convenience method that combines parsing and setting query parameters.
422    ///
423    /// # Errors
424    ///
425    /// Returns [`QueryError`] if the string is not a valid query string.
426    ///
427    /// # Examples
428    ///
429    /// ```
430    /// use agent_uri::AgentUriBuilder;
431    ///
432    /// let builder = AgentUriBuilder::new()
433    ///     .try_query("version=2.0&ttl=300")?;
434    /// # Ok::<(), agent_uri::QueryError>(())
435    /// ```
436    pub fn try_query(self, s: &str) -> Result<Self, QueryError> {
437        let query = QueryParams::parse(s)?;
438        Ok(self.query(query))
439    }
440
441    /// Parses and sets the fragment from a string.
442    ///
443    /// This is a convenience method that combines parsing and setting the fragment.
444    ///
445    /// # Errors
446    ///
447    /// Returns [`FragmentError`] if the string is not a valid fragment.
448    ///
449    /// # Examples
450    ///
451    /// ```
452    /// use agent_uri::AgentUriBuilder;
453    ///
454    /// let builder = AgentUriBuilder::new()
455    ///     .try_fragment("summarization")?;
456    /// # Ok::<(), agent_uri::FragmentError>(())
457    /// ```
458    pub fn try_fragment(self, s: &str) -> Result<Self, FragmentError> {
459        let fragment = Fragment::parse(s)?;
460        Ok(self.fragment(fragment))
461    }
462
463    /// Sets the query if provided, otherwise leaves it unchanged.
464    ///
465    /// This is useful when the query is optional and may be `None`.
466    ///
467    /// # Examples
468    ///
469    /// ```
470    /// use agent_uri::{AgentUriBuilder, QueryParams};
471    ///
472    /// let query = Some(QueryParams::parse("version=2.0").unwrap());
473    /// let builder = AgentUriBuilder::new()
474    ///     .maybe_query(query);
475    /// ```
476    #[must_use]
477    pub fn maybe_query(self, query: Option<QueryParams>) -> Self {
478        match query {
479            Some(q) => self.query(q),
480            None => self,
481        }
482    }
483
484    /// Sets the fragment if provided, otherwise leaves it unchanged.
485    ///
486    /// This is useful when the fragment is optional and may be `None`.
487    ///
488    /// # Examples
489    ///
490    /// ```
491    /// use agent_uri::{AgentUriBuilder, Fragment};
492    ///
493    /// let fragment = Some(Fragment::parse("test").unwrap());
494    /// let builder = AgentUriBuilder::new()
495    ///     .maybe_fragment(fragment);
496    /// ```
497    #[must_use]
498    pub fn maybe_fragment(self, fragment: Option<Fragment>) -> Self {
499        match fragment {
500            Some(f) => self.fragment(f),
501            None => self,
502        }
503    }
504
505    /// Parses and sets the query from a string if provided.
506    ///
507    /// This is useful when the query string is optional and may be `None`.
508    ///
509    /// # Errors
510    ///
511    /// Returns [`QueryError`] if the provided string is not a valid query string.
512    ///
513    /// # Examples
514    ///
515    /// ```
516    /// use agent_uri::AgentUriBuilder;
517    ///
518    /// let query_str = Some("version=2.0");
519    /// let builder = AgentUriBuilder::new()
520    ///     .maybe_query_str(query_str)?;
521    /// # Ok::<(), agent_uri::QueryError>(())
522    /// ```
523    pub fn maybe_query_str(self, s: Option<&str>) -> Result<Self, QueryError> {
524        match s {
525            Some(s) => self.try_query(s),
526            None => Ok(self),
527        }
528    }
529
530    /// Parses and sets the fragment from a string if provided.
531    ///
532    /// This is useful when the fragment string is optional and may be `None`.
533    ///
534    /// # Errors
535    ///
536    /// Returns [`FragmentError`] if the provided string is not a valid fragment.
537    ///
538    /// # Examples
539    ///
540    /// ```
541    /// use agent_uri::AgentUriBuilder;
542    ///
543    /// let fragment_str = Some("summarization");
544    /// let builder = AgentUriBuilder::new()
545    ///     .maybe_fragment_str(fragment_str)?;
546    /// # Ok::<(), agent_uri::FragmentError>(())
547    /// ```
548    pub fn maybe_fragment_str(self, s: Option<&str>) -> Result<Self, FragmentError> {
549        match s {
550            Some(s) => self.try_fragment(s),
551            None => Ok(self),
552        }
553    }
554}
555
556#[cfg(test)]
557mod tests {
558    use super::*;
559
560    fn sample_trust_root() -> TrustRoot {
561        TrustRoot::parse("anthropic.com").unwrap()
562    }
563
564    fn sample_capability_path() -> CapabilityPath {
565        CapabilityPath::parse("assistant/chat").unwrap()
566    }
567
568    fn sample_agent_id() -> AgentId {
569        AgentId::new("llm_chat")
570    }
571
572    #[test]
573    fn new_creates_empty_builder() {
574        let builder = AgentUriBuilder::new();
575        assert!(builder.trust_root.is_none());
576        assert!(builder.capability_path.is_none());
577        assert!(builder.agent_id.is_none());
578    }
579
580    #[test]
581    fn trust_root_transitions_to_has_trust_root() {
582        let builder = AgentUriBuilder::new().trust_root(sample_trust_root());
583        assert!(builder.trust_root.is_some());
584        assert!(builder.capability_path.is_none());
585    }
586
587    #[test]
588    fn capability_path_transitions_to_has_capability_path() {
589        let builder = AgentUriBuilder::new()
590            .trust_root(sample_trust_root())
591            .capability_path(sample_capability_path());
592        assert!(builder.trust_root.is_some());
593        assert!(builder.capability_path.is_some());
594        assert!(builder.agent_id.is_none());
595    }
596
597    #[test]
598    fn agent_id_transitions_to_ready() {
599        let builder = AgentUriBuilder::new()
600            .trust_root(sample_trust_root())
601            .capability_path(sample_capability_path())
602            .agent_id(sample_agent_id());
603        assert!(builder.trust_root.is_some());
604        assert!(builder.capability_path.is_some());
605        assert!(builder.agent_id.is_some());
606    }
607
608    #[test]
609    fn build_creates_valid_uri() {
610        let uri = AgentUriBuilder::new()
611            .trust_root(sample_trust_root())
612            .capability_path(sample_capability_path())
613            .agent_id(sample_agent_id())
614            .build()
615            .unwrap();
616
617        assert_eq!(uri.trust_root().host_str(), "anthropic.com");
618        assert_eq!(uri.capability_path().as_str(), "assistant/chat");
619        assert_eq!(uri.agent_id().prefix().as_str(), "llm_chat");
620    }
621
622    #[test]
623    fn build_with_query_includes_query() {
624        let query = QueryParams::parse("version=2.0&ttl=300").unwrap();
625
626        let uri = AgentUriBuilder::new()
627            .trust_root(sample_trust_root())
628            .query(query)
629            .capability_path(sample_capability_path())
630            .agent_id(sample_agent_id())
631            .build()
632            .unwrap();
633
634        assert_eq!(uri.query().version(), Some("2.0"));
635        assert_eq!(uri.query().ttl(), Some(300));
636    }
637
638    #[test]
639    fn build_with_fragment_includes_fragment() {
640        let fragment = Fragment::parse("summarization").unwrap();
641
642        let uri = AgentUriBuilder::new()
643            .trust_root(sample_trust_root())
644            .capability_path(sample_capability_path())
645            .fragment(fragment)
646            .agent_id(sample_agent_id())
647            .build()
648            .unwrap();
649
650        assert_eq!(uri.fragment().map(Fragment::as_str), Some("summarization"));
651    }
652
653    #[test]
654    fn build_with_all_optionals() {
655        let query = QueryParams::parse("version=2.0").unwrap();
656        let fragment = Fragment::parse("test").unwrap();
657
658        let uri = AgentUriBuilder::new()
659            .trust_root(sample_trust_root())
660            .capability_path(sample_capability_path())
661            .agent_id(sample_agent_id())
662            .query(query)
663            .fragment(fragment)
664            .build()
665            .unwrap();
666
667        assert_eq!(uri.query().version(), Some("2.0"));
668        assert_eq!(uri.fragment().map(Fragment::as_str), Some("test"));
669    }
670
671    #[test]
672    fn build_too_long_returns_error() {
673        // Create components that are individually valid but together exceed 512 chars.
674        // The basic components (scheme + trust_root + path + agent_id) max out at ~484,
675        // so we need a long query string to push over 512.
676
677        // Trust root: ~115 chars (55 + 1 + 55 + 4)
678        let long_domain = format!("{}.{}.com", "a".repeat(55), "b".repeat(55));
679        let trust_root = TrustRoot::parse(&long_domain).unwrap();
680
681        // Capability path: ~251 chars (28 segments × 8 chars + 27 slashes = 224 + 27)
682        let long_path = (0..28).map(|_| "abcdefgh").collect::<Vec<_>>().join("/");
683        let capability_path = CapabilityPath::parse(&long_path).unwrap();
684
685        // Agent ID: ~63 chars (prefix + underscore + 26 char suffix)
686        let agent_id = AgentId::new("very_long_prefix_name_for_this_test");
687
688        // Long query parameter to push over 512
689        // Current components total 506 chars, need 7+ more to exceed 512
690        let long_query = format!("custom={}", "x".repeat(70));
691        let query = QueryParams::parse(&long_query).unwrap();
692
693        let result = AgentUriBuilder::new()
694            .trust_root(trust_root)
695            .capability_path(capability_path)
696            .agent_id(agent_id)
697            .query(query)
698            .build();
699
700        let Err(BuilderError::UriTooLong { max, actual }) = result else {
701            panic!("expected BuilderError::UriTooLong");
702        };
703        assert_eq!(max, 512);
704        assert!(actual > 512);
705    }
706
707    #[test]
708    fn query_can_be_set_at_any_state() {
709        let query = QueryParams::parse("version=1.0").unwrap();
710
711        // Set query after trust_root
712        let builder1 = AgentUriBuilder::new()
713            .trust_root(sample_trust_root())
714            .query(query.clone());
715        assert!(!builder1.query.is_empty());
716
717        // Set query after capability_path
718        let builder2 = AgentUriBuilder::new()
719            .trust_root(sample_trust_root())
720            .capability_path(sample_capability_path())
721            .query(query.clone());
722        assert!(!builder2.query.is_empty());
723
724        // Set query after agent_id (in Ready state)
725        let builder3 = AgentUriBuilder::new()
726            .trust_root(sample_trust_root())
727            .capability_path(sample_capability_path())
728            .agent_id(sample_agent_id())
729            .query(query);
730        assert!(!builder3.query.is_empty());
731    }
732
733    #[test]
734    fn fragment_can_be_set_at_any_state() {
735        let fragment = Fragment::parse("test").unwrap();
736
737        // Set fragment after trust_root
738        let builder1 = AgentUriBuilder::new()
739            .trust_root(sample_trust_root())
740            .fragment(fragment.clone());
741        assert!(builder1.fragment.is_some());
742
743        // Set fragment after capability_path
744        let builder2 = AgentUriBuilder::new()
745            .trust_root(sample_trust_root())
746            .capability_path(sample_capability_path())
747            .fragment(fragment.clone());
748        assert!(builder2.fragment.is_some());
749
750        // Set fragment after agent_id (in Ready state)
751        let builder3 = AgentUriBuilder::new()
752            .trust_root(sample_trust_root())
753            .capability_path(sample_capability_path())
754            .agent_id(sample_agent_id())
755            .fragment(fragment);
756        assert!(builder3.fragment.is_some());
757    }
758
759    #[test]
760    fn default_creates_empty_builder() {
761        let builder: AgentUriBuilder<Empty> = AgentUriBuilder::default();
762        assert!(builder.trust_root.is_none());
763    }
764
765    #[test]
766    fn clone_preserves_state() {
767        let builder = AgentUriBuilder::new()
768            .trust_root(sample_trust_root())
769            .capability_path(sample_capability_path());
770
771        let cloned = builder.clone();
772        assert!(cloned.trust_root.is_some());
773        assert!(cloned.capability_path.is_some());
774    }
775
776    #[test]
777    fn debug_output_is_useful() {
778        let builder = AgentUriBuilder::new().trust_root(sample_trust_root());
779
780        let debug_str = format!("{builder:?}");
781        assert!(debug_str.contains("AgentUriBuilder"));
782        assert!(debug_str.contains("trust_root"));
783    }
784}