nix_compat/nar/listing/
mod.rs1use std::{
12 collections::BTreeMap,
13 path::{Component, Path},
14};
15
16use serde::{Deserialize, Serialize, ser::SerializeMap};
17
18#[cfg(test)]
19mod test;
20
21#[derive(Debug, thiserror::Error)]
22pub enum ListingError {
23 #[error("unsupported path component")]
30 UnsupportedPathComponent,
31 #[error("invalid encoding for entry component")]
32 InvalidEncoding,
33}
34
35#[derive(Debug, Deserialize)]
36#[serde(tag = "type", rename_all = "lowercase")]
37pub enum ListingEntry {
38 Regular {
39 size: u64,
40 #[serde(default)]
41 executable: bool,
42 #[serde(rename = "narOffset")]
43 nar_offset: u64,
44 },
45 Directory {
46 entries: BTreeMap<String, ListingEntry>,
50 },
51 Symlink {
52 target: String,
53 },
54}
55
56impl Serialize for ListingEntry {
58 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
59 where
60 S: serde::Serializer,
61 {
62 match self {
63 ListingEntry::Regular {
64 size,
65 executable,
66 nar_offset,
67 } => {
68 let mut map = serializer.serialize_map(Some(if *executable { 4 } else { 3 }))?;
69 if *executable {
70 map.serialize_entry("executable", &true)?;
71 }
72 map.serialize_entry("narOffset", nar_offset)?;
73 map.serialize_entry("size", size)?;
74 map.serialize_entry("type", "regular")?;
75 map.end()
76 }
77 ListingEntry::Directory { entries } => {
78 let mut map = serializer.serialize_map(Some(2))?;
79 map.serialize_entry("entries", entries)?;
80 map.serialize_entry("type", "directory")?;
81 map.end()
82 }
83 ListingEntry::Symlink { target } => {
84 let mut map = serializer.serialize_map(Some(2))?;
85 map.serialize_entry("target", target)?;
86 map.serialize_entry("type", "symlink")?;
87 map.end()
88 }
89 }
90 }
91}
92
93impl ListingEntry {
94 pub fn locate<P: AsRef<Path>>(&self, path: P) -> Result<Option<&ListingEntry>, ListingError> {
100 let mut cur = self;
104 for component in path.as_ref().components() {
105 match component {
106 Component::CurDir => continue,
107 Component::RootDir | Component::Prefix(_) | Component::ParentDir => {
108 return Err(ListingError::UnsupportedPathComponent);
109 }
110 Component::Normal(file_or_dir_name) => {
111 if let Self::Directory { entries } = cur {
112 let entry_name = file_or_dir_name
116 .to_str()
117 .ok_or(ListingError::InvalidEncoding)?;
118
119 if let Some(new_entry) = entries.get(entry_name) {
120 cur = new_entry;
121 } else {
122 return Ok(None);
123 }
124 } else {
125 return Ok(None);
126 }
127 }
128 }
129 }
130
131 Ok(Some(cur))
133 }
134}
135
136#[derive(Debug)]
137pub struct ListingVersion<const V: u8>;
138
139#[derive(Debug, thiserror::Error)]
140#[error("Invalid version: {0}")]
141struct ListingVersionError(u8);
142
143impl<'de, const V: u8> Deserialize<'de> for ListingVersion<V> {
144 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
145 where
146 D: serde::Deserializer<'de>,
147 {
148 let value = u8::deserialize(deserializer)?;
149 if value == V {
150 Ok(ListingVersion::<V>)
151 } else {
152 Err(serde::de::Error::custom(ListingVersionError(value)))
153 }
154 }
155}
156
157impl<const V: u8> Serialize for ListingVersion<V> {
158 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
159 where
160 S: serde::Serializer,
161 {
162 V.serialize(serializer)
163 }
164}
165
166#[derive(Debug, Deserialize, Serialize)]
167#[serde(untagged)]
168#[non_exhaustive]
169pub enum Listing {
170 V1 {
171 root: ListingEntry,
172 version: ListingVersion<1>,
173 },
174}