Skip to main content

gatekeep_keepsake/
binding.rs

1use gatekeep::{Fact, FactId, GatekeepError, SubjectSlot};
2use keepsake::{RelationId, RelationSpec};
3use thiserror::Error;
4
5/// Whether a keepsake-backed fact is resolved during query preparation.
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
7pub enum QueryPresence {
8    /// Resolve the fact from the current principal's active keepsakes.
9    #[default]
10    Resolve,
11    /// Leave the fact unknown so query lowering can evaluate it per row.
12    Defer,
13}
14
15/// Mapping from one gatekeep fact to one keepsake relation.
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct FactBinding {
18    pub(crate) fact: FactId,
19    pub(crate) relation_id: RelationId,
20    pub(crate) subject_slot: Option<SubjectSlot>,
21    pub(crate) query_presence: QueryPresence,
22}
23
24impl FactBinding {
25    /// Builds a binding that resolves in both decision and query mode.
26    #[must_use]
27    pub const fn new(fact: FactId, relation_id: RelationId) -> Self {
28        Self::with_subject_and_query_presence(fact, relation_id, None, QueryPresence::Resolve)
29    }
30
31    /// Builds a binding with explicit query-mode behavior.
32    #[must_use]
33    pub const fn with_query_presence(
34        fact: FactId,
35        relation_id: RelationId,
36        query_presence: QueryPresence,
37    ) -> Self {
38        Self::with_subject_and_query_presence(fact, relation_id, None, query_presence)
39    }
40
41    /// Builds a binding for a request-scoped subject slot.
42    #[must_use]
43    pub const fn on_subject(
44        fact: FactId,
45        relation_id: RelationId,
46        subject_slot: SubjectSlot,
47    ) -> Self {
48        Self::with_subject_and_query_presence(
49            fact,
50            relation_id,
51            Some(subject_slot),
52            QueryPresence::Resolve,
53        )
54    }
55
56    /// Builds a binding for a request-scoped subject slot with explicit
57    /// query-mode behavior.
58    #[must_use]
59    pub const fn with_subject_and_query_presence(
60        fact: FactId,
61        relation_id: RelationId,
62        subject_slot: Option<SubjectSlot>,
63        query_presence: QueryPresence,
64    ) -> Self {
65        Self {
66            fact,
67            relation_id,
68            subject_slot,
69            query_presence,
70        }
71    }
72
73    /// Binds a typed gatekeep fact to a typed keepsake relation.
74    ///
75    /// # Errors
76    ///
77    /// Returns [`FactBindingError::Gatekeep`] if the fact marker exposes an
78    /// invalid stable id.
79    pub fn for_relation_spec<F, R>() -> Result<Self, FactBindingError>
80    where
81        F: Fact,
82        R: RelationSpec,
83    {
84        Self::for_relation_spec_with_query_presence::<F, R>(QueryPresence::Resolve)
85    }
86
87    /// Binds a typed gatekeep fact to a typed keepsake relation on a
88    /// request-scoped subject.
89    ///
90    /// # Errors
91    ///
92    /// Returns [`FactBindingError::Gatekeep`] if the fact marker exposes an
93    /// invalid stable id.
94    pub fn for_relation_spec_on_subject<F, R>(
95        subject_slot: SubjectSlot,
96    ) -> Result<Self, FactBindingError>
97    where
98        F: Fact,
99        R: RelationSpec,
100    {
101        Self::for_relation_spec_on_subject_with_query_presence::<F, R>(
102            subject_slot,
103            QueryPresence::Resolve,
104        )
105    }
106
107    /// Binds a typed gatekeep fact to a typed keepsake relation that is
108    /// resolved during query preparation.
109    ///
110    /// # Errors
111    ///
112    /// Returns [`FactBindingError::Gatekeep`] if the fact marker exposes an
113    /// invalid stable id.
114    pub fn resolve_relation<F, R>() -> Result<Self, FactBindingError>
115    where
116        F: Fact,
117        R: RelationSpec,
118    {
119        Self::for_relation_spec_with_query_presence::<F, R>(QueryPresence::Resolve)
120    }
121
122    /// Binds a typed gatekeep fact to a typed keepsake relation that is left
123    /// unknown during query preparation for row-level lowering.
124    ///
125    /// # Errors
126    ///
127    /// Returns [`FactBindingError::Gatekeep`] if the fact marker exposes an
128    /// invalid stable id.
129    pub fn defer_relation<F, R>() -> Result<Self, FactBindingError>
130    where
131        F: Fact,
132        R: RelationSpec,
133    {
134        Self::for_relation_spec_with_query_presence::<F, R>(QueryPresence::Defer)
135    }
136
137    /// Binds a typed gatekeep fact to a typed keepsake relation, with explicit
138    /// query-mode behavior.
139    ///
140    /// # Errors
141    ///
142    /// Returns [`FactBindingError::Gatekeep`] if the fact marker exposes an
143    /// invalid stable id.
144    pub fn for_relation_spec_with_query_presence<F, R>(
145        query_presence: QueryPresence,
146    ) -> Result<Self, FactBindingError>
147    where
148        F: Fact,
149        R: RelationSpec,
150    {
151        Ok(Self::with_query_presence(
152            F::ID.to_owned_id()?,
153            R::ID,
154            query_presence,
155        ))
156    }
157
158    /// Binds a typed gatekeep fact to a typed keepsake relation on a
159    /// request-scoped subject, with explicit query-mode behavior.
160    ///
161    /// # Errors
162    ///
163    /// Returns [`FactBindingError::Gatekeep`] if the fact marker exposes an
164    /// invalid stable id.
165    pub fn for_relation_spec_on_subject_with_query_presence<F, R>(
166        subject_slot: SubjectSlot,
167        query_presence: QueryPresence,
168    ) -> Result<Self, FactBindingError>
169    where
170        F: Fact,
171        R: RelationSpec,
172    {
173        Ok(Self::with_subject_and_query_presence(
174            F::ID.to_owned_id()?,
175            R::ID,
176            Some(subject_slot),
177            query_presence,
178        ))
179    }
180
181    /// Returns the gatekeep fact id.
182    #[must_use]
183    pub const fn fact(&self) -> &FactId {
184        &self.fact
185    }
186
187    /// Returns the keepsake relation id.
188    #[must_use]
189    pub const fn relation_id(&self) -> RelationId {
190        self.relation_id
191    }
192
193    /// Returns the request-scoped subject slot for this binding.
194    #[must_use]
195    pub const fn subject_slot(&self) -> Option<&SubjectSlot> {
196        self.subject_slot.as_ref()
197    }
198
199    /// Returns query-mode resolution behavior.
200    #[must_use]
201    pub const fn query_presence(&self) -> QueryPresence {
202        self.query_presence
203    }
204}
205
206/// Errors returned while building typed fact bindings.
207#[derive(Debug, Error, Clone, PartialEq, Eq)]
208pub enum FactBindingError {
209    /// The gatekeep fact marker had an invalid id.
210    #[error(transparent)]
211    Gatekeep(#[from] GatekeepError),
212}