Skip to main content

reflectapi_schema/
symbol.rs

1/// Stable, unique identifier for symbols that persists across all pipeline stages
2#[derive(
3    Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
4)]
5pub struct SymbolId {
6    pub kind: SymbolKind,
7    pub path: Vec<String>,
8    pub disambiguator: u32,
9}
10
11#[derive(
12    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
13)]
14pub enum SymbolKind {
15    Struct,
16    Enum,
17    TypeAlias,
18    Endpoint,
19    Variant,
20    Field,
21    Primitive,
22}
23
24/// Well-known stdlib and common crate types used in ReflectAPI schemas.
25/// Each entry is (fully-qualified name, SymbolKind).
26pub const STDLIB_TYPES: &[(&str, SymbolKind)] = &[
27    ("std::option::Option", SymbolKind::Enum),
28    ("std::vec::Vec", SymbolKind::Primitive),
29    ("std::collections::HashMap", SymbolKind::Primitive),
30    ("std::collections::BTreeMap", SymbolKind::Primitive),
31    ("std::string::String", SymbolKind::Primitive),
32    ("std::tuple::Tuple0", SymbolKind::Primitive),
33    ("std::boxed::Box", SymbolKind::Primitive),
34    ("std::rc::Rc", SymbolKind::Primitive),
35    ("std::sync::Arc", SymbolKind::Primitive),
36    ("i32", SymbolKind::Primitive),
37    ("u32", SymbolKind::Primitive),
38    ("i64", SymbolKind::Primitive),
39    ("u64", SymbolKind::Primitive),
40    ("f32", SymbolKind::Primitive),
41    ("f64", SymbolKind::Primitive),
42    ("bool", SymbolKind::Primitive),
43    ("u8", SymbolKind::Primitive),
44    ("i8", SymbolKind::Primitive),
45    ("chrono::Utc", SymbolKind::Primitive),
46    ("chrono::FixedOffset", SymbolKind::Primitive),
47    ("chrono::DateTime", SymbolKind::Primitive),
48    ("uuid::Uuid", SymbolKind::Primitive),
49    ("url::Url", SymbolKind::Primitive),
50    ("serde_json::Value", SymbolKind::Primitive),
51];
52
53/// Prefixes that identify well-known stdlib and common crate types.
54/// Used for broad matching when exact name lookup is not sufficient
55/// (e.g., types not explicitly listed in STDLIB_TYPES).
56pub const STDLIB_TYPE_PREFIXES: &[&str] = &["std::", "chrono::", "uuid::"];
57
58impl Default for SymbolId {
59    fn default() -> Self {
60        Self {
61            kind: SymbolKind::Struct,
62            path: vec!["unknown".to_string()],
63            disambiguator: 0,
64        }
65    }
66}
67
68impl SymbolId {
69    /// Create a new symbol ID with path and kind
70    pub fn new(kind: SymbolKind, path: Vec<String>) -> Self {
71        Self {
72            kind,
73            path,
74            disambiguator: 0,
75        }
76    }
77
78    /// Create a new symbol ID with disambiguation
79    pub fn with_disambiguator(kind: SymbolKind, path: Vec<String>, disambiguator: u32) -> Self {
80        Self {
81            kind,
82            path,
83            disambiguator,
84        }
85    }
86
87    /// Check if this is an unknown/default symbol ID
88    pub fn is_unknown(&self) -> bool {
89        self.path.len() == 1 && self.path[0] == "unknown"
90    }
91
92    /// Create a symbol ID for a struct
93    pub fn struct_id(path: Vec<String>) -> Self {
94        Self::new(SymbolKind::Struct, path)
95    }
96
97    /// Create a symbol ID for an enum
98    pub fn enum_id(path: Vec<String>) -> Self {
99        Self::new(SymbolKind::Enum, path)
100    }
101
102    /// Create a symbol ID for an endpoint/function
103    pub fn endpoint_id(path: Vec<String>) -> Self {
104        Self::new(SymbolKind::Endpoint, path)
105    }
106
107    /// Create a symbol ID for a variant
108    pub fn variant_id(enum_path: Vec<String>, variant_name: String) -> Self {
109        let mut path = enum_path;
110        path.push(variant_name);
111        Self::new(SymbolKind::Variant, path)
112    }
113
114    /// Create a symbol ID for a field
115    pub fn field_id(parent_path: Vec<String>, field_name: String) -> Self {
116        let mut path = parent_path;
117        path.push(field_name);
118        Self::new(SymbolKind::Field, path)
119    }
120
121    /// Get the simple name (last component of path)
122    pub fn name(&self) -> Option<&str> {
123        self.path.last().map(|s| s.as_str())
124    }
125
126    /// Get the qualified name as a "::" separated string
127    pub fn qualified_name(&self) -> String {
128        self.path.join("::")
129    }
130
131    /// Check if this is the same symbol (ignoring disambiguator)
132    pub fn same_symbol(&self, other: &SymbolId) -> bool {
133        self.kind == other.kind && self.path == other.path
134    }
135}
136
137impl std::fmt::Display for SymbolId {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        write!(f, "{:?}({})", self.kind, self.qualified_name())?;
140        if self.disambiguator > 0 {
141            write!(f, "#{}", self.disambiguator)?;
142        }
143        Ok(())
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_symbol_id_creation() {
153        let struct_id = SymbolId::struct_id(vec!["api".to_string(), "User".to_string()]);
154        assert_eq!(struct_id.kind, SymbolKind::Struct);
155        assert_eq!(struct_id.path, vec!["api", "User"]);
156        assert_eq!(struct_id.disambiguator, 0);
157        assert_eq!(struct_id.name(), Some("User"));
158        assert_eq!(struct_id.qualified_name(), "api::User");
159    }
160
161    #[test]
162    fn test_symbol_id_display() {
163        let struct_id = SymbolId::struct_id(vec!["api".to_string(), "User".to_string()]);
164        assert_eq!(format!("{struct_id}"), "Struct(api::User)");
165
166        let disambiguated = SymbolId::with_disambiguator(
167            SymbolKind::Struct,
168            vec!["api".to_string(), "User".to_string()],
169            1,
170        );
171        assert_eq!(format!("{disambiguated}"), "Struct(api::User)#1");
172    }
173
174    #[test]
175    fn test_same_symbol() {
176        let id1 = SymbolId::struct_id(vec!["api".to_string(), "User".to_string()]);
177        let id2 = SymbolId::with_disambiguator(
178            SymbolKind::Struct,
179            vec!["api".to_string(), "User".to_string()],
180            1,
181        );
182        assert!(id1.same_symbol(&id2));
183    }
184}