Skip to main content

agent_uri_dht/
query.rs

1//! Lookup queries.
2
3use agent_uri::{CapabilityPath, TrustRoot};
4
5/// How a capability path is matched during lookup.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub enum MatchMode {
8    /// Return only agents registered at exactly this path.
9    Exact,
10    /// Return agents at this path and every descendant path.
11    Prefix,
12}
13
14/// A capability lookup.
15///
16/// Every query is scoped to one trust root, because every DHT key is derived
17/// from one. There is deliberately no cross-trust-root query.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Query {
20    trust_root: TrustRoot,
21    capability_path: CapabilityPath,
22    match_mode: MatchMode,
23}
24
25impl Query {
26    /// Creates a query for agents at exactly this capability path.
27    #[must_use]
28    pub const fn exact(trust_root: TrustRoot, capability_path: CapabilityPath) -> Self {
29        Self {
30            trust_root,
31            capability_path,
32            match_mode: MatchMode::Exact,
33        }
34    }
35
36    /// Creates a query for agents at this capability path and its descendants.
37    #[must_use]
38    pub const fn prefix(trust_root: TrustRoot, capability_path: CapabilityPath) -> Self {
39        Self {
40            trust_root,
41            capability_path,
42            match_mode: MatchMode::Prefix,
43        }
44    }
45
46    /// Returns the trust root this query is scoped to.
47    #[must_use]
48    pub const fn trust_root(&self) -> &TrustRoot {
49        &self.trust_root
50    }
51
52    /// Returns the capability path being queried.
53    #[must_use]
54    pub const fn capability_path(&self) -> &CapabilityPath {
55        &self.capability_path
56    }
57
58    /// Returns the match mode.
59    #[must_use]
60    pub const fn match_mode(&self) -> MatchMode {
61        self.match_mode
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    fn parts() -> (TrustRoot, CapabilityPath) {
70        (
71            TrustRoot::parse("anthropic.com").unwrap(),
72            CapabilityPath::parse("assistant/chat").unwrap(),
73        )
74    }
75
76    #[test]
77    fn exact_query_records_its_mode() {
78        let (tr, path) = parts();
79        let q = Query::exact(tr, path);
80        assert_eq!(q.match_mode(), MatchMode::Exact);
81        assert_eq!(q.trust_root().as_str(), "anthropic.com");
82        assert_eq!(q.capability_path().as_str(), "assistant/chat");
83    }
84
85    #[test]
86    fn prefix_query_records_its_mode() {
87        let (tr, path) = parts();
88        let q = Query::prefix(tr, path);
89        assert_eq!(q.match_mode(), MatchMode::Prefix);
90    }
91
92    #[test]
93    fn queries_differing_only_in_mode_are_not_equal() {
94        let (tr, path) = parts();
95        assert_ne!(
96            Query::exact(tr.clone(), path.clone()),
97            Query::prefix(tr, path)
98        );
99    }
100}