1use std::collections::{BTreeMap, BTreeSet};
2
3use async_trait::async_trait;
4use gatekeep::{
5 Context, Fact, FactId, FactResolver, KnownFacts, PartialFacts, Presence, ResolveError,
6};
7use keepsake::{
8 ActiveRelation, ActiveRelationSource, RelationId, RelationSpec,
9 SubjectRef as KeepsakeSubjectRef,
10};
11
12use crate::{
13 FactBinding, FactBindingError, KeepsakeResolveError, QueryPresence, SubjectMapper,
14 TenantScopedSubjectMapper,
15};
16
17#[derive(Clone, Debug)]
19pub struct KeepsakeResolver<S, M = TenantScopedSubjectMapper> {
20 source: S,
21 subject_mapper: M,
22 bindings: BTreeMap<FactId, FactBinding>,
23}
24
25impl<S> KeepsakeResolver<S, TenantScopedSubjectMapper> {
26 #[must_use]
28 pub const fn new(source: S) -> Self {
29 Self::with_subject_mapper(source, TenantScopedSubjectMapper)
30 }
31}
32
33impl<S, M> KeepsakeResolver<S, M> {
34 #[must_use]
36 pub const fn with_subject_mapper(source: S, subject_mapper: M) -> Self {
37 Self {
38 source,
39 subject_mapper,
40 bindings: BTreeMap::new(),
41 }
42 }
43
44 #[must_use]
46 pub fn map_subjects<Next>(self, subject_mapper: Next) -> KeepsakeResolver<S, Next> {
47 KeepsakeResolver {
48 source: self.source,
49 subject_mapper,
50 bindings: self.bindings,
51 }
52 }
53
54 #[must_use]
56 pub fn with_binding(mut self, binding: FactBinding) -> Self {
57 self.insert_binding(binding);
58 self
59 }
60
61 pub fn insert_binding(&mut self, binding: FactBinding) {
63 self.bindings.insert(binding.fact.clone(), binding);
64 }
65
66 pub fn with_relation_spec<F, R>(self) -> Result<Self, FactBindingError>
73 where
74 F: Fact,
75 R: RelationSpec,
76 {
77 self.with_relation_spec_query_presence::<F, R>(QueryPresence::Resolve)
78 }
79
80 pub fn with_resolved_relation<F, R>(self) -> Result<Self, FactBindingError>
88 where
89 F: Fact,
90 R: RelationSpec,
91 {
92 self.with_relation_spec_query_presence::<F, R>(QueryPresence::Resolve)
93 }
94
95 pub fn with_deferred_relation<F, R>(self) -> Result<Self, FactBindingError>
103 where
104 F: Fact,
105 R: RelationSpec,
106 {
107 self.with_relation_spec_query_presence::<F, R>(QueryPresence::Defer)
108 }
109
110 pub fn with_relation_spec_query_presence<F, R>(
117 self,
118 query_presence: QueryPresence,
119 ) -> Result<Self, FactBindingError>
120 where
121 F: Fact,
122 R: RelationSpec,
123 {
124 Ok(
125 self.with_binding(FactBinding::for_relation_spec_with_query_presence::<F, R>(
126 query_presence,
127 )?),
128 )
129 }
130
131 pub fn with_relation_spec_on_subject<F, R>(
139 self,
140 subject_slot: gatekeep::SubjectSlot,
141 ) -> Result<Self, FactBindingError>
142 where
143 F: Fact,
144 R: RelationSpec,
145 {
146 Ok(
147 self.with_binding(FactBinding::for_relation_spec_on_subject::<F, R>(
148 subject_slot,
149 )?),
150 )
151 }
152
153 #[must_use]
155 pub const fn bindings(&self) -> &BTreeMap<FactId, FactBinding> {
156 &self.bindings
157 }
158
159 #[must_use]
161 pub const fn source(&self) -> &S {
162 &self.source
163 }
164
165 #[must_use]
167 pub const fn subject_mapper(&self) -> &M {
168 &self.subject_mapper
169 }
170}
171
172#[async_trait]
173impl<S, M> FactResolver for KeepsakeResolver<S, M>
174where
175 S: ActiveRelationSource,
176 M: SubjectMapper,
177{
178 type Error = KeepsakeResolveError<S::Error>;
179
180 async fn resolve_for_decision(
181 &self,
182 required: &[FactId],
183 cx: &Context,
184 ) -> Result<KnownFacts, ResolveError<Self::Error>> {
185 let bindings = self.bindings_for(required)?;
186 let active_relations = self.active_relation_ids_by_subject(cx, &bindings).await?;
187 let entries = bindings.into_iter().map(|binding| {
188 let presence = relation_presence(
189 &active_relations,
190 binding.subject_slot.as_ref(),
191 binding.relation_id,
192 );
193 (binding.fact.clone(), presence)
194 });
195 Ok(KnownFacts::from_entries(entries).map_err(KeepsakeResolveError::Gatekeep)?)
196 }
197
198 async fn resolve_for_query(
199 &self,
200 required: &[FactId],
201 cx: &Context,
202 ) -> Result<PartialFacts, ResolveError<Self::Error>> {
203 let bindings = self.bindings_for(required)?;
204 let needs_active_lookup = bindings
205 .iter()
206 .any(|binding| binding.query_presence == QueryPresence::Resolve);
207 let active_relations = if needs_active_lookup {
208 let resolved_bindings = bindings
209 .iter()
210 .copied()
211 .filter(|binding| binding.query_presence == QueryPresence::Resolve)
212 .collect::<Vec<_>>();
213 self.active_relation_ids_by_subject(cx, &resolved_bindings)
214 .await?
215 } else {
216 BTreeSet::new()
217 };
218 let entries = bindings.into_iter().map(|binding| {
219 let presence = match binding.query_presence {
220 QueryPresence::Resolve => relation_presence(
221 &active_relations,
222 binding.subject_slot.as_ref(),
223 binding.relation_id,
224 ),
225 QueryPresence::Defer => Presence::Unknown,
226 };
227 (binding.fact.clone(), presence)
228 });
229 Ok(PartialFacts::from_entries(entries))
230 }
231}
232
233impl<S, M> KeepsakeResolver<S, M>
234where
235 S: ActiveRelationSource,
236 M: SubjectMapper,
237{
238 fn bindings_for<'binding>(
239 &'binding self,
240 required: &[FactId],
241 ) -> Result<Vec<&'binding FactBinding>, ResolveError<KeepsakeResolveError<S::Error>>> {
242 required
243 .iter()
244 .map(|fact| {
245 self.bindings
246 .get(fact)
247 .ok_or_else(|| ResolveError::MissingFact(fact.clone()))
248 })
249 .collect()
250 }
251
252 async fn active_relation_ids_by_subject(
253 &self,
254 cx: &Context,
255 bindings: &[&FactBinding],
256 ) -> Result<
257 BTreeSet<(Option<gatekeep::SubjectSlot>, RelationId)>,
258 ResolveError<KeepsakeResolveError<S::Error>>,
259 > {
260 let mut grouped = BTreeMap::<Option<gatekeep::SubjectSlot>, SubjectLookup>::new();
261 for binding in bindings {
262 let subject = self
263 .subject_for_binding(cx, binding)
264 .map_err(|error| match error {
265 SubjectLookupError::MissingSlot(slot) => ResolveError::MissingSubject {
266 fact: binding.fact.clone(),
267 slot,
268 },
269 SubjectLookupError::Keepsake(error) => {
270 ResolveError::Backend(KeepsakeResolveError::from(error))
271 }
272 })?;
273 grouped
274 .entry(binding.subject_slot.clone())
275 .or_insert_with(|| SubjectLookup {
276 subject,
277 relation_ids: BTreeSet::new(),
278 })
279 .relation_ids
280 .insert(binding.relation_id);
281 }
282
283 let mut active = BTreeSet::new();
284 for (slot, lookup) in grouped {
285 let relation_ids = lookup.relation_ids.into_iter().collect::<Vec<_>>();
286 let active_relations = self
287 .source
288 .active_relations_for_subject_by_ids(&lookup.subject, &relation_ids)
289 .await
290 .map_err(KeepsakeResolveError::Source)?;
291 for relation_id in active_relation_ids(active_relations) {
292 active.insert((slot.clone(), relation_id));
293 }
294 }
295 Ok(active)
296 }
297
298 fn subject_for_binding(
299 &self,
300 cx: &Context,
301 binding: &FactBinding,
302 ) -> Result<KeepsakeSubjectRef, SubjectLookupError> {
303 if let Some(slot) = &binding.subject_slot {
304 let Some(subject) = cx.subjects.get(slot) else {
305 return Err(SubjectLookupError::MissingSlot(slot.clone()));
306 };
307 return KeepsakeSubjectRef::new(subject.kind(), subject.id())
308 .map_err(SubjectLookupError::Keepsake);
309 }
310 self.subject_mapper
311 .subject(cx)
312 .map_err(SubjectLookupError::Keepsake)
313 }
314}
315
316struct SubjectLookup {
317 subject: KeepsakeSubjectRef,
318 relation_ids: BTreeSet<RelationId>,
319}
320
321enum SubjectLookupError {
322 MissingSlot(gatekeep::SubjectSlot),
323 Keepsake(keepsake::KeepsakeError),
324}
325
326fn active_relation_ids(active_relations: Vec<ActiveRelation>) -> BTreeSet<RelationId> {
327 active_relations
328 .into_iter()
329 .map(|active| active.keepsake().relation_id())
330 .collect()
331}
332
333fn relation_presence(
334 active_relations: &BTreeSet<(Option<gatekeep::SubjectSlot>, RelationId)>,
335 subject_slot: Option<&gatekeep::SubjectSlot>,
336 relation_id: RelationId,
337) -> Presence {
338 if active_relations.contains(&(subject_slot.cloned(), relation_id)) {
339 Presence::Present
340 } else {
341 Presence::Absent
342 }
343}