Skip to main content

mcp_server_sqlite/
access_control.rs

1//! Rule-based access control for the MCP SQLite server.
2//!
3//! This module implements the permission system that decides which SQL
4//! operations the server may execute. Permissions are configured at startup
5//! through the `--allow` and `--deny` CLI flags, each taking an *access control
6//! selector* that identifies the operation — and optionally the exact resources
7//! — the rule applies to.
8//!
9//! # Selector syntax
10//!
11//! Every SQLite authorization action (`Read`, `Insert`, `CreateTable`,
12//! `DropIndex`, `Function`, etc.) has a corresponding selector type. A selector
13//! is written as:
14//!
15//! - `Action` — matches **all** operations of that type (every field defaults
16//!   to the wildcard `*`).
17//! - `Action(value)` — pins the first field to a specific value; remaining
18//!   fields stay as wildcards.
19//! - `Action(value1.value2)` — pins both fields.
20//! - `Action(*.value2)` — wildcard first field, pinned second field.
21//!
22//! For example, the `Read` selector has two fields (`table_name` and
23//! `column_name`):
24//!
25//! | CLI flag                       | Meaning                        |
26//! |--------------------------------|--------------------------------|
27//! | `--allow Read`                 | allow all column reads         |
28//! | `--deny  Read(Secrets)`        | deny reads on `Secrets`        |
29//! | `--allow Read(Secrets.id)`     | allow reads on `Secrets.id`    |
30//! | `--deny  Read(*.ssn)`          | deny reads on any `ssn` column |
31//!
32//! # Specificity
33//!
34//! Each selector has a *specificity* equal to the number of its fields that are
35//! pinned to concrete values (i.e. not `*`). A bare `Read` has specificity 0,
36//! `Read(Students)` has specificity 1, and `Read(Students.name)` has
37//! specificity 2. More specific rules always outrank less specific ones.
38//!
39//! # Resolution algorithm
40//!
41//! When SQLite asks "is this operation allowed?", the [`AuthorizationResolver`]
42//! evaluates rules as follows:
43//!
44//! 1. **Collect matching rules.** For each configured rule whose selector
45//!    covers the requested operation (globs match anything, values must match
46//!    exactly), note its specificity.
47//!
48//! 2. **Most specific wins.** Rules are evaluated from the highest specificity
49//!    level to the lowest. The first level that contains at least one matching
50//!    rule determines the outcome.
51//!
52//! 3. **Deny breaks ties.** If both allow *and* deny rules match at the same
53//!    specificity level, the result is **deny** (fail-closed).
54//!
55//! 4. **Default fallback.** If no rule matches at any level, the per-action
56//!    default is used. The default depends on how the resolver was constructed
57//!    (e.g. [`AuthorizationResolver::new_allow_everything`] ).
58//!
59//! One sentence summary: **the most specific matching rule wins; ties go to
60//! deny; no match defers to the default.**
61//!
62//! # Examples
63//!
64//! ## Read-only server with one table blocked
65//!
66//! ```text
67//! --allow Read --deny Read(Secrets)
68//! ```
69//!
70//! All column reads are allowed except anything touching the `Secrets` table.
71//! `Read(Secrets)` has specificity 1 which beats the blanket `Read` at
72//! specificity 0, so the deny wins for any column in `Secrets`.
73//!
74//! ## Read-only with a carve-out
75//!
76//! ```text
77//! --allow Read --deny Read(Secrets) --allow Read(Secrets.id)
78//! ```
79//!
80//! All reads are allowed. Reads on `Secrets` are denied — except for the `id`
81//! column, which is explicitly re-allowed at specificity 2 (both fields
82//! pinned), beating the specificity-1 deny.
83//!
84//! ## Conflicting rules at the same specificity
85//!
86//! ```text
87//! --deny Read(Students) --allow Read(*.name)
88//! ```
89//!
90//! A read on `Students.name` matches both rules. `Read(Students)` has
91//! specificity 1 (table pinned, column glob) and `Read(*.name)` also has
92//! specificity 1 (table glob, column pinned). Because both match at the same
93//! level, **deny wins**. To allow `Students.name`, add a more specific rule:
94//! `--allow Read(Students.name)` (specificity 2).
95//!
96//! ## Deny everything, allow specific functions
97//!
98//! ```text
99//! --deny Function --allow Function(count) --allow Function(sum)
100//! ```
101//!
102//! All SQL function calls are denied except `count` and `sum`, which win at
103//! specificity 1 over the blanket deny at specificity 0.
104//!
105//! ## Lock down DDL while permitting reads and inserts
106//!
107//! ```text
108//! --allow Read --allow Insert --allow Select --allow Transaction
109//! ```
110//!
111//! With a deny-everything default, only the explicitly allowed actions proceed.
112//! All DDL (`CreateTable`, `DropIndex`, etc.) remains denied because no allow
113//! rule exists for those actions.
114//!
115//! # Code generation
116//!
117//! The `define_access_control_selector_types!` macro generates the selector
118//! structs, the [`AccessControlSelector`] enum, the
119//! [`AccessControlSelectorSet`], and the [`AuthorizationResolver`] from a set
120//! of struct declarations. Each struct's field types are wrapped in
121//! [`ValueOrGlob`] so that every field can independently be a concrete value or
122//! a wildcard. The macro also generates `Display` / `FromStr` round-tripping,
123//! builder methods, and the specificity-aware resolution logic described above.
124
125use std::{error::Error, fmt::Display, str::FromStr};
126
127/// Counts the number of identifier tokens passed to it at compile time.
128///
129/// This is used internally by `define_access_control_selector_types!` to
130/// determine `FIELD_COUNT` for each generated selector struct, which in turn
131/// drives field-count validation during parsing.
132///
133/// # Examples
134///
135/// ```ignore
136/// count_idents!(a, b, c) // expands to 3
137/// count_idents!()        // expands to 0
138/// ```
139macro_rules! count_idents {
140    ($ident: ident, $($rest_idents: ident),* $(,)?) => {
141        1 + count_idents!($($rest_idents),*)
142    };
143    ($ident: ident) => {
144        1
145    };
146    () => {
147        0
148    };
149}
150
151/// Builds a `format!`-compatible template string with `{}` placeholders joined
152/// by dots, matching the dot-separated field syntax used in selector strings.
153///
154/// For a selector struct with fields `table_name` and `column_name`, this
155/// produces `"{}.{}"`, which is interpolated inside `format!` to render
156/// something like `Students.name`.
157///
158/// # Examples
159///
160/// ```ignore
161/// formatting_string_literal!(a, b)  // expands to "{}.{}"
162/// formatting_string_literal!(a)     // expands to "{}"
163/// formatting_string_literal!()      // expands to ""
164/// ```
165macro_rules! formatting_string_literal {
166    ($ident: ident, $($rest_idents: ident),* $(,)?) => {
167        concat!("{}.", formatting_string_literal!($($rest_idents),*))
168    };
169    ($ident: ident) => {
170        "{}"
171    };
172    () => {
173        ""
174    };
175}
176
177/// Generates access control selector structs, an umbrella
178/// [`AccessControlSelector`] enum, and all associated trait implementations
179/// from a set of struct declarations.
180///
181/// Each struct declaration passed to this macro produces:
182///
183/// - A public struct whose field types are each wrapped in [`ValueOrGlob<T>`],
184///   so every field can independently hold a concrete value or a wildcard glob.
185/// - A variant on [`AccessControlSelector`] named `{Struct}` that wraps the
186///   generated struct.
187/// - `Display` formatting that round-trips with the CLI string syntax: bare
188///   identifier when all fields are globs (e.g., `Read`), or identifier with
189///   parenthesized dot-separated fields otherwise (e.g.,
190///   `Read(Students.name)`).
191/// - `FromStr` parsing that accepts the same syntax and validates field counts
192///   and selector identifiers.
193/// - `From<T> for String` and `TryFrom<String> for T` conversions.
194/// - Builder methods: `new(...)`, `empty()`, and `with_{field}(...)` for
195///   ergonomic construction.
196/// - Inspection methods: `is_all_glob`, `is_all_value`, `is_any_glob`,
197///   `is_any_value`.
198///
199/// Doc comments (`#[doc = "..."]`) and other attributes placed on the struct or
200/// its fields in the macro invocation are forwarded to both the generated
201/// struct and the corresponding enum variant via the `$(#[$struct_meta])*` and
202/// `$(#[$field_meta])*` captures.
203///
204/// # Usage
205///
206/// ```ignore
207/// define_access_control_selector_types! {
208///     /// Controls read access at the table and column level.
209///     pub struct Read {
210///         /// The table targeted by this selector.
211///         pub table_name: String,
212///         /// The column targeted by this selector.
213///         pub column_name: String,
214///     }
215/// }
216/// ```
217macro_rules! define_access_control_selector_types {
218    (
219        $(
220            $(#[$struct_meta: meta])*
221            $struct_vis: vis struct $struct_ident: ident {
222                $(
223                    $(#[$field_meta: meta])*
224                    $field_vis: vis $field_ident: ident: $field_ty: ty
225                ),* $(,)?
226            }
227        )*
228    ) => {
229        paste::paste! {
230            /// Resolves incoming SQLite authorization requests against a set of
231            /// configured allow/deny rules and per-action default permissions.
232            ///
233            /// The resolver holds an [`AccessControlSelectorSet`] containing
234            /// all user-supplied rules, plus a default
235            /// [`rusqlite::hooks::Authorization`] for each action type that is
236            /// used when no rule matches. Call [`authorization`] with an
237            /// [`AuthContext`] to obtain the final verdict.
238            ///
239            /// [`AuthContext`]: rusqlite::hooks::AuthContext
240            ///
241            /// [`authorization`]: AuthorizationResolver::authorization
242            pub struct AuthorizationResolver {
243                /// The set of allow/deny rules to evaluate.
244                pub selector_set: AccessControlSelectorSet,
245                $(
246                    /// The fallback authorization returned when no rule in the
247                    /// selector set matches this action type.
248                    pub [< $struct_ident:snake _default_permissions >]: rusqlite::hooks::Authorization,
249                )*
250            }
251
252            const _: () = {
253                impl AuthorizationResolver {
254                    /// Creates a resolver that permits every action by default.
255                    /// Rules added to the selector set can then selectively
256                    /// deny specific operations.
257                    pub fn new_allow_everything() -> Self {
258                        Self {
259                            selector_set: Default::default(),
260                            $(
261                                [< $struct_ident:snake _default_permissions >]: rusqlite::hooks::Authorization::Allow,
262                            )*
263                        }
264                    }
265
266                    /// Creates a resolver that denies every action by default.
267                    /// Rules added to the selector set can then selectively
268                    /// allow specific operations.
269                    pub fn new_deny_everything() -> Self {
270                        Self {
271                            selector_set: Default::default(),
272                            $(
273                                [< $struct_ident:snake _default_permissions >]: rusqlite::hooks::Authorization::Deny,
274                            )*
275                        }
276                    }
277
278                    pub fn with_selector(mut self, selector: impl Into<AccessControlSelector>, allow: bool) -> Self {
279                        self.selector_set = self.selector_set.with_selector(selector, allow);
280                        self
281                    }
282
283                    $(
284                        #[doc = concat!(
285                            "Sets the default authorization returned for [`",
286                            stringify!($struct_ident),
287                            "`] actions when no rule matches."
288                        )]
289                        pub const fn [< with_ $struct_ident:snake _default_permissions >](
290                            mut self,
291                            default_permissions: rusqlite::hooks::Authorization
292                        ) -> Self {
293                            self.[< $struct_ident:snake _default_permissions >] = default_permissions;
294                            self
295                        }
296                    )*
297
298                    /// Evaluates the configured rules against an incoming
299                    /// SQLite authorization request. Dispatches to the
300                    /// appropriate type-specific check on the selector set and
301                    /// falls back to the per-action default when no rule
302                    /// matches. Unrecognized actions are denied
303                    /// unconditionally.
304                    pub fn authorization(
305                        &self,
306                        ctx: rusqlite::hooks::AuthContext<'_>
307                    ) -> rusqlite::hooks::Authorization {
308                        match ctx.action {
309                            $(
310                                rusqlite::hooks::AuthAction::$struct_ident { $($field_ident,)* .. } => {
311                                    self
312                                        .selector_set
313                                        .[< check_ $struct_ident: snake >]($(&$field_ident),*)
314                                        .map(Into::into)
315                                        .unwrap_or(self.[< $struct_ident:snake _default_permissions >])
316                                }
317                            )*,
318                            _ => rusqlite::hooks::Authorization::Deny
319                        }
320                    }
321                }
322            };
323
324            /// Holds all configured access control rules, grouped by selector
325            /// type and indexed by specificity.
326            ///
327            /// Each selector type gets its own [`BTreeMap`] keyed by the
328            /// selector's [`specificity`] value (number of concrete
329            /// [`Value`](ValueOrGlob::Value) fields). This layout enables the
330            /// policy resolver to iterate from most-specific to least-specific
331            /// rules efficiently: the highest-specificity match wins, with deny
332            /// breaking ties.
333            ///
334            /// [`BTreeMap`]: std::collections::BTreeMap
335            /// [`specificity`]: #method.specificity
336            #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
337            pub struct AccessControlSelectorSet {
338                $(
339                    $(#[$struct_meta])*
340                    pub [<$struct_ident: snake _selectors>]: std::collections::BTreeMap<usize, Vec<Permission<$struct_ident>>>,
341                )*
342            }
343
344            const _: () = {
345                impl AccessControlSelectorSet {
346                    /// Creates an empty [`AccessControlSelectorSet`] with no
347                    /// rules.
348                    pub fn new() -> Self {
349                        Default::default()
350                    }
351
352                    /// Adds a rule by converting any selector type into an
353                    /// [`AccessControlSelector`] and dispatching it to the
354                    /// appropriate type-specific builder. `allow` controls
355                    /// whether the rule permits or denies the matched
356                    /// operation.
357                    pub fn with_selector(
358                        self,
359                        selector: impl Into<AccessControlSelector>,
360                        allow: bool
361                    ) -> Self {
362                        let selector = selector.into();
363                        match selector {
364                            $(
365                                AccessControlSelector::$struct_ident(selector)
366                                    => self.[<with_ $struct_ident: snake _selector>](
367                                        selector,
368                                        allow
369                                    )
370                            ),*
371                        }
372                    }
373
374                    $(
375                        #[doc = concat!(
376                            "Adds a [`", stringify!($struct_ident),
377                            "`] rule, inserting it into the ",
378                            "bucket matching its specificity."
379                        )]
380                        pub fn [<with_ $struct_ident: snake _selector>](
381                            mut self,
382                            selector: $struct_ident,
383                            allow: bool
384                        ) -> Self {
385                            self.[<$struct_ident: snake _selectors>]
386                                .entry(selector.specificity())
387                                .or_default()
388                                .push(Permission {
389                                    selector,
390                                    allow
391                                });
392                            self
393                        }
394
395                        #[doc = concat!(
396                            "Resolves the configured [`",
397                            stringify!($struct_ident),
398                            "`] rules against the given field values. ",
399                            "Iterates from highest to lowest specificity; ",
400                            "at each level, deny wins if both allow and deny ",
401                            "match. Returns `None` when no rule matches."
402                        )]
403                        #[allow(clippy::ptr_arg)]
404                        pub fn [< check_ $struct_ident: snake >](&self, $($field_ident: &(impl PartialEq<$field_ty> + ?Sized)),*) -> Option<Authorization> {
405                            for permissions in self.[<$struct_ident: snake _selectors>].values().rev() {
406                                let mut allow_encountered = false;
407                                let mut deny_encountered = false;
408                                let authorizations = permissions.iter().filter_map(|permission| permission.check($($field_ident),*));
409                                for authorization in authorizations {
410                                    match authorization {
411                                        Authorization::Allow => {
412                                            allow_encountered = true
413                                        }
414                                        Authorization::Deny => {
415                                            deny_encountered = true
416                                        }
417                                    }
418                                    if allow_encountered == true && deny_encountered == true {
419                                        return Some(Authorization::Deny)
420                                    }
421                                }
422                                match (allow_encountered, deny_encountered) {
423                                    (false, false) => {
424                                        continue
425                                    },
426                                    (true, false) => {
427                                        return Some(Authorization::Allow)
428                                    },
429                                    (_, true) => {
430                                        return Some(Authorization::Deny)
431                                    },
432                                }
433                            }
434                            None
435                        }
436                    )*
437                }
438            };
439
440            /// Umbrella enum unifying all access control selector types into a
441            /// single type for use with the CLI argument parser.
442            ///
443            /// Each variant wraps a specific selector struct generated by
444            /// `define_access_control_selector_types!`. Parsing a selector
445            /// string through this enum automatically dispatches to the correct
446            /// variant based on the leading identifier.
447            #[allow(clippy::enum_variant_names)]
448            #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
449            pub enum AccessControlSelector {
450                $(
451                    $(#[$struct_meta])*
452                    $struct_ident($struct_ident)
453                ),*
454            }
455
456            const _: () = {
457                use core::fmt::{Display, Formatter, Result as FmtResult};
458
459                use $crate::access_control::AccessControlSelectorParseError;
460                use $crate::access_control::AccessControlSelectorParseError::*;
461
462                impl AccessControlSelector {
463                    $(
464                        #[doc = concat!(
465                            "Returns `true` if this is the [`",
466                            stringify!($struct_ident),
467                            "`] variant."
468                        )]
469                        pub const fn [<is_ $struct_ident: snake>](&self) -> bool {
470                            matches!(self, Self::$struct_ident(..))
471                        }
472
473                        #[doc = concat!(
474                            "Returns a reference to the inner [`",
475                            stringify!($struct_ident),
476                            "`] if this is that variant, ",
477                            "or `None` otherwise."
478                        )]
479                        pub const fn [<as_ $struct_ident: snake>](&self) -> Option<&$struct_ident> {
480                            if let Self::$struct_ident(value) = self {
481                                Some(value)
482                            } else {
483                                None
484                            }
485                        }
486
487                        #[doc = concat!(
488                            "Consumes `self` and returns the ",
489                            "inner [`",
490                            stringify!($struct_ident),
491                            "`] if this is that variant, ",
492                            "or `None` otherwise."
493                        )]
494                        pub fn [<into_ $struct_ident: snake>](self) -> Option<$struct_ident> {
495                            if let Self::$struct_ident(value) = self {
496                                Some(value)
497                            } else {
498                                None
499                            }
500                        }
501                    )*
502                }
503
504                /// Delegates to the wrapped selector variant's [`Display`]
505                /// implementation, producing the CLI selector syntax.
506                impl Display for AccessControlSelector {
507                    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
508                        match self {
509                            $(
510                                Self::$struct_ident(selector) => Display::fmt(selector, f),
511                            )*
512                        }
513                    }
514                }
515
516                /// Converts an [`AccessControlSelector`] into its string
517                /// representation via [`Display`].
518                impl From<AccessControlSelector> for String {
519                    fn from(v: AccessControlSelector) -> Self {
520                        v.to_string()
521                    }
522                }
523
524                /// Parses a selector string by extracting the leading
525                /// identifier and dispatching to the corresponding variant's
526                /// [`FromStr`] implementation.
527                ///
528                /// # Errors
529                ///
530                /// Returns
531                /// [`AccessControlSelectorParseError::InvalidAccessControlSelectorIdentifier`]
532                /// when the leading identifier does not match any known
533                /// selector variant, and propagates any error from the
534                /// variant-level parser.
535                impl FromStr for AccessControlSelector {
536                    type Err = AccessControlSelectorParseError;
537
538                    fn from_str(s: &str) -> Result<Self, Self::Err> {
539                        let selector = s
540                            .split("(")
541                            .next()
542                            .ok_or_else(|| InvalidAccessControlSelectorIdentifier {
543                                found: None,
544                                expected: &[$(stringify!($struct_ident)),*],
545                                access_control_selector_string: s.into(),
546                            })?;
547
548                        match selector {
549                            $(
550                                stringify!($struct_ident) => s.parse().map(Self::$struct_ident),
551                            )*
552                            other => Err(InvalidAccessControlSelectorIdentifier {
553                                found: Some(other.into()),
554                                expected: &[$(stringify!($struct_ident)),*],
555                                access_control_selector_string: s.into(),
556                            })
557                        }
558                    }
559                }
560
561                /// Parses an owned `String` into an [`AccessControlSelector`]
562                /// by delegating to [`FromStr`].
563                impl TryFrom<String> for AccessControlSelector {
564                    type Error = AccessControlSelectorParseError;
565
566                    fn try_from(v: String) -> Result<Self, Self::Error> {
567                        v.parse()
568                    }
569                }
570
571                $(
572                    #[doc = concat!(
573                        "Converts a [`",
574                        stringify!($struct_ident),
575                        "`] into its corresponding [`",
576                        "AccessControlSelector`] variant."
577                    )]
578                    impl From<$struct_ident> for AccessControlSelector {
579                        fn from(value: $struct_ident) -> Self {
580                            Self::$struct_ident(value)
581                        }
582                    }
583                )*
584            };
585
586            $(
587                $(#[$struct_meta])*
588                #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
589                $struct_vis struct $struct_ident {
590                    $(
591                        $(#[$field_meta])*
592                        $field_vis $field_ident: ValueOrGlob<$field_ty>
593                    ),*
594                }
595
596                const _: () = {
597                    use core::fmt::{Display, Formatter, Result as FmtResult};
598                    use core::convert::*;
599                    use core::str::*;
600
601                    use $crate::access_control::{AccessControlSelectorParseError};
602                    use $crate::access_control::AccessControlSelectorParseError::*;
603
604                    impl $struct_ident {
605                        #[doc = concat!(
606                            "The number of fields declared on [`",
607                            stringify!($struct_ident),
608                            "`], used for field-count validation during parsing."
609                        )]
610                        const FIELD_COUNT: usize = count_idents!($($field_ident),*);
611
612                        #[doc = concat!(
613                            "Creates a new [`", stringify!($struct_ident),
614                            "`] with the provided field values."
615                        )]
616                        #[doc = ""]
617                        #[doc = concat!(
618                            "Each argument accepts any type that implements ",
619                            "`Into<ValueOrGlob<T>>`, so callers can pass a raw ",
620                            "value, a [`ValueOrGlob`], or an `Option<T>`."
621                        )]
622                        pub fn new(
623                            $(
624                                $field_ident: impl Into<ValueOrGlob<$field_ty>>
625                            ),*
626                        ) -> Self {
627                            Self {
628                                $(
629                                    $field_ident: $field_ident.into()
630                                ),*
631                            }
632                        }
633
634                        #[doc = concat!(
635                            "Creates a [`", stringify!($struct_ident),
636                            "`] where every field is a [`Glob`](ValueOrGlob::Glob), ",
637                            "matching all possible values."
638                        )]
639                        pub fn empty() -> Self {
640                            Self {
641                                $(
642                                    $field_ident: Default::default()
643                                ),*
644                            }
645                        }
646
647                        $(
648                            #[doc = concat!(
649                                "Sets [`", stringify!($struct_ident),
650                                "::", stringify!($field_ident),
651                                "`] and returns `self` for method chaining."
652                            )]
653                            #[doc = ""]
654                            $(#[$field_meta])*
655                            pub fn [< with_ $field_ident >](mut self, value: impl Into<ValueOrGlob<$field_ty>>) -> Self {
656                                self.$field_ident = value.into();
657                                self
658                            }
659                        )*
660
661                        #[doc = concat!(
662                            "Returns `true` if every field on this [`",
663                            stringify!($struct_ident),
664                            "`] is a [`Glob`](ValueOrGlob::Glob)."
665                        )]
666                        pub const fn is_all_glob(&self) -> bool {
667                            if Self::FIELD_COUNT != 0 {
668                                true $(&& ValueOrGlob::is_glob(&self.$field_ident))*
669                            } else {
670                                false
671                            }
672                        }
673
674                        #[doc = concat!(
675                            "Returns `true` if every field on this [`",
676                            stringify!($struct_ident),
677                            "`] is a [`Value`](ValueOrGlob::Value)."
678                        )]
679                        pub const fn is_all_value(&self) -> bool {
680                            if Self::FIELD_COUNT != 0 {
681                                true $(&& ValueOrGlob::is_value(&self.$field_ident))*
682                            } else {
683                                false
684                            }
685                        }
686
687                        #[doc = concat!(
688                            "Returns `true` if at least one field on this [`",
689                            stringify!($struct_ident),
690                            "`] is a [`Glob`](ValueOrGlob::Glob)."
691                        )]
692                        pub const fn is_any_glob(&self) -> bool {
693                            if Self::FIELD_COUNT != 0 {
694                                false $(|| ValueOrGlob::is_glob(&self.$field_ident))*
695                            } else {
696                                false
697                            }
698                        }
699
700                        #[doc = concat!(
701                            "Returns `true` if at least one field on this [`",
702                            stringify!($struct_ident),
703                            "`] is a [`Value`](ValueOrGlob::Value)."
704                        )]
705                        pub const fn is_any_value(&self) -> bool {
706                            if Self::FIELD_COUNT != 0 {
707                               false  $(|| ValueOrGlob::is_value(&self.$field_ident))*
708                            } else {
709                                false
710                            }
711                        }
712
713                        #[doc = concat!(
714                            "Returns the number of ",
715                            "[`Value`](ValueOrGlob::Value) ",
716                            "fields on this [`",
717                            stringify!($struct_ident),
718                            "`], used to rank competing ",
719                            "policy rules during resolution. ",
720                            "A higher specificity means the ",
721                            "rule targets a narrower set of ",
722                            "operations."
723                        )]
724                        #[allow(unused_mut)]
725                        pub const fn specificity(&self) -> usize {
726                            let mut specificity = 0;
727
728                            $(
729                                if self.$field_ident.is_value() {
730                                    specificity += 1
731                                }
732                            )*
733
734                            specificity
735                        }
736
737                        #[doc = concat!(
738                            "Returns `true` if this selector ",
739                            "covers the given field values. ",
740                            "A [`Glob`](ValueOrGlob::Glob) ",
741                            "field matches any value; a ",
742                            "[`Value`](ValueOrGlob::Value) ",
743                            "field matches only when equal."
744                        )]
745                        #[allow(clippy::ptr_arg)]
746                        pub fn matches(&self, $($field_ident: &(impl PartialEq<$field_ty> + ?Sized)),*) -> bool {
747                            true $(&& (self.$field_ident.is_glob() || self.$field_ident.as_value().is_some_and(|field_value| PartialEq::<$field_ty>::eq($field_ident, field_value))))*
748                        }
749                    }
750
751                    #[allow(clippy::ptr_arg)]
752                    impl Permission<$struct_ident> {
753                        #[doc = concat!(
754                            "If the inner [`",
755                            stringify!($struct_ident),
756                            "`] selector matches the given ",
757                            "fields, returns the ",
758                            "[`Authorization`] implied by ",
759                            "this permission's `allow` flag. ",
760                            "Returns `None` on no match."
761                        )]
762                        pub fn check(&self, $($field_ident: &(impl PartialEq<$field_ty> + ?Sized)),*) -> Option<Authorization> {
763                            self.selector.matches($($field_ident),*).then(|| match self.allow {
764                                true => Authorization::Allow,
765                                false => Authorization::Deny,
766                            })
767                        }
768                    }
769
770                    #[doc = concat!(
771                        "Defaults to [`", stringify!($struct_ident),
772                        "::empty`], producing a selector where every field ",
773                        "is a glob."
774                    )]
775                    impl Default for $struct_ident {
776                        fn default() -> Self {
777                            Self::empty()
778                        }
779                    }
780
781                    #[doc = concat!(
782                        "Formats this [`", stringify!($struct_ident),
783                        "`] using the CLI selector syntax. When all fields ",
784                        "are globs, renders the bare identifier `",
785                        stringify!($struct_ident),
786                        "`; otherwise renders the identifier with ",
787                        "parenthesized dot-separated fields."
788                    )]
789                    impl Display for $struct_ident {
790                        fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
791                            if Self::FIELD_COUNT == 0 || self.is_all_glob() {
792                                core::write!(f, stringify!($struct_ident))
793                            } else {
794                                core::write!(
795                                    f,
796                                    concat!(
797                                        stringify!($struct_ident),
798                                        "(",
799                                        formatting_string_literal!($($field_ident),*),
800                                        ")"
801                                    ),
802                                    $(self.$field_ident),*
803                                )
804                            }
805                        }
806                    }
807
808                    #[doc = concat!(
809                        "Converts a [`", stringify!($struct_ident),
810                        "`] into its string representation via [`Display`]."
811                    )]
812                    impl From<$struct_ident> for String {
813                        fn from(value: $struct_ident) -> Self {
814                            value.to_string()
815                        }
816                    }
817
818                    #[doc = concat!(
819                        "Parses a selector string into a [`",
820                        stringify!($struct_ident),
821                        "`]. Accepts the syntax `",
822                        stringify!($struct_ident),
823                        "` or `", stringify!($struct_ident),
824                        "(field1.field2)`."
825                    )]
826                    #[allow(unused_mut, unused_variables)]
827                    impl FromStr for $struct_ident {
828                        type Err = AccessControlSelectorParseError;
829
830                        fn from_str(s: &str) -> Result<Self, Self::Err> {
831                            let parsed_access_control_selector = ParsedAccessControlSelector::<'_, {Self::FIELD_COUNT}>::new(s)?;
832
833                            if parsed_access_control_selector.access_control_selector_ident != stringify!($struct_ident) {
834                                return Err(IncorrectAccessControlSelectorIdentifier {
835                                    expected: stringify!($struct_ident).into(),
836                                    found: parsed_access_control_selector.access_control_selector_ident.into(),
837                                    access_control_selector_string: s.into()
838                                })
839                            }
840
841                            let mut access_control_selector = Self::default();
842                            let mut fields = parsed_access_control_selector.fields.into_iter().flatten().map(str::trim);
843                            $(
844                                let field = fields.next();
845                                if let Some(field) = field {
846                                    let field_value = ValueOrGlob::<$field_ty>::from_str(field).map_err(|err| FailedToParseFieldValue {
847                                        field_ident: stringify!($field_ident).into(),
848                                        field_value: field.into(),
849                                        error: Box::new(err) as _
850                                    })?;
851                                    access_control_selector = access_control_selector.[< with_ $field_ident >](field_value)
852                                }
853                            )*
854
855                            Ok(access_control_selector)
856                        }
857                    }
858
859                    #[doc = concat!(
860                        "Parses an owned `String` into a [`",
861                        stringify!($struct_ident),
862                        "`] by delegating to [`FromStr`]."
863                    )]
864                    impl TryFrom<String> for $struct_ident {
865                        type Error = AccessControlSelectorParseError;
866
867                        fn try_from(v: String) -> Result<Self, Self::Error> {
868                            v.parse()
869                        }
870                    }
871                };
872            )*
873        }
874    };
875}
876
877define_access_control_selector_types! {
878    /// Selector for `CREATE INDEX` statements, scoped to a table and index
879    /// name.
880    ///
881    /// # String syntax examples
882    ///
883    /// - `CreateIndex` -- any index on any table.
884    /// - `CreateIndex(Students)` -- any index on `Students`.
885    /// - `CreateIndex(Students.idx_email)` -- one specific index.
886    pub struct CreateIndex {
887        /// The table the index is created on, or `*` for any.
888        pub table_name: String,
889        /// The name of the index being created, or `*` for any.
890        pub index_name: String,
891    }
892
893    /// Selector for `CREATE TABLE` statements.
894    ///
895    /// # String syntax examples
896    ///
897    /// - `CreateTable` -- any table.
898    /// - `CreateTable(Students)` -- only `Students`.
899    pub struct CreateTable {
900        /// The name of the table being created, or `*` for any.
901        pub table_name: String,
902    }
903
904    /// Selector for `CREATE TEMP INDEX` statements, scoped to a table and index
905    /// name.
906    ///
907    /// # String syntax examples
908    ///
909    /// - `CreateTempIndex` -- any temporary index on any table.
910    /// - `CreateTempIndex(Sessions)` -- temp indexes on `Sessions`.
911    pub struct CreateTempIndex {
912        /// The table the temp index is created on, or `*` for any.
913        pub table_name: String,
914        /// The name of the temp index being created, or `*` for any.
915        pub index_name: String,
916    }
917
918    /// Selector for `CREATE TEMP TABLE` statements.
919    ///
920    /// # String syntax examples
921    ///
922    /// - `CreateTempTable` -- any temporary table.
923    /// - `CreateTempTable(scratch)` -- only `scratch`.
924    pub struct CreateTempTable {
925        /// The name of the temp table being created, or `*` for any.
926        pub table_name: String,
927    }
928
929    /// Selector for `CREATE TEMP TRIGGER` statements, scoped to a table and
930    /// trigger name.
931    ///
932    /// # String syntax examples
933    ///
934    /// - `CreateTempTrigger` -- any temp trigger on any table.
935    /// - `CreateTempTrigger(Orders.trg_audit)` -- one specific trigger.
936    pub struct CreateTempTrigger {
937        /// The table the temp trigger fires on, or `*` for any.
938        pub table_name: String,
939        /// The name of the temp trigger, or `*` for any.
940        pub trigger_name: String,
941    }
942
943    /// Selector for `CREATE TEMP VIEW` statements.
944    ///
945    /// # String syntax examples
946    ///
947    /// - `CreateTempView` -- any temporary view.
948    /// - `CreateTempView(recent_orders)` -- only `recent_orders`.
949    pub struct CreateTempView {
950        /// The name of the temp view being created, or `*` for any.
951        pub view_name: String,
952    }
953
954    /// Selector for `CREATE TRIGGER` statements, scoped to a table and trigger
955    /// name.
956    ///
957    /// # String syntax examples
958    ///
959    /// - `CreateTrigger` -- any trigger on any table.
960    /// - `CreateTrigger(Orders.trg_audit)` -- one specific trigger.
961    pub struct CreateTrigger {
962        /// The table the trigger fires on, or `*` for any.
963        pub table_name: String,
964        /// The name of the trigger being created, or `*` for any.
965        pub trigger_name: String,
966    }
967
968    /// Selector for `CREATE VIEW` statements.
969    ///
970    /// # String syntax examples
971    ///
972    /// - `CreateView` -- any view.
973    /// - `CreateView(active_users)` -- only `active_users`.
974    pub struct CreateView {
975        /// The name of the view being created, or `*` for any.
976        pub view_name: String,
977    }
978
979    /// Selector for `DELETE` statements, scoped to a table.
980    ///
981    /// # String syntax examples
982    ///
983    /// - `Delete` -- delete from any table.
984    /// - `Delete(Sessions)` -- only from `Sessions`.
985    pub struct Delete {
986        /// The table being deleted from, or `*` for any.
987        pub table_name: String,
988    }
989
990    /// Selector for `DROP INDEX` statements, scoped to a table and index name.
991    ///
992    /// # String syntax examples
993    ///
994    /// - `DropIndex` -- any index on any table.
995    /// - `DropIndex(Students.idx_email)` -- one specific index.
996    pub struct DropIndex {
997        /// The table the index belongs to, or `*` for any.
998        pub table_name: String,
999        /// The name of the index being dropped, or `*` for any.
1000        pub index_name: String,
1001    }
1002
1003    /// Selector for `DROP TABLE` statements.
1004    ///
1005    /// # String syntax examples
1006    ///
1007    /// - `DropTable` -- any table.
1008    /// - `DropTable(Students)` -- only `Students`.
1009    pub struct DropTable {
1010        /// The name of the table being dropped, or `*` for any.
1011        pub table_name: String,
1012    }
1013
1014    /// Selector for `DROP TEMP INDEX` statements, scoped to a table and index
1015    /// name.
1016    ///
1017    /// # String syntax examples
1018    ///
1019    /// - `DropTempIndex` -- any temporary index.
1020    /// - `DropTempIndex(Sessions.idx_ts)` -- one specific index.
1021    pub struct DropTempIndex {
1022        /// The table the temp index belongs to, or `*` for any.
1023        pub table_name: String,
1024        /// The name of the temp index being dropped, or `*` for any.
1025        pub index_name: String,
1026    }
1027
1028    /// Selector for `DROP TEMP TABLE` statements.
1029    ///
1030    /// # String syntax examples
1031    ///
1032    /// - `DropTempTable` -- any temporary table.
1033    /// - `DropTempTable(scratch)` -- only `scratch`.
1034    pub struct DropTempTable {
1035        /// The name of the temp table being dropped, or `*` for any.
1036        pub table_name: String,
1037    }
1038
1039    /// Selector for `DROP TEMP TRIGGER` statements, scoped to a table and
1040    /// trigger name.
1041    ///
1042    /// # String syntax examples
1043    ///
1044    /// - `DropTempTrigger` -- any temp trigger.
1045    /// - `DropTempTrigger(Orders.trg_audit)` -- one specific trigger.
1046    pub struct DropTempTrigger {
1047        /// The table the temp trigger fires on, or `*` for any.
1048        pub table_name: String,
1049        /// The name of the temp trigger being dropped, or `*`.
1050        pub trigger_name: String,
1051    }
1052
1053    /// Selector for `DROP TEMP VIEW` statements.
1054    ///
1055    /// # String syntax examples
1056    ///
1057    /// - `DropTempView` -- any temporary view.
1058    /// - `DropTempView(recent_orders)` -- only `recent_orders`.
1059    pub struct DropTempView {
1060        /// The name of the temp view being dropped, or `*` for any.
1061        pub view_name: String,
1062    }
1063
1064    /// Selector for `DROP TRIGGER` statements, scoped to a table and trigger
1065    /// name.
1066    ///
1067    /// # String syntax examples
1068    ///
1069    /// - `DropTrigger` -- any trigger on any table.
1070    /// - `DropTrigger(Orders.trg_audit)` -- one specific trigger.
1071    pub struct DropTrigger {
1072        /// The table the trigger fires on, or `*` for any.
1073        pub table_name: String,
1074        /// The name of the trigger being dropped, or `*` for any.
1075        pub trigger_name: String,
1076    }
1077
1078    /// Selector for `DROP VIEW` statements.
1079    ///
1080    /// # String syntax examples
1081    ///
1082    /// - `DropView` -- any view.
1083    /// - `DropView(active_users)` -- only `active_users`.
1084    pub struct DropView {
1085        /// The name of the view being dropped, or `*` for any.
1086        pub view_name: String,
1087    }
1088
1089    /// Selector for `INSERT` statements, scoped to a table.
1090    ///
1091    /// # String syntax examples
1092    ///
1093    /// - `Insert` -- insert into any table.
1094    /// - `Insert(Students)` -- only into `Students`.
1095    pub struct Insert {
1096        /// The table being inserted into, or `*` for any.
1097        pub table_name: String,
1098    }
1099
1100    /// Selector for `PRAGMA` statements, scoped to a pragma name
1101    ///
1102    /// # String syntax examples
1103    ///
1104    /// - `Pragma` -- any pragma.
1105    /// - `Pragma(journal_mode)` -- only `journal_mode`.
1106    pub struct Pragma {
1107        /// The pragma name, or `*` for any.
1108        pub pragma_name: String,
1109    }
1110
1111    /// Selector for column-level read operations, scoped to a table and column.
1112    ///
1113    /// When both fields are globs, this matches all read operations across the
1114    /// entire database. Specifying a `table_name` narrows the scope to a single
1115    /// table, and additionally specifying a `column_name` narrows it to one
1116    /// column.
1117    ///
1118    /// # String syntax examples
1119    ///
1120    /// - `Read` -- all tables, all columns.
1121    /// - `Read(Students)` -- the `Students` table, all columns.
1122    /// - `Read(Students.name)` -- only the `name` column of `Students`.
1123    /// - `Read(*.name)` -- the `name` column across every table.
1124    pub struct Read {
1125        /// The target table name, or `*` to match any table.
1126        pub table_name: String,
1127        /// The target column name, or `*` to match any column.
1128        pub column_name: String,
1129    }
1130
1131    /// Selector for bare `SELECT` operations (the statement-level authorization
1132    /// check, not column-level reads).
1133    pub struct Select {}
1134
1135    /// Selector for transaction control operations (`BEGIN`, `COMMIT`,
1136    /// `ROLLBACK`).
1137    ///
1138    /// # String syntax examples
1139    ///
1140    /// - `Transaction` -- any transaction operation.
1141    /// - `Transaction(BEGIN)` -- only `BEGIN` statements.
1142    pub struct Transaction {
1143        /// The transaction operation name, or `*` for any.
1144        pub operation: TransactionOperation,
1145    }
1146
1147    /// Selector for `UPDATE` statements, scoped to a table and column.
1148    ///
1149    /// # String syntax examples
1150    ///
1151    /// - `Update` -- any column on any table.
1152    /// - `Update(Students)` -- any column on `Students`.
1153    /// - `Update(Students.email)` -- only the `email` column.
1154    pub struct Update {
1155        /// The table being updated, or `*` for any.
1156        pub table_name: String,
1157        /// The column being updated, or `*` for any.
1158        pub column_name: String,
1159    }
1160
1161    /// Selector for `ATTACH DATABASE` statements, scoped to a filename.
1162    ///
1163    /// # String syntax examples
1164    ///
1165    /// - `Attach` -- any attachment.
1166    /// - `Attach(other.db)` -- only `other.db`.
1167    pub struct Attach {
1168        /// The filename being attached, or `*` for any.
1169        pub filename: String,
1170    }
1171
1172    /// Selector for `DETACH DATABASE` statements, scoped to a database name.
1173    ///
1174    /// # String syntax examples
1175    ///
1176    /// - `Detach` -- any detachment.
1177    /// - `Detach(aux)` -- only the `aux` database.
1178    pub struct Detach {
1179        /// The database name being detached, or `*` for any.
1180        pub database_name: String,
1181    }
1182
1183    /// Selector for `ALTER TABLE` statements, scoped to a database and table.
1184    ///
1185    /// # String syntax examples
1186    ///
1187    /// - `AlterTable` -- any alter on any table.
1188    /// - `AlterTable(main.Students)` -- `Students` in `main`.
1189    pub struct AlterTable {
1190        /// The database the table belongs to, or `*` for any.
1191        pub database_name: String,
1192        /// The table being altered, or `*` for any.
1193        pub table_name: String,
1194    }
1195
1196    /// Selector for `REINDEX` statements, scoped to an index name.
1197    ///
1198    /// # String syntax examples
1199    ///
1200    /// - `Reindex` -- any index.
1201    /// - `Reindex(idx_email)` -- only `idx_email`.
1202    pub struct Reindex {
1203        /// The name of the index being reindexed, or `*` for any.
1204        pub index_name: String,
1205    }
1206
1207    /// Selector for `ANALYZE` statements, scoped to a table.
1208    ///
1209    /// # String syntax examples
1210    ///
1211    /// - `Analyze` -- any table.
1212    /// - `Analyze(Students)` -- only `Students`.
1213    pub struct Analyze {
1214        /// The table being analyzed, or `*` for any.
1215        pub table_name: String,
1216    }
1217
1218    /// Selector for `CREATE VIRTUAL TABLE` statements, scoped to a table and
1219    /// module name.
1220    ///
1221    /// # String syntax examples
1222    ///
1223    /// - `CreateVtable` -- any virtual table.
1224    /// - `CreateVtable(docs.fts5)` -- `docs` using `fts5`.
1225    pub struct CreateVtable {
1226        /// The name of the virtual table, or `*` for any.
1227        pub table_name: String,
1228        /// The module backing the virtual table, or `*` for any.
1229        pub module_name: String,
1230    }
1231
1232    /// Selector for `DROP VIRTUAL TABLE` statements, scoped to a table and
1233    /// module name.
1234    ///
1235    /// # String syntax examples
1236    ///
1237    /// - `DropVtable` -- any virtual table.
1238    /// - `DropVtable(docs.fts5)` -- `docs` backed by `fts5`.
1239    pub struct DropVtable {
1240        /// The name of the virtual table, or `*` for any.
1241        pub table_name: String,
1242        /// The module backing the virtual table, or `*` for any.
1243        pub module_name: String,
1244    }
1245
1246    /// Selector for SQL function invocations within a query.
1247    ///
1248    /// # String syntax examples
1249    ///
1250    /// - `Function` -- any function.
1251    /// - `Function(load_extension)` -- only `load_extension`.
1252    pub struct Function {
1253        /// The name of the function being called, or `*` for any.
1254        pub function_name: String,
1255    }
1256
1257    /// Selector for `SAVEPOINT`, `RELEASE`, and `ROLLBACK TO` operations on
1258    /// named savepoints.
1259    ///
1260    /// # String syntax examples
1261    ///
1262    /// - `Savepoint` -- any savepoint operation.
1263    /// - `Savepoint(RELEASE)` -- only `RELEASE` operations.
1264    /// - `Savepoint(RELEASE.sp1)` -- `RELEASE` on savepoint `sp1`.
1265    pub struct Savepoint {
1266        /// The savepoint operation, or `*` for any.
1267        pub operation: TransactionOperation,
1268        /// The savepoint name, or `*` for any.
1269        pub savepoint_name: String,
1270    }
1271
1272    /// Selector for recursive query authorization checks.
1273    pub struct Recursive {}
1274}
1275
1276/// A wildcard marker that matches any value in a selector field.
1277///
1278/// `Glob` is the unit type behind [`ValueOrGlob::Glob`]. It displays as `*` and
1279/// is produced whenever a selector field is omitted or explicitly set to `*` in
1280/// the CLI string syntax.
1281#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1282pub struct Glob;
1283
1284/// Renders the glob as the wildcard character `*`.
1285impl Display for Glob {
1286    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1287        write!(f, "*")
1288    }
1289}
1290
1291/// Either a concrete value of type `T` or a wildcard [`Glob`].
1292///
1293/// Every field on a generated selector struct is wrapped in this enum so that
1294/// it can independently be pinned to a specific value or left as a glob to
1295/// match anything. This is the core abstraction that allows selectors to
1296/// express patterns like `Read(Students.*)` (specific table, any column) or
1297/// `Read(*.name)` (any table, specific column).
1298///
1299/// The `Default` implementation returns `Glob`, matching the convention that an
1300/// unspecified field matches everything.
1301#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1302pub enum ValueOrGlob<T> {
1303    /// A concrete, pinned value that this selector field must match exactly.
1304    Value(T),
1305    /// A wildcard that matches any value for this selector field.
1306    Glob(Glob),
1307}
1308
1309/// Defaults to [`ValueOrGlob::Glob`] so that omitted fields match everything.
1310impl<T> Default for ValueOrGlob<T> {
1311    fn default() -> Self {
1312        Self::new_glob()
1313    }
1314}
1315
1316impl<T> ValueOrGlob<T> {
1317    /// Creates a [`ValueOrGlob::Value`] by converting the argument into `T`.
1318    ///
1319    /// Accepts anything that implements `Into<T>` so callers can pass owned
1320    /// values, string slices (when `T = String`), and other convertible types
1321    /// without explicit conversion at the call site.
1322    pub fn new_value(value: impl Into<T>) -> Self {
1323        Self::Value(value.into())
1324    }
1325
1326    /// Creates a [`ValueOrGlob::Glob`], representing a wildcard that matches
1327    /// any value.
1328    pub const fn new_glob() -> Self {
1329        Self::Glob(Glob)
1330    }
1331
1332    /// Returns `true` if this is a concrete [`Value`](ValueOrGlob::Value).
1333    pub const fn is_value(&self) -> bool {
1334        matches!(self, Self::Value(..))
1335    }
1336
1337    /// Returns `true` if this is a wildcard [`Glob`](ValueOrGlob::Glob).
1338    pub const fn is_glob(&self) -> bool {
1339        matches!(self, Self::Glob(..))
1340    }
1341
1342    /// Returns a reference to the inner value if this is a
1343    /// [`Value`](ValueOrGlob::Value), or `None` if it is a glob.
1344    pub const fn as_value(&self) -> Option<&T> {
1345        match self {
1346            ValueOrGlob::Value(value) => Some(value),
1347            ValueOrGlob::Glob(_) => None,
1348        }
1349    }
1350
1351    /// Returns a reference to the inner [`Glob`] if this is a
1352    /// [`Glob`](ValueOrGlob::Glob) variant, or `None` if it is a value.
1353    pub const fn as_glob(&self) -> Option<&Glob> {
1354        match self {
1355            ValueOrGlob::Value(_) => None,
1356            ValueOrGlob::Glob(glob) => Some(glob),
1357        }
1358    }
1359
1360    /// Consumes `self` and returns the inner `T` if this is a
1361    /// [`Value`](ValueOrGlob::Value), or `None` if it is a glob.
1362    pub fn into_value(self) -> Option<T> {
1363        match self {
1364            ValueOrGlob::Value(value) => Some(value),
1365            ValueOrGlob::Glob(_) => None,
1366        }
1367    }
1368
1369    /// Consumes `self` and returns the inner [`Glob`] if this is a
1370    /// [`Glob`](ValueOrGlob::Glob) variant, or `None` if it is a value.
1371    pub fn into_glob(self) -> Option<Glob> {
1372        match self {
1373            ValueOrGlob::Value(_) => None,
1374            ValueOrGlob::Glob(glob) => Some(glob),
1375        }
1376    }
1377}
1378
1379/// Delegates to the inner value's `Display` for [`Value`](ValueOrGlob::Value),
1380/// or renders `*` for [`Glob`](ValueOrGlob::Glob).
1381impl<T> Display for ValueOrGlob<T>
1382where
1383    T: Display,
1384{
1385    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1386        match self {
1387            ValueOrGlob::Value(value) => Display::fmt(value, f),
1388            ValueOrGlob::Glob(glob) => Display::fmt(glob, f),
1389        }
1390    }
1391}
1392
1393/// Converts a [`ValueOrGlob`] to its string representation, enabling `.into()`
1394/// at call sites that expect a `String`.
1395impl<T> From<ValueOrGlob<T>> for String
1396where
1397    T: Display,
1398{
1399    fn from(value: ValueOrGlob<T>) -> Self {
1400        value.to_string()
1401    }
1402}
1403
1404/// Parses `"*"` as a [`Glob`](ValueOrGlob::Glob) and anything else by
1405/// delegating to `T::from_str`, yielding a [`Value`](ValueOrGlob::Value) on
1406/// success.
1407impl<T> FromStr for ValueOrGlob<T>
1408where
1409    T: FromStr,
1410{
1411    type Err = T::Err;
1412
1413    fn from_str(s: &str) -> Result<Self, Self::Err> {
1414        if s == "*" {
1415            Ok(Self::Glob(Glob))
1416        } else {
1417            T::from_str(s).map(Self::Value)
1418        }
1419    }
1420}
1421
1422/// Parses an owned `String` into a [`ValueOrGlob`] by delegating to
1423/// [`FromStr`], enabling `value.try_into()` at call sites.
1424impl<T> TryFrom<String> for ValueOrGlob<T>
1425where
1426    T: FromStr,
1427{
1428    type Error = T::Err;
1429
1430    fn try_from(value: String) -> Result<Self, Self::Error> {
1431        value.parse()
1432    }
1433}
1434
1435/// Converts an `Option<T>` into a [`ValueOrGlob`]: `Some(v)` becomes
1436/// [`Value(v)`](ValueOrGlob::Value) and `None` becomes
1437/// [`Glob`](ValueOrGlob::Glob), mirroring the semantics of "present means
1438/// pinned, absent means match-all."
1439impl<T> From<Option<T>> for ValueOrGlob<T> {
1440    fn from(value: Option<T>) -> Self {
1441        match value {
1442            Some(value) => Self::Value(value),
1443            None => Self::Glob(Glob),
1444        }
1445    }
1446}
1447
1448/// Intermediate representation produced by splitting a raw selector string into
1449/// its identifier and optional dot-separated field values.
1450///
1451/// This is an internal parsing helper consumed by the `FromStr` implementations
1452/// generated by `define_access_control_selector_types!`. The const generic
1453/// `FIELD_COUNT` is set to the number of fields declared on the target struct,
1454/// and is used to validate that the input does not contain more fields than the
1455/// struct expects.
1456struct ParsedAccessControlSelector<'a, const FIELD_COUNT: usize> {
1457    /// The selector identifier portion of the input string (e.g., `"Read"`).
1458    pub access_control_selector_ident: &'a str,
1459    /// The dot-separated field values inside the parentheses, if any were
1460    /// present. `None` when the input was a bare identifier like `"Read"`.
1461    pub fields: Option<std::str::Split<'a, char>>,
1462}
1463
1464impl<'a, const FIELD_COUNT: usize>
1465    ParsedAccessControlSelector<'a, FIELD_COUNT>
1466{
1467    /// Parses a raw selector string into its identifier and optional fields.
1468    ///
1469    /// The input `string` is expected to follow the syntax `Identifier` or
1470    /// `Identifier(field1.field2)`. The method validates balanced parentheses
1471    /// and enforces that the number of dot-separated fields does not exceed
1472    /// `FIELD_COUNT`.
1473    ///
1474    /// # Errors
1475    ///
1476    /// Returns an [`AccessControlSelectorParseError`] when the identifier is
1477    /// missing, the closing parenthesis is absent, or there are more fields
1478    /// than the target struct declares.
1479    pub fn new(
1480        string: &'a str,
1481    ) -> Result<Self, AccessControlSelectorParseError> {
1482        use AccessControlSelectorParseError::*;
1483
1484        let mut brace_split = string.splitn(2, '(');
1485
1486        // Get the identifier of the access_control_selector. If it doesn't exist then error
1487        // out.
1488        let access_control_selector_ident = brace_split
1489            .next()
1490            .ok_or_else(|| NoAccessControlSelectorIdentifierFound {
1491                expected: stringify!($struct_ident).into(),
1492                access_control_selector_string: string.to_string(),
1493            })?
1494            .trim();
1495
1496        // Check if there's a remaining portion to the string. If there isn't,
1497        // then it means that this was a access_control_selector identifier without anything
1498        // else which is accepted.
1499        if let Some(remaining_string) = brace_split.next() {
1500            // Split once at the closing brace, if we fail to do that then this
1501            // is a malformed access_control_selector.
1502            let Some((inner_fields, _)) = remaining_string.split_once(')')
1503            else {
1504                return Err(NoClosingBraceFound {
1505                    access_control_selector_string: string.to_string(),
1506                });
1507            };
1508
1509            // Count the number of dot separators in the remaining string. This
1510            // number can be at most `FIELD_COUNT - 1`. For access_control_selectors with no
1511            // fields the subtraction fails which would return a correct invalid
1512            // number of fields error.
1513            let number_of_fields =
1514                inner_fields.chars().filter(|c| matches!(c, '.')).count() + 1;
1515            if number_of_fields > FIELD_COUNT {
1516                return Err(InvalidNumberOfFields {
1517                    found: number_of_fields,
1518                    maximum_expected: FIELD_COUNT,
1519                    access_control_selector_string: string.to_string(),
1520                });
1521            }
1522
1523            // Split the inner fields (without the closing paren) by dots and return.
1524            let fields = inner_fields.split('.');
1525
1526            Ok(Self {
1527                access_control_selector_ident,
1528                fields: Some(fields),
1529            })
1530        } else {
1531            Ok(Self {
1532                access_control_selector_ident,
1533                fields: None,
1534            })
1535        }
1536    }
1537}
1538
1539/// Pairs a selector with a permission disposition (allow or deny).
1540///
1541/// Stored inside [`AccessControlSelectorSet`] and evaluated by the policy
1542/// resolver. When a [`Permission`] 's selector matches an incoming operation,
1543/// its `allow` flag determines whether the operation is permitted or forbidden.
1544#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1545pub struct Permission<T> {
1546    /// The selector that defines which operations this rule applies to.
1547    pub selector: T,
1548    /// `true` to permit the matched operation, `false` to deny it.
1549    pub allow: bool,
1550}
1551
1552/// The outcome of a policy check against the configured rule set.
1553#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1554pub enum Authorization {
1555    /// The operation is permitted.
1556    Allow,
1557    /// The operation is forbidden.
1558    Deny,
1559}
1560
1561/// Maps this crate's [`Authorization`] to rusqlite's equivalent so that policy
1562/// verdicts can be returned directly from the authorization hook.
1563impl From<Authorization> for rusqlite::hooks::Authorization {
1564    fn from(value: Authorization) -> Self {
1565        match value {
1566            Authorization::Allow => Self::Allow,
1567            Authorization::Deny => Self::Deny,
1568        }
1569    }
1570}
1571
1572/// The type of transaction or savepoint operation being authorized.
1573///
1574/// Mirrors [`rusqlite::hooks::TransactionOperation`] but is owned by this crate
1575/// so it can derive [`Display`] and [`FromStr`] via `strum`, enabling
1576/// round-trip parsing in selector strings like `Transaction(BEGIN)`.
1577#[derive(
1578    Clone,
1579    Copy,
1580    Debug,
1581    Eq,
1582    PartialEq,
1583    PartialOrd,
1584    Ord,
1585    Hash,
1586    strum::Display,
1587    strum::EnumString,
1588)]
1589#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1590pub enum TransactionOperation {
1591    /// An unrecognized or unknown operation type.
1592    Unknown,
1593    /// A `BEGIN` transaction statement.
1594    Begin,
1595    /// A `RELEASE` savepoint statement.
1596    Release,
1597    /// A `ROLLBACK` statement.
1598    Rollback,
1599}
1600
1601/// Converts this crate's [`TransactionOperation`] into rusqlite's equivalent
1602/// for use in authorization hook responses.
1603impl From<TransactionOperation> for rusqlite::hooks::TransactionOperation {
1604    fn from(value: TransactionOperation) -> Self {
1605        match value {
1606            TransactionOperation::Unknown => Self::Unknown,
1607            TransactionOperation::Begin => Self::Begin,
1608            TransactionOperation::Release => Self::Release,
1609            TransactionOperation::Rollback => Self::Rollback,
1610        }
1611    }
1612}
1613
1614/// Allows comparing this crate's [`TransactionOperation`] against rusqlite's
1615/// variant by converting to the rusqlite type first.
1616impl PartialEq<rusqlite::hooks::TransactionOperation> for TransactionOperation {
1617    fn eq(&self, other: &rusqlite::hooks::TransactionOperation) -> bool {
1618        &rusqlite::hooks::TransactionOperation::from(*self) == other
1619    }
1620}
1621
1622/// Allows comparing rusqlite's `TransactionOperation` against this crate's
1623/// [`TransactionOperation`] by converting to the rusqlite type first.
1624impl PartialEq<TransactionOperation> for rusqlite::hooks::TransactionOperation {
1625    fn eq(&self, other: &TransactionOperation) -> bool {
1626        &rusqlite::hooks::TransactionOperation::from(*other) == self
1627    }
1628}
1629
1630/// Enumerates the ways parsing an access control selector string can fail.
1631///
1632/// Each variant captures the original input string together with contextual
1633/// information about what went wrong, so that error messages can be actionable
1634/// and specific.
1635#[derive(Debug, thiserror::Error)]
1636pub enum AccessControlSelectorParseError {
1637    /// The input string could not be split into an identifier portion at all.
1638    ///
1639    /// This typically means the string was empty or otherwise malformed in a
1640    /// way that prevented even the first token from being extracted.
1641    #[error(
1642        "no access control selector identifier found in \"{access_control_selector_string}\", \
1643         expected \"{expected}\""
1644    )]
1645    NoAccessControlSelectorIdentifierFound {
1646        /// The identifier that was expected (set by the specific struct's
1647        /// `FromStr`).
1648        expected: String,
1649        /// The raw input string that failed to parse.
1650        access_control_selector_string: String,
1651    },
1652    /// The identifier was successfully extracted but does not match the target
1653    /// struct's name.
1654    ///
1655    /// Returned when parsing into a specific selector type (e.g.,
1656    /// `"Write".parse::<Read>()`) where the identifier does not agree with the
1657    /// struct being parsed into.
1658    #[error(
1659        "incorrect access control selector identifier in \"{access_control_selector_string}\": \
1660         expected \"{expected}\", found \"{found}\""
1661    )]
1662    IncorrectAccessControlSelectorIdentifier {
1663        /// The identifier that was expected.
1664        expected: String,
1665        /// The identifier that was actually found in the input.
1666        found: String,
1667        /// The raw input string that failed to parse.
1668        access_control_selector_string: String,
1669    },
1670    /// The identifier does not match any known selector variant in the
1671    /// [`AccessControlSelector`] enum.
1672    ///
1673    /// Returned when parsing through the umbrella enum and the leading token is
1674    /// not recognized.
1675    #[error(
1676        "invalid access control selector identifier in \"{access_control_selector_string}\": \
1677         found {found:?}, expected one of {expected:?}"
1678    )]
1679    InvalidAccessControlSelectorIdentifier {
1680        /// The identifier that was found, or `None` if extraction failed.
1681        found: Option<String>,
1682        /// The set of valid selector identifiers.
1683        expected: &'static [&'static str],
1684        /// The raw input string that failed to parse.
1685        access_control_selector_string: String,
1686    },
1687    /// A `(` was found but the matching `)` was missing.
1688    #[error(
1689        "no closing parenthesis found in \"{access_control_selector_string}\""
1690    )]
1691    NoClosingBraceFound {
1692        /// The raw input string that failed to parse.
1693        access_control_selector_string: String,
1694    },
1695    /// The number of dot-separated fields inside the parentheses exceeds the
1696    /// maximum that the target struct declares.
1697    #[error(
1698        "invalid number of fields in \"{access_control_selector_string}\": \
1699         found {found}, maximum expected {maximum_expected}"
1700    )]
1701    InvalidNumberOfFields {
1702        /// The number of fields found in the input.
1703        found: usize,
1704        /// The maximum number of fields the target struct accepts.
1705        maximum_expected: usize,
1706        /// The raw input string that failed to parse.
1707        access_control_selector_string: String,
1708    },
1709    /// An individual field value inside the parentheses could not be parsed
1710    /// into its target type.
1711    #[error(
1712        "failed to parse field \"{field_ident}\" with value \"{field_value}\": {error}"
1713    )]
1714    FailedToParseFieldValue {
1715        /// The name of the struct field that failed to parse.
1716        field_ident: String,
1717        /// The raw string value that could not be converted.
1718        field_value: String,
1719        /// The underlying parse error.
1720        error: Box<dyn Error + Send + Sync>,
1721    },
1722}
1723
1724/// Predefined permission presets for common access control configurations.
1725///
1726/// Each variant maps to an [`AuthorizationResolver`] constructor that sets
1727/// sensible defaults for a particular use case. Variants are ordered from most
1728/// restrictive to least restrictive. The preset is selected via the `--preset`
1729/// CLI flag and determines the baseline permissions before any `--allow` /
1730/// `--deny` overrides are applied.
1731#[derive(
1732    Clone,
1733    Copy,
1734    Debug,
1735    Default,
1736    PartialEq,
1737    Eq,
1738    PartialOrd,
1739    Ord,
1740    Hash,
1741    strum::Display,
1742    strum::EnumString,
1743    clap::ValueEnum,
1744)]
1745#[strum(serialize_all = "kebab-case")]
1746pub enum Preset {
1747    /// Denies all operations. Useful as a starting point when every permitted
1748    /// action must be explicitly allowed via `--allow` flags.
1749    DenyEverything,
1750    /// Allows reads, selects, transactions, SQL functions, recursive CTEs, and
1751    /// pragmas. Denies all data modification and DDL.
1752    #[default]
1753    ReadOnly,
1754    /// Extends read-only with insert, update, delete, savepoints, analyze,
1755    /// reindex, and temporary object operations. Permanent DDL, attach, and
1756    /// detach remain denied.
1757    ReadWrite,
1758    /// Extends read-write with permanent DDL (create/drop tables, indexes,
1759    /// triggers, views, and alter table). Attach, detach, and virtual table
1760    /// operations remain denied.
1761    FullDdl,
1762    /// Allows all operations with no restrictions. Intended for development and
1763    /// testing only.
1764    AllowEverything,
1765}
1766
1767/// Converts a [`Preset`] into the corresponding [`AuthorizationResolver`] by
1768/// calling the matching constructor.
1769impl From<Preset> for AuthorizationResolver {
1770    fn from(preset: Preset) -> Self {
1771        match preset {
1772            Preset::DenyEverything => Self::new_deny_everything(),
1773            Preset::ReadOnly => Self::new_read_only(),
1774            Preset::ReadWrite => Self::new_read_write(),
1775            Preset::FullDdl => Self::new_full_ddl(),
1776            Preset::AllowEverything => Self::new_allow_everything(),
1777        }
1778    }
1779}
1780
1781impl AuthorizationResolver {
1782    /// Creates a read-only resolver.
1783    ///
1784    /// Allows reads, selects, transactions, SQL functions, recursive CTEs, and
1785    /// pragmas. Denies all data modification (insert, update, delete) and all
1786    /// DDL (create, drop, alter, attach, detach). Users can layer additional
1787    /// `--allow` / `--deny` rules on top to fine-tune access.
1788    pub fn new_read_only() -> Self {
1789        use rusqlite::hooks::Authorization::Allow;
1790
1791        Self::new_deny_everything()
1792            .with_read_default_permissions(Allow)
1793            .with_select_default_permissions(Allow)
1794            .with_transaction_default_permissions(Allow)
1795            .with_function_default_permissions(Allow)
1796            .with_recursive_default_permissions(Allow)
1797            .with_pragma_default_permissions(Allow)
1798    }
1799
1800    /// Creates a read-write resolver that forbids schema changes.
1801    ///
1802    /// Extends [`new_read_only`](Self::new_read_only) with insert, update,
1803    /// delete, savepoints, analyze, reindex, and temporary object
1804    /// creation/deletion. Permanent DDL (create/drop table, index, trigger,
1805    /// view, virtual table), attach, and detach remain denied.
1806    pub fn new_read_write() -> Self {
1807        use rusqlite::hooks::Authorization::Allow;
1808
1809        Self::new_read_only()
1810            .with_insert_default_permissions(Allow)
1811            .with_update_default_permissions(Allow)
1812            .with_delete_default_permissions(Allow)
1813            .with_savepoint_default_permissions(Allow)
1814            .with_analyze_default_permissions(Allow)
1815            .with_reindex_default_permissions(Allow)
1816            .with_create_temp_table_default_permissions(Allow)
1817            .with_create_temp_index_default_permissions(Allow)
1818            .with_create_temp_view_default_permissions(Allow)
1819            .with_create_temp_trigger_default_permissions(Allow)
1820            .with_drop_temp_table_default_permissions(Allow)
1821            .with_drop_temp_index_default_permissions(Allow)
1822            .with_drop_temp_view_default_permissions(Allow)
1823            .with_drop_temp_trigger_default_permissions(Allow)
1824    }
1825
1826    /// Creates a resolver that allows all data and DDL operations but denies
1827    /// operations that reach outside the database file.
1828    ///
1829    /// Extends [`new_read_write`](Self::new_read_write) with permanent DDL
1830    /// (create/drop tables, indexes, triggers, views, and alter table). Attach,
1831    /// detach, and virtual table operations remain denied because they can
1832    /// access the filesystem or load arbitrary code.
1833    pub fn new_full_ddl() -> Self {
1834        use rusqlite::hooks::Authorization::Allow;
1835
1836        Self::new_read_write()
1837            .with_create_table_default_permissions(Allow)
1838            .with_drop_table_default_permissions(Allow)
1839            .with_alter_table_default_permissions(Allow)
1840            .with_create_index_default_permissions(Allow)
1841            .with_drop_index_default_permissions(Allow)
1842            .with_create_trigger_default_permissions(Allow)
1843            .with_drop_trigger_default_permissions(Allow)
1844            .with_create_view_default_permissions(Allow)
1845            .with_drop_view_default_permissions(Allow)
1846    }
1847}
1848
1849#[cfg(test)]
1850mod tests {
1851    use super::*;
1852
1853    #[test]
1854    fn glob_display_shows_asterisk() {
1855        // Arrange
1856        let glob = Glob;
1857
1858        // Act
1859        let displayed = glob.to_string();
1860
1861        // Assert
1862        assert_eq!(displayed, "*");
1863    }
1864
1865    #[test]
1866    fn value_or_glob_new_value_creates_value_variant() {
1867        // Arrange
1868        let input = "hello";
1869
1870        // Act
1871        let vog = ValueOrGlob::<String>::new_value(input);
1872
1873        // Assert
1874        assert_eq!(vog, ValueOrGlob::Value("hello".to_string()));
1875    }
1876
1877    #[test]
1878    fn value_or_glob_new_glob_creates_glob_variant() {
1879        // Arrange
1880        // (no setup needed)
1881
1882        // Act
1883        let vog = ValueOrGlob::<String>::new_glob();
1884
1885        // Assert
1886        assert_eq!(vog, ValueOrGlob::Glob(Glob));
1887    }
1888
1889    #[test]
1890    fn value_or_glob_default_returns_glob() {
1891        // Arrange
1892        // (no setup needed)
1893
1894        // Act
1895        let vog = ValueOrGlob::<String>::default();
1896
1897        // Assert
1898        assert_eq!(vog, ValueOrGlob::Glob(Glob));
1899    }
1900
1901    #[test]
1902    fn value_or_glob_is_value_returns_true_for_value() {
1903        // Arrange
1904        let vog = ValueOrGlob::<String>::new_value("test");
1905
1906        // Act
1907        let result = vog.is_value();
1908
1909        // Assert
1910        assert!(result);
1911    }
1912
1913    #[test]
1914    fn value_or_glob_is_value_returns_false_for_glob() {
1915        // Arrange
1916        let vog = ValueOrGlob::<String>::new_glob();
1917
1918        // Act
1919        let result = vog.is_value();
1920
1921        // Assert
1922        assert!(!result);
1923    }
1924
1925    #[test]
1926    fn value_or_glob_is_glob_returns_true_for_glob() {
1927        // Arrange
1928        let vog = ValueOrGlob::<String>::new_glob();
1929
1930        // Act
1931        let result = vog.is_glob();
1932
1933        // Assert
1934        assert!(result);
1935    }
1936
1937    #[test]
1938    fn value_or_glob_is_glob_returns_false_for_value() {
1939        // Arrange
1940        let vog = ValueOrGlob::<String>::new_value("test");
1941
1942        // Act
1943        let result = vog.is_glob();
1944
1945        // Assert
1946        assert!(!result);
1947    }
1948
1949    #[test]
1950    fn value_or_glob_as_value_returns_some_for_value() {
1951        // Arrange
1952        let vog = ValueOrGlob::<String>::new_value("hello");
1953
1954        // Act
1955        let result = vog.as_value();
1956
1957        // Assert
1958        assert_eq!(result, Some(&"hello".to_string()));
1959    }
1960
1961    #[test]
1962    fn value_or_glob_as_value_returns_none_for_glob() {
1963        // Arrange
1964        let vog = ValueOrGlob::<String>::new_glob();
1965
1966        // Act
1967        let result = vog.as_value();
1968
1969        // Assert
1970        assert_eq!(result, None);
1971    }
1972
1973    #[test]
1974    fn value_or_glob_as_glob_returns_some_for_glob() {
1975        // Arrange
1976        let vog = ValueOrGlob::<String>::new_glob();
1977
1978        // Act
1979        let result = vog.as_glob();
1980
1981        // Assert
1982        assert_eq!(result, Some(&Glob));
1983    }
1984
1985    #[test]
1986    fn value_or_glob_as_glob_returns_none_for_value() {
1987        // Arrange
1988        let vog = ValueOrGlob::<String>::new_value("test");
1989
1990        // Act
1991        let result = vog.as_glob();
1992
1993        // Assert
1994        assert_eq!(result, None);
1995    }
1996
1997    #[test]
1998    fn value_or_glob_into_value_returns_some_for_value() {
1999        // Arrange
2000        let vog = ValueOrGlob::<String>::new_value("hello");
2001
2002        // Act
2003        let result = vog.into_value();
2004
2005        // Assert
2006        assert_eq!(result, Some("hello".to_string()));
2007    }
2008
2009    #[test]
2010    fn value_or_glob_into_value_returns_none_for_glob() {
2011        // Arrange
2012        let vog = ValueOrGlob::<String>::new_glob();
2013
2014        // Act
2015        let result = vog.into_value();
2016
2017        // Assert
2018        assert_eq!(result, None);
2019    }
2020
2021    #[test]
2022    fn value_or_glob_into_glob_returns_some_for_glob() {
2023        // Arrange
2024        let vog = ValueOrGlob::<String>::new_glob();
2025
2026        // Act
2027        let result = vog.into_glob();
2028
2029        // Assert
2030        assert_eq!(result, Some(Glob));
2031    }
2032
2033    #[test]
2034    fn value_or_glob_into_glob_returns_none_for_value() {
2035        // Arrange
2036        let vog = ValueOrGlob::<String>::new_value("test");
2037
2038        // Act
2039        let result = vog.into_glob();
2040
2041        // Assert
2042        assert_eq!(result, None);
2043    }
2044
2045    #[test]
2046    fn value_or_glob_display_shows_inner_value_for_value() {
2047        // Arrange
2048        let vog = ValueOrGlob::<String>::new_value("Students");
2049
2050        // Act
2051        let displayed = vog.to_string();
2052
2053        // Assert
2054        assert_eq!(displayed, "Students");
2055    }
2056
2057    #[test]
2058    fn value_or_glob_display_shows_asterisk_for_glob() {
2059        // Arrange
2060        let vog = ValueOrGlob::<String>::new_glob();
2061
2062        // Act
2063        let displayed = vog.to_string();
2064
2065        // Assert
2066        assert_eq!(displayed, "*");
2067    }
2068
2069    #[test]
2070    fn value_or_glob_from_str_with_asterisk_returns_glob() {
2071        // Arrange
2072        let input = "*";
2073
2074        // Act
2075        let result = input.parse::<ValueOrGlob<String>>().unwrap();
2076
2077        // Assert
2078        assert_eq!(result, ValueOrGlob::Glob(Glob));
2079    }
2080
2081    #[test]
2082    fn value_or_glob_from_str_with_text_returns_value() {
2083        // Arrange
2084        let input = "Students";
2085
2086        // Act
2087        let result = input.parse::<ValueOrGlob<String>>().unwrap();
2088
2089        // Assert
2090        assert_eq!(result, ValueOrGlob::Value("Students".to_string()));
2091    }
2092
2093    #[test]
2094    fn value_or_glob_from_some_option_gives_value() {
2095        // Arrange
2096        let opt = Some("name".to_string());
2097
2098        // Act
2099        let vog = ValueOrGlob::<String>::from(opt);
2100
2101        // Assert
2102        assert_eq!(vog, ValueOrGlob::Value("name".to_string()));
2103    }
2104
2105    #[test]
2106    fn value_or_glob_from_none_option_gives_glob() {
2107        // Arrange
2108        let opt = Option::<String>::None;
2109
2110        // Act
2111        let vog = ValueOrGlob::<String>::from(opt);
2112
2113        // Assert
2114        assert_eq!(vog, ValueOrGlob::Glob(Glob));
2115    }
2116
2117    #[test]
2118    fn value_or_glob_into_string_for_value() {
2119        // Arrange
2120        let vog = ValueOrGlob::<String>::new_value("hello");
2121
2122        // Act
2123        let s = String::from(vog);
2124
2125        // Assert
2126        assert_eq!(s, "hello");
2127    }
2128
2129    #[test]
2130    fn value_or_glob_into_string_for_glob() {
2131        // Arrange
2132        let vog = ValueOrGlob::<String>::new_glob();
2133
2134        // Act
2135        let s = String::from(vog);
2136
2137        // Assert
2138        assert_eq!(s, "*");
2139    }
2140
2141    #[test]
2142    fn read_new_creates_with_given_values() {
2143        // Arrange
2144        let table = ValueOrGlob::<String>::new_value("Students");
2145        let column = ValueOrGlob::<String>::new_value("name");
2146
2147        // Act
2148        let read = Read::new(table, column);
2149
2150        // Assert
2151        assert_eq!(read.table_name, ValueOrGlob::Value("Students".to_string()));
2152        assert_eq!(read.column_name, ValueOrGlob::Value("name".to_string()));
2153    }
2154
2155    #[test]
2156    fn read_empty_creates_with_all_globs() {
2157        // Arrange
2158        // (no setup needed)
2159
2160        // Act
2161        let read = Read::empty();
2162
2163        // Assert
2164        assert_eq!(read.table_name, ValueOrGlob::Glob(Glob));
2165        assert_eq!(read.column_name, ValueOrGlob::Glob(Glob));
2166    }
2167
2168    #[test]
2169    fn read_with_table_name_sets_table_name() {
2170        // Arrange
2171        let read = Read::empty();
2172
2173        // Act
2174        let read =
2175            read.with_table_name(ValueOrGlob::<String>::new_value("Grades"));
2176
2177        // Assert
2178        assert_eq!(read.table_name, ValueOrGlob::Value("Grades".to_string()));
2179        assert_eq!(read.column_name, ValueOrGlob::Glob(Glob));
2180    }
2181
2182    #[test]
2183    fn read_with_column_name_sets_column_name() {
2184        // Arrange
2185        let read = Read::empty();
2186
2187        // Act
2188        let read =
2189            read.with_column_name(ValueOrGlob::<String>::new_value("score"));
2190
2191        // Assert
2192        assert_eq!(read.table_name, ValueOrGlob::Glob(Glob));
2193        assert_eq!(read.column_name, ValueOrGlob::Value("score".to_string()));
2194    }
2195
2196    #[test]
2197    fn read_is_all_glob_true_when_both_glob() {
2198        // Arrange
2199        let read = Read::empty();
2200
2201        // Act
2202        let result = read.is_all_glob();
2203
2204        // Assert
2205        assert!(result);
2206    }
2207
2208    #[test]
2209    fn read_is_all_glob_false_when_table_is_value() {
2210        // Arrange
2211        let read = Read::empty()
2212            .with_table_name(ValueOrGlob::<String>::new_value("Students"));
2213
2214        // Act
2215        let result = read.is_all_glob();
2216
2217        // Assert
2218        assert!(!result);
2219    }
2220
2221    #[test]
2222    fn read_is_all_glob_false_when_column_is_value() {
2223        // Arrange
2224        let read = Read::empty()
2225            .with_column_name(ValueOrGlob::<String>::new_value("name"));
2226
2227        // Act
2228        let result = read.is_all_glob();
2229
2230        // Assert
2231        assert!(!result);
2232    }
2233
2234    #[test]
2235    fn read_is_all_value_true_when_both_value() {
2236        // Arrange
2237        let read = Read::new(
2238            ValueOrGlob::<String>::new_value("Students"),
2239            ValueOrGlob::<String>::new_value("name"),
2240        );
2241
2242        // Act
2243        let result = read.is_all_value();
2244
2245        // Assert
2246        assert!(result);
2247    }
2248
2249    #[test]
2250    fn read_is_all_value_false_when_one_is_glob() {
2251        // Arrange
2252        let read = Read::empty()
2253            .with_table_name(ValueOrGlob::<String>::new_value("Students"));
2254
2255        // Act
2256        let result = read.is_all_value();
2257
2258        // Assert
2259        assert!(!result);
2260    }
2261
2262    #[test]
2263    fn read_is_any_glob_true_when_one_is_glob() {
2264        // Arrange
2265        let read = Read::empty()
2266            .with_table_name(ValueOrGlob::<String>::new_value("Students"));
2267
2268        // Act
2269        let result = read.is_any_glob();
2270
2271        // Assert
2272        assert!(result);
2273    }
2274
2275    #[test]
2276    fn read_is_any_glob_false_when_both_value() {
2277        // Arrange
2278        let read = Read::new(
2279            ValueOrGlob::<String>::new_value("Students"),
2280            ValueOrGlob::<String>::new_value("name"),
2281        );
2282
2283        // Act
2284        let result = read.is_any_glob();
2285
2286        // Assert
2287        assert!(!result);
2288    }
2289
2290    #[test]
2291    fn read_is_any_value_true_when_one_is_value() {
2292        // Arrange
2293        let read = Read::empty()
2294            .with_column_name(ValueOrGlob::<String>::new_value("name"));
2295
2296        // Act
2297        let result = read.is_any_value();
2298
2299        // Assert
2300        assert!(result);
2301    }
2302
2303    #[test]
2304    fn read_is_any_value_false_when_both_glob() {
2305        // Arrange
2306        let read = Read::empty();
2307
2308        // Act
2309        let result = read.is_any_value();
2310
2311        // Assert
2312        assert!(!result);
2313    }
2314
2315    #[test]
2316    fn read_display_all_glob_shows_read_without_parens() {
2317        // Arrange
2318        let read = Read::empty();
2319
2320        // Act
2321        let displayed = read.to_string();
2322
2323        // Assert
2324        assert_eq!(displayed, "Read");
2325    }
2326
2327    #[test]
2328    fn read_display_specific_shows_read_with_table_and_column() {
2329        // Arrange
2330        let read = Read::new(
2331            ValueOrGlob::<String>::new_value("Students"),
2332            ValueOrGlob::<String>::new_value("name"),
2333        );
2334
2335        // Act
2336        let displayed = read.to_string();
2337
2338        // Assert
2339        assert_eq!(displayed, "Read(Students.name)");
2340    }
2341
2342    #[test]
2343    fn read_display_glob_table_value_column() {
2344        // Arrange
2345        let read = Read::empty()
2346            .with_column_name(ValueOrGlob::<String>::new_value("name"));
2347
2348        // Act
2349        let displayed = read.to_string();
2350
2351        // Assert
2352        assert_eq!(displayed, "Read(*.name)");
2353    }
2354
2355    #[test]
2356    fn read_display_value_table_glob_column() {
2357        // Arrange
2358        let read = Read::empty()
2359            .with_table_name(ValueOrGlob::<String>::new_value("Students"));
2360
2361        // Act
2362        let displayed = read.to_string();
2363
2364        // Assert
2365        assert_eq!(displayed, "Read(Students.*)");
2366    }
2367
2368    #[test]
2369    fn read_from_str_bare_read_parses_to_all_glob() {
2370        // Arrange
2371        let input = "Read";
2372
2373        // Act
2374        let read = input.parse::<Read>().unwrap();
2375
2376        // Assert
2377        assert_eq!(read.table_name, ValueOrGlob::Glob(Glob));
2378        assert_eq!(read.column_name, ValueOrGlob::Glob(Glob));
2379    }
2380
2381    #[test]
2382    fn read_from_str_specific_table_and_column_parses_correctly() {
2383        // Arrange
2384        let input = "Read(Students.name)";
2385
2386        // Act
2387        let read = input.parse::<Read>().unwrap();
2388
2389        // Assert
2390        assert_eq!(read.table_name, ValueOrGlob::Value("Students".to_string()));
2391        assert_eq!(read.column_name, ValueOrGlob::Value("name".to_string()));
2392    }
2393
2394    #[test]
2395    fn read_from_str_glob_table_value_column_parses_correctly() {
2396        // Arrange
2397        let input = "Read(*.name)";
2398
2399        // Act
2400        let read = input.parse::<Read>().unwrap();
2401
2402        // Assert
2403        assert_eq!(read.table_name, ValueOrGlob::Glob(Glob));
2404        assert_eq!(read.column_name, ValueOrGlob::Value("name".to_string()));
2405    }
2406
2407    #[test]
2408    fn read_from_str_value_table_glob_column_parses_correctly() {
2409        // Arrange
2410        let input = "Read(Students.*)";
2411
2412        // Act
2413        let read = input.parse::<Read>().unwrap();
2414
2415        // Assert
2416        assert_eq!(read.table_name, ValueOrGlob::Value("Students".to_string()));
2417        assert_eq!(read.column_name, ValueOrGlob::Glob(Glob));
2418    }
2419
2420    #[test]
2421    fn read_from_str_round_trip_produces_same_result() {
2422        // Arrange
2423        let input = "Read(Students.name)";
2424
2425        // Act
2426        let read = input.parse::<Read>().unwrap();
2427        let displayed = read.to_string();
2428        let reparsed = displayed.parse::<Read>().unwrap();
2429
2430        // Assert
2431        assert_eq!(
2432            reparsed.table_name,
2433            ValueOrGlob::Value("Students".to_string())
2434        );
2435        assert_eq!(
2436            reparsed.column_name,
2437            ValueOrGlob::Value("name".to_string())
2438        );
2439    }
2440
2441    #[test]
2442    fn read_from_str_round_trip_bare_read_produces_same_result() {
2443        // Arrange
2444        let input = "Read";
2445
2446        // Act
2447        let read = input.parse::<Read>().unwrap();
2448        let displayed = read.to_string();
2449        let reparsed = displayed.parse::<Read>().unwrap();
2450
2451        // Assert
2452        assert_eq!(reparsed.table_name, ValueOrGlob::Glob(Glob));
2453        assert_eq!(reparsed.column_name, ValueOrGlob::Glob(Glob));
2454        assert_eq!(displayed, "Read");
2455    }
2456
2457    #[test]
2458    fn read_into_string_works() {
2459        // Arrange
2460        let read = Read::new(
2461            ValueOrGlob::<String>::new_value("Students"),
2462            ValueOrGlob::<String>::new_value("name"),
2463        );
2464
2465        // Act
2466        let s = String::from(read);
2467
2468        // Assert
2469        assert_eq!(s, "Read(Students.name)");
2470    }
2471
2472    #[test]
2473    fn read_try_from_string_works() {
2474        // Arrange
2475        let input = "Read(Students.name)".to_string();
2476
2477        // Act
2478        let read = Read::try_from(input).unwrap();
2479
2480        // Assert
2481        assert_eq!(read.table_name, ValueOrGlob::Value("Students".to_string()));
2482        assert_eq!(read.column_name, ValueOrGlob::Value("name".to_string()));
2483    }
2484
2485    #[test]
2486    fn access_control_selector_from_str_bare_read_parses_to_read_selector() {
2487        // Arrange
2488        let input = "Read";
2489
2490        // Act
2491        let selector = input.parse::<AccessControlSelector>().unwrap();
2492
2493        // Assert
2494        match selector {
2495            AccessControlSelector::Read(read) => {
2496                assert_eq!(read.table_name, ValueOrGlob::Glob(Glob));
2497                assert_eq!(read.column_name, ValueOrGlob::Glob(Glob));
2498            }
2499            other => panic!("expected ReadSelector, got {other}"),
2500        }
2501    }
2502
2503    #[test]
2504    fn access_control_selector_from_str_specific_parses_correctly() {
2505        // Arrange
2506        let input = "Read(Students.name)";
2507
2508        // Act
2509        let selector = input.parse::<AccessControlSelector>().unwrap();
2510
2511        // Assert
2512        match selector {
2513            AccessControlSelector::Read(read) => {
2514                assert_eq!(
2515                    read.table_name,
2516                    ValueOrGlob::Value("Students".to_string())
2517                );
2518                assert_eq!(
2519                    read.column_name,
2520                    ValueOrGlob::Value("name".to_string())
2521                );
2522            }
2523            other => panic!("expected ReadSelector, got {other}"),
2524        }
2525    }
2526
2527    #[test]
2528    fn access_control_selector_display_round_trip_works() {
2529        // Arrange
2530        let input = "Read(Students.name)";
2531
2532        // Act
2533        let selector = input.parse::<AccessControlSelector>().unwrap();
2534        let displayed = selector.to_string();
2535        let reparsed = displayed.parse::<AccessControlSelector>().unwrap();
2536
2537        // Assert
2538        match reparsed {
2539            AccessControlSelector::Read(read) => {
2540                assert_eq!(
2541                    read.table_name,
2542                    ValueOrGlob::Value("Students".to_string())
2543                );
2544                assert_eq!(
2545                    read.column_name,
2546                    ValueOrGlob::Value("name".to_string())
2547                );
2548            }
2549            other => panic!("expected ReadSelector, got {other}"),
2550        }
2551    }
2552
2553    #[test]
2554    fn access_control_selector_try_from_string_works() {
2555        // Arrange
2556        let input = "Read(Students.name)".to_string();
2557
2558        // Act
2559        let selector = AccessControlSelector::try_from(input).unwrap();
2560
2561        // Assert
2562        match selector {
2563            AccessControlSelector::Read(read) => {
2564                assert_eq!(
2565                    read.table_name,
2566                    ValueOrGlob::Value("Students".to_string())
2567                );
2568                assert_eq!(
2569                    read.column_name,
2570                    ValueOrGlob::Value("name".to_string())
2571                );
2572            }
2573            other => panic!("expected ReadSelector, got {other}"),
2574        }
2575    }
2576
2577    #[test]
2578    fn parsing_with_missing_closing_paren_returns_error() {
2579        // Arrange
2580        let input = "Read(Students.name";
2581
2582        // Act
2583        let result = input.parse::<Read>();
2584
2585        // Assert
2586        assert!(matches!(
2587            result,
2588            Err(AccessControlSelectorParseError::NoClosingBraceFound { .. })
2589        ));
2590    }
2591
2592    #[test]
2593    fn parsing_with_too_many_fields_returns_error() {
2594        // Arrange
2595        let input = "Read(Students.name.extra)";
2596
2597        // Act
2598        let result = input.parse::<Read>();
2599
2600        // Assert
2601        assert!(matches!(
2602            result,
2603            Err(AccessControlSelectorParseError::InvalidNumberOfFields { .. })
2604        ));
2605    }
2606
2607    #[test]
2608    fn parsing_with_wrong_identifier_returns_error_for_struct() {
2609        // Arrange
2610        let input = "Write(Students.name)";
2611
2612        // Act
2613        let result = input.parse::<Read>();
2614
2615        // Assert
2616        assert!(matches!(
2617            result,
2618            Err(
2619                AccessControlSelectorParseError::IncorrectAccessControlSelectorIdentifier {
2620                    ..
2621                }
2622            )
2623        ));
2624    }
2625
2626    #[test]
2627    fn parsing_with_wrong_identifier_returns_error_for_enum() {
2628        // Arrange
2629        let input = "Write(Students.name)";
2630
2631        // Act
2632        let result = input.parse::<AccessControlSelector>();
2633
2634        // Assert
2635        assert!(matches!(
2636            result,
2637            Err(
2638                AccessControlSelectorParseError::InvalidAccessControlSelectorIdentifier {
2639                    ..
2640                }
2641            )
2642        ));
2643    }
2644
2645    #[test]
2646    fn parsing_empty_string_returns_error_for_enum() {
2647        // Arrange
2648        let input = "";
2649
2650        // Act
2651        let result = input.parse::<AccessControlSelector>();
2652
2653        // Assert
2654        assert!(result.is_err());
2655    }
2656
2657    #[test]
2658    fn parsing_empty_string_returns_error_for_struct() {
2659        // Arrange
2660        let input = "";
2661
2662        // Act
2663        let result = input.parse::<Read>();
2664
2665        // Assert
2666        assert!(matches!(
2667            result,
2668            Err(
2669                AccessControlSelectorParseError::IncorrectAccessControlSelectorIdentifier {
2670                    ..
2671                }
2672            )
2673        ));
2674    }
2675
2676    #[test]
2677    fn empty_selector_set_returns_none_for_read() {
2678        // Arrange
2679        let set = AccessControlSelectorSet::new();
2680
2681        // Act
2682        let result = set.check_read("Students", "name");
2683
2684        // Assert
2685        assert_eq!(result, None);
2686    }
2687
2688    #[test]
2689    fn single_allow_rule_matches_and_returns_allow() {
2690        // Arrange
2691        let set = AccessControlSelectorSet::new()
2692            .with_read_selector(Read::empty(), true);
2693
2694        // Act
2695        let result = set.check_read("Students", "name");
2696
2697        // Assert
2698        assert_eq!(result, Some(Authorization::Allow));
2699    }
2700
2701    #[test]
2702    fn single_deny_rule_matches_and_returns_deny() {
2703        // Arrange
2704        let set = AccessControlSelectorSet::new()
2705            .with_read_selector(Read::empty(), false);
2706
2707        // Act
2708        let result = set.check_read("Students", "name");
2709
2710        // Assert
2711        assert_eq!(result, Some(Authorization::Deny));
2712    }
2713
2714    #[test]
2715    fn single_allow_rule_that_does_not_match_returns_none() {
2716        // Arrange
2717        let set = AccessControlSelectorSet::new().with_read_selector(
2718            Read::new(
2719                ValueOrGlob::new_value("Secrets"),
2720                ValueOrGlob::new_glob(),
2721            ),
2722            true,
2723        );
2724
2725        // Act
2726        let result = set.check_read("Students", "name");
2727
2728        // Assert
2729        assert_eq!(result, None);
2730    }
2731
2732    #[test]
2733    fn single_deny_rule_that_does_not_match_returns_none() {
2734        // Arrange
2735        let set = AccessControlSelectorSet::new().with_read_selector(
2736            Read::new(
2737                ValueOrGlob::new_value("Secrets"),
2738                ValueOrGlob::new_glob(),
2739            ),
2740            false,
2741        );
2742
2743        // Act
2744        let result = set.check_read("Students", "name");
2745
2746        // Assert
2747        assert_eq!(result, None);
2748    }
2749
2750    #[test]
2751    fn higher_specificity_allow_beats_lower_specificity_deny() {
2752        // Arrange
2753        let set = AccessControlSelectorSet::new()
2754            .with_read_selector(Read::empty(), false)
2755            .with_read_selector(
2756                Read::new(
2757                    ValueOrGlob::new_value("Students"),
2758                    ValueOrGlob::new_glob(),
2759                ),
2760                true,
2761            );
2762
2763        // Act
2764        let result = set.check_read("Students", "name");
2765
2766        // Assert
2767        assert_eq!(result, Some(Authorization::Allow));
2768    }
2769
2770    #[test]
2771    fn higher_specificity_deny_beats_lower_specificity_allow() {
2772        // Arrange
2773        let set = AccessControlSelectorSet::new()
2774            .with_read_selector(Read::empty(), true)
2775            .with_read_selector(
2776                Read::new(
2777                    ValueOrGlob::new_value("Secrets"),
2778                    ValueOrGlob::new_glob(),
2779                ),
2780                false,
2781            );
2782
2783        // Act
2784        let result = set.check_read("Secrets", "ssn");
2785
2786        // Assert
2787        assert_eq!(result, Some(Authorization::Deny));
2788    }
2789
2790    #[test]
2791    fn specificity_2_beats_specificity_1_beats_specificity_0() {
2792        // Arrange
2793        let set = AccessControlSelectorSet::new()
2794            .with_read_selector(Read::empty(), true)
2795            .with_read_selector(
2796                Read::new(
2797                    ValueOrGlob::new_value("Secrets"),
2798                    ValueOrGlob::new_glob(),
2799                ),
2800                false,
2801            )
2802            .with_read_selector(
2803                Read::new(
2804                    ValueOrGlob::new_value("Secrets"),
2805                    ValueOrGlob::new_value("id"),
2806                ),
2807                true,
2808            );
2809
2810        // Act
2811        let allow_all = set.check_read("Public", "data");
2812        let deny_table = set.check_read("Secrets", "ssn");
2813        let allow_carveout = set.check_read("Secrets", "id");
2814
2815        // Assert
2816        assert_eq!(allow_all, Some(Authorization::Allow));
2817        assert_eq!(deny_table, Some(Authorization::Deny));
2818        assert_eq!(allow_carveout, Some(Authorization::Allow));
2819    }
2820
2821    #[test]
2822    fn same_specificity_allow_and_deny_resolves_to_deny() {
2823        // Arrange
2824        let set = AccessControlSelectorSet::new()
2825            .with_read_selector(
2826                Read::new(
2827                    ValueOrGlob::new_value("Students"),
2828                    ValueOrGlob::new_glob(),
2829                ),
2830                true,
2831            )
2832            .with_read_selector(
2833                Read::new(
2834                    ValueOrGlob::new_glob(),
2835                    ValueOrGlob::new_value("name"),
2836                ),
2837                false,
2838            );
2839
2840        // Act
2841        let result = set.check_read("Students", "name");
2842
2843        // Assert
2844        assert_eq!(result, Some(Authorization::Deny));
2845    }
2846
2847    #[test]
2848    fn same_specificity_multiple_allows_no_deny_returns_allow() {
2849        // Arrange
2850        let set = AccessControlSelectorSet::new()
2851            .with_read_selector(
2852                Read::new(ValueOrGlob::new_value("A"), ValueOrGlob::new_glob()),
2853                true,
2854            )
2855            .with_read_selector(
2856                Read::new(ValueOrGlob::new_glob(), ValueOrGlob::new_value("x")),
2857                true,
2858            );
2859
2860        // Act
2861        let result = set.check_read("A", "x");
2862
2863        // Assert
2864        assert_eq!(result, Some(Authorization::Allow));
2865    }
2866
2867    #[test]
2868    fn same_specificity_multiple_denys_no_allow_returns_deny() {
2869        // Arrange
2870        let set = AccessControlSelectorSet::new()
2871            .with_read_selector(
2872                Read::new(ValueOrGlob::new_value("A"), ValueOrGlob::new_glob()),
2873                false,
2874            )
2875            .with_read_selector(
2876                Read::new(ValueOrGlob::new_glob(), ValueOrGlob::new_value("x")),
2877                false,
2878            );
2879
2880        // Act
2881        let result = set.check_read("A", "x");
2882
2883        // Assert
2884        assert_eq!(result, Some(Authorization::Deny));
2885    }
2886
2887    #[test]
2888    fn non_matching_rules_are_skipped_to_lower_specificity() {
2889        // Arrange
2890        let set = AccessControlSelectorSet::new()
2891            .with_read_selector(Read::empty(), true)
2892            .with_read_selector(
2893                Read::new(
2894                    ValueOrGlob::new_value("Other"),
2895                    ValueOrGlob::new_glob(),
2896                ),
2897                false,
2898            );
2899
2900        // Act
2901        let result = set.check_read("Students", "name");
2902
2903        // Assert
2904        assert_eq!(result, Some(Authorization::Allow));
2905    }
2906
2907    #[test]
2908    fn rules_with_specificity_gap_still_resolve_correctly() {
2909        // Arrange
2910        let set = AccessControlSelectorSet::new()
2911            .with_read_selector(Read::empty(), false)
2912            .with_read_selector(
2913                Read::new(
2914                    ValueOrGlob::new_value("Students"),
2915                    ValueOrGlob::new_value("name"),
2916                ),
2917                true,
2918            );
2919
2920        // Act
2921        let carveout = set.check_read("Students", "name");
2922        let blocked = set.check_read("Students", "ssn");
2923
2924        // Assert
2925        assert_eq!(carveout, Some(Authorization::Allow));
2926        assert_eq!(blocked, Some(Authorization::Deny));
2927    }
2928
2929    #[test]
2930    fn glob_table_pinned_column_matches_any_table() {
2931        // Arrange
2932        let set = AccessControlSelectorSet::new().with_read_selector(
2933            Read::new(ValueOrGlob::new_glob(), ValueOrGlob::new_value("ssn")),
2934            false,
2935        );
2936
2937        // Act
2938        let result_a = set.check_read("Students", "ssn");
2939        let result_b = set.check_read("Employees", "ssn");
2940        let result_c = set.check_read("Students", "name");
2941
2942        // Assert
2943        assert_eq!(result_a, Some(Authorization::Deny));
2944        assert_eq!(result_b, Some(Authorization::Deny));
2945        assert_eq!(result_c, None);
2946    }
2947
2948    #[test]
2949    fn pinned_table_glob_column_matches_any_column() {
2950        // Arrange
2951        let set = AccessControlSelectorSet::new().with_read_selector(
2952            Read::new(
2953                ValueOrGlob::new_value("Secrets"),
2954                ValueOrGlob::new_glob(),
2955            ),
2956            false,
2957        );
2958
2959        // Act
2960        let result_a = set.check_read("Secrets", "ssn");
2961        let result_b = set.check_read("Secrets", "id");
2962        let result_c = set.check_read("Public", "data");
2963
2964        // Assert
2965        assert_eq!(result_a, Some(Authorization::Deny));
2966        assert_eq!(result_b, Some(Authorization::Deny));
2967        assert_eq!(result_c, None);
2968    }
2969
2970    #[test]
2971    fn fully_pinned_selector_matches_only_exact_pair() {
2972        // Arrange
2973        let set = AccessControlSelectorSet::new().with_read_selector(
2974            Read::new(
2975                ValueOrGlob::new_value("Students"),
2976                ValueOrGlob::new_value("name"),
2977            ),
2978            false,
2979        );
2980
2981        // Act
2982        let exact = set.check_read("Students", "name");
2983        let wrong_col = set.check_read("Students", "id");
2984        let wrong_table = set.check_read("Other", "name");
2985
2986        // Assert
2987        assert_eq!(exact, Some(Authorization::Deny));
2988        assert_eq!(wrong_col, None);
2989        assert_eq!(wrong_table, None);
2990    }
2991
2992    #[test]
2993    fn read_rules_do_not_affect_delete_checks() {
2994        // Arrange
2995        let set = AccessControlSelectorSet::new()
2996            .with_read_selector(Read::empty(), false);
2997
2998        // Act
2999        let read_result = set.check_read("Students", "name");
3000        let delete_result = set.check_delete("Students");
3001
3002        // Assert
3003        assert_eq!(read_result, Some(Authorization::Deny));
3004        assert_eq!(delete_result, None);
3005    }
3006
3007    #[test]
3008    fn delete_single_field_selector_works() {
3009        // Arrange
3010        let set = AccessControlSelectorSet::new()
3011            .with_delete_selector(Delete::empty(), true)
3012            .with_delete_selector(
3013                Delete::new(ValueOrGlob::new_value("AuditLog")),
3014                false,
3015            );
3016
3017        // Act
3018        let allowed = set.check_delete("Students");
3019        let denied = set.check_delete("AuditLog");
3020
3021        // Assert
3022        assert_eq!(allowed, Some(Authorization::Allow));
3023        assert_eq!(denied, Some(Authorization::Deny));
3024    }
3025
3026    #[test]
3027    fn select_zero_field_selector_allow_works() {
3028        // Arrange
3029        let set = AccessControlSelectorSet::new()
3030            .with_select_selector(Select {}, true);
3031
3032        // Act
3033        let result = set.check_select();
3034
3035        // Assert
3036        assert_eq!(result, Some(Authorization::Allow));
3037    }
3038
3039    #[test]
3040    fn select_zero_field_allow_and_deny_resolves_to_deny() {
3041        // Arrange
3042        let set = AccessControlSelectorSet::new()
3043            .with_select_selector(Select {}, true)
3044            .with_select_selector(Select {}, false);
3045
3046        // Act
3047        let result = set.check_select();
3048
3049        // Assert
3050        assert_eq!(result, Some(Authorization::Deny));
3051    }
3052
3053    #[test]
3054    fn with_selector_dispatches_to_correct_type() {
3055        // Arrange
3056        let read = AccessControlSelector::from(Read::empty());
3057        let set = AccessControlSelectorSet::new().with_selector(read, false);
3058
3059        // Act
3060        let result = set.check_read("anything", "anything");
3061
3062        // Assert
3063        assert_eq!(result, Some(Authorization::Deny));
3064    }
3065
3066    #[test]
3067    fn resolver_allow_everything_defaults_to_allow() {
3068        // Arrange
3069        let resolver = AuthorizationResolver::new_allow_everything();
3070
3071        // Act
3072        let result = resolver.selector_set.check_read("X", "Y");
3073
3074        // Assert
3075        assert_eq!(result, None);
3076        assert_eq!(
3077            resolver.read_default_permissions,
3078            rusqlite::hooks::Authorization::Allow,
3079        );
3080    }
3081
3082    #[test]
3083    fn resolver_deny_everything_defaults_to_deny() {
3084        // Arrange
3085        let resolver = AuthorizationResolver::new_deny_everything();
3086
3087        // Act
3088        let result = resolver.selector_set.check_read("X", "Y");
3089
3090        // Assert
3091        assert_eq!(result, None);
3092        assert_eq!(
3093            resolver.read_default_permissions,
3094            rusqlite::hooks::Authorization::Deny,
3095        );
3096    }
3097
3098    #[test]
3099    fn resolver_per_action_default_override_works() {
3100        // Arrange
3101        let resolver = AuthorizationResolver::new_allow_everything()
3102            .with_read_default_permissions(
3103                rusqlite::hooks::Authorization::Deny,
3104            );
3105
3106        // Act / Assert
3107        assert_eq!(
3108            resolver.read_default_permissions,
3109            rusqlite::hooks::Authorization::Deny,
3110        );
3111        assert_eq!(
3112            resolver.insert_default_permissions,
3113            rusqlite::hooks::Authorization::Allow,
3114        );
3115    }
3116
3117    #[test]
3118    fn doc_example_read_only_with_table_blocked() {
3119        // Arrange
3120        let set = AccessControlSelectorSet::new()
3121            .with_selector("Read".parse::<Read>().unwrap(), true)
3122            .with_selector("Read(Secrets)".parse::<Read>().unwrap(), false);
3123
3124        // Act
3125        let public_read = set.check_read("Public", "data");
3126        let secrets_read = set.check_read("Secrets", "ssn");
3127
3128        // Assert
3129        assert_eq!(public_read, Some(Authorization::Allow));
3130        assert_eq!(secrets_read, Some(Authorization::Deny));
3131    }
3132
3133    #[test]
3134    fn doc_example_read_only_with_carveout() {
3135        // Arrange
3136        let set = AccessControlSelectorSet::new()
3137            .with_selector("Read".parse::<Read>().unwrap(), true)
3138            .with_selector("Read(Secrets)".parse::<Read>().unwrap(), false)
3139            .with_selector("Read(Secrets.id)".parse::<Read>().unwrap(), true);
3140
3141        // Act
3142        let public = set.check_read("Public", "data");
3143        let secrets_ssn = set.check_read("Secrets", "ssn");
3144        let secrets_id = set.check_read("Secrets", "id");
3145
3146        // Assert
3147        assert_eq!(public, Some(Authorization::Allow));
3148        assert_eq!(secrets_ssn, Some(Authorization::Deny));
3149        assert_eq!(secrets_id, Some(Authorization::Allow));
3150    }
3151
3152    #[test]
3153    fn doc_example_conflicting_same_specificity() {
3154        // Arrange
3155        let set = AccessControlSelectorSet::new()
3156            .with_selector("Read(Students)".parse::<Read>().unwrap(), false)
3157            .with_selector("Read(*.name)".parse::<Read>().unwrap(), true);
3158
3159        // Act
3160        let result = set.check_read("Students", "name");
3161
3162        // Assert
3163        assert_eq!(result, Some(Authorization::Deny));
3164    }
3165
3166    #[test]
3167    fn doc_example_deny_functions_allow_specific() {
3168        // Arrange
3169        let set = AccessControlSelectorSet::new()
3170            .with_selector("Function".parse::<Function>().unwrap(), false)
3171            .with_selector("Function(count)".parse::<Function>().unwrap(), true)
3172            .with_selector("Function(sum)".parse::<Function>().unwrap(), true);
3173
3174        // Act
3175        let count = set.check_function("count");
3176        let sum = set.check_function("sum");
3177        let evil = set.check_function("load_extension");
3178
3179        // Assert
3180        assert_eq!(count, Some(Authorization::Allow));
3181        assert_eq!(sum, Some(Authorization::Allow));
3182        assert_eq!(evil, Some(Authorization::Deny));
3183    }
3184
3185    #[test]
3186    fn multiple_non_matching_rules_returns_none() {
3187        // Arrange
3188        let set = AccessControlSelectorSet::new()
3189            .with_read_selector(
3190                Read::new(
3191                    ValueOrGlob::new_value("A"),
3192                    ValueOrGlob::new_value("x"),
3193                ),
3194                true,
3195            )
3196            .with_read_selector(
3197                Read::new(
3198                    ValueOrGlob::new_value("B"),
3199                    ValueOrGlob::new_value("y"),
3200                ),
3201                false,
3202            );
3203
3204        // Act
3205        let result = set.check_read("C", "z");
3206
3207        // Assert
3208        assert_eq!(result, None);
3209    }
3210
3211    #[test]
3212    fn deny_at_higher_specificity_is_not_overridden_by_lower_allow() {
3213        // Arrange
3214        let set = AccessControlSelectorSet::new()
3215            .with_read_selector(Read::empty(), true)
3216            .with_read_selector(
3217                Read::new(
3218                    ValueOrGlob::new_value("Secrets"),
3219                    ValueOrGlob::new_value("ssn"),
3220                ),
3221                false,
3222            );
3223
3224        // Act
3225        let result = set.check_read("Secrets", "ssn");
3226
3227        // Assert
3228        assert_eq!(result, Some(Authorization::Deny));
3229    }
3230
3231    #[test]
3232    fn only_matching_rules_at_a_level_contribute_to_verdict() {
3233        // Arrange
3234        let set = AccessControlSelectorSet::new()
3235            .with_read_selector(
3236                Read::new(ValueOrGlob::new_value("A"), ValueOrGlob::new_glob()),
3237                false,
3238            )
3239            .with_read_selector(
3240                Read::new(ValueOrGlob::new_value("B"), ValueOrGlob::new_glob()),
3241                true,
3242            );
3243
3244        // Act
3245        let result_a = set.check_read("A", "x");
3246        let result_b = set.check_read("B", "x");
3247
3248        // Assert
3249        assert_eq!(result_a, Some(Authorization::Deny));
3250        assert_eq!(result_b, Some(Authorization::Allow));
3251    }
3252
3253    #[test]
3254    fn insertion_order_does_not_affect_specificity_ranking() {
3255        // Arrange
3256        let set = AccessControlSelectorSet::new()
3257            .with_read_selector(
3258                Read::new(
3259                    ValueOrGlob::new_value("T"),
3260                    ValueOrGlob::new_value("c"),
3261                ),
3262                true,
3263            )
3264            .with_read_selector(Read::empty(), false);
3265
3266        // Act
3267        let result = set.check_read("T", "c");
3268
3269        // Assert
3270        assert_eq!(result, Some(Authorization::Allow));
3271    }
3272
3273    #[test]
3274    fn three_specificity_levels_middle_deny_overridden_by_top() {
3275        // Arrange
3276        let set = AccessControlSelectorSet::new()
3277            .with_read_selector(Read::empty(), true)
3278            .with_read_selector(
3279                Read::new(ValueOrGlob::new_value("T"), ValueOrGlob::new_glob()),
3280                false,
3281            )
3282            .with_read_selector(
3283                Read::new(
3284                    ValueOrGlob::new_value("T"),
3285                    ValueOrGlob::new_value("ok"),
3286                ),
3287                true,
3288            );
3289
3290        // Act
3291        let other_table = set.check_read("X", "y");
3292        let denied_col = set.check_read("T", "secret");
3293        let carveout = set.check_read("T", "ok");
3294
3295        // Assert
3296        assert_eq!(other_table, Some(Authorization::Allow));
3297        assert_eq!(denied_col, Some(Authorization::Deny));
3298        assert_eq!(carveout, Some(Authorization::Allow));
3299    }
3300
3301    #[test]
3302    fn duplicate_allow_rules_still_return_allow() {
3303        // Arrange
3304        let set = AccessControlSelectorSet::new()
3305            .with_read_selector(Read::empty(), true)
3306            .with_read_selector(Read::empty(), true);
3307
3308        // Act
3309        let result = set.check_read("T", "c");
3310
3311        // Assert
3312        assert_eq!(result, Some(Authorization::Allow));
3313    }
3314
3315    #[test]
3316    fn duplicate_deny_rules_still_return_deny() {
3317        // Arrange
3318        let set = AccessControlSelectorSet::new()
3319            .with_read_selector(Read::empty(), false)
3320            .with_read_selector(Read::empty(), false);
3321
3322        // Act
3323        let result = set.check_read("T", "c");
3324
3325        // Assert
3326        assert_eq!(result, Some(Authorization::Deny));
3327    }
3328
3329    #[test]
3330    fn read_only_allows_reads_and_selects() {
3331        // Arrange
3332        let r = AuthorizationResolver::new_read_only();
3333
3334        // Act / Assert
3335        assert_eq!(
3336            r.read_default_permissions,
3337            rusqlite::hooks::Authorization::Allow,
3338        );
3339        assert_eq!(
3340            r.select_default_permissions,
3341            rusqlite::hooks::Authorization::Allow,
3342        );
3343        assert_eq!(
3344            r.transaction_default_permissions,
3345            rusqlite::hooks::Authorization::Allow,
3346        );
3347        assert_eq!(
3348            r.function_default_permissions,
3349            rusqlite::hooks::Authorization::Allow,
3350        );
3351        assert_eq!(
3352            r.recursive_default_permissions,
3353            rusqlite::hooks::Authorization::Allow,
3354        );
3355        assert_eq!(
3356            r.pragma_default_permissions,
3357            rusqlite::hooks::Authorization::Allow,
3358        );
3359    }
3360
3361    #[test]
3362    fn read_only_denies_writes_and_ddl() {
3363        // Arrange
3364        let r = AuthorizationResolver::new_read_only();
3365
3366        // Act / Assert
3367        assert_eq!(
3368            r.insert_default_permissions,
3369            rusqlite::hooks::Authorization::Deny,
3370        );
3371        assert_eq!(
3372            r.update_default_permissions,
3373            rusqlite::hooks::Authorization::Deny,
3374        );
3375        assert_eq!(
3376            r.delete_default_permissions,
3377            rusqlite::hooks::Authorization::Deny,
3378        );
3379        assert_eq!(
3380            r.create_table_default_permissions,
3381            rusqlite::hooks::Authorization::Deny,
3382        );
3383        assert_eq!(
3384            r.drop_table_default_permissions,
3385            rusqlite::hooks::Authorization::Deny,
3386        );
3387        assert_eq!(
3388            r.attach_default_permissions,
3389            rusqlite::hooks::Authorization::Deny,
3390        );
3391    }
3392
3393    #[test]
3394    fn read_write_allows_data_modification() {
3395        // Arrange
3396        let r = AuthorizationResolver::new_read_write();
3397
3398        // Act / Assert
3399        assert_eq!(
3400            r.insert_default_permissions,
3401            rusqlite::hooks::Authorization::Allow,
3402        );
3403        assert_eq!(
3404            r.update_default_permissions,
3405            rusqlite::hooks::Authorization::Allow,
3406        );
3407        assert_eq!(
3408            r.delete_default_permissions,
3409            rusqlite::hooks::Authorization::Allow,
3410        );
3411        assert_eq!(
3412            r.savepoint_default_permissions,
3413            rusqlite::hooks::Authorization::Allow,
3414        );
3415        assert_eq!(
3416            r.analyze_default_permissions,
3417            rusqlite::hooks::Authorization::Allow,
3418        );
3419    }
3420
3421    #[test]
3422    fn read_write_allows_temp_objects_but_denies_permanent_ddl() {
3423        // Arrange
3424        let r = AuthorizationResolver::new_read_write();
3425
3426        // Act / Assert
3427        assert_eq!(
3428            r.create_temp_table_default_permissions,
3429            rusqlite::hooks::Authorization::Allow,
3430        );
3431        assert_eq!(
3432            r.drop_temp_table_default_permissions,
3433            rusqlite::hooks::Authorization::Allow,
3434        );
3435        assert_eq!(
3436            r.create_table_default_permissions,
3437            rusqlite::hooks::Authorization::Deny,
3438        );
3439        assert_eq!(
3440            r.drop_table_default_permissions,
3441            rusqlite::hooks::Authorization::Deny,
3442        );
3443        assert_eq!(
3444            r.alter_table_default_permissions,
3445            rusqlite::hooks::Authorization::Deny,
3446        );
3447        assert_eq!(
3448            r.attach_default_permissions,
3449            rusqlite::hooks::Authorization::Deny,
3450        );
3451    }
3452
3453    #[test]
3454    fn full_ddl_allows_permanent_schema_changes() {
3455        // Arrange
3456        let r = AuthorizationResolver::new_full_ddl();
3457
3458        // Act / Assert
3459        assert_eq!(
3460            r.create_table_default_permissions,
3461            rusqlite::hooks::Authorization::Allow,
3462        );
3463        assert_eq!(
3464            r.drop_table_default_permissions,
3465            rusqlite::hooks::Authorization::Allow,
3466        );
3467        assert_eq!(
3468            r.alter_table_default_permissions,
3469            rusqlite::hooks::Authorization::Allow,
3470        );
3471        assert_eq!(
3472            r.create_index_default_permissions,
3473            rusqlite::hooks::Authorization::Allow,
3474        );
3475        assert_eq!(
3476            r.create_view_default_permissions,
3477            rusqlite::hooks::Authorization::Allow,
3478        );
3479        assert_eq!(
3480            r.create_trigger_default_permissions,
3481            rusqlite::hooks::Authorization::Allow,
3482        );
3483    }
3484
3485    #[test]
3486    fn full_ddl_still_denies_attach_detach_and_vtables() {
3487        // Arrange
3488        let r = AuthorizationResolver::new_full_ddl();
3489
3490        // Act / Assert
3491        assert_eq!(
3492            r.attach_default_permissions,
3493            rusqlite::hooks::Authorization::Deny,
3494        );
3495        assert_eq!(
3496            r.detach_default_permissions,
3497            rusqlite::hooks::Authorization::Deny,
3498        );
3499        assert_eq!(
3500            r.create_vtable_default_permissions,
3501            rusqlite::hooks::Authorization::Deny,
3502        );
3503        assert_eq!(
3504            r.drop_vtable_default_permissions,
3505            rusqlite::hooks::Authorization::Deny,
3506        );
3507    }
3508
3509    #[test]
3510    fn presets_are_composable_with_per_action_overrides() {
3511        // Arrange
3512        let r = AuthorizationResolver::new_read_only()
3513            .with_insert_default_permissions(
3514                rusqlite::hooks::Authorization::Allow,
3515            );
3516
3517        // Act / Assert
3518        assert_eq!(
3519            r.insert_default_permissions,
3520            rusqlite::hooks::Authorization::Allow,
3521        );
3522        assert_eq!(
3523            r.update_default_permissions,
3524            rusqlite::hooks::Authorization::Deny,
3525        );
3526        assert_eq!(
3527            r.read_default_permissions,
3528            rusqlite::hooks::Authorization::Allow,
3529        );
3530    }
3531}