1use crate::error::TypeError;
2use crate::fields::{
3 decode_bytes_value, decode_field_header, decode_varint_value, encode_bytes_field,
4 encode_nested_field, encode_varint_field, skip_field,
5};
6
7#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct FileTreeBlock {
29 pub root_path: String,
30 pub entries: Vec<FileEntry>,
31}
32
33#[derive(Clone, Debug, PartialEq, Eq)]
51pub struct FileEntry {
52 pub name: String,
53 pub kind: FileEntryKind,
54 pub size: u64,
55 pub children: Vec<FileEntry>,
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
60pub enum FileEntryKind {
61 File = 0,
62 Directory = 1,
63}
64
65impl FileEntry {
66 fn encode(&self) -> Vec<u8> {
68 let mut buf = Vec::new();
69 encode_bytes_field(&mut buf, 1, self.name.as_bytes());
70 encode_varint_field(&mut buf, 2, self.kind as u64);
71 encode_varint_field(&mut buf, 3, self.size);
72 for child in &self.children {
73 encode_nested_field(&mut buf, 4, &child.encode());
74 }
75 buf
76 }
77
78 fn decode(mut buf: &[u8]) -> Result<Self, TypeError> {
80 let mut name: Option<String> = None;
81 let mut kind: Option<FileEntryKind> = None;
82 let mut size: u64 = 0;
83 let mut children = Vec::new();
84
85 while !buf.is_empty() {
86 let (header, n) = decode_field_header(buf)?;
87 buf = &buf[n..];
88
89 match header.field_id {
90 1 => {
91 let (data, n) = decode_bytes_value(buf)?;
92 buf = &buf[n..];
93 name = Some(String::from_utf8_lossy(data).into_owned());
94 }
95 2 => {
96 let (v, n) = decode_varint_value(buf)?;
97 buf = &buf[n..];
98 kind = Some(match v {
99 0 => FileEntryKind::File,
100 1 => FileEntryKind::Directory,
101 other => {
102 return Err(TypeError::InvalidEnumValue {
103 enum_name: "FileEntryKind",
104 value: other as u8,
105 });
106 }
107 });
108 }
109 3 => {
110 let (v, n) = decode_varint_value(buf)?;
111 buf = &buf[n..];
112 size = v;
113 }
114 4 => {
115 let (data, n) = decode_bytes_value(buf)?;
116 buf = &buf[n..];
117 children.push(FileEntry::decode(data)?);
118 }
119 _ => {
120 let n = skip_field(buf, header.wire_type)?;
121 buf = &buf[n..];
122 }
123 }
124 }
125
126 Ok(Self {
127 name: name.ok_or(TypeError::MissingRequiredField { field: "name" })?,
128 kind: kind.ok_or(TypeError::MissingRequiredField { field: "kind" })?,
129 size,
130 children,
131 })
132 }
133}
134
135impl FileTreeBlock {
136 pub fn encode_body(&self) -> Vec<u8> {
138 let mut buf = Vec::new();
139 encode_bytes_field(&mut buf, 1, self.root_path.as_bytes());
140 for entry in &self.entries {
141 encode_nested_field(&mut buf, 2, &entry.encode());
142 }
143 buf
144 }
145
146 pub fn decode_body(mut buf: &[u8]) -> Result<Self, TypeError> {
148 let mut root_path: Option<String> = None;
149 let mut entries = Vec::new();
150
151 while !buf.is_empty() {
152 let (header, n) = decode_field_header(buf)?;
153 buf = &buf[n..];
154
155 match header.field_id {
156 1 => {
157 let (data, n) = decode_bytes_value(buf)?;
158 buf = &buf[n..];
159 root_path = Some(String::from_utf8_lossy(data).into_owned());
160 }
161 2 => {
162 let (data, n) = decode_bytes_value(buf)?;
163 buf = &buf[n..];
164 entries.push(FileEntry::decode(data)?);
165 }
166 _ => {
167 let n = skip_field(buf, header.wire_type)?;
168 buf = &buf[n..];
169 }
170 }
171 }
172
173 Ok(Self {
174 root_path: root_path.ok_or(TypeError::MissingRequiredField { field: "root_path" })?,
175 entries,
176 })
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn roundtrip_flat_tree() {
186 let block = FileTreeBlock {
187 root_path: "/project".to_string(),
188 entries: vec![
189 FileEntry {
190 name: "Cargo.toml".to_string(),
191 kind: FileEntryKind::File,
192 size: 256,
193 children: vec![],
194 },
195 FileEntry {
196 name: "README.md".to_string(),
197 kind: FileEntryKind::File,
198 size: 1024,
199 children: vec![],
200 },
201 ],
202 };
203 let body = block.encode_body();
204 let decoded = FileTreeBlock::decode_body(&body).unwrap();
205 assert_eq!(decoded, block);
206 }
207
208 #[test]
209 fn roundtrip_nested_directories() {
210 let block = FileTreeBlock {
211 root_path: "/app".to_string(),
212 entries: vec![FileEntry {
213 name: "src".to_string(),
214 kind: FileEntryKind::Directory,
215 size: 0,
216 children: vec![
217 FileEntry {
218 name: "main.rs".to_string(),
219 kind: FileEntryKind::File,
220 size: 512,
221 children: vec![],
222 },
223 FileEntry {
224 name: "lib".to_string(),
225 kind: FileEntryKind::Directory,
226 size: 0,
227 children: vec![FileEntry {
228 name: "utils.rs".to_string(),
229 kind: FileEntryKind::File,
230 size: 128,
231 children: vec![],
232 }],
233 },
234 ],
235 }],
236 };
237 let body = block.encode_body();
238 let decoded = FileTreeBlock::decode_body(&body).unwrap();
239 assert_eq!(decoded, block);
240 }
241
242 #[test]
243 fn empty_tree() {
244 let block = FileTreeBlock {
245 root_path: "/empty".to_string(),
246 entries: vec![],
247 };
248 let body = block.encode_body();
249 let decoded = FileTreeBlock::decode_body(&body).unwrap();
250 assert_eq!(decoded, block);
251 }
252}