Skip to main content

scrape_core/dom/
state.rs

1//! Document lifecycle state types.
2//!
3//! These types encode the document's lifecycle state at the type level,
4//! enabling compile-time enforcement of valid operations.
5//!
6//! # States
7//!
8//! - [`Building`] - Document under construction
9//! - [`Queryable`] - Document ready for queries
10//! - [`Sealed`] - Fully immutable document
11//!
12//! # State Transitions
13//!
14//! ```text
15//! Building --[.build()]--> Queryable --[.seal()]--> Sealed
16//! ```
17//!
18//! The state is encoded at the type level using [`PhantomData`](std::marker::PhantomData)
19//! and has zero runtime overhead.
20
21mod private {
22    pub trait Sealed {}
23}
24
25/// Marker trait for document states.
26///
27/// This trait is sealed - only the states defined in this module
28/// can implement it. This prevents external code from defining
29/// new document states.
30pub trait DocumentState: private::Sealed + Clone + Copy + Default {}
31
32/// Document is being constructed.
33///
34/// In this state:
35/// - Structure can be modified (`create_element`, `append_child`, etc.)
36/// - Navigation available (`parent`, `children`)
37/// - Query methods (`find`, `find_all`) are NOT available
38#[derive(Debug, Clone, Copy, Default)]
39pub struct Building;
40
41impl private::Sealed for Building {}
42impl DocumentState for Building {}
43
44/// Document is built and ready for querying.
45///
46/// In this state:
47/// - Full navigation and query methods available
48/// - Structure cannot be modified
49/// - Index can be built for faster queries
50#[derive(Debug, Clone, Copy, Default)]
51pub struct Queryable;
52
53impl private::Sealed for Queryable {}
54impl DocumentState for Queryable {}
55
56/// Document is sealed and fully immutable.
57///
58/// In this state:
59/// - Same as Queryable, but guarantees no mutations ever
60/// - Enables potential future optimizations (e.g., shared memory)
61/// - Cannot transition to any other state
62#[derive(Debug, Clone, Copy, Default)]
63pub struct Sealed;
64
65impl private::Sealed for Sealed {}
66impl DocumentState for Sealed {}
67
68/// Trait for states that support querying.
69///
70/// Both [`Queryable`] and [`Sealed`] states support querying operations.
71pub trait QueryableState: DocumentState {}
72impl QueryableState for Queryable {}
73impl QueryableState for Sealed {}
74
75/// Trait for states that support modification.
76///
77/// Only the [`Building`] state supports structural modifications.
78pub trait MutableState: DocumentState {}
79impl MutableState for Building {}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    #[allow(clippy::no_effect_underscore_binding)]
87    fn states_are_copy() {
88        // Copy trait is verified by using value twice
89        let b = Building;
90        let _b2 = b; // First use (copy)
91        let _b3 = b; // Second use (copy) - would fail without Copy
92
93        let q = Queryable;
94        let _q2 = q;
95        let _q3 = q;
96
97        let s = Sealed;
98        let _s2 = s;
99        let _s3 = s;
100    }
101
102    #[test]
103    fn states_are_default() {
104        let _: Building = Building;
105        let _: Queryable = Queryable;
106        let _: Sealed = Sealed;
107    }
108
109    #[test]
110    fn states_are_zero_sized() {
111        assert_eq!(std::mem::size_of::<Building>(), 0);
112        assert_eq!(std::mem::size_of::<Queryable>(), 0);
113        assert_eq!(std::mem::size_of::<Sealed>(), 0);
114    }
115
116    #[test]
117    fn building_is_mutable_state() {
118        fn require_mutable<S: MutableState>(_: S) {}
119        require_mutable(Building);
120    }
121
122    #[test]
123    fn queryable_is_queryable_state() {
124        fn require_queryable<S: QueryableState>(_: S) {}
125        require_queryable(Queryable);
126        require_queryable(Sealed);
127    }
128}