Skip to main content

meta_language/
access.rs

1//! Read-only and mutable engine access controls.
2//!
3//! [`LinkNetwork`] is mutable by construction. This module adds the
4//! [`AccessMode`](crate::configuration::AccessMode)-driven counterpart: a
5//! frozen [`ReadOnlyNetwork`] view that exposes only `&self` operations
6//! (query, project, reconstruct, verify, serialize) and makes mutation a
7//! compile-time error, plus an [`EngineNetwork`] boundary that honours the
8//! configured access mode and rejects mutation at runtime with a clear
9//! diagnostic.
10//!
11//! The frozen view reuses the same `Arc<LinkNetwork>` sharing as
12//! [`NetworkSnapshot`](crate::snapshots::NetworkSnapshot), so read-only access
13//! composes with snapshot versioning instead of duplicating it.
14
15use std::error::Error;
16use std::fmt;
17use std::ops::Deref;
18use std::sync::Arc;
19
20use crate::configuration::{AccessMode, ParseConfiguration};
21use crate::link_network::LinkNetwork;
22
23/// Error raised when a mutation is attempted through a read-only engine handle.
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub struct ReadOnlyViolation;
26
27impl fmt::Display for ReadOnlyViolation {
28    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
29        formatter.write_str(
30            "engine is configured read-only; mutation is rejected. \
31             Re-parse with AccessMode::Mutable or fork an editable copy via \
32             ReadOnlyNetwork::to_mutable before mutating.",
33        )
34    }
35}
36
37impl Error for ReadOnlyViolation {}
38
39/// Compile-time read-only view over a shared links network.
40///
41/// `ReadOnlyNetwork` derefs to `&LinkNetwork`, so every non-mutating public
42/// operation is reachable while the `&mut self` mutators (`insert_link`,
43/// `set_references`, `set_span`, `set_flags`, `apply_substitution`, ...) are
44/// unreachable: there is no `DerefMut`, so the borrow checker rejects any
45/// attempt to call them. The wrapped network is held behind an `Arc`, so
46/// cloning a view shares one allocation rather than copying the network.
47///
48/// Read-only operations compile and run:
49///
50/// ```
51/// use meta_language::{LinkNetwork, ParseConfiguration};
52///
53/// let view = LinkNetwork::parse("alpha", "plain-text", ParseConfiguration::default()).freeze();
54/// assert_eq!(view.reconstruct_text(), "alpha");
55/// ```
56///
57/// Mutation does not compile, because the mutators require `&mut LinkNetwork`
58/// and the view only ever yields `&LinkNetwork`:
59///
60/// ```compile_fail
61/// use meta_language::{LinkMetadata, LinkNetwork, ParseConfiguration};
62///
63/// let view = LinkNetwork::parse("alpha", "plain-text", ParseConfiguration::default()).freeze();
64/// view.insert_link([], LinkMetadata::new()); // error: cannot borrow as mutable
65/// ```
66#[derive(Clone, Debug)]
67pub struct ReadOnlyNetwork {
68    network: Arc<LinkNetwork>,
69}
70
71impl ReadOnlyNetwork {
72    /// Freezes an owned network into a read-only view.
73    #[must_use]
74    pub fn new(network: LinkNetwork) -> Self {
75        Self {
76            network: Arc::new(network),
77        }
78    }
79
80    /// Wraps an already shared network as a read-only view.
81    ///
82    /// This reuses the existing allocation, allowing read-only access to
83    /// compose with snapshot versioning without re-cloning the network.
84    #[must_use]
85    pub const fn from_shared(network: Arc<LinkNetwork>) -> Self {
86        Self { network }
87    }
88
89    /// Borrows the underlying immutable network.
90    #[must_use]
91    pub fn network(&self) -> &LinkNetwork {
92        &self.network
93    }
94
95    /// Borrows the shared network handle.
96    #[must_use]
97    pub const fn shared(&self) -> &Arc<LinkNetwork> {
98        &self.network
99    }
100
101    /// Consumes the view and returns the shared network handle.
102    #[must_use]
103    pub fn into_shared(self) -> Arc<LinkNetwork> {
104        self.network
105    }
106
107    /// Number of handles sharing the frozen network allocation.
108    #[must_use]
109    pub fn shared_count(&self) -> usize {
110        Arc::strong_count(&self.network)
111    }
112
113    /// Forks an editable copy so callers can return to a mutable engine.
114    #[must_use]
115    pub fn to_mutable(&self) -> LinkNetwork {
116        self.network.as_ref().clone()
117    }
118
119    /// Consumes the view and returns an editable network, reusing the
120    /// allocation when this is the only handle and cloning otherwise.
121    #[must_use]
122    pub fn into_mutable(self) -> LinkNetwork {
123        Arc::try_unwrap(self.network).unwrap_or_else(|shared| shared.as_ref().clone())
124    }
125}
126
127impl Deref for ReadOnlyNetwork {
128    type Target = LinkNetwork;
129
130    fn deref(&self) -> &Self::Target {
131        &self.network
132    }
133}
134
135impl PartialEq for ReadOnlyNetwork {
136    fn eq(&self, other: &Self) -> bool {
137        self.network == other.network
138    }
139}
140
141impl Eq for ReadOnlyNetwork {}
142
143impl From<LinkNetwork> for ReadOnlyNetwork {
144    fn from(network: LinkNetwork) -> Self {
145        Self::new(network)
146    }
147}
148
149impl LinkNetwork {
150    /// Freezes this network into a read-only view, consuming it.
151    ///
152    /// Mutators are unreachable on the returned [`ReadOnlyNetwork`] at compile
153    /// time; only `&self` operations remain available.
154    #[must_use]
155    pub fn freeze(self) -> ReadOnlyNetwork {
156        ReadOnlyNetwork::new(self)
157    }
158
159    /// Returns a read-only view sharing a clone of this network.
160    #[must_use]
161    pub fn as_read_only(&self) -> ReadOnlyNetwork {
162        ReadOnlyNetwork::new(self.clone())
163    }
164
165    /// Parses source text honouring the configured engine access mode.
166    ///
167    /// Under [`AccessMode::Mutable`] (the default) this returns an editable
168    /// network; under [`AccessMode::ReadOnly`] it returns the frozen form,
169    /// where mutation attempts at the engine boundary fail with a clear
170    /// diagnostic.
171    #[must_use]
172    pub fn parse_engine(
173        text: &str,
174        language: &str,
175        configuration: ParseConfiguration,
176    ) -> EngineNetwork {
177        let network = Self::parse(text, language, configuration);
178        EngineNetwork::with_access_mode(network, configuration.access_mode())
179    }
180}
181
182/// Access-mode-aware engine handle returned by configured parsing.
183///
184/// This is the runtime boundary where the configured
185/// [`AccessMode`](crate::configuration::AccessMode) is enforced: a read-only
186/// engine yields a frozen view and rejects [`EngineNetwork::as_mutable`] with a
187/// [`ReadOnlyViolation`], while a mutable engine hands back the editable
188/// network.
189#[derive(Clone, Debug, PartialEq, Eq)]
190pub enum EngineNetwork {
191    /// An editable network produced under [`AccessMode::Mutable`].
192    Mutable(LinkNetwork),
193    /// A frozen view produced under [`AccessMode::ReadOnly`].
194    ReadOnly(ReadOnlyNetwork),
195}
196
197impl EngineNetwork {
198    /// Wraps a network according to the supplied access mode.
199    #[must_use]
200    pub fn with_access_mode(network: LinkNetwork, access_mode: AccessMode) -> Self {
201        match access_mode {
202            AccessMode::Mutable => Self::Mutable(network),
203            AccessMode::ReadOnly => Self::ReadOnly(network.freeze()),
204        }
205    }
206
207    /// The access mode this handle was created with.
208    #[must_use]
209    pub const fn access_mode(&self) -> AccessMode {
210        match self {
211            Self::Mutable(_) => AccessMode::Mutable,
212            Self::ReadOnly(_) => AccessMode::ReadOnly,
213        }
214    }
215
216    /// Whether this handle permits mutation.
217    #[must_use]
218    pub const fn is_mutable(&self) -> bool {
219        matches!(self, Self::Mutable(_))
220    }
221
222    /// Whether this handle is read-only.
223    #[must_use]
224    pub const fn is_read_only(&self) -> bool {
225        matches!(self, Self::ReadOnly(_))
226    }
227
228    /// Borrows the underlying network for read-only operations regardless of
229    /// the access mode.
230    #[must_use]
231    pub fn network(&self) -> &LinkNetwork {
232        match self {
233            Self::Mutable(network) => network,
234            Self::ReadOnly(view) => view.network(),
235        }
236    }
237
238    /// Borrows the network mutably, or fails with a clear diagnostic when the
239    /// engine is read-only.
240    ///
241    /// # Errors
242    ///
243    /// Returns [`ReadOnlyViolation`] when this handle was created under
244    /// [`AccessMode::ReadOnly`].
245    pub fn as_mutable(&mut self) -> Result<&mut LinkNetwork, ReadOnlyViolation> {
246        match self {
247            Self::Mutable(network) => Ok(network),
248            Self::ReadOnly(_) => Err(ReadOnlyViolation),
249        }
250    }
251
252    /// Converts this handle into a read-only view, freezing a mutable network.
253    #[must_use]
254    pub fn into_read_only(self) -> ReadOnlyNetwork {
255        match self {
256            Self::Mutable(network) => network.freeze(),
257            Self::ReadOnly(view) => view,
258        }
259    }
260
261    /// Converts this handle into an editable network, forking a read-only view.
262    #[must_use]
263    pub fn into_mutable(self) -> LinkNetwork {
264        match self {
265            Self::Mutable(network) => network,
266            Self::ReadOnly(view) => view.into_mutable(),
267        }
268    }
269}
270
271impl Deref for EngineNetwork {
272    type Target = LinkNetwork;
273
274    fn deref(&self) -> &Self::Target {
275        self.network()
276    }
277}