1use std::collections::{BTreeMap, BTreeSet};
2
3use sim_kernel::{ContentId, Error, Result, Symbol};
4
5use crate::{CompiledIntent, IntentStatus};
6
7#[derive(Clone, Debug, Default)]
14pub struct IntentLibrary {
15 intents: BTreeMap<IntentKey, CompiledIntent>,
16 source_index: BTreeMap<ContentId, BTreeSet<IntentKey>>,
17}
18
19impl IntentLibrary {
20 pub fn new() -> Self {
22 Self::default()
23 }
24
25 pub fn store(&mut self, intent: CompiledIntent) -> Result<()> {
31 let key = IntentKey::new(&intent.name, intent.version);
32 if let Some(existing) = self.intents.get(&key) {
33 if existing.status == IntentStatus::Golden && existing != &intent {
34 return Err(Error::Eval(format!(
35 "golden intent {} v{} is immutable",
36 key.name, key.version
37 )));
38 }
39 if existing.packet != intent.packet {
40 return Err(Error::Eval(format!(
41 "compiled intent {} v{} changes packet; store a new version",
42 key.name, key.version
43 )));
44 }
45 }
46
47 if let Some(existing) = self.intents.insert(key.clone(), intent.clone()) {
48 self.remove_source_key(&existing.source, &key);
49 }
50 self.source_index
51 .entry(intent.source.clone())
52 .or_default()
53 .insert(key);
54 Ok(())
55 }
56
57 pub fn fetch(&self, name: &Symbol, version: u32) -> Option<&CompiledIntent> {
59 self.intents.get(&IntentKey::new(name, version))
60 }
61
62 pub fn by_source(&self, source: &ContentId) -> Vec<&CompiledIntent> {
64 self.source_index
65 .get(source)
66 .into_iter()
67 .flat_map(|keys| keys.iter())
68 .filter_map(|key| self.intents.get(key))
69 .collect()
70 }
71
72 pub fn golden_by_source(&self, source: &ContentId) -> Option<&CompiledIntent> {
74 self.by_source(source)
75 .into_iter()
76 .filter(|intent| intent.status == IntentStatus::Golden)
77 .max_by_key(|intent| intent.version)
78 }
79
80 pub fn next_version(&self, name: &Symbol) -> u32 {
82 self.intents
83 .keys()
84 .filter(|key| &key.name == name)
85 .map(|key| key.version)
86 .max()
87 .unwrap_or(0)
88 .saturating_add(1)
89 }
90
91 pub(crate) fn store_resolved(&mut self, mut intent: CompiledIntent) -> Result<CompiledIntent> {
92 intent.version = self
93 .version_for_packet(&intent.name, &intent.source, &intent.packet)
94 .unwrap_or_else(|| self.next_version(&intent.name));
95 self.store(intent.clone())?;
96 Ok(intent)
97 }
98
99 fn version_for_packet(
100 &self,
101 name: &Symbol,
102 source: &ContentId,
103 packet: &ContentId,
104 ) -> Option<u32> {
105 self.intents.iter().find_map(|(key, intent)| {
106 (&key.name == name && &intent.source == source && &intent.packet == packet)
107 .then_some(key.version)
108 })
109 }
110
111 fn remove_source_key(&mut self, source: &ContentId, key: &IntentKey) {
112 if let Some(keys) = self.source_index.get_mut(source) {
113 keys.remove(key);
114 if keys.is_empty() {
115 self.source_index.remove(source);
116 }
117 }
118 }
119}
120
121#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
122struct IntentKey {
123 name: Symbol,
124 version: u32,
125}
126
127impl IntentKey {
128 fn new(name: &Symbol, version: u32) -> Self {
129 Self {
130 name: name.clone(),
131 version,
132 }
133 }
134}