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}