1use crate::symbol::SymbolId;
29use serde::{Deserialize, Serialize};
30use smallvec::SmallVec;
31use std::collections::HashMap;
32
33pub use super::specflow_common::{ConstraintKind, IntentKind, SpecSource};
35
36#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
42#[repr(transparent)]
43pub struct SpecNodeId(u32);
44
45impl SpecNodeId {
46 const KIND_SHIFT: u32 = 30;
47 const KIND_MASK: u32 = 0xC000_0000;
48 const INDEX_MASK: u32 = 0x3FFF_FFFF;
49
50 const KIND_GROUP: u32 = 0;
51 const KIND_SPEC: u32 = 1;
52 const KIND_CONSTRAINT: u32 = 2;
53 const KIND_INTENT: u32 = 3;
54
55 #[inline]
57 pub const fn group(idx: u32) -> Self {
58 Self((Self::KIND_GROUP << Self::KIND_SHIFT) | idx)
59 }
60
61 #[inline]
63 pub const fn spec(idx: u32) -> Self {
64 Self((Self::KIND_SPEC << Self::KIND_SHIFT) | idx)
65 }
66
67 #[inline]
69 pub const fn constraint(idx: u32) -> Self {
70 Self((Self::KIND_CONSTRAINT << Self::KIND_SHIFT) | idx)
71 }
72
73 #[inline]
75 pub const fn intent(idx: u32) -> Self {
76 Self((Self::KIND_INTENT << Self::KIND_SHIFT) | idx)
77 }
78
79 #[inline]
81 pub const fn kind(self) -> SpecNodeKind {
82 match (self.0 & Self::KIND_MASK) >> Self::KIND_SHIFT {
83 Self::KIND_GROUP => SpecNodeKind::Group,
84 Self::KIND_SPEC => SpecNodeKind::SpecAlias,
85 Self::KIND_CONSTRAINT => SpecNodeKind::Constraint,
86 Self::KIND_INTENT => SpecNodeKind::Intent,
87 _ => unreachable!(),
88 }
89 }
90
91 #[inline]
93 pub const fn index(self) -> u32 {
94 self.0 & Self::INDEX_MASK
95 }
96
97 #[inline]
99 pub const fn as_u32(self) -> u32 {
100 self.0
101 }
102}
103
104#[derive(Copy, Clone, Eq, PartialEq, Debug)]
106pub enum SpecNodeKind {
107 Group,
109 SpecAlias,
111 Constraint,
113 Intent,
115}
116
117#[derive(Clone, Debug, Serialize, Deserialize)]
124pub struct GroupData {
125 pub name: String,
127 pub description: Option<String>,
129}
130
131#[derive(Clone, Debug, Serialize, Deserialize)]
133pub struct SpecAliasData {
134 pub alias_id: SymbolId,
136 pub alias_name: String,
138 pub wrapped_type_id: Option<SymbolId>,
140 pub wrapped_type_name: Option<String>,
142 pub group_idx: u32,
144 pub source: SpecSource,
146}
147
148#[derive(Clone, Debug, Serialize, Deserialize)]
151pub struct ConstraintData {
152 pub kind: ConstraintKind,
154}
155
156#[derive(Clone, Debug, Serialize, Deserialize)]
159pub struct IntentData {
160 pub description: String,
162 pub kind: IntentKind,
164}
165
166#[derive(Clone, Debug, Default, Serialize, Deserialize)]
172pub struct SpecLookupTable {
173 pub symbol_to_spec: HashMap<SymbolId, u32>,
175
176 pub name_to_group: HashMap<String, u32>,
178
179 pub group_to_specs: Vec<SmallVec<[u32; 8]>>,
181}
182
183#[derive(Clone, Debug, Default, Serialize)]
193pub struct SpecFlowGraphV2 {
194 groups: Vec<GroupData>,
199
200 spec_aliases: Vec<SpecAliasData>,
202
203 constraints: Vec<ConstraintData>,
205
206 intents: Vec<IntentData>,
208
209 spec_dependencies: HashMap<u32, SmallVec<[u32; 2]>>,
214
215 spec_to_constraints: HashMap<u32, SmallVec<[u32; 2]>>,
217
218 spec_to_intents: HashMap<u32, SmallVec<[u32; 2]>>,
220
221 spec_related: HashMap<u32, SmallVec<[u32; 2]>>,
223
224 lookup: SpecLookupTable,
228}
229
230impl SpecFlowGraphV2 {
231 pub fn new() -> Self {
233 Self::default()
234 }
235
236 pub fn add_group(
242 &mut self,
243 name: impl Into<String>,
244 description: Option<String>,
245 ) -> SpecNodeId {
246 let name = name.into();
247
248 if let Some(&idx) = self.lookup.name_to_group.get(&name) {
250 if description.is_some() {
252 self.groups[idx as usize].description = description;
253 }
254 return SpecNodeId::group(idx);
255 }
256
257 let idx = self.groups.len() as u32;
259 self.groups.push(GroupData {
260 name: name.clone(),
261 description,
262 });
263 self.lookup.name_to_group.insert(name, idx);
264 self.lookup.group_to_specs.push(SmallVec::new());
265
266 SpecNodeId::group(idx)
267 }
268
269 pub fn add_spec_alias(
271 &mut self,
272 alias_id: SymbolId,
273 alias_name: String,
274 group_name: &str,
275 wrapped_type_id: Option<SymbolId>,
276 wrapped_type_name: Option<String>,
277 source: SpecSource,
278 ) -> SpecNodeId {
279 let group_node = self.add_group(group_name, None);
281 let group_idx = group_node.index();
282
283 let spec_idx = self.spec_aliases.len() as u32;
285 self.spec_aliases.push(SpecAliasData {
286 alias_id,
287 alias_name,
288 wrapped_type_id,
289 wrapped_type_name,
290 group_idx,
291 source,
292 });
293
294 self.lookup.symbol_to_spec.insert(alias_id, spec_idx);
296 self.lookup.group_to_specs[group_idx as usize].push(spec_idx);
297
298 SpecNodeId::spec(spec_idx)
299 }
300
301 pub fn add_constraint(&mut self, kind: ConstraintKind) -> SpecNodeId {
303 let idx = self.constraints.len() as u32;
304 self.constraints.push(ConstraintData { kind });
305 SpecNodeId::constraint(idx)
306 }
307
308 pub fn add_intent(&mut self, description: impl Into<String>, kind: IntentKind) -> SpecNodeId {
310 let idx = self.intents.len() as u32;
311 self.intents.push(IntentData {
312 description: description.into(),
313 kind,
314 });
315 SpecNodeId::intent(idx)
316 }
317
318 pub fn add_dependency(&mut self, from: SpecNodeId, to: SpecNodeId) {
324 debug_assert!(from.kind() == SpecNodeKind::SpecAlias);
325 debug_assert!(to.kind() == SpecNodeKind::SpecAlias);
326 self.spec_dependencies
327 .entry(from.index())
328 .or_default()
329 .push(to.index());
330 }
331
332 pub fn add_related(&mut self, a: SpecNodeId, b: SpecNodeId) {
334 debug_assert!(a.kind() == SpecNodeKind::SpecAlias);
335 debug_assert!(b.kind() == SpecNodeKind::SpecAlias);
336 self.spec_related
337 .entry(a.index())
338 .or_default()
339 .push(b.index());
340 self.spec_related
341 .entry(b.index())
342 .or_default()
343 .push(a.index());
344 }
345
346 pub fn link_constraint(&mut self, spec: SpecNodeId, constraint: SpecNodeId) {
348 debug_assert!(spec.kind() == SpecNodeKind::SpecAlias);
349 debug_assert!(constraint.kind() == SpecNodeKind::Constraint);
350 self.spec_to_constraints
351 .entry(spec.index())
352 .or_default()
353 .push(constraint.index());
354 }
355
356 pub fn link_intent(&mut self, spec: SpecNodeId, intent: SpecNodeId) {
358 debug_assert!(spec.kind() == SpecNodeKind::SpecAlias);
359 debug_assert!(intent.kind() == SpecNodeKind::Intent);
360 self.spec_to_intents
361 .entry(spec.index())
362 .or_default()
363 .push(intent.index());
364 }
365
366 pub fn group_by_name(&self, name: &str) -> Option<SpecNodeId> {
372 self.lookup
373 .name_to_group
374 .get(name)
375 .map(|&idx| SpecNodeId::group(idx))
376 }
377
378 pub fn spec_by_symbol(&self, id: SymbolId) -> Option<SpecNodeId> {
380 self.lookup
381 .symbol_to_spec
382 .get(&id)
383 .map(|&idx| SpecNodeId::spec(idx))
384 }
385
386 pub fn specs_in_group(&self, group: SpecNodeId) -> impl Iterator<Item = SpecNodeId> + '_ {
388 debug_assert!(group.kind() == SpecNodeKind::Group);
389 self.lookup
390 .group_to_specs
391 .get(group.index() as usize)
392 .into_iter()
393 .flat_map(|v| v.iter().map(|&idx| SpecNodeId::spec(idx)))
394 }
395
396 pub fn dependencies(&self, spec: SpecNodeId) -> impl Iterator<Item = SpecNodeId> + '_ {
398 debug_assert!(spec.kind() == SpecNodeKind::SpecAlias);
399 self.spec_dependencies
400 .get(&spec.index())
401 .into_iter()
402 .flat_map(|v| v.iter().map(|&idx| SpecNodeId::spec(idx)))
403 }
404
405 pub fn constraints(&self, spec: SpecNodeId) -> impl Iterator<Item = SpecNodeId> + '_ {
407 debug_assert!(spec.kind() == SpecNodeKind::SpecAlias);
408 self.spec_to_constraints
409 .get(&spec.index())
410 .into_iter()
411 .flat_map(|v| v.iter().map(|&idx| SpecNodeId::constraint(idx)))
412 }
413
414 pub fn get_group(&self, id: SpecNodeId) -> Option<&GroupData> {
420 debug_assert!(id.kind() == SpecNodeKind::Group);
421 self.groups.get(id.index() as usize)
422 }
423
424 pub fn get_spec_alias(&self, id: SpecNodeId) -> Option<&SpecAliasData> {
426 debug_assert!(id.kind() == SpecNodeKind::SpecAlias);
427 self.spec_aliases.get(id.index() as usize)
428 }
429
430 pub fn get_constraint(&self, id: SpecNodeId) -> Option<&ConstraintData> {
432 debug_assert!(id.kind() == SpecNodeKind::Constraint);
433 self.constraints.get(id.index() as usize)
434 }
435
436 pub fn get_intent(&self, id: SpecNodeId) -> Option<&IntentData> {
438 debug_assert!(id.kind() == SpecNodeKind::Intent);
439 self.intents.get(id.index() as usize)
440 }
441
442 pub fn all_groups(&self) -> impl Iterator<Item = (SpecNodeId, &GroupData)> {
448 self.groups
449 .iter()
450 .enumerate()
451 .map(|(i, data)| (SpecNodeId::group(i as u32), data))
452 }
453
454 pub fn all_spec_aliases(&self) -> impl Iterator<Item = (SpecNodeId, &SpecAliasData)> {
456 self.spec_aliases
457 .iter()
458 .enumerate()
459 .map(|(i, data)| (SpecNodeId::spec(i as u32), data))
460 }
461
462 pub fn group_count(&self) -> usize {
468 self.groups.len()
469 }
470
471 pub fn spec_count(&self) -> usize {
473 self.spec_aliases.len()
474 }
475
476 pub fn constraint_count(&self) -> usize {
478 self.constraints.len()
479 }
480
481 pub fn node_count(&self) -> usize {
483 self.groups.len() + self.spec_aliases.len() + self.constraints.len() + self.intents.len()
484 }
485
486 pub fn is_empty(&self) -> bool {
488 self.node_count() == 0
489 }
490
491 pub fn group_names(&self) -> impl Iterator<Item = &str> {
497 self.groups.iter().map(|g| g.name.as_str())
498 }
499
500 pub fn specs_in_group_by_name(&self, name: &str) -> impl Iterator<Item = SpecNodeId> + '_ {
502 self.lookup
503 .name_to_group
504 .get(name)
505 .and_then(|&idx| self.lookup.group_to_specs.get(idx as usize))
506 .into_iter()
507 .flat_map(|v| v.iter().map(|&idx| SpecNodeId::spec(idx)))
508 }
509
510 pub fn dependents(&self, spec: SpecNodeId) -> impl Iterator<Item = SpecNodeId> + '_ {
512 debug_assert!(spec.kind() == SpecNodeKind::SpecAlias);
513 let target_idx = spec.index();
514 self.spec_dependencies
515 .iter()
516 .filter(move |(_, deps)| deps.contains(&target_idx))
517 .map(|(&from_idx, _)| SpecNodeId::spec(from_idx))
518 }
519
520 pub fn edge_count(&self) -> usize {
522 let dep_edges: usize = self.spec_dependencies.values().map(|v| v.len()).sum();
523 let constraint_edges: usize = self.spec_to_constraints.values().map(|v| v.len()).sum();
524 let intent_edges: usize = self.spec_to_intents.values().map(|v| v.len()).sum();
525 let related_edges: usize = self.spec_related.values().map(|v| v.len()).sum();
526 let belongs_to_edges = self.spec_aliases.len(); dep_edges + constraint_edges + intent_edges + related_edges + belongs_to_edges
529 }
530
531 pub fn related(&self, spec: SpecNodeId) -> impl Iterator<Item = SpecNodeId> + '_ {
533 debug_assert!(spec.kind() == SpecNodeKind::SpecAlias);
534 self.spec_related
535 .get(&spec.index())
536 .into_iter()
537 .flat_map(|v| v.iter().map(|&idx| SpecNodeId::spec(idx)))
538 }
539
540 pub fn intents(&self, spec: SpecNodeId) -> impl Iterator<Item = SpecNodeId> + '_ {
542 debug_assert!(spec.kind() == SpecNodeKind::SpecAlias);
543 self.spec_to_intents
544 .get(&spec.index())
545 .into_iter()
546 .flat_map(|v| v.iter().map(|&idx| SpecNodeId::intent(idx)))
547 }
548
549 pub fn spec_name(&self, id: SpecNodeId) -> Option<&str> {
555 self.get_spec_alias(id).map(|data| data.alias_name.as_str())
556 }
557
558 pub fn wrapped_type_name(&self, id: SpecNodeId) -> Option<&str> {
560 self.get_spec_alias(id)
561 .and_then(|data| data.wrapped_type_name.as_deref())
562 }
563
564 pub fn spec_group_name(&self, id: SpecNodeId) -> Option<&str> {
566 self.get_spec_alias(id)
567 .and_then(|data| self.groups.get(data.group_idx as usize))
568 .map(|g| g.name.as_str())
569 }
570
571 pub fn spec_by_name(&self, name: &str) -> Option<SpecNodeId> {
573 for (id, data) in self.all_spec_aliases() {
574 if data.alias_name == name {
575 return Some(id);
576 }
577 }
578 None
579 }
580}
581
582use super::type_alias_registry::TypeAliasRegistry;
587use crate::symbol::SymbolRegistry;
588
589pub struct SpecFlowBuilderV2<'a> {
598 alias_registry: &'a TypeAliasRegistry,
599 symbol_registry: &'a SymbolRegistry,
600}
601
602impl<'a> SpecFlowBuilderV2<'a> {
603 pub fn new(alias_registry: &'a TypeAliasRegistry, symbol_registry: &'a SymbolRegistry) -> Self {
605 Self {
606 alias_registry,
607 symbol_registry,
608 }
609 }
610
611 pub fn build(self) -> SpecFlowGraphV2 {
613 let mut graph = SpecFlowGraphV2::new();
614
615 for spec_info in self.alias_registry.spec_aliases() {
617 let alias_name = self
619 .symbol_registry
620 .resolve(spec_info.entry.alias_id)
621 .map(|path| path.name().to_string())
622 .unwrap_or_default();
623
624 let wrapped_type_name = spec_info.entry.resolved.and_then(|wrapped_id| {
625 self.symbol_registry
626 .resolve(wrapped_id)
627 .map(|path| path.to_string())
628 });
629
630 graph.add_spec_alias(
631 spec_info.entry.alias_id,
632 alias_name,
633 &spec_info.group_name,
634 spec_info.entry.resolved,
635 wrapped_type_name,
636 SpecSource::TypeAlias,
637 );
638 }
639
640 graph
641 }
642}
643
644#[cfg(test)]
649mod tests {
650 use super::*;
651 use crate::symbol::{SymbolPath, SymbolRegistry};
652 use crate::SymbolKind;
653
654 fn create_test_symbol(registry: &mut SymbolRegistry, name: &str) -> SymbolId {
655 registry
656 .register(SymbolPath::parse(name).unwrap(), SymbolKind::TypeAlias)
657 .unwrap()
658 }
659
660 #[test]
661 fn test_empty_graph() {
662 let graph = SpecFlowGraphV2::new();
663 assert!(graph.is_empty());
664 assert_eq!(graph.node_count(), 0);
665 }
666
667 #[test]
668 fn test_add_group() {
669 let mut graph = SpecFlowGraphV2::new();
670 let g1 = graph.add_group("ConfigGroup", Some("Config types".to_string()));
671 let g2 = graph.add_group("ConfigGroup", None); assert_eq!(g1, g2);
674 assert_eq!(graph.group_count(), 1);
675
676 let data = graph.get_group(g1).unwrap();
677 assert_eq!(data.name, "ConfigGroup");
678 assert_eq!(data.description, Some("Config types".to_string()));
679 }
680
681 #[test]
682 fn test_add_spec_alias() {
683 let mut symbol_registry = SymbolRegistry::new();
684 let alias_id = create_test_symbol(&mut symbol_registry, "test::DbConfig");
685 let wrapped_id = create_test_symbol(&mut symbol_registry, "test::DatabaseConfig");
686
687 let mut graph = SpecFlowGraphV2::new();
688 let spec = graph.add_spec_alias(
689 alias_id,
690 "DbConfig".to_string(),
691 "ConfigGroup",
692 Some(wrapped_id),
693 Some("test::DatabaseConfig".to_string()),
694 SpecSource::TypeAlias,
695 );
696
697 assert_eq!(graph.spec_count(), 1);
698 assert_eq!(graph.group_count(), 1); let data = graph.get_spec_alias(spec).unwrap();
702 assert_eq!(data.alias_id, alias_id);
703 assert_eq!(data.wrapped_type_id, Some(wrapped_id));
704
705 assert_eq!(graph.spec_by_symbol(alias_id), Some(spec));
707
708 let group = graph.group_by_name("ConfigGroup").unwrap();
710 let specs: Vec<_> = graph.specs_in_group(group).collect();
711 assert_eq!(specs.len(), 1);
712 assert_eq!(specs[0], spec);
713 }
714
715 #[test]
716 fn test_dependencies() {
717 let mut symbol_registry = SymbolRegistry::new();
718 let db_id = create_test_symbol(&mut symbol_registry, "test::DbConfig");
719 let cache_id = create_test_symbol(&mut symbol_registry, "test::CacheConfig");
720
721 let mut graph = SpecFlowGraphV2::new();
722 let db_spec = graph.add_spec_alias(
723 db_id,
724 "DbConfig".to_string(),
725 "ConfigGroup",
726 None,
727 None,
728 SpecSource::TypeAlias,
729 );
730 let cache_spec = graph.add_spec_alias(
731 cache_id,
732 "CacheConfig".to_string(),
733 "ConfigGroup",
734 None,
735 None,
736 SpecSource::TypeAlias,
737 );
738
739 graph.add_dependency(cache_spec, db_spec);
741
742 let deps: Vec<_> = graph.dependencies(cache_spec).collect();
743 assert_eq!(deps.len(), 1);
744 assert_eq!(deps[0], db_spec);
745 }
746
747 #[test]
748 fn test_constraints() {
749 let mut symbol_registry = SymbolRegistry::new();
750 let id = create_test_symbol(&mut symbol_registry, "test::UserId");
751
752 let mut graph = SpecFlowGraphV2::new();
753 let spec = graph.add_spec_alias(
754 id,
755 "UserId".to_string(),
756 "DomainGroup",
757 None,
758 None,
759 SpecSource::TypeAlias,
760 );
761 let constraint = graph.add_constraint(ConstraintKind::Range {
762 min: Some(1),
763 max: None,
764 });
765
766 graph.link_constraint(spec, constraint);
767
768 let constraints: Vec<_> = graph.constraints(spec).collect();
769 assert_eq!(constraints.len(), 1);
770
771 let data = graph.get_constraint(constraints[0]).unwrap();
772 assert!(matches!(
773 data.kind,
774 ConstraintKind::Range {
775 min: Some(1),
776 max: None
777 }
778 ));
779 }
780}