Skip to content

Commit

Permalink
digest: Add AsRef<str>
Browse files Browse the repository at this point in the history
Conceptually `Digest()` is just a parsed string wrapper. Many
cases want to get access to the full value without allocating,
and the introduction of the type was a regression from that point of
view in 0.7.

Luckily, we can change our internals in such a way that it's
safe to add the impl. Do that by holding onto the full value,
with a duplicate small boxed str for the algorithm only in
the degenerate case that it's an unknown type.

If we had this it would have made containers/bootc#800
less likely.

Signed-off-by: Colin Walters <[email protected]>
  • Loading branch information
cgwalters committed Sep 23, 2024
1 parent a84f16e commit 8aa75aa
Showing 1 changed file with 29 additions and 12 deletions.
41 changes: 29 additions & 12 deletions src/image/digest.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Functionality corresponding to <https://github.com/opencontainers/image-spec/blob/main/descriptor.md#digests>.

use std::fmt::{Display, Write};
use std::fmt::Display;
use std::str::FromStr;

/// A digest algorithm; at the current time only SHA-256
Expand Down Expand Up @@ -97,17 +97,25 @@ fn char_is_encoded(c: char) -> bool {

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Digest {
/// The algorithm
/// The algorithm; we need to hold a copy of this
/// right now as we ended up returning a reference
/// from the accessor. It probably would have been
/// better to have both borrowed/owned DigestAlgorithm
/// versions and our accessor just returns a borrowed version.
algorithm: DigestAlgorithm,
/// The digest value
digest: Box<str>,
value: Box<str>,
split: usize,
}

impl AsRef<str> for Digest {
fn as_ref(&self) -> &str {
&self.value
}
}

impl Display for Digest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.algorithm.as_ref())?;
f.write_char(':')?;
f.write_str(&self.digest)
f.write_str(self.as_ref())
}
}

Expand Down Expand Up @@ -143,7 +151,7 @@ impl Digest {
/// is guaranteed to be a valid length with only lowercase hexadecimal
/// characters. For example with SHA-256, the length is 64.
pub fn digest(&self) -> &str {
&self.digest
&self.value[self.split + 1..]
}
}

Expand All @@ -159,6 +167,7 @@ impl TryFrom<String> for Digest {
type Error = crate::OciSpecError;

fn try_from(s: String) -> Result<Self, Self::Error> {
let s = s.into_boxed_str();
let Some(split) = s.find(':') else {
return Err(crate::OciSpecError::Other("missing ':' in digest".into()));
};
Expand Down Expand Up @@ -204,8 +213,11 @@ impl TryFrom<String> for Digest {
)));
}
}
let digest = value.to_owned().into_boxed_str();
Ok(Self { algorithm, digest })
Ok(Self {
algorithm,
value: s,
split,
})
}
}

Expand All @@ -227,7 +239,8 @@ impl From<Sha256Digest> for Digest {
fn from(value: Sha256Digest) -> Self {
Self {
algorithm: DigestAlgorithm::Sha256,
digest: value.digest,
value: format!("sha256:{}", value.digest()).into(),
split: 6,
}
}
}
Expand All @@ -252,7 +265,9 @@ impl FromStr for Sha256Digest {
let v = format!("{alg}:{digest}");
let d = Digest::from_str(&v)?;
match d.algorithm {
DigestAlgorithm::Sha256 => Ok(Self { digest: d.digest }),
DigestAlgorithm::Sha256 => Ok(Self {
digest: d.digest().into(),
}),
o => Err(crate::OciSpecError::Other(format!(
"Expected algorithm sha256 but found {o}",
))),
Expand Down Expand Up @@ -334,6 +349,8 @@ mod tests {
let d = Digest::from_str(VALID_DIGEST_SHA384).unwrap();
assert_eq!(d.algorithm(), &DigestAlgorithm::Sha384);
assert_eq!(d.digest(), expected_value);
// Verify we can cheaply coerce to a string
assert_eq!(d.as_ref(), VALID_DIGEST_SHA384);
let base_digest = Digest::from(d.clone());
assert_eq!(base_digest.digest(), expected_value);
}
Expand Down

0 comments on commit 8aa75aa

Please sign in to comment.