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