Skip to main content

ryo_pattern/
relation.rs

1//! Relation queries with logical grouping
2//!
3//! Provides `any`/`all`/`none` logical operators for relation-based filtering.
4
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8/// Relation conditions with logical grouping
9#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
10pub struct Relations {
11    /// At least one relation matches (OR)
12    #[serde(default, skip_serializing_if = "Option::is_none")]
13    pub any: Option<Vec<Relation>>,
14
15    /// Every relation must match (AND)
16    #[serde(default, skip_serializing_if = "Option::is_none")]
17    pub all: Option<Vec<Relation>>,
18
19    /// No relation matches (NOT)
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub none: Option<Vec<Relation>>,
22}
23
24impl Relations {
25    /// Construct an empty `Relations` set.
26    pub fn new() -> Self {
27        Self::default()
28    }
29
30    /// Add a relation to `any` group
31    pub fn any(mut self, relation: Relation) -> Self {
32        self.any.get_or_insert_with(Vec::new).push(relation);
33        self
34    }
35
36    /// Add a relation to `all` group
37    pub fn all(mut self, relation: Relation) -> Self {
38        self.all.get_or_insert_with(Vec::new).push(relation);
39        self
40    }
41
42    /// Add a relation to `none` group
43    pub fn none(mut self, relation: Relation) -> Self {
44        self.none.get_or_insert_with(Vec::new).push(relation);
45        self
46    }
47
48    /// Check if relations is empty
49    pub fn is_empty(&self) -> bool {
50        self.any.is_none() && self.all.is_none() && self.none.is_none()
51    }
52}
53
54/// Single relation condition
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
56pub struct Relation {
57    /// Relation type
58    pub kind: RelationKind,
59
60    /// Target query (simplified for now - will be expanded)
61    pub target: RelationTarget,
62
63    /// Include transitive relations
64    #[serde(default)]
65    pub transitive: bool,
66
67    /// Maximum traversal depth for transitive queries (None = unlimited)
68    #[serde(default, skip_serializing_if = "Option::is_none")]
69    pub max_depth: Option<u32>,
70
71    /// Traversal scope for transitive queries
72    #[serde(default)]
73    pub scope: TraversalScope,
74
75    /// Bind result to variable
76    #[serde(default, skip_serializing_if = "Option::is_none")]
77    pub r#as: Option<String>,
78}
79
80impl Relation {
81    /// Create a new relation
82    pub fn new(kind: RelationKind, target: RelationTarget) -> Self {
83        Self {
84            kind,
85            target,
86            transitive: false,
87            max_depth: None,
88            scope: TraversalScope::default(),
89            r#as: None,
90        }
91    }
92
93    /// Set transitive mode
94    pub fn transitive(mut self) -> Self {
95        self.transitive = true;
96        self
97    }
98
99    /// Set max depth
100    pub fn with_max_depth(mut self, depth: u32) -> Self {
101        self.max_depth = Some(depth);
102        self
103    }
104
105    /// Set scope
106    pub fn with_scope(mut self, scope: TraversalScope) -> Self {
107        self.scope = scope;
108        self
109    }
110
111    /// Bind to variable
112    pub fn bind(mut self, var: impl Into<String>) -> Self {
113        self.r#as = Some(var.into());
114        self
115    }
116}
117
118/// Simplified target for relation queries
119#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
120pub struct RelationTarget {
121    /// Symbol kind to match
122    #[serde(default, skip_serializing_if = "Option::is_none")]
123    pub kind: Option<TargetKind>,
124
125    /// Name match condition
126    #[serde(default, skip_serializing_if = "Option::is_none")]
127    pub r#match: Option<TargetMatch>,
128}
129
130impl RelationTarget {
131    /// Construct an empty `RelationTarget` (no kind / no match).
132    pub fn new() -> Self {
133        Self {
134            kind: None,
135            r#match: None,
136        }
137    }
138
139    /// Set the target symbol kind.
140    pub fn kind(mut self, kind: TargetKind) -> Self {
141        self.kind = Some(kind);
142        self
143    }
144
145    /// Set the target symbol name (exact match).
146    pub fn name(mut self, name: impl Into<String>) -> Self {
147        self.r#match = Some(TargetMatch {
148            name: Some(name.into()),
149            ..Default::default()
150        });
151        self
152    }
153}
154
155impl Default for RelationTarget {
156    fn default() -> Self {
157        Self::new()
158    }
159}
160
161/// Target symbol kind
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
163pub enum TargetKind {
164    /// `fn` item.
165    Function,
166    /// `struct` item.
167    Struct,
168    /// `enum` item.
169    Enum,
170    /// `trait` item.
171    Trait,
172    /// `impl` block.
173    Impl,
174    /// `mod` item.
175    Mod,
176    /// `const` item.
177    Const,
178    /// `static` item.
179    Static,
180    /// `type` alias.
181    TypeAlias,
182}
183
184/// Target match conditions
185#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
186pub struct TargetMatch {
187    /// Exact name or pattern
188    #[serde(default, skip_serializing_if = "Option::is_none")]
189    pub name: Option<String>,
190
191    /// Glob pattern for name
192    #[serde(default, skip_serializing_if = "Option::is_none")]
193    pub pattern: Option<String>,
194
195    /// Regex pattern for name
196    #[serde(default, skip_serializing_if = "Option::is_none")]
197    pub regex: Option<String>,
198
199    /// Required attributes
200    #[serde(default, skip_serializing_if = "Option::is_none")]
201    pub attributes: Option<Vec<String>>,
202}
203
204/// Traversal scope for transitive queries
205#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
206pub enum TraversalScope {
207    /// Only symbols within the workspace
208    #[default]
209    Workspace,
210    /// Include external dependencies
211    All,
212}
213
214/// Relation kind enumeration.
215///
216/// Three axes of symbol usage:
217/// - **Call**: `Calls` / `CalledBy` — function call relationships
218/// - **Type**: `TypeReferences` / `TypeReferencedBy` — type usage relationships
219/// - **Trait**: `Implements` / `ImplementedBy` — trait implementation relationships
220///
221/// Plus structural containment: `Contains` / `ContainedBy`.
222#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
223pub enum RelationKind {
224    /// Source calls target (function call)
225    Calls,
226    /// Source is called by target
227    CalledBy,
228    /// Source references target as a type (field type, param type, return type, etc.)
229    TypeReferences,
230    /// Source's type is referenced by target
231    TypeReferencedBy,
232    /// Source implements target trait
233    Implements,
234    /// Source trait is implemented by target
235    ImplementedBy,
236    /// Source contains target (parent-child)
237    Contains,
238    /// Source is contained by target
239    ContainedBy,
240}
241
242impl RelationKind {
243    /// Get the reverse relation kind
244    pub fn reverse(&self) -> Self {
245        match self {
246            Self::Calls => Self::CalledBy,
247            Self::CalledBy => Self::Calls,
248            Self::TypeReferences => Self::TypeReferencedBy,
249            Self::TypeReferencedBy => Self::TypeReferences,
250            Self::Implements => Self::ImplementedBy,
251            Self::ImplementedBy => Self::Implements,
252            Self::Contains => Self::ContainedBy,
253            Self::ContainedBy => Self::Contains,
254        }
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261
262    #[test]
263    fn test_relations_builder() {
264        let rels = Relations::new()
265            .any(Relation::new(
266                RelationKind::Calls,
267                RelationTarget::new()
268                    .kind(TargetKind::Function)
269                    .name("unwrap"),
270            ))
271            .none(Relation::new(
272                RelationKind::TypeReferences,
273                RelationTarget::new()
274                    .kind(TargetKind::Struct)
275                    .name("Database"),
276            ));
277
278        assert!(rels.any.is_some());
279        assert!(rels.none.is_some());
280        assert!(rels.all.is_none());
281    }
282
283    #[test]
284    fn test_relation_transitive() {
285        let rel = Relation::new(RelationKind::CalledBy, RelationTarget::new())
286            .transitive()
287            .with_max_depth(10)
288            .with_scope(TraversalScope::Workspace);
289
290        assert!(rel.transitive);
291        assert_eq!(rel.max_depth, Some(10));
292        assert_eq!(rel.scope, TraversalScope::Workspace);
293    }
294
295    #[test]
296    fn test_relation_kind_reverse() {
297        assert_eq!(RelationKind::Calls.reverse(), RelationKind::CalledBy);
298        assert_eq!(
299            RelationKind::TypeReferences.reverse(),
300            RelationKind::TypeReferencedBy
301        );
302        assert_eq!(
303            RelationKind::Implements.reverse(),
304            RelationKind::ImplementedBy
305        );
306    }
307}