aster_forge_cloud_files_core/
cache_write.rs

1//! Durable provider-cache byte installation and coverage reconciliation.
2
3use std::fmt;
4
5use crate::{ByteRange, CloudFilesCoreError, ContentCacheKey, Result, SessionGeneration};
6
7/// Stable identity of one provider-cache write operation.
8#[derive(Clone, PartialEq, Eq, Hash)]
9pub struct ContentCacheWriteOperationId(String);
10
11impl ContentCacheWriteOperationId {
12    /// Creates a non-empty opaque write identity.
13    /// # Errors
14    ///
15    /// Returns an error when validation fails or an underlying backend, store, or platform
16    /// operation fails.
17    pub fn new(value: impl Into<String>) -> Result<Self> {
18        let value = value.into();
19        if value.is_empty() {
20            return Err(CloudFilesCoreError::empty(
21                "content cache write operation id",
22            ));
23        }
24        Ok(Self(value))
25    }
26
27    /// Returns the opaque operation identity.
28    #[must_use]
29    pub fn as_str(&self) -> &str {
30        &self.0
31    }
32}
33
34impl fmt::Debug for ContentCacheWriteOperationId {
35    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
36        formatter
37            .debug_struct("ContentCacheWriteOperationId")
38            .field("byte_len", &self.0.len())
39            .finish()
40    }
41}
42
43/// Logical bytes installed by one atomic provider-cache write.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45pub enum ContentCacheWriteExtent {
46    /// Installs the complete file, including a zero-byte file.
47    Whole,
48    /// Installs one non-empty sparse byte range.
49    Range(ByteRange),
50}
51
52/// Durable write intent captured before temporary-file or sparse-byte effects.
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct ContentCacheWriteIntent {
55    operation_id: ContentCacheWriteOperationId,
56    cache_key: ContentCacheKey,
57    expected_size: u64,
58    extent: ContentCacheWriteExtent,
59    session_generation: SessionGeneration,
60}
61
62impl ContentCacheWriteIntent {
63    /// Creates and validates a revision-bound provider-cache write intent.
64    /// # Errors
65    ///
66    /// Returns an error when validation fails or an underlying backend, store, or platform
67    /// operation fails.
68    pub fn new(
69        operation_id: ContentCacheWriteOperationId,
70        cache_key: ContentCacheKey,
71        expected_size: u64,
72        extent: ContentCacheWriteExtent,
73        session_generation: SessionGeneration,
74    ) -> Result<Self> {
75        if let ContentCacheWriteExtent::Range(range) = extent
76            && range.end_exclusive() > expected_size
77        {
78            return Err(CloudFilesCoreError::invalid_content_cache_write(
79                "cache write range exceeds the expected content size",
80            ));
81        }
82        Ok(Self {
83            operation_id,
84            cache_key,
85            expected_size,
86            extent,
87            session_generation,
88        })
89    }
90
91    /// Returns the durable operation identity.
92    #[must_use]
93    pub const fn operation_id(&self) -> &ContentCacheWriteOperationId {
94        &self.operation_id
95    }
96
97    /// Returns the exact revision-bound cache identity.
98    #[must_use]
99    pub const fn cache_key(&self) -> &ContentCacheKey {
100        &self.cache_key
101    }
102
103    /// Returns the expected size of the exact content revision.
104    #[must_use]
105    pub const fn expected_size(&self) -> u64 {
106        self.expected_size
107    }
108
109    /// Returns the logical extent whose physical bytes are installed.
110    #[must_use]
111    pub const fn extent(&self) -> ContentCacheWriteExtent {
112        self.extent
113    }
114
115    /// Returns the platform session generation that created the write.
116    #[must_use]
117    pub const fn session_generation(&self) -> SessionGeneration {
118        self.session_generation
119    }
120}
121
122/// Durable provider-cache write state.
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
124pub enum ContentCacheWriteState {
125    /// Intent and entry reservation are durable; physical bytes may be absent.
126    IntentPersisted,
127    /// Temporary/sparse bytes were atomically installed and can be observed after restart.
128    PhysicalBytesCommitted,
129    /// Provider range coverage reflects the installed physical bytes.
130    CoverageCommitted,
131    /// The operation is terminal and its cache-write reservation was released.
132    Completed,
133}
134
135/// Result of an idempotent cache-write transition.
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
137pub enum ContentCacheWriteRecordTransition {
138    /// Durable state changed.
139    Applied,
140    /// Equivalent or later durable state already contains the transition.
141    AlreadyApplied,
142}
143
144/// Recoverable provider-cache write journal record.
145#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct ContentCacheWriteRecord {
147    intent: ContentCacheWriteIntent,
148    state: ContentCacheWriteState,
149}
150
151impl ContentCacheWriteRecord {
152    /// Creates the first recoverable state after the entry write reservation is durable.
153    #[must_use]
154    pub const fn persist(intent: ContentCacheWriteIntent) -> Self {
155        Self {
156            intent,
157            state: ContentCacheWriteState::IntentPersisted,
158        }
159    }
160
161    /// Returns the immutable write intent.
162    #[must_use]
163    pub const fn intent(&self) -> &ContentCacheWriteIntent {
164        &self.intent
165    }
166
167    /// Returns the current durable state.
168    #[must_use]
169    pub const fn state(&self) -> ContentCacheWriteState {
170        self.state
171    }
172
173    /// Records that physical bytes were atomically installed.
174    /// # Errors
175    ///
176    /// Returns an error when validation fails or an underlying backend, store, or platform
177    /// operation fails.
178    pub fn mark_physical_bytes_committed(&mut self) -> Result<ContentCacheWriteRecordTransition> {
179        match self.state {
180            ContentCacheWriteState::IntentPersisted => {
181                self.state = ContentCacheWriteState::PhysicalBytesCommitted;
182                Ok(ContentCacheWriteRecordTransition::Applied)
183            }
184            ContentCacheWriteState::PhysicalBytesCommitted
185            | ContentCacheWriteState::CoverageCommitted
186            | ContentCacheWriteState::Completed => {
187                Ok(ContentCacheWriteRecordTransition::AlreadyApplied)
188            }
189        }
190    }
191
192    /// Marks sparse coverage committed after physical bytes are observable.
193    /// # Errors
194    ///
195    /// Returns an error when validation fails or an underlying backend, store, or platform
196    /// operation fails.
197    pub fn mark_coverage_committed(&mut self) -> Result<ContentCacheWriteRecordTransition> {
198        match self.state {
199            ContentCacheWriteState::PhysicalBytesCommitted => {
200                self.state = ContentCacheWriteState::CoverageCommitted;
201                Ok(ContentCacheWriteRecordTransition::Applied)
202            }
203            ContentCacheWriteState::CoverageCommitted | ContentCacheWriteState::Completed => {
204                Ok(ContentCacheWriteRecordTransition::AlreadyApplied)
205            }
206            ContentCacheWriteState::IntentPersisted => {
207                Err(CloudFilesCoreError::invalid_content_cache_write(
208                    "coverage commit requires observable physical bytes",
209                ))
210            }
211        }
212    }
213
214    /// Marks the write terminal after coverage commit.
215    /// # Errors
216    ///
217    /// Returns an error when validation fails or an underlying backend, store, or platform
218    /// operation fails.
219    pub fn complete(&mut self) -> Result<ContentCacheWriteRecordTransition> {
220        match self.state {
221            ContentCacheWriteState::CoverageCommitted => {
222                self.state = ContentCacheWriteState::Completed;
223                Ok(ContentCacheWriteRecordTransition::Applied)
224            }
225            ContentCacheWriteState::Completed => {
226                Ok(ContentCacheWriteRecordTransition::AlreadyApplied)
227            }
228            _ => Err(CloudFilesCoreError::invalid_content_cache_write(
229                "cache write completion requires committed coverage",
230            )),
231        }
232    }
233}