Skip to main content

reflectapi_schema/
semantic.rs

1/// Semantic Intermediate Representation for ReflectAPI
2///
3/// This module provides immutable, semantically-validated representations
4/// of API schemas that have been processed through the normalization pipeline.
5/// Unlike the raw schema types, these representations are guaranteed to be:
6/// - Fully resolved (no dangling references)
7/// - Semantically consistent (no conflicting definitions)
8/// - Deterministically ordered (BTreeMap/BTreeSet for stable output)
9use crate::SymbolId;
10use std::collections::{BTreeMap, BTreeSet};
11
12/// Semantic schema with fully resolved types and deterministic ordering
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct SemanticSchema {
15    pub id: SymbolId,
16    pub name: String,
17    pub description: String,
18
19    /// Functions ordered by SymbolId for deterministic output
20    pub functions: BTreeMap<SymbolId, SemanticFunction>,
21
22    /// All type definitions ordered by SymbolId
23    pub types: BTreeMap<SymbolId, SemanticType>,
24
25    /// Symbol table for efficient lookups
26    pub symbol_table: SymbolTable,
27}
28
29/// Fully resolved function definition
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct SemanticFunction {
32    pub id: SymbolId,
33    pub name: String,
34    pub path: String,
35    pub description: String,
36    pub deprecation_note: Option<String>,
37
38    /// Resolved type references (no dangling pointers)
39    pub input_type: Option<SymbolId>,
40    pub input_headers: Option<SymbolId>,
41    pub output_type: Option<SymbolId>,
42    pub error_type: Option<SymbolId>,
43
44    pub serialization: Vec<crate::SerializationMode>,
45    pub readonly: bool,
46    pub tags: BTreeSet<String>,
47}
48
49/// Resolved type definition with semantic validation
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub enum SemanticType {
52    Primitive(SemanticPrimitive),
53    Struct(SemanticStruct),
54    Enum(SemanticEnum),
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct SemanticPrimitive {
59    pub id: SymbolId,
60    pub name: String,
61    pub original_name: String,
62    pub description: String,
63
64    /// Resolved generic parameters
65    pub parameters: Vec<SemanticTypeParameter>,
66
67    /// Resolved fallback type reference
68    pub fallback: Option<SymbolId>,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct SemanticStruct {
73    pub id: SymbolId,
74    pub name: String,
75    pub original_name: String,
76    pub serde_name: String,
77    pub description: String,
78
79    /// Resolved generic parameters
80    pub parameters: Vec<SemanticTypeParameter>,
81
82    /// Fields ordered deterministically
83    pub fields: BTreeMap<SymbolId, SemanticField>,
84
85    /// Semantic properties
86    pub transparent: bool,
87    pub is_tuple: bool,
88    pub is_unit: bool,
89
90    /// Language-specific configuration
91    pub codegen_config: crate::LanguageSpecificTypeCodegenConfig,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct SemanticEnum {
96    pub id: SymbolId,
97    pub name: String,
98    pub original_name: String,
99    pub serde_name: String,
100    pub description: String,
101
102    /// Resolved generic parameters
103    pub parameters: Vec<SemanticTypeParameter>,
104
105    /// Variants ordered deterministically
106    pub variants: BTreeMap<SymbolId, SemanticVariant>,
107
108    /// Serde representation strategy
109    pub representation: crate::Representation,
110
111    /// Language-specific configuration
112    pub codegen_config: crate::LanguageSpecificTypeCodegenConfig,
113}
114
115#[derive(Debug, Clone, PartialEq, Eq)]
116pub struct SemanticField {
117    pub id: SymbolId,
118    pub name: String,
119    pub serde_name: String,
120    pub description: String,
121    pub deprecation_note: Option<String>,
122
123    /// Resolved type reference
124    pub type_ref: ResolvedTypeReference,
125
126    /// Field properties
127    pub required: bool,
128    pub flattened: bool,
129
130    /// Transform callback for custom processing
131    pub transform_callback: String,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub struct SemanticVariant {
136    pub id: SymbolId,
137    pub name: String,
138    pub serde_name: String,
139    pub description: String,
140
141    /// Fields ordered deterministically
142    pub fields: BTreeMap<SymbolId, SemanticField>,
143
144    /// Variant properties
145    pub discriminant: Option<isize>,
146    pub untagged: bool,
147    pub field_style: FieldStyle,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
151pub enum FieldStyle {
152    Named,
153    Unnamed,
154    Unit,
155}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub struct SemanticTypeParameter {
159    pub name: String,
160    pub description: String,
161
162    /// Constraints on the type parameter
163    pub bounds: Vec<SymbolId>,
164    pub default: Option<SymbolId>,
165}
166
167/// Resolved type reference with guaranteed validity
168#[derive(Debug, Clone, PartialEq, Eq)]
169pub struct ResolvedTypeReference {
170    /// Target symbol (guaranteed to exist in symbol table)
171    pub target: SymbolId,
172
173    /// Resolved generic arguments
174    pub arguments: Vec<ResolvedTypeReference>,
175
176    /// Original type reference for debugging
177    pub original_name: String,
178}
179
180/// Symbol table for efficient lookups and validation
181#[derive(Debug, Clone, PartialEq, Eq)]
182pub struct SymbolTable {
183    /// Map from SymbolId to symbol information
184    pub symbols: BTreeMap<SymbolId, SymbolInfo>,
185
186    /// Map from name path to SymbolId for lookups
187    name_to_id: BTreeMap<Vec<String>, SymbolId>,
188
189    /// Dependencies between symbols
190    pub dependencies: BTreeMap<SymbolId, BTreeSet<SymbolId>>,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub struct SymbolInfo {
195    pub id: SymbolId,
196    pub name: String,
197    pub path: Vec<String>,
198    pub kind: crate::SymbolKind,
199
200    /// Whether this symbol is fully resolved
201    pub resolved: bool,
202
203    /// Dependencies of this symbol
204    pub dependencies: BTreeSet<SymbolId>,
205}
206
207impl SymbolTable {
208    pub fn new() -> Self {
209        Self {
210            symbols: BTreeMap::new(),
211            name_to_id: BTreeMap::new(),
212            dependencies: BTreeMap::new(),
213        }
214    }
215
216    /// Register a new symbol in the table
217    pub fn register(&mut self, symbol: SymbolInfo) {
218        let id = symbol.id.clone();
219        let path = symbol.path.clone();
220
221        self.symbols.insert(id.clone(), symbol);
222        self.name_to_id.insert(path, id);
223    }
224
225    /// Lookup symbol by ID
226    pub fn get(&self, id: &SymbolId) -> Option<&SymbolInfo> {
227        self.symbols.get(id)
228    }
229
230    /// Lookup symbol by name path
231    pub fn get_by_path(&self, path: &[String]) -> Option<&SymbolInfo> {
232        self.name_to_id
233            .get(path)
234            .and_then(|id| self.symbols.get(id))
235    }
236
237    /// Get all symbols of a specific kind
238    pub fn get_by_kind<'a>(
239        &'a self,
240        kind: &'a crate::SymbolKind,
241    ) -> impl Iterator<Item = &'a SymbolInfo> + 'a {
242        self.symbols.values().filter(move |info| &info.kind == kind)
243    }
244
245    /// Add dependency relationship
246    pub fn add_dependency(&mut self, dependent: SymbolId, dependency: SymbolId) {
247        self.dependencies
248            .entry(dependent.clone())
249            .or_default()
250            .insert(dependency.clone());
251
252        // Update symbol info
253        if let Some(symbol) = self.symbols.get_mut(&dependent) {
254            symbol.dependencies.insert(dependency);
255        }
256    }
257
258    /// Get dependencies of a symbol
259    pub fn get_dependencies(&self, id: &SymbolId) -> Option<&BTreeSet<SymbolId>> {
260        self.dependencies.get(id)
261    }
262
263    /// Topological sort for dependency resolution
264    pub fn topological_sort(&self) -> Result<Vec<SymbolId>, Vec<SymbolId>> {
265        let mut visited = BTreeSet::new();
266        let mut temp_visited = BTreeSet::new();
267        let mut result = Vec::new();
268
269        for id in self.symbols.keys() {
270            if !visited.contains(id) {
271                self.visit_topological(id, &mut visited, &mut temp_visited, &mut result)?;
272            }
273        }
274
275        Ok(result)
276    }
277
278    fn visit_topological(
279        &self,
280        id: &SymbolId,
281        visited: &mut BTreeSet<SymbolId>,
282        temp_visited: &mut BTreeSet<SymbolId>,
283        result: &mut Vec<SymbolId>,
284    ) -> Result<(), Vec<SymbolId>> {
285        if temp_visited.contains(id) {
286            return Err(vec![id.clone()]);
287        }
288
289        if visited.contains(id) {
290            return Ok(());
291        }
292
293        temp_visited.insert(id.clone());
294
295        if let Some(dependencies) = self.dependencies.get(id) {
296            for dep in dependencies {
297                self.visit_topological(dep, visited, temp_visited, result)?;
298            }
299        }
300
301        temp_visited.remove(id);
302        visited.insert(id.clone());
303        result.push(id.clone());
304
305        Ok(())
306    }
307}
308
309impl Default for SymbolTable {
310    fn default() -> Self {
311        Self::new()
312    }
313}
314
315impl SemanticSchema {
316    /// Look up a type by its name via the symbol table's resolution cache.
317    /// Falls back to linear scan if the name isn't in the symbol table.
318    /// Also checks original_name for lookups by pre-normalization qualified name.
319    pub fn get_type_by_name(&self, name: &str) -> Option<&SemanticType> {
320        // Try symbol table lookup first (O(log n))
321        let path = name.split("::").map(|s| s.to_string()).collect::<Vec<_>>();
322        if let Some(info) = self.symbol_table.get_by_path(&path) {
323            if let Some(ty) = self.types.get(&info.id) {
324                return Some(ty);
325            }
326        }
327        // Fallback: linear scan by name (handles post-normalization name changes)
328        if let Some(ty) = self.types.values().find(|t| t.name() == name) {
329            return Some(ty);
330        }
331        // Fallback: linear scan by original_name (handles pre-normalization lookups)
332        self.types.values().find(|t| t.original_name() == name)
333    }
334
335    /// Look up a type by SymbolId.
336    pub fn get_type(&self, id: &SymbolId) -> Option<&SemanticType> {
337        self.types.get(id)
338    }
339
340    /// Iterate all types in deterministic order.
341    pub fn types(&self) -> impl Iterator<Item = &SemanticType> {
342        self.types.values()
343    }
344
345    /// Iterate all functions in deterministic order.
346    pub fn functions(&self) -> impl Iterator<Item = &SemanticFunction> {
347        self.functions.values()
348    }
349
350    /// Ordered type names (deterministic via BTreeMap).
351    pub fn type_names(&self) -> impl Iterator<Item = &str> {
352        self.types.values().map(|t| t.name())
353    }
354}
355
356impl SemanticType {
357    pub fn id(&self) -> &SymbolId {
358        match self {
359            SemanticType::Primitive(p) => &p.id,
360            SemanticType::Struct(s) => &s.id,
361            SemanticType::Enum(e) => &e.id,
362        }
363    }
364
365    pub fn name(&self) -> &str {
366        match self {
367            SemanticType::Primitive(p) => &p.name,
368            SemanticType::Struct(s) => &s.name,
369            SemanticType::Enum(e) => &e.name,
370        }
371    }
372
373    pub fn original_name(&self) -> &str {
374        match self {
375            SemanticType::Primitive(p) => &p.original_name,
376            SemanticType::Struct(s) => &s.original_name,
377            SemanticType::Enum(e) => &e.original_name,
378        }
379    }
380}
381
382impl ResolvedTypeReference {
383    /// Create a new resolved type reference
384    pub fn new(
385        target: SymbolId,
386        arguments: Vec<ResolvedTypeReference>,
387        original_name: String,
388    ) -> Self {
389        Self {
390            target,
391            arguments,
392            original_name,
393        }
394    }
395
396    /// Check if this is a primitive type reference
397    pub fn is_primitive(&self, symbol_table: &SymbolTable) -> bool {
398        symbol_table
399            .get(&self.target)
400            .map(|info| matches!(info.kind, crate::SymbolKind::Primitive))
401            .unwrap_or(false)
402    }
403
404    /// Check if this is a generic type (has arguments)
405    pub fn is_generic(&self) -> bool {
406        !self.arguments.is_empty()
407    }
408}
409
410#[cfg(test)]
411mod tests {
412    use super::*;
413    use crate::{SymbolId, SymbolKind};
414
415    #[test]
416    fn test_symbol_table_basic_operations() {
417        let mut table = SymbolTable::new();
418
419        let user_id = SymbolId::struct_id(vec!["User".to_string()]);
420        let user_info = SymbolInfo {
421            id: user_id.clone(),
422            name: "User".to_string(),
423            path: vec!["User".to_string()],
424            kind: SymbolKind::Struct,
425            resolved: true,
426            dependencies: BTreeSet::new(),
427        };
428
429        table.register(user_info);
430
431        assert!(table.get(&user_id).is_some());
432        assert!(table.get_by_path(&["User".to_string()]).is_some());
433
434        let structs: Vec<_> = table.get_by_kind(&SymbolKind::Struct).collect();
435        assert_eq!(structs.len(), 1);
436    }
437
438    #[test]
439    fn test_symbol_table_dependencies() {
440        let mut table = SymbolTable::new();
441
442        let user_id = SymbolId::struct_id(vec!["User".to_string()]);
443        let post_id = SymbolId::struct_id(vec!["Post".to_string()]);
444
445        table.register(SymbolInfo {
446            id: user_id.clone(),
447            name: "User".to_string(),
448            path: vec!["User".to_string()],
449            kind: SymbolKind::Struct,
450            resolved: true,
451            dependencies: BTreeSet::new(),
452        });
453
454        table.register(SymbolInfo {
455            id: post_id.clone(),
456            name: "Post".to_string(),
457            path: vec!["Post".to_string()],
458            kind: SymbolKind::Struct,
459            resolved: true,
460            dependencies: BTreeSet::new(),
461        });
462
463        table.add_dependency(post_id.clone(), user_id.clone());
464
465        let deps = table.get_dependencies(&post_id).unwrap();
466        assert!(deps.contains(&user_id));
467
468        let sorted = table.topological_sort().unwrap();
469        let user_pos = sorted.iter().position(|id| id == &user_id).unwrap();
470        let post_pos = sorted.iter().position(|id| id == &post_id).unwrap();
471        assert!(
472            user_pos < post_pos,
473            "User should come before Post in topological order"
474        );
475    }
476
477    #[test]
478    fn test_resolved_type_reference() {
479        let string_id = SymbolId::new(SymbolKind::Primitive, vec!["String".to_string()]);
480        let vec_id = SymbolId::new(SymbolKind::Struct, vec!["Vec".to_string()]);
481
482        let string_ref =
483            ResolvedTypeReference::new(string_id.clone(), vec![], "String".to_string());
484
485        let vec_string_ref =
486            ResolvedTypeReference::new(vec_id, vec![string_ref], "Vec<String>".to_string());
487
488        assert!(!vec_string_ref.arguments.is_empty());
489        assert!(vec_string_ref.is_generic());
490        assert_eq!(vec_string_ref.arguments[0].target, string_id);
491    }
492}