Commit

Author:

Hash:

Timestamp:

+295 -109 +/-7 browse

Kevin Schoon [me@kevinschoon.com]

c3812251654ec0d3108f68ed22cebc393fce8c25

Tue, 15 Apr 2025 16:15:19 +0000 (1 month ago)

refactor address to be owned, add reverse addr parsing
1diff --git a/README.md b/README.md
2index 9b6b5be..09eb6a3 100644
3--- a/README.md
4+++ b/README.md
5 @@ -9,6 +9,11 @@ be implemented in the future. This library is intended to be used in
6
7 Note that this code has only been tested for use with Podman.
8
9+ ## OCI Distribution Test
10+
11+ The distribution spec contains a compliance test that is package in the form of a container.
12+ Build the container provided [here](https://github.com/opencontainers/distribution-spec/tree/main/conformance)
13+ and then run `scripts/conformance_test.sh`. Results are saved in the `results/` directory.
14
15 ## Running the Example
16
17 @@ -31,4 +36,5 @@ And finally interact with the dev server:
18 podman login localhost:8700
19 podman tag alpine:3 localhost:8700/alpine:3
20 podman push localhost:8700/alpine:3
21+ podman pull localhost:8700/alpine:3
22 ```
23 diff --git a/src/address.rs b/src/address.rs
24index 18b3690..88347a5 100644
25--- a/src/address.rs
26+++ b/src/address.rs
27 @@ -1,15 +1,15 @@
28 use std::{
29+ collections::VecDeque,
30 fmt::Display,
31 path::{Path, PathBuf},
32+ str::FromStr,
33 };
34
35 use oci_spec::image::Digest;
36 use relative_path::RelativePath;
37 use uuid::Uuid;
38
39- use crate::Namespace;
40-
41- const SEPARATOR: &str = "/";
42+ use crate::{Namespace, SEPARATOR};
43
44 fn digest_prefix(digest: &Digest) -> &str {
45 match digest.algorithm() {
46 @@ -21,42 +21,43 @@ fn digest_prefix(digest: &Digest) -> &str {
47 }
48 }
49
50- pub enum Address<'a> {
51+ #[derive(Debug, PartialEq, Eq)]
52+ pub enum Address {
53 Tag {
54- namespace: &'a Namespace,
55- name: &'a str,
56+ namespace: Namespace,
57+ name: String,
58 },
59 TagDirectory {
60- namespace: &'a Namespace,
61+ namespace: Namespace,
62 },
63 Reference {
64- namespace: &'a Namespace,
65- digest: &'a Digest,
66+ namespace: Namespace,
67+ digest: Digest,
68 },
69 TempBlob {
70- uuid: &'a Uuid,
71- namespace: &'a Namespace,
72+ uuid: Uuid,
73+ namespace: Namespace,
74 },
75 Blob {
76- digest: &'a Digest,
77+ digest: Digest,
78 },
79 ManifestRevision {
80- namespace: &'a Namespace,
81- digest: &'a Digest,
82+ namespace: Namespace,
83+ digest: Digest,
84 },
85 LayerLink {
86- namespace: &'a Namespace,
87- digest: &'a Digest,
88+ namespace: Namespace,
89+ digest: Digest,
90 },
91 }
92
93- impl Display for Address<'_> {
94+ impl Display for Address {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 write!(f, "{}", self.components().join(SEPARATOR))
97 }
98 }
99
100- impl Address<'_> {
101+ impl Address {
102 pub fn path(&self, base_dir: &Path) -> PathBuf {
103 let parts = self.components().join(SEPARATOR);
104 RelativePath::new(&parts).to_path(base_dir)
105 @@ -101,7 +102,7 @@ impl Address<'_> {
106 let first_two: String = digest_str.chars().take(2).collect();
107 Vec::from_iter(
108 format!(
109- "blobs/{}/{}/{}",
110+ "blobs/{}/{}/{}/data",
111 digest_prefix(digest),
112 first_two,
113 digest_str
114 @@ -134,54 +135,155 @@ impl Address<'_> {
115 }
116 }
117
118+ impl FromStr for Address {
119+ type Err = crate::error::Error;
120+
121+ fn from_str(s: &str) -> Result<Self, Self::Err> {
122+ let mut split: VecDeque<String> = s.split(SEPARATOR).map(|part| part.to_string()).collect();
123+ let n_components = split.len();
124+ if n_components < 3 {
125+ return Err(crate::error::Error::UnparsableAddress(s.to_string()));
126+ }
127+ let first = split.pop_front().unwrap();
128+ match first.as_str() {
129+ "blobs" => {
130+ // example: sha256/57/57f2ae062b76cff6f5a511fe6f907decfdefd6495e6afa31c44e0a6a1eca146f/data
131+ let algorithm = split.pop_front().unwrap();
132+ split.pop_front();
133+ let digest = split.pop_front().unwrap();
134+ let digest = Digest::from_str(&format!("{}:{}", algorithm, digest))?;
135+ Ok(Address::Blob { digest })
136+ }
137+ "repositories" => {
138+ if s.ends_with("tags") {
139+ // example: /{namespace}/tags
140+ split.pop_back();
141+ let namespace = Namespace::try_from(
142+ split.iter().cloned().collect::<Vec<String>>().as_slice(),
143+ )?;
144+ Ok(Address::TagDirectory { namespace })
145+ } else if s.ends_with("current/link") {
146+ // example: /{namespace}/tags/{name}/current/link
147+ let name = split.get(split.len() - 3).cloned().unwrap();
148+ split.truncate(split.len() - 4);
149+ let namespace = Namespace::try_from(
150+ split.iter().cloned().collect::<Vec<String>>().as_slice(),
151+ )?;
152+ Ok(Address::Tag { namespace, name })
153+ } else if split.get(split.len() - 2).is_some_and(|item| item == "tmp") {
154+ // example: /hello/world/tmp/614f0a00-169e-4586-a6ed-cc52894130ae
155+ let uuid = Uuid::from_str(&split.pop_back().unwrap())?;
156+ split.pop_back();
157+ let namespace = Namespace::try_from(
158+ split.iter().cloned().collect::<Vec<String>>().as_slice(),
159+ )?;
160+ Ok(Address::TempBlob { uuid, namespace })
161+ } else if split
162+ .get(split.len() - 4)
163+ .is_some_and(|item| item == "revisions")
164+ {
165+ // example: /hello/world/manifests/revisions/sha256/57f2ae062b76cff6f5a511fe6f907decfdefd6495e6afa31c44e0a6a1eca146f/link
166+ let alogrithm = split.get(split.len() - 3).unwrap();
167+ let digest = split.get(split.len() - 2).unwrap();
168+ let digest = Digest::from_str(&format!("{}:{}", alogrithm, digest))?;
169+ split.truncate(split.len() - 5);
170+ let namespace = Namespace::try_from(
171+ split.iter().cloned().collect::<Vec<String>>().as_slice(),
172+ )?;
173+ Ok(Address::ManifestRevision { namespace, digest })
174+ } else {
175+ Err(crate::error::Error::UnparsableAddress(s.to_string()))
176+ }
177+ }
178+ _ => Err(crate::error::Error::UnparsableAddress(s.to_string())),
179+ }
180+ }
181+ }
182+
183 #[cfg(test)]
184 mod test {
185 use std::str::FromStr;
186
187 use super::*;
188
189- #[test]
190- pub fn addresses() {
191- let namespace = Namespace::from_str("hello/world").unwrap();
192- assert!(
193- Address::TagDirectory {
194- namespace: &namespace
195+ fn check_address(addr: &Address, expected: &str) {
196+ let actual = addr.to_string();
197+ if actual != expected {
198+ panic!("Expected: '{}', got: '{}'", expected, actual);
199+ }
200+ let reverse: Result<Address, crate::error::Error> = expected.parse();
201+ match reverse {
202+ Ok(reversed_addr) => {
203+ if !reversed_addr.eq(addr) {
204+ panic!("{:?} != {:?}", addr, reversed_addr)
205+ }
206 }
207- .to_string()
208- == "repositories/hello/world/tags"
209- );
210- assert!(
211- Address::Tag {
212- namespace: &namespace,
213- name: "latest"
214+ Err(err) => {
215+ panic!("Cannot parse: {} into Address", err);
216 }
217- .to_string()
218- == "repositories/hello/world/tags/latest/current/link"
219+ }
220+ }
221+
222+ #[test]
223+ pub fn addresses_to_string() {
224+ let namespace = &Namespace::from_str("hello/world").unwrap();
225+ check_address(
226+ &Address::TagDirectory {
227+ namespace: namespace.clone(),
228+ },
229+ "repositories/hello/world/tags",
230+ );
231+ check_address(
232+ &Address::Tag {
233+ namespace: namespace.clone(),
234+ name: String::from("latest"),
235+ },
236+ "repositories/hello/world/tags/latest/current/link",
237 );
238 let uuid = Uuid::new_v4();
239- assert!(
240- Address::TempBlob {
241- uuid: &uuid,
242- namespace: &namespace
243- }
244- .to_string()
245- == format!("repositories/hello/world/tmp/{}", uuid)
246+ check_address(
247+ &Address::TempBlob {
248+ uuid,
249+ namespace: namespace.clone(),
250+ },
251+ &format!("repositories/hello/world/tmp/{}", uuid),
252 );
253 let digest = Digest::from_str(
254 "sha256:57f2ae062b76cff6f5a511fe6f907decfdefd6495e6afa31c44e0a6a1eca146f",
255 )
256 .unwrap();
257- assert!(
258- Address::Blob { digest: &digest }.to_string()
259- == "blobs/sha256/57/57f2ae062b76cff6f5a511fe6f907decfdefd6495e6afa31c44e0a6a1eca146f"
260+ check_address(
261+ &Address::Blob {
262+ digest: digest.clone(),
263+ },
264+ "blobs/sha256/57/57f2ae062b76cff6f5a511fe6f907decfdefd6495e6afa31c44e0a6a1eca146f/data",
265+ );
266+ check_address(
267+ &Address::ManifestRevision {
268+ namespace: namespace.clone(),
269+ digest: digest.clone(),
270+ },
271+ "repositories/hello/world/manifests/revisions/sha256/57f2ae062b76cff6f5a511fe6f907decfdefd6495e6afa31c44e0a6a1eca146f/link",
272 );
273- assert!(
274- Address::ManifestRevision {
275- namespace: &namespace,
276- digest: &digest
277+
278+ ["repositories/hello/world/fuu/bar/tags", "repositories/hello/world///fuu/bar/tags"]
279+ .iter()
280+ .for_each(|item| {
281+ if let Err(err) = Address::from_str(item) {
282+ panic!("Cannot parse {}, got: {}", item, err)
283+ }
284+ });
285+
286+ [
287+ "repositories/tags",
288+ "repositories/__/revisions/INVALID/INVALID/link",
289+ ]
290+ .iter()
291+ .for_each(|item| {
292+ if let Ok(addr) = Address::from_str(item) {
293+ panic!("Item {} should be unparsable, got: {}", item, addr)
294 }
295- .to_string()
296- == "repositories/hello/world/manifests/revisions/sha256/57f2ae062b76cff6f5a511fe6f907decfdefd6495e6afa31c44e0a6a1eca146f/link"
297- )
298+ })
299+ // todo
300 }
301 }
302 diff --git a/src/error.rs b/src/error.rs
303index 9f746eb..a6a96e1 100644
304--- a/src/error.rs
305+++ b/src/error.rs
306 @@ -9,5 +9,9 @@ pub enum Error {
307 #[error("Namespace Invalid: {0}")]
308 Namespace(String),
309 #[error("Error parsing OCI Specification: {0}")]
310- OciSpec(#[from] oci_spec::OciSpecError)
311+ OciSpec(#[from] oci_spec::OciSpecError),
312+ #[error("Cannot parse address from string: {0}")]
313+ UnparsableAddress(String),
314+ #[error("Invalid UUID: {0}")]
315+ Uuid(#[from] uuid::Error)
316 }
317 diff --git a/src/lib.rs b/src/lib.rs
318index 46c898f..11acdaa 100644
319--- a/src/lib.rs
320+++ b/src/lib.rs
321 @@ -1,9 +1,10 @@
322+ use std::iter::{FromIterator, Iterator};
323 use std::{fmt::Display, str::FromStr};
324
325 use error::Error;
326 pub use oci_spec::image::Digest;
327 use regex::Regex;
328- use relative_path::RelativePath;
329+ use relative_path::{RelativePath, RelativePathBuf};
330
331 pub mod address;
332 pub mod error;
333 @@ -16,22 +17,40 @@ pub mod axum;
334 #[cfg(feature = "storage-fs")]
335 pub mod storage_fs;
336
337+ pub const SEPARATOR: &str = "/";
338+
339 const NAME_REGEXP_MATCH: &str =
340 r"[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*(\/[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*)*";
341
342 #[derive(Clone, Debug)]
343 pub enum TagOrDigest {
344 Tag(String),
345- Digest(Digest)
346+ Digest(Digest),
347 }
348
349 // TODO: Consider 255 char namespace limit - hostname length per spec docs
350- #[derive(Clone)]
351- pub struct Namespace(String);
352+ #[derive(Clone, Debug, PartialEq, Eq)]
353+ pub struct Namespace(Vec<String>);
354
355 impl Namespace {
356- pub fn path(&self) -> &RelativePath {
357- RelativePath::new(&self.0)
358+ pub fn path(&self) -> RelativePathBuf {
359+ RelativePath::new(&self.0.join(SEPARATOR)).to_relative_path_buf()
360+ }
361+ }
362+
363+ impl TryFrom<&[&str]> for Namespace {
364+ type Error = Error;
365+
366+ fn try_from(value: &[&str]) -> Result<Self, Self::Error> {
367+ Namespace::from_str(value.join(SEPARATOR).as_str())
368+ }
369+ }
370+
371+ impl TryFrom<&[String]> for Namespace {
372+ type Error = Error;
373+
374+ fn try_from(value: &[String]) -> Result<Self, Self::Error> {
375+ Namespace::from_str(value.join(SEPARATOR).as_str())
376 }
377 }
378
379 @@ -41,25 +60,36 @@ impl FromStr for Namespace {
380 fn from_str(s: &str) -> Result<Self, Self::Err> {
381 let regexp = Regex::new(NAME_REGEXP_MATCH).unwrap();
382 if regexp.is_match(s) {
383- Ok(Namespace(s.to_string()))
384+ Ok(Namespace(
385+ s.split(SEPARATOR).map(|part| part.to_string()).collect(),
386+ ))
387 } else {
388 Err(Error::Namespace(s.to_string()))
389 }
390 }
391 }
392
393- impl Display for Namespace {
394- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395- write!(f, "{}", self.0)
396+ impl IntoIterator for Namespace {
397+ type Item = String;
398+ type IntoIter = std::vec::IntoIter<String>;
399+
400+ fn into_iter(self) -> Self::IntoIter {
401+ self.0.into_iter()
402 }
403 }
404
405- impl AsRef<str> for Namespace {
406- fn as_ref(&self) -> &str {
407- &self.0
408+ impl Display for Namespace {
409+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
410+ write!(f, "{}", self.0.join(SEPARATOR))
411 }
412 }
413
414+ // impl AsRef<str> for Namespace {
415+ // fn as_ref(&self) -> &str {
416+ // &self.0.join(SEPARATOR)
417+ // }
418+ // }
419+
420 #[cfg(test)]
421 mod test {
422 use super::*;
423 @@ -70,5 +100,6 @@ mod test {
424 Namespace::from_str("fuu/bar").unwrap();
425 Namespace::from_str("fuu/bar/baz/").unwrap();
426 Namespace::from_str("fuu/bar/baz/qux").unwrap();
427+ Namespace::try_from(vec!["fuu", "bar"].as_slice()).unwrap();
428 }
429 }
430 diff --git a/src/oci_interface.rs b/src/oci_interface.rs
431index d6b087b..4966940 100644
432--- a/src/oci_interface.rs
433+++ b/src/oci_interface.rs
434 @@ -25,7 +25,7 @@ pub struct OciInterface {
435 }
436
437 impl OciInterface {
438- async fn resolve_link(&self, link: &Address<'_>) -> Result<Digest, Error> {
439+ async fn resolve_link(&self, link: &Address) -> Result<Digest, Error> {
440 if !link.is_link() {
441 panic!("Address is not a link: {}", link);
442 }
443 @@ -45,8 +45,8 @@ impl OciInterface {
444 self.storage
445 .write_all(
446 &Address::TempBlob {
447- uuid: &uuid,
448- namespace,
449+ uuid,
450+ namespace: namespace.clone(),
451 },
452 &[],
453 )
454 @@ -63,8 +63,13 @@ impl OciInterface {
455 ) -> Result<(), Error> {
456 self.storage
457 .mv(
458- &Address::TempBlob { uuid, namespace },
459- &Address::Blob { digest },
460+ &Address::TempBlob {
461+ uuid: *uuid,
462+ namespace: namespace.clone(),
463+ },
464+ &Address::Blob {
465+ digest: digest.clone(),
466+ },
467 )
468 .await
469 .map_err(Error::Storage)?;
470 @@ -73,7 +78,10 @@ impl OciInterface {
471
472 pub async fn delete_blob(&self, namespace: &Namespace, digest: &Digest) -> Result<(), Error> {
473 self.storage
474- .delete(&Address::LayerLink { namespace, digest })
475+ .delete(&Address::LayerLink {
476+ namespace: namespace.clone(),
477+ digest: digest.clone(),
478+ })
479 .await
480 .map_err(Error::Storage)?;
481 Ok(())
482 @@ -88,7 +96,10 @@ impl OciInterface {
483 where
484 S: Stream<Item = Result<Bytes, Error>>,
485 {
486- let tmp_blob_addr = &Address::TempBlob { namespace, uuid };
487+ let tmp_blob_addr = &Address::TempBlob {
488+ namespace: namespace.clone(),
489+ uuid: *uuid,
490+ };
491 while let Some(item) = stream.next().await {
492 let chunk = item?.to_vec();
493 self.storage
494 @@ -105,14 +116,22 @@ impl OciInterface {
495 digest: &Digest,
496 content: &[u8],
497 ) -> Result<(), Error> {
498- let uuid = &Uuid::new_v4();
499- let tmp_blob_addr = &Address::TempBlob { namespace, uuid };
500+ let uuid = Uuid::new_v4();
501+ let tmp_blob_addr = &Address::TempBlob {
502+ namespace: namespace.clone(),
503+ uuid,
504+ };
505 self.storage
506 .write_all(tmp_blob_addr, content)
507 .await
508 .map_err(Error::Storage)?;
509 self.storage
510- .mv(tmp_blob_addr, &Address::Blob { digest })
511+ .mv(
512+ tmp_blob_addr,
513+ &Address::Blob {
514+ digest: digest.clone(),
515+ },
516+ )
517 .await
518 .map_err(Error::Storage)?;
519 Ok(())
520 @@ -127,8 +146,8 @@ impl OciInterface {
521 ) -> Result<(), Error> {
522 let uuid = Uuid::new_v4();
523 let tmp_blob_addr = &Address::TempBlob {
524- uuid: &uuid,
525- namespace,
526+ uuid,
527+ namespace: namespace.clone(),
528 };
529 self.storage
530 .write_all(tmp_blob_addr, manifest_bytes.to_vec().as_slice())
531 @@ -138,7 +157,9 @@ impl OciInterface {
532 let hashed = Sha256::digest(manifest_bytes);
533 let hash_str = base16ct::lower::encode_string(&hashed);
534 let digest = Digest::from_str(&format!("sha256:{}", hash_str)).unwrap();
535- let blob_address = &Address::Blob { digest: &digest };
536+ let blob_address = &Address::Blob {
537+ digest: digest.clone(),
538+ };
539 self.storage
540 .mv(tmp_blob_addr, blob_address)
541 .await
542 @@ -146,8 +167,8 @@ impl OciInterface {
543 self.storage
544 .write_all(
545 &Address::Reference {
546- namespace,
547- digest: &digest,
548+ namespace: namespace.clone(),
549+ digest: digest.clone(),
550 },
551 digest.to_string().as_bytes(),
552 )
553 @@ -159,7 +180,10 @@ impl OciInterface {
554 let digest = layer.digest();
555 let digest_str = digest.to_string();
556 let digest_bytes = digest_str.as_bytes();
557- let layer_addr = Address::LayerLink { namespace, digest };
558+ let layer_addr = Address::LayerLink {
559+ namespace: namespace.clone(),
560+ digest: digest.clone(),
561+ };
562 self.storage
563 .write_all(&layer_addr, digest_bytes)
564 .await
565 @@ -173,7 +197,7 @@ impl OciInterface {
566 self.storage
567 .write_all(
568 &Address::Blob {
569- digest: oci_config_digest,
570+ digest: oci_config_digest.clone(),
571 },
572 decoded.as_slice(),
573 )
574 @@ -182,8 +206,8 @@ impl OciInterface {
575 self.storage
576 .write_all(
577 &Address::LayerLink {
578- namespace,
579- digest: oci_config_digest,
580+ namespace: namespace.clone(),
581+ digest: oci_config_digest.clone(),
582 },
583 decoded.as_slice(),
584 )
585 @@ -194,7 +218,10 @@ impl OciInterface {
586 if let TagOrDigest::Tag(name) = tag_or_digest {
587 self.storage
588 .write_all(
589- &Address::Tag { namespace, name },
590+ &Address::Tag {
591+ namespace: namespace.clone(),
592+ name: name.clone(),
593+ },
594 digest.to_string().as_bytes(),
595 )
596 .await
597 @@ -212,12 +239,18 @@ impl OciInterface {
598 let blob_addr = Address::Blob {
599 digest: match tag_or_digest {
600 TagOrDigest::Tag(name) => {
601- &self.resolve_link(&Address::Tag { namespace, name }).await?
602+ self.resolve_link(&Address::Tag {
603+ namespace: namespace.clone(),
604+ name: name.clone(),
605+ })
606+ .await?
607 }
608 TagOrDigest::Digest(digest) => {
609- &self
610- .resolve_link(&Address::Reference { namespace, digest })
611- .await?
612+ self.resolve_link(&Address::Reference {
613+ namespace: namespace.clone(),
614+ digest: digest.clone(),
615+ })
616+ .await?
617 }
618 },
619 };
620 @@ -232,7 +265,9 @@ impl OciInterface {
621 }
622
623 pub async fn has_blob(&self, digest: &Digest) -> Result<bool, Error> {
624- let blob_addr = Address::Blob { digest };
625+ let blob_addr = Address::Blob {
626+ digest: digest.clone(),
627+ };
628 self.storage
629 .exists(&blob_addr)
630 .await
631 @@ -247,12 +282,18 @@ impl OciInterface {
632 let blob_addr = Address::Blob {
633 digest: match tag_or_digest {
634 TagOrDigest::Tag(name) => {
635- &self.resolve_link(&Address::Tag { namespace, name }).await?
636+ self.resolve_link(&Address::Tag {
637+ namespace: namespace.clone(),
638+ name: name.clone(),
639+ })
640+ .await?
641 }
642 TagOrDigest::Digest(digest) => {
643- &self
644- .resolve_link(&Address::Reference { namespace, digest })
645- .await?
646+ self.resolve_link(&Address::Reference {
647+ namespace: namespace.clone(),
648+ digest: digest.clone(),
649+ })
650+ .await?
651 }
652 },
653 };
654 @@ -263,7 +304,9 @@ impl OciInterface {
655 }
656
657 pub async fn read_blob(&self, digest: &Digest) -> Result<InnerStream, Error> {
658- let blob_addr = Address::Blob { digest };
659+ let blob_addr = Address::Blob {
660+ digest: digest.clone(),
661+ };
662 self.storage.read(&blob_addr).await.map_err(Error::Storage)
663 }
664 }
665 diff --git a/src/storage.rs b/src/storage.rs
666index d808848..2c62c19 100644
667--- a/src/storage.rs
668+++ b/src/storage.rs
669 @@ -46,18 +46,18 @@ pub trait StorageIface: Sync + Send {
670 // async fn stat(&self, addr: &Address) -> Result<Option<Object>, Error>;
671 // async fn read_bytes(&self, addr: &Address) -> Result<Option<Vec<u8>>, Error>;
672 /// Check if an object exists at the given address
673- async fn exists<'a>(&self, path: &Address<'a>) -> Result<bool, Error>;
674+ async fn exists<'a>(&self, path: &Address) -> Result<bool, Error>;
675 /// Write bytes to the address, truncating any existing object
676- async fn write_all<'a>(&self, path: &Address<'a>, bytes: &[u8]) -> Result<(), Error>;
677+ async fn write_all<'a>(&self, path: &Address, bytes: &[u8]) -> Result<(), Error>;
678
679 /// write bytes to a file that has already been created
680- async fn write<'a>(&self, path: &Address<'a>, bytes: &[u8]) -> Result<(), Error>;
681+ async fn write<'a>(&self, path: &Address, bytes: &[u8]) -> Result<(), Error>;
682
683- async fn mv<'a>(&self, src: &Address<'a>, dst: &Address<'a>) -> Result<(), Error>;
684+ async fn mv<'a>(&self, src: &Address, dst: &Address) -> Result<(), Error>;
685 // fn mv (&self, )std::future::Future<Output = ()> + Send
686- async fn read<'a>(&self, src: &Address<'a>) -> Result<InnerStream, Error>;
687- async fn read_bytes<'a>(&self, src: &Address<'a>) -> Result<Bytes, Error>;
688- async fn delete<'a>(&self, src: &Address<'a>) -> Result<(), Error>;
689+ async fn read<'a>(&self, src: &Address) -> Result<InnerStream, Error>;
690+ async fn read_bytes<'a>(&self, src: &Address) -> Result<Bytes, Error>;
691+ async fn delete<'a>(&self, src: &Address) -> Result<(), Error>;
692 }
693
694 #[derive(Debug, Clone)]
695 diff --git a/src/storage_fs.rs b/src/storage_fs.rs
696index cf725f9..19895f2 100644
697--- a/src/storage_fs.rs
698+++ b/src/storage_fs.rs
699 @@ -32,7 +32,7 @@ impl FileSystem {
700
701 #[async_trait::async_trait]
702 impl StorageIface for FileSystem {
703- async fn exists<'a>(&self, addr: &Address<'a>) -> Result<bool, Error> {
704+ async fn exists<'a>(&self, addr: &Address) -> Result<bool, Error> {
705 let path = addr.path(&self.base);
706 if tokio::fs::try_exists(&path)
707 .await
708 @@ -45,7 +45,7 @@ impl StorageIface for FileSystem {
709 }
710 }
711
712- async fn write_all<'a>(&self, addr: &Address<'a>, bytes: &[u8]) -> Result<(), Error> {
713+ async fn write_all<'a>(&self, addr: &Address, bytes: &[u8]) -> Result<(), Error> {
714 let path = addr.path(&self.base);
715 self.ensure_dir(&path).await?;
716 let mut fp = tokio::fs::OpenOptions::new()
717 @@ -61,7 +61,7 @@ impl StorageIface for FileSystem {
718 Ok(())
719 }
720
721- async fn write<'a>(&self, addr: &Address<'a>, bytes: &[u8]) -> Result<(), Error> {
722+ async fn write<'a>(&self, addr: &Address, bytes: &[u8]) -> Result<(), Error> {
723 let path = addr.path(&self.base);
724 let mut fp = tokio::fs::OpenOptions::new()
725 .create(false)
726 @@ -76,7 +76,7 @@ impl StorageIface for FileSystem {
727 Ok(())
728 }
729
730- async fn mv<'a>(&self, src: &Address<'a>, dst: &Address) -> Result<(), Error> {
731+ async fn mv<'a>(&self, src: &Address, dst: &Address) -> Result<(), Error> {
732 let src_path = src.path(&self.base);
733 let dst_path = dst.path(&self.base);
734 self.ensure_dir(&dst_path).await?;
735 @@ -85,7 +85,7 @@ impl StorageIface for FileSystem {
736 Ok(())
737 }
738
739- async fn read<'a>(&self, src: &Address<'a>) -> Result<InnerStream, Error> {
740+ async fn read<'a>(&self, src: &Address) -> Result<InnerStream, Error> {
741 let path = src.path(&self.base);
742 let fp = tokio::fs::File::open(path.as_path())
743 .await
744 @@ -97,7 +97,7 @@ impl StorageIface for FileSystem {
745 Ok(InnerStream::new(stream.boxed()))
746 }
747
748- async fn read_bytes<'a>(&self, src: &Address<'a>) -> Result<Bytes, Error> {
749+ async fn read_bytes<'a>(&self, src: &Address) -> Result<Bytes, Error> {
750 let path = src.path(&self.base);
751 let payload = tokio::fs::read(path.as_path())
752 .await
753 @@ -108,7 +108,7 @@ impl StorageIface for FileSystem {
754 Ok(Bytes::from(payload))
755 }
756
757- async fn delete<'a>(&self, src: &Address<'a>) -> Result<(), Error> {
758+ async fn delete<'a>(&self, src: &Address) -> Result<(), Error> {
759 let path = src.path(&self.base);
760 tokio::fs::remove_file(path.as_path()).await?;
761 Ok(())