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::{ActiveRelation, ActiveRelationSource, RelationId, RelationSpec};
8
9use crate::{
10 FactBinding, FactBindingError, KeepsakeRelationTarget, KeepsakeResolveError,
11 KeepsakeTargetError, QueryPresence, SubjectMapper, TenantScopedSubjectMapper,
12};
13
14#[derive(Clone, Debug)]
16pub struct KeepsakeResolver<S, M = TenantScopedSubjectMapper> {
17 source: S,
18 subject_mapper: M,
19 bindings: BTreeMap<FactId, FactBinding>,
20}
21
22impl<S> KeepsakeResolver<S, TenantScopedSubjectMapper> {
23 #[must_use]
25 pub const fn new(source: S) -> Self {
26 Self::with_subject_mapper(source, TenantScopedSubjectMapper)
27 }
28}
29
30impl<S, M> KeepsakeResolver<S, M> {
31 #[must_use]
33 pub const fn with_subject_mapper(source: S, subject_mapper: M) -> Self {
34 Self {
35 source,
36 subject_mapper,
37 bindings: BTreeMap::new(),
38 }
39 }
40
41 #[must_use]
43 pub fn map_subjects<Next>(self, subject_mapper: Next) -> KeepsakeResolver<S, Next> {
44 KeepsakeResolver {
45 source: self.source,
46 subject_mapper,
47 bindings: self.bindings,
48 }
49 }
50
51 #[must_use]
53 pub fn with_binding(mut self, binding: FactBinding) -> Self {
54 self.insert_binding(binding);
55 self
56 }
57
58 pub fn insert_binding(&mut self, binding: FactBinding) {
60 self.bindings.insert(binding.fact.clone(), binding);
61 }
62
63 pub fn with_relation_spec<F, R>(self) -> Result<Self, FactBindingError>
70 where
71 F: Fact,
72 R: RelationSpec,
73 {
74 self.with_relation_spec_query_presence::<F, R>(QueryPresence::Resolve)
75 }
76
77 pub fn with_resolved_relation<F, R>(self) -> Result<Self, FactBindingError>
85 where
86 F: Fact,
87 R: RelationSpec,
88 {
89 self.with_relation_spec_query_presence::<F, R>(QueryPresence::Resolve)
90 }
91
92 pub fn with_deferred_relation<F, R>(self) -> Result<Self, FactBindingError>
100 where
101 F: Fact,
102 R: RelationSpec,
103 {
104 self.with_relation_spec_query_presence::<F, R>(QueryPresence::Defer)
105 }
106
107 pub fn with_relation_spec_query_presence<F, R>(
114 self,
115 query_presence: QueryPresence,
116 ) -> Result<Self, FactBindingError>
117 where
118 F: Fact,
119 R: RelationSpec,
120 {
121 Ok(
122 self.with_binding(FactBinding::for_relation_spec_with_query_presence::<F, R>(
123 query_presence,
124 )?),
125 )
126 }
127
128 pub fn with_relation_spec_on_subject<F, R>(
136 self,
137 subject_slot: gatekeep::SubjectSlot,
138 ) -> Result<Self, FactBindingError>
139 where
140 F: Fact,
141 R: RelationSpec,
142 {
143 Ok(
144 self.with_binding(FactBinding::for_relation_spec_on_subject::<F, R>(
145 subject_slot,
146 )?),
147 )
148 }
149
150 #[must_use]
152 pub const fn bindings(&self) -> &BTreeMap<FactId, FactBinding> {
153 &self.bindings
154 }
155
156 #[must_use]
158 pub const fn source(&self) -> &S {
159 &self.source
160 }
161
162 #[must_use]
164 pub const fn subject_mapper(&self) -> &M {
165 &self.subject_mapper
166 }
167}
168
169impl<S, M> KeepsakeResolver<S, M>
170where
171 M: SubjectMapper,
172{
173 pub fn target_for_binding(
181 &self,
182 binding: &FactBinding,
183 cx: &Context,
184 ) -> Result<KeepsakeRelationTarget, KeepsakeTargetError> {
185 let subject = if let Some(slot) = &binding.subject_slot {
186 let Some(subject) = cx.subjects.get(slot) else {
187 return Err(KeepsakeTargetError::MissingSubjectSlot {
188 fact: binding.fact.clone(),
189 slot: slot.clone(),
190 });
191 };
192 keepsake::SubjectRef::new(subject.kind(), subject.id()).map_err(|source| {
193 KeepsakeTargetError::Subject {
194 fact: binding.fact.clone(),
195 source,
196 }
197 })?
198 } else {
199 self.subject_mapper
200 .subject(cx)
201 .map_err(|source| KeepsakeTargetError::Subject {
202 fact: binding.fact.clone(),
203 source,
204 })?
205 };
206
207 Ok(KeepsakeRelationTarget {
208 fact: binding.fact.clone(),
209 subject,
210 relation_id: binding.relation_id,
211 subject_slot: binding.subject_slot.clone(),
212 })
213 }
214
215 pub fn target_for_fact(
223 &self,
224 fact: &FactId,
225 cx: &Context,
226 ) -> Result<KeepsakeRelationTarget, KeepsakeTargetError> {
227 let binding = self
228 .bindings
229 .get(fact)
230 .ok_or_else(|| KeepsakeTargetError::MissingBinding { fact: fact.clone() })?;
231 self.target_for_binding(binding, cx)
232 }
233
234 pub fn targets_for_facts(
241 &self,
242 facts: &[FactId],
243 cx: &Context,
244 ) -> Result<Vec<KeepsakeRelationTarget>, KeepsakeTargetError> {
245 facts
246 .iter()
247 .map(|fact| self.target_for_fact(fact, cx))
248 .collect()
249 }
250}
251
252#[async_trait]
253impl<S, M> FactResolver for KeepsakeResolver<S, M>
254where
255 S: ActiveRelationSource,
256 M: SubjectMapper,
257{
258 type Error = KeepsakeResolveError<S::Error>;
259
260 async fn resolve_for_decision(
261 &self,
262 required: &[FactId],
263 cx: &Context,
264 ) -> Result<KnownFacts, ResolveError<Self::Error>> {
265 let bindings = self.bindings_for(required)?;
266 let active_relations = self.active_relation_ids_by_subject(cx, &bindings).await?;
267 let entries = bindings.into_iter().map(|binding| {
268 let presence = relation_presence(
269 &active_relations,
270 binding.subject_slot.as_ref(),
271 binding.relation_id,
272 );
273 (binding.fact.clone(), presence)
274 });
275 Ok(KnownFacts::from_entries(entries).map_err(KeepsakeResolveError::Gatekeep)?)
276 }
277
278 async fn resolve_for_query(
279 &self,
280 required: &[FactId],
281 cx: &Context,
282 ) -> Result<PartialFacts, ResolveError<Self::Error>> {
283 let bindings = self.bindings_for(required)?;
284 let needs_active_lookup = bindings
285 .iter()
286 .any(|binding| binding.query_presence == QueryPresence::Resolve);
287 let active_relations = if needs_active_lookup {
288 let resolved_bindings = bindings
289 .iter()
290 .copied()
291 .filter(|binding| binding.query_presence == QueryPresence::Resolve)
292 .collect::<Vec<_>>();
293 self.active_relation_ids_by_subject(cx, &resolved_bindings)
294 .await?
295 } else {
296 BTreeSet::new()
297 };
298 let entries = bindings.into_iter().map(|binding| {
299 let presence = match binding.query_presence {
300 QueryPresence::Resolve => relation_presence(
301 &active_relations,
302 binding.subject_slot.as_ref(),
303 binding.relation_id,
304 ),
305 QueryPresence::Defer => Presence::Unknown,
306 };
307 (binding.fact.clone(), presence)
308 });
309 Ok(PartialFacts::from_entries(entries))
310 }
311}
312
313impl<S, M> KeepsakeResolver<S, M>
314where
315 S: ActiveRelationSource,
316 M: SubjectMapper,
317{
318 fn bindings_for<'binding>(
319 &'binding self,
320 required: &[FactId],
321 ) -> Result<Vec<&'binding FactBinding>, ResolveError<KeepsakeResolveError<S::Error>>> {
322 required
323 .iter()
324 .map(|fact| {
325 self.bindings
326 .get(fact)
327 .ok_or_else(|| ResolveError::MissingFact(fact.clone()))
328 })
329 .collect()
330 }
331
332 async fn active_relation_ids_by_subject(
333 &self,
334 cx: &Context,
335 bindings: &[&FactBinding],
336 ) -> Result<
337 BTreeSet<(Option<gatekeep::SubjectSlot>, RelationId)>,
338 ResolveError<KeepsakeResolveError<S::Error>>,
339 > {
340 let mut grouped = BTreeMap::<Option<gatekeep::SubjectSlot>, SubjectLookup>::new();
341 for binding in bindings {
342 let target = self
343 .target_for_binding(binding, cx)
344 .map_err(|error| match error {
345 KeepsakeTargetError::MissingBinding { fact } => ResolveError::MissingFact(fact),
346 KeepsakeTargetError::MissingSubjectSlot { fact, slot } => {
347 ResolveError::MissingSubject { fact, slot }
348 }
349 KeepsakeTargetError::Subject { source, .. } => {
350 ResolveError::Backend(KeepsakeResolveError::from(source))
351 }
352 })?;
353 grouped
354 .entry(target.subject_slot)
355 .or_insert_with(|| SubjectLookup {
356 subject: target.subject,
357 relation_ids: BTreeSet::new(),
358 })
359 .relation_ids
360 .insert(target.relation_id);
361 }
362
363 let mut active = BTreeSet::new();
364 for (slot, lookup) in grouped {
365 let relation_ids = lookup.relation_ids.into_iter().collect::<Vec<_>>();
366 let active_relations = self
367 .source
368 .active_relations_for_subject_by_ids(&lookup.subject, &relation_ids)
369 .await
370 .map_err(KeepsakeResolveError::Source)?;
371 for relation_id in active_relation_ids(active_relations) {
372 active.insert((slot.clone(), relation_id));
373 }
374 }
375 Ok(active)
376 }
377}
378
379struct SubjectLookup {
380 subject: keepsake::SubjectRef,
381 relation_ids: BTreeSet<RelationId>,
382}
383
384fn active_relation_ids(active_relations: Vec<ActiveRelation>) -> BTreeSet<RelationId> {
385 active_relations
386 .into_iter()
387 .map(|active| active.keepsake().relation_id())
388 .collect()
389}
390
391fn relation_presence(
392 active_relations: &BTreeSet<(Option<gatekeep::SubjectSlot>, RelationId)>,
393 subject_slot: Option<&gatekeep::SubjectSlot>,
394 relation_id: RelationId,
395) -> Presence {
396 if active_relations.contains(&(subject_slot.cloned(), relation_id)) {
397 Presence::Present
398 } else {
399 Presence::Absent
400 }
401}