Skip to content

Commit eb129cc

Browse files
committed
feat: add sync method to read a target from the in-memory repository
Previously the only way to read thimgs from an EphemeralRepository is thorugh the RepositoryProvider trait, which exposes and async `fetch_target` method. This is more complex and slower than necessary, as since this is in-memory we can just return a reference to the slice and not copy it around.
1 parent ad6ad37 commit eb129cc

1 file changed

Lines changed: 63 additions & 0 deletions

File tree

‎tuf/src/repository/ephemeral.rs‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ where
4646
pub(crate) fn metadata(&self) -> &HashMap<(MetadataPath, MetadataVersion), Box<[u8]>> {
4747
&self.metadata
4848
}
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+
}
4962
}
5063

5164
impl<D> RepositoryProvider<D> for EphemeralRepository<D>
@@ -134,6 +147,19 @@ impl<'a, D> EphemeralBatchUpdate<'a, D>
134147
where
135148
D: DataInterchange + Sync,
136149
{
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+
137163
/// Write all the metadata and targets in the [EphemeralBatchUpdate] to the source
138164
/// [EphemeralRepository] in a single batch operation.
139165
pub fn commit(self) {
@@ -259,6 +285,43 @@ mod test {
259285
})
260286
}
261287

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+
262325
#[test]
263326
fn ephemeral_repo_batch_update() {
264327
block_on(async {

0 commit comments

Comments
 (0)