|
46 | 46 | pub(crate) fn metadata(&self) -> &HashMap<(MetadataPath, MetadataVersion), Box<[u8]>> { |
47 | 47 | &self.metadata |
48 | 48 | } |
| 49 | + |
| 50 | + /// Synchronously fetch the raw bytes of a stored target, without going |
| 51 | + /// through the async [`RepositoryProvider`] trait. |
| 52 | + /// |
| 53 | + /// Since an [`EphemeralRepository`] is fully in-memory, no I/O is required |
| 54 | + /// to read the target and no async runtime is needed. Returns |
| 55 | + /// [`Error::TargetNotFound`] if the target has not been stored. |
| 56 | + pub fn get_target(&self, target_path: &TargetPath) -> Result<&[u8]> { |
| 57 | + self.targets |
| 58 | + .get(target_path) |
| 59 | + .map(|b| &**b) |
| 60 | + .ok_or_else(|| Error::TargetNotFound(target_path.clone())) |
| 61 | + } |
49 | 62 | } |
50 | 63 |
|
51 | 64 | impl<D> RepositoryProvider<D> for EphemeralRepository<D> |
@@ -134,6 +147,19 @@ impl<'a, D> EphemeralBatchUpdate<'a, D> |
134 | 147 | where |
135 | 148 | D: DataInterchange + Sync, |
136 | 149 | { |
| 150 | + /// Synchronously fetch the raw bytes of a stored target, without going |
| 151 | + /// through the async [`RepositoryProvider`] trait. |
| 152 | + /// |
| 153 | + /// The staged (uncommitted) targets shadow the parent's targets, matching |
| 154 | + /// the behavior of [`RepositoryProvider::fetch_target`]. |
| 155 | + pub fn get_target(&self, target_path: &TargetPath) -> Result<&[u8]> { |
| 156 | + if let Some(bytes) = self.staging_repo.targets.get(target_path) { |
| 157 | + Ok(&**bytes) |
| 158 | + } else { |
| 159 | + self.parent_repo.get_target(target_path) |
| 160 | + } |
| 161 | + } |
| 162 | + |
137 | 163 | /// Write all the metadata and targets in the [EphemeralBatchUpdate] to the source |
138 | 164 | /// [EphemeralRepository] in a single batch operation. |
139 | 165 | pub fn commit(self) { |
@@ -259,6 +285,43 @@ mod test { |
259 | 285 | }) |
260 | 286 | } |
261 | 287 |
|
| 288 | + #[test] |
| 289 | + fn ephemeral_repo_get_target_sync() { |
| 290 | + block_on(async { |
| 291 | + let mut repo = EphemeralRepository::<Json>::new(); |
| 292 | + let path = TargetPath::new("sync-target").unwrap(); |
| 293 | + |
| 294 | + // Missing target returns TargetNotFound. |
| 295 | + assert_matches!( |
| 296 | + repo.get_target(&path), |
| 297 | + Err(Error::TargetNotFound(p)) if p == path |
| 298 | + ); |
| 299 | + |
| 300 | + // After storing, we can read the bytes back synchronously. |
| 301 | + let data: &[u8] = b"in-memory bytes"; |
| 302 | + repo.store_target(&path, &mut &*data).await.unwrap(); |
| 303 | + assert_eq!(repo.get_target(&path).unwrap(), data); |
| 304 | + |
| 305 | + // Batch update: staged bytes shadow the parent's bytes, and the |
| 306 | + // parent's bytes remain visible for un-staged targets. |
| 307 | + let other_path = TargetPath::new("other").unwrap(); |
| 308 | + let other_data: &[u8] = b"parent only"; |
| 309 | + repo.store_target(&other_path, &mut &*other_data) |
| 310 | + .await |
| 311 | + .unwrap(); |
| 312 | + |
| 313 | + let mut batch = repo.batch_update(); |
| 314 | + let staged: &[u8] = b"staged bytes"; |
| 315 | + batch.store_target(&path, &mut &*staged).await.unwrap(); |
| 316 | + assert_eq!(batch.get_target(&path).unwrap(), staged); |
| 317 | + assert_eq!(batch.get_target(&other_path).unwrap(), other_data); |
| 318 | + |
| 319 | + // Dropping the batch without committing leaves the parent unchanged. |
| 320 | + drop(batch); |
| 321 | + assert_eq!(repo.get_target(&path).unwrap(), data); |
| 322 | + }) |
| 323 | + } |
| 324 | + |
262 | 325 | #[test] |
263 | 326 | fn ephemeral_repo_batch_update() { |
264 | 327 | block_on(async { |
|
0 commit comments