Initial commit
Thanks @riley for the help with code review. Ily ♥️♥️♥️♥️♥️♥️♥️
This commit is contained in:
commit
5145168d05
12 changed files with 495 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
/Cargo.lock
|
8
Cargo.toml
Normal file
8
Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "narinfo"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "A parser for the narinfo file format"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
derive_builder = { version = "0.11.2", default-features = false }
|
3
nix-cache-info.sample
Normal file
3
nix-cache-info.sample
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
StoreDir: /nix/store
|
||||||
|
WantMassQuery: 1
|
||||||
|
Priority: 40
|
2
rustfmt.toml
Normal file
2
rustfmt.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
hard_tabs = true
|
||||||
|
normalize_comments = true
|
10
sample.narinfo
Normal file
10
sample.narinfo
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
StorePath: /nix/store/zzxrhj9056vjlanfjkinvhd7458yc2z8-liblouis-3.22.0
|
||||||
|
URL: nar/0ccqg4il1m9qqh8b6x0x8nn7pjcphr82h2qdfc5gqq8dy7h2kp9x.nar.xz
|
||||||
|
Compression: xz
|
||||||
|
FileHash: sha256:0ccqg4il1m9qqh8b6x0x8nn7pjcphr82h2qdfc5gqq8dy7h2kp9x
|
||||||
|
FileSize: 1914556
|
||||||
|
NarHash: sha256:0c8ld5yxcr6a6j63mvrqbqiy08q6f85wd74817ai7pvd5nkidcqw
|
||||||
|
NarSize: 11374872
|
||||||
|
References: mhhlymrg2m70r8h94cwhv2d7a0c8l7g6-glibc-2.34-210 ppn8983d9b5r6k7mnhkbg6rqw7vgl1ij-libyaml-0.2.5 qm2lv1gpbyn0rsfai40cbvj3h4gz69yc-bash-5.1-p16 sn0w3f12547crckss4ybmnxmi29gpgq7-perl-5.34.1 zzxrhj9056vjlanfjkinvhd7458yc2z8-liblouis-3.22.0
|
||||||
|
Deriver: dlxmsgfc0am35fh0kiy88zqr91x2dn5j-liblouis-3.22.0.drv
|
||||||
|
Sig: cache.nixos.org-1:BJ5QGcOta2s76XC6sep9DbAv0x3TILh3hHSKyR+9rFWYuBDTWdHs1KHeUEpw2espE/zPPBp2yURO6/J4Dhf9DQ==
|
26
src/error.rs
Normal file
26
src/error.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// The error type returned by all the parsing functions in this crate.
|
||||||
|
pub enum ParsingError<'a> {
|
||||||
|
InvalidIntValue { line: &'a str },
|
||||||
|
UnknownKey { key: &'a str },
|
||||||
|
InvalidLine { line: &'a str },
|
||||||
|
InvalidSignature(&'a str),
|
||||||
|
MissingField(&'static str),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<derive_builder::UninitializedFieldError> for ParsingError<'static> {
|
||||||
|
fn from(e: derive_builder::UninitializedFieldError) -> Self {
|
||||||
|
Self::MissingField(e.field_name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ParsingError<'a> {
|
||||||
|
pub(crate) fn try_parse_int<'b>(value: &'b str, line: &'a str) -> ParsingResult<'a, usize> {
|
||||||
|
value
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ParsingError::InvalidIntValue { line })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The result type returned by all the parsing functions in this crate.
|
||||||
|
pub type ParsingResult<'a, T> = core::result::Result<T, ParsingError<'a>>;
|
15
src/lib.rs
Normal file
15
src/lib.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#![no_std]
|
||||||
|
#![deny(clippy::all)]
|
||||||
|
//! Parse the nix substituter(cache) related files.
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
|
||||||
|
mod narinfo;
|
||||||
|
mod nix_cache_info;
|
||||||
|
mod sig;
|
||||||
|
|
||||||
|
pub use crate::narinfo::NarInfo;
|
||||||
|
pub use sig::Sig;
|
||||||
|
pub use nix_cache_info::NixCacheInfo;
|
130
src/narinfo/from_str.rs
Normal file
130
src/narinfo/from_str.rs
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
use super::{NarInfo, NarInfoBuilder};
|
||||||
|
use crate::{
|
||||||
|
error::{ParsingError, ParsingResult},
|
||||||
|
sig::Sig,
|
||||||
|
};
|
||||||
|
use alloc::{borrow::Cow, vec::Vec};
|
||||||
|
|
||||||
|
impl<'a> NarInfo<'a> {
|
||||||
|
/// Parses the contents of a narinfo file from str.
|
||||||
|
/// This function is also used to implement the [TryFrom] trait for &'a str.
|
||||||
|
///
|
||||||
|
/// For duplicate keys the last value is used(same behaviour as libstore).
|
||||||
|
/// Unknown keys return an error(unlike libstore where they're simply ignored).
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn http_get_str(_url: &str) -> &'static str { include_str!("../../sample.narinfo") }
|
||||||
|
/// use narinfo::NarInfo;
|
||||||
|
/// let data: &str =
|
||||||
|
/// http_get_str("https://cache.nixos.org/zn2h0kln2b02x4x6jqxymd4sg9cwvdsx.narinfo");
|
||||||
|
///
|
||||||
|
/// let parsed = NarInfo::parse(data).unwrap();
|
||||||
|
/// assert_eq!(parsed.store_path, "/nix/store/zzxrhj9056vjlanfjkinvhd7458yc2z8-liblouis-3.22.0");
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
pub fn parse(value: &'a str) -> ParsingResult<Self> {
|
||||||
|
let mut builder = NarInfoBuilder::default();
|
||||||
|
for line in value.lines() {
|
||||||
|
let (key, value) = line
|
||||||
|
.split_once(':')
|
||||||
|
.map(|(k, v)| (k, v.trim()))
|
||||||
|
.ok_or(ParsingError::InvalidLine { line })?;
|
||||||
|
|
||||||
|
match key {
|
||||||
|
"StorePath" => builder.store_path(Cow::from(value)),
|
||||||
|
"URL" => builder.url(value),
|
||||||
|
"Compression" => builder.compression(Some(Cow::from(value))),
|
||||||
|
"FileHash" => builder.file_hash(Some(value)),
|
||||||
|
"NarHash" => builder.nar_hash(Cow::from(value)),
|
||||||
|
"NarSize" => {
|
||||||
|
let size: usize = ParsingError::try_parse_int(value, line)?;
|
||||||
|
builder.nar_size(size)
|
||||||
|
}
|
||||||
|
"FileSize" => {
|
||||||
|
let size: usize = ParsingError::try_parse_int(value, line)?;
|
||||||
|
builder.file_size(Some(size))
|
||||||
|
}
|
||||||
|
"Deriver" => builder.deriver(Some(Cow::from(value))),
|
||||||
|
"System" => builder.system(Some(Cow::from(value))),
|
||||||
|
"References" => builder.references(value.split(' ').map(Cow::from).collect()),
|
||||||
|
// TODO: replace with try_collect once that gets stabilized
|
||||||
|
"Sig" => builder.sigs(value.split(' ').map(Sig::try_from).try_fold(
|
||||||
|
Vec::new(),
|
||||||
|
|mut a, c| {
|
||||||
|
c.map(|c| {
|
||||||
|
a.push(c);
|
||||||
|
a
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)?),
|
||||||
|
_ => return Err(ParsingError::UnknownKey { key }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a str> for NarInfo<'a> {
|
||||||
|
type Error = crate::error::ParsingError<'a>;
|
||||||
|
|
||||||
|
fn try_from(value: &'a str) -> ParsingResult<Self> {
|
||||||
|
NarInfo::parse(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use alloc::borrow::Cow;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_sample_narinfo() {
|
||||||
|
let sample = include_str!("../../sample.narinfo");
|
||||||
|
let info = NarInfo::try_from(sample).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
info.store_path,
|
||||||
|
"/nix/store/zzxrhj9056vjlanfjkinvhd7458yc2z8-liblouis-3.22.0"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
info.url,
|
||||||
|
"nar/0ccqg4il1m9qqh8b6x0x8nn7pjcphr82h2qdfc5gqq8dy7h2kp9x.nar.xz"
|
||||||
|
);
|
||||||
|
assert_eq!(info.compression, Some(Cow::Borrowed("xz")));
|
||||||
|
assert_eq!(
|
||||||
|
info.file_hash,
|
||||||
|
Some("sha256:0ccqg4il1m9qqh8b6x0x8nn7pjcphr82h2qdfc5gqq8dy7h2kp9x")
|
||||||
|
);
|
||||||
|
assert_eq!(info.file_size, Some(1914556));
|
||||||
|
assert_eq!(
|
||||||
|
info.nar_hash,
|
||||||
|
"sha256:0c8ld5yxcr6a6j63mvrqbqiy08q6f85wd74817ai7pvd5nkidcqw"
|
||||||
|
);
|
||||||
|
assert_eq!(info.nar_size, 11374872);
|
||||||
|
|
||||||
|
let expected_refs = [
|
||||||
|
"mhhlymrg2m70r8h94cwhv2d7a0c8l7g6-glibc-2.34-210",
|
||||||
|
"ppn8983d9b5r6k7mnhkbg6rqw7vgl1ij-libyaml-0.2.5",
|
||||||
|
"qm2lv1gpbyn0rsfai40cbvj3h4gz69yc-bash-5.1-p16",
|
||||||
|
"sn0w3f12547crckss4ybmnxmi29gpgq7-perl-5.34.1",
|
||||||
|
"zzxrhj9056vjlanfjkinvhd7458yc2z8-liblouis-3.22.0",
|
||||||
|
];
|
||||||
|
for (a, b) in info.references.iter().zip(expected_refs) {
|
||||||
|
assert_eq!(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
info.deriver,
|
||||||
|
Some(Cow::Borrowed(
|
||||||
|
"dlxmsgfc0am35fh0kiy88zqr91x2dn5j-liblouis-3.22.0.drv"
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
let sig = &info.sigs[0];
|
||||||
|
assert_eq!(sig.key_name, "cache.nixos.org-1");
|
||||||
|
assert_eq!(sig.sig, "BJ5QGcOta2s76XC6sep9DbAv0x3TILh3hHSKyR+9rFWYuBDTWdHs1KHeUEpw2espE/zPPBp2yURO6/J4Dhf9DQ==");
|
||||||
|
|
||||||
|
assert!(info.sigs.len() == 1);
|
||||||
|
}
|
||||||
|
}
|
67
src/narinfo/mod.rs
Normal file
67
src/narinfo/mod.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
mod from_str;
|
||||||
|
mod to_str;
|
||||||
|
|
||||||
|
use crate::sig::Sig;
|
||||||
|
use alloc::{borrow::Cow, vec::Vec};
|
||||||
|
pub use from_str::*;
|
||||||
|
pub use to_str::*;
|
||||||
|
|
||||||
|
use derive_builder::Builder;
|
||||||
|
|
||||||
|
type FileSize = usize;
|
||||||
|
|
||||||
|
/// Struct representing the narinfo file fetched from a nix substituter
|
||||||
|
/// Based on the [unofficial spec](https://fzakaria.github.io/nix-http-binary-cache-api-spec)
|
||||||
|
/// and the [libstore narinfo parsing code.](https://github.com/NixOS/nix/blob/6776e65fd960e25b55d11a03324f9007b6dc2a0b/src/libstore/nar-info.cc)
|
||||||
|
#[derive(Builder, Eq, PartialEq, Debug)]
|
||||||
|
#[builder(no_std)]
|
||||||
|
#[builder(build_fn(error = "crate::error::ParsingError<'static>"))]
|
||||||
|
pub struct NarInfo<'a> {
|
||||||
|
/// The full store path, including the name part (e.g., glibc-2.7). It must match the requested store path.
|
||||||
|
pub store_path: Cow<'a, str>,
|
||||||
|
|
||||||
|
/// The URL of the NAR, relative to the binary cache URL.
|
||||||
|
pub url: &'a str,
|
||||||
|
|
||||||
|
/// The compression method; Usuall xz or bzip2.
|
||||||
|
#[builder(default)]
|
||||||
|
pub compression: Option<Cow<'a, str>>,
|
||||||
|
|
||||||
|
/// The cryptographic hash of the NAR (decompressed) in base 32.
|
||||||
|
pub nar_hash: Cow<'a, str>,
|
||||||
|
/// The size of the decompressed NAR file.
|
||||||
|
pub nar_size: FileSize,
|
||||||
|
|
||||||
|
/// The cryptographic hash of the file to download in base32.
|
||||||
|
#[builder(default)]
|
||||||
|
pub file_hash: Option<&'a str>,
|
||||||
|
/// The size of the downloaded(compressed) NAR file.
|
||||||
|
#[builder(default)]
|
||||||
|
pub file_size: Option<FileSize>,
|
||||||
|
|
||||||
|
/// The deriver of the store path, without the Nix store prefix. This field is optional.
|
||||||
|
#[builder(default)]
|
||||||
|
pub deriver: Option<Cow<'a, str>>,
|
||||||
|
|
||||||
|
/// The Nix platform type of this binary(eg. x86_64-linux).
|
||||||
|
#[builder(default)]
|
||||||
|
pub system: Option<Cow<'a, str>>,
|
||||||
|
|
||||||
|
/// Store paths for direct runtime dependencies.
|
||||||
|
#[builder(default)]
|
||||||
|
pub references: Vec<Cow<'a, str>>,
|
||||||
|
|
||||||
|
/// A collection of the signatures signing this package.
|
||||||
|
#[builder(default)]
|
||||||
|
pub sigs: Vec<Sig<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> NarInfo<'a> {
|
||||||
|
/// Get the builder for this struct.
|
||||||
|
/// The builder is generated by [derive_builder](https://lib.rs/crates/derive_builder)
|
||||||
|
// The builder is is used internally by the parsing code so we might as well expose it.
|
||||||
|
// If we ever decide to not use derive_builder we can simply hide it behind an optional feature flag
|
||||||
|
pub fn builder() -> NarInfoBuilder<'a> {
|
||||||
|
NarInfoBuilder::default()
|
||||||
|
}
|
||||||
|
}
|
59
src/narinfo/to_str.rs
Normal file
59
src/narinfo/to_str.rs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
use super::NarInfo;
|
||||||
|
use core::fmt::{self, Write};
|
||||||
|
|
||||||
|
impl<'a> NarInfo<'a> {
|
||||||
|
/// Serializes the narinfo struct into the text format
|
||||||
|
/// ```
|
||||||
|
/// use narinfo::NarInfo;
|
||||||
|
///
|
||||||
|
/// let info = NarInfo::parse(include_str!("../../sample.narinfo")).unwrap();
|
||||||
|
/// let mut serialized = String::new();
|
||||||
|
/// info.serialize_into(&mut serialized).unwrap();
|
||||||
|
/// ```
|
||||||
|
pub fn serialize_into<T: Write>(&self, w: &mut T) -> fmt::Result {
|
||||||
|
write!(w, "StorePath: {}", self.store_path)?;
|
||||||
|
write!(w, "\nURL: {}", self.url)?;
|
||||||
|
write!(w, "\nNarHash: {}", self.nar_hash)?;
|
||||||
|
write!(w, "\nNarSize: {}", self.nar_size)?;
|
||||||
|
write!(w, "\nReferences: {}", self.references.join(" "))?;
|
||||||
|
|
||||||
|
write!(w, "\nSig: ")?;
|
||||||
|
for sig in self.sigs.iter() {
|
||||||
|
sig.serialize_into(w)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(compression) = &self.compression {
|
||||||
|
write!(w, "\nCompression: {}", compression)?;
|
||||||
|
};
|
||||||
|
if let Some(file_hash) = self.file_hash {
|
||||||
|
write!(w, "\nFileHash: {}", file_hash)?;
|
||||||
|
};
|
||||||
|
if let Some(file_size) = self.file_size {
|
||||||
|
write!(w, "\nFileSize: {}", file_size)?;
|
||||||
|
};
|
||||||
|
if let Some(deriver) = &self.deriver {
|
||||||
|
write!(w, "\nDeriver: {}", deriver)?;
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use alloc::string::String;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_deserialize_eq() {
|
||||||
|
let sample = include_str!("../../sample.narinfo");
|
||||||
|
|
||||||
|
let info = NarInfo::parse(sample).unwrap();
|
||||||
|
let mut serialized = String::new();
|
||||||
|
info.serialize_into(&mut serialized).unwrap();
|
||||||
|
let info2 = NarInfo::parse(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(info, info2);
|
||||||
|
}
|
||||||
|
}
|
118
src/nix_cache_info.rs
Normal file
118
src/nix_cache_info.rs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
use crate::error::{ParsingError, ParsingResult};
|
||||||
|
use alloc::borrow::Cow;
|
||||||
|
use core::fmt;
|
||||||
|
use core::fmt::Write;
|
||||||
|
use derive_builder::Builder;
|
||||||
|
|
||||||
|
/// Struct representing the nix-cache-info file fetched from a nix substituter, describing the
|
||||||
|
/// substituter
|
||||||
|
/// Based on the [unofficial spec](https://fzakaria.github.io/nix-http-binary-cache-api-spec)
|
||||||
|
/// and the [libstore narinfo parsing code.](https://github.com/NixOS/nix/blob/af4e8b00fb986acf32d7e4cd4fff7218b38958df/src/libstore/binary-cache-store.cc#L37)
|
||||||
|
#[derive(Builder, Eq, PartialEq, Debug)]
|
||||||
|
#[builder(no_std)]
|
||||||
|
#[builder(build_fn(error = "crate::error::ParsingError<'static>"))]
|
||||||
|
#[builder(setter(into))]
|
||||||
|
pub struct NixCacheInfo<'a> {
|
||||||
|
/// The path of the Nix store to which this binary cache applies. Binaries are not relocatable — a binary built for `/nix/store` won’t generally work in `/home/alice/store` — so to prevent binaries from being used in a wrong store, a binary cache is only used if its StoreDir matches the local Nix configuration. The default path on nixos is `/nix/store`.
|
||||||
|
pub store_dir: Cow<'a, str>,
|
||||||
|
/// Query operations such as `nix-env -qas` can cause thousands of cache queries, and thus thousands of HTTP requests, to determine which packages are available in binary form. While these requests are small, not every server may appreciate a potential onslaught of queries. If WantMassQuery is set to 0 (default), “mass queries” such as `nix-env -qas` will skip this cache. Thus a package may appear not to have a binary substitute. However, the binary will still be used when you actually install the package. If WantMassQuery is set to 1, mass queries will use this cache.
|
||||||
|
#[builder(default)]
|
||||||
|
pub wants_mass_query: bool,
|
||||||
|
/// Each binary cache has a priority (defaulting to 50). Binary caches are checked for binaries in order of ascending priority; thus a higher number denotes a lower priority. The binary cache cache.nixos.org has priority 40.
|
||||||
|
#[builder(default = "50")]
|
||||||
|
pub priority: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> NixCacheInfo<'a> {
|
||||||
|
/// Parses the contents of a narinfo file from str.
|
||||||
|
/// This function is also used to implement the [TryFrom] trait for &'a str.
|
||||||
|
///
|
||||||
|
/// For duplicate keys the last value is used(same behaviour as libstore).
|
||||||
|
/// Unknown keys return an error(unlike libstore where they're simply ignored).
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn http_get_str(_url: &str) -> &'static str { include_str!("../nix-cache-info.sample") }
|
||||||
|
/// use narinfo::NixCacheInfo;
|
||||||
|
/// let data: &str =
|
||||||
|
/// http_get_str("https://cache.nixos.org/nix-cache-info");
|
||||||
|
///
|
||||||
|
/// let parsed = NixCacheInfo::parse(data).unwrap();
|
||||||
|
/// assert_eq!(parsed.store_dir, "/nix/store");
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
pub fn parse(value: &'a str) -> ParsingResult<Self> {
|
||||||
|
let mut builder = NixCacheInfoBuilder::default();
|
||||||
|
for line in value.lines() {
|
||||||
|
let (key, value) = line
|
||||||
|
.split_once(':')
|
||||||
|
.map(|(k, v)| (k, v.trim()))
|
||||||
|
.ok_or(ParsingError::InvalidLine { line })?;
|
||||||
|
match key {
|
||||||
|
"StoreDir" => builder.store_dir(value),
|
||||||
|
"WantMassQuery" => {
|
||||||
|
let wants_mass_query: usize = ParsingError::try_parse_int(value, line)?;
|
||||||
|
builder.wants_mass_query(wants_mass_query >= 1)
|
||||||
|
}
|
||||||
|
"Priority" => builder.priority(ParsingError::try_parse_int(value, line)?),
|
||||||
|
_ => return Err(ParsingError::UnknownKey { key }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serializes the narinfo struct into the text format
|
||||||
|
/// ```
|
||||||
|
/// use narinfo::NixCacheInfo;
|
||||||
|
///
|
||||||
|
/// let info = NixCacheInfo::parse(include_str!("../nix-cache-info.sample")).unwrap();
|
||||||
|
/// let mut serialized = String::new();
|
||||||
|
/// info.serialize_into(&mut serialized).unwrap();
|
||||||
|
/// ```
|
||||||
|
pub fn serialize_into<T: Write>(&self, w: &mut T) -> fmt::Result {
|
||||||
|
write!(w, "StoreDir: {}", self.store_dir)?;
|
||||||
|
write!(w, "\nPriority: {}", self.priority)?;
|
||||||
|
let mass_query_int = if self.wants_mass_query { 1 } else { 0 };
|
||||||
|
write!(w, "\nWantMassQuery: {}", mass_query_int)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the builder for this struct.
|
||||||
|
/// The builder is generated by [derive_builder](https://lib.rs/crates/derive_builder)
|
||||||
|
// The builder is is used internally by the parsing code so we might as well expose it.
|
||||||
|
// If we ever decide to not use derive_builder we can simply hide it behind an optional feature flag
|
||||||
|
pub fn builder() -> NixCacheInfoBuilder<'a> {
|
||||||
|
NixCacheInfoBuilder::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use alloc::string::String;
|
||||||
|
|
||||||
|
static SAMPLE_NIX_CACHE_INFO: &str = include_str!("../nix-cache-info.sample");
|
||||||
|
#[test]
|
||||||
|
fn parses_sample_nix_cache_info() {
|
||||||
|
let info = NixCacheInfo::parse(SAMPLE_NIX_CACHE_INFO).unwrap();
|
||||||
|
assert_eq!(info.store_dir, "/nix/store");
|
||||||
|
assert_eq!(info.priority, 40);
|
||||||
|
assert!(info.wants_mass_query);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serializes_then_deserializes_into_same_result() {
|
||||||
|
let info = NixCacheInfoBuilder::default()
|
||||||
|
.store_dir("/home/alice/nix")
|
||||||
|
.wants_mass_query(true)
|
||||||
|
.priority(69_usize)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let mut serialized = String::new();
|
||||||
|
info.serialize_into(&mut serialized).unwrap();
|
||||||
|
|
||||||
|
let deserialized = NixCacheInfo::parse(&serialized).unwrap();
|
||||||
|
assert_eq!(deserialized.store_dir, "/home/alice/nix");
|
||||||
|
assert_eq!(deserialized.priority, 69);
|
||||||
|
assert!(deserialized.wants_mass_query);
|
||||||
|
}
|
||||||
|
}
|
55
src/sig.rs
Normal file
55
src/sig.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use alloc::borrow::Cow;
|
||||||
|
|
||||||
|
use crate::error::{ParsingError, ParsingResult};
|
||||||
|
use core::fmt::{self, Write};
|
||||||
|
|
||||||
|
/// A signature of a nix narinfo file. Computed over the StorePath, NarHash, NarSize and References fields using the Ed25519 public-key signature system.
|
||||||
|
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||||
|
pub struct Sig<'a> {
|
||||||
|
/// The name of the key, eg `cache.example.org-1` for cache.nixos.org
|
||||||
|
pub key_name: Cow<'a, str>,
|
||||||
|
/// The actual signature
|
||||||
|
pub sig: Cow<'a, str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a str> for Sig<'a> {
|
||||||
|
type Error = ParsingError<'a>;
|
||||||
|
|
||||||
|
fn try_from(value: &'a str) -> ParsingResult<Self> {
|
||||||
|
Sig::parse(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Neither the parse nor the serializa method is public since
|
||||||
|
// it doesn't really make sense to de/serialize the
|
||||||
|
// sig into the narinfo format outside of de/serializing a whole narinfo
|
||||||
|
impl<'a> Sig<'a> {
|
||||||
|
pub(crate) fn parse(value: &'a str) -> ParsingResult<Self> {
|
||||||
|
match value.split_once(':') {
|
||||||
|
Some((key, sig)) => Ok(Sig {
|
||||||
|
key_name: key.into(),
|
||||||
|
sig: sig.into(),
|
||||||
|
}),
|
||||||
|
None => Err(ParsingError::InvalidSignature(value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn serialize_into<T: Write>(&self, w: &mut T) -> fmt::Result {
|
||||||
|
write!(w, "{}:{}", self.key_name, self.sig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use alloc::string::String;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const SAMPLE_SIG: &str = "cache.nixos.org-1:BJ5QGcOta2s76XC6sep9DbAv0x3TILh3hHSKyR+9rFWYuBDTWdHs1KHeUEpw2espE/zPPBp2yURO6/J4Dhf9DQ==";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_deserialize_eq() {
|
||||||
|
let sig = Sig::parse(SAMPLE_SIG).unwrap();
|
||||||
|
let mut serialized = String::new();
|
||||||
|
sig.serialize_into(&mut serialized).unwrap();
|
||||||
|
assert_eq!(serialized, SAMPLE_SIG);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue