diff --git a/src/action/base/fetch_and_unpack_nix_substituter.rs b/src/action/base/fetch_and_unpack_nix_substituter.rs index 4b4c134..5b58df2 100644 --- a/src/action/base/fetch_and_unpack_nix_substituter.rs +++ b/src/action/base/fetch_and_unpack_nix_substituter.rs @@ -9,6 +9,7 @@ use base64::Engine; use bytes::Buf; use ed25519_dalek::VerifyingKey; use reqwest::Url; +use serde::{Deserialize, Serialize}; use sha2::Digest; use tokio::io::AsyncWriteExt; use tracing::{span, Span}; @@ -104,8 +105,7 @@ impl FetchAndUnpackNixSubstituter { ) -> Result { for substituter in &self.substituters { let narinfo_url = substituter - .join(&format!("{}.narinfo", &output.digest)) - // TODO: Give more useful error message here + .join(&format!("{}.narinfo", &output.digest())) .map_err(|err| UrlOrPathError::Url("".to_string(), err)) .map_err(ActionErrorKind::UrlOrPathError) .map_err(Self::error)?; @@ -137,7 +137,7 @@ impl FetchAndUnpackNixSubstituter { } Err(Self::error(SubstitutionError::NonexistantNarInfo( - output.full_path.clone(), + output.full_path().clone(), ))) } } @@ -153,7 +153,7 @@ impl Action for FetchAndUnpackNixSubstituter { "Fetch {} from substituters to `{}`", self.targets .iter() - .map(|t| format!("`{}`", t.full_path)) + .map(|t| format!("`{}`", t.full_path())) .collect::>() .join(", "), self.dest.display() @@ -281,9 +281,7 @@ impl Action for FetchAndUnpackNixSubstituter { return Err(Self::error(SubstitutionError::BadNar(narinfo.url.clone()))); } - // TODO: Figure out a better way to make relative - // Maybe simplify StorePath? - let out_dir = self.dest.join("nix-/store").join(output.full_name); + let out_dir = self.dest.join("nix-/store").join(output.full_name()); let decoder = nix_nar::Decoder::new(decompressed_nar.reader()) .map_err(|e| SubstitutionError::Unpack(narinfo.url.clone(), e)) @@ -297,14 +295,14 @@ impl Action for FetchAndUnpackNixSubstituter { // File format isn't documented anywhere but implementation is simple: // https://git.lix.systems/lix-project/lix/src/commit/d461cc1d7b2f489c3886f147166ba5b5e0e37541/src/libstore/store-api.cc#L846 // Unwrapping because string can't fail methods in std::fmt::Write - write!(reginfo, "{}\n", output.full_path).unwrap(); + write!(reginfo, "{}\n", output.full_path()).unwrap(); write!(reginfo, "sha256:{}\n", narinfo.nar_hash).unwrap(); write!(reginfo, "{}\n", narinfo.nar_size).unwrap(); // Leave deriver empty, same as lix binary tarballs reginfo.push('\n'); write!(reginfo, "{}\n", narinfo.references.len()).unwrap(); for reference in &narinfo.references { - write!(reginfo, "{}\n", reference.full_path).unwrap(); + write!(reginfo, "{}\n", reference.full_path()).unwrap(); } } @@ -355,21 +353,30 @@ fn parse_key(key: &str) -> Result<(String, VerifyingKey), SubstitutionError> { } /// Utility struct representing a store path -#[derive(Clone, Eq, Debug)] -struct StorePath { +#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +#[serde(transparent)] +struct StorePath(String); + +impl StorePath { + /// The full name of a path, not including STORE_DIR, + /// e.g. `n50jk09x9hshwx1lh6k3qaiygc7yxbv9-lix-2.90.0-rc1` + pub fn full_name(&self) -> &str { + &self.0 + } + + /// The base32 hash part of a store path, + /// e.g. `n50jk09x9hshwx1lh6k3qaiygc7yxbv9` + pub fn digest(&self) -> &str { + &self.0.split_once('-').unwrap().0 + } + /// Full path of the output including STORE_DIR, /// as seen in StorePath in narinfo /// e.g. `/nix/store/n50jk09x9hshwx1lh6k3qaiygc7yxbv9-lix-2.90.0-rc1` - pub full_path: String, - /// The base32 hash part of a store path, - /// e.g. `n50jk09x9hshwx1lh6k3qaiygc7yxbv9` - pub digest: String, - /// The full name of a path, not including STORE_DIR, - /// e.g. `n50jk09x9hshwx1lh6k3qaiygc7yxbv9-lix-2.90.0-rc1` - pub full_name: String, -} + pub fn full_path(&self) -> String { + format!("{}{}", STORE_DIR, &self.0) + } -impl StorePath { pub fn from_full_path(full_path: &str) -> Option { if !full_path.starts_with(STORE_DIR) { return None; @@ -377,6 +384,18 @@ impl StorePath { let (_, full_name) = full_path.split_at(STORE_DIR.len()); + Self::from_full_name(full_name) + } + + pub fn from_path(path: &Path) -> Result { + let path_str = path + .to_str() + .ok_or_else(|| SubstitutionError::InvalidStorePath(path.to_owned()))?; + Self::from_full_path(path_str) + .ok_or_else(|| SubstitutionError::InvalidStorePath(path.to_owned())) + } + + pub fn from_full_name(full_name: &str) -> Option { let (digest, name) = full_name.split_once('-')?; if digest.len() != 32 @@ -397,75 +416,7 @@ impl StorePath { return None; } - Some(Self { - full_path: full_path.to_string(), - digest: digest.to_string(), - full_name: full_name.to_string(), - }) - } - - pub fn from_path(path: &Path) -> Result { - let path_str = path - .to_str() - .ok_or_else(|| SubstitutionError::InvalidStorePath(path.to_owned()))?; - Self::from_full_path(path_str) - .ok_or_else(|| SubstitutionError::InvalidStorePath(path.to_owned())) - } - - pub fn from_full_name(full_name: &str) -> Option { - Self::from_full_path( - &Path::new(STORE_DIR) - .join(&full_name) - .into_os_string() - .into_string() - .ok()?, - ) - } -} - -impl std::hash::Hash for StorePath { - fn hash(&self, state: &mut H) { - self.full_path.hash(state); - } -} - -impl PartialEq for StorePath { - fn eq(&self, other: &Self) -> bool { - self.full_path == other.full_path - } -} - -impl serde::Serialize for StorePath { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.full_path.serialize(serializer) - } -} - -impl<'de> serde::Deserialize<'de> for StorePath { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_string(StorePathVisitor) - } -} - -struct StorePathVisitor; -impl serde::de::Visitor<'_> for StorePathVisitor { - type Value = StorePath; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a valid nix store path starting with /nix/store") - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - StorePath::from_full_path(value).ok_or_else(|| E::custom("invalid store path")) + Some(Self(full_name.to_string())) } } @@ -592,13 +543,13 @@ impl NarInfo { // https://git.lix.systems/lix-project/lix/src/commit/d461cc1d7b2f489c3886f147166ba5b5e0e37541/src/libstore/path-info.cc#L25 let fingerprint = format!( "1;{};sha256:{};{};{}", - self.store_path.full_path, + self.store_path.full_path(), self.nar_hash, self.nar_size, self.references .iter() - .map(|reference| reference.full_path.as_ref()) - .collect::>() + .map(|reference| reference.full_path()) + .collect::>() .join(",") );