1use crate::enums::Lang;
2use crate::error::TypeError;
3use crate::fields::{
4 decode_bytes_value, decode_field_header, decode_varint_value, encode_bytes_field,
5 encode_varint_field, skip_field,
6};
7
8#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct CodeBlock {
32 pub lang: Lang,
33 pub path: String,
34 pub content: Vec<u8>,
35 pub line_range: Option<(u32, u32)>,
38}
39
40impl CodeBlock {
41 pub fn encode_body(&self) -> Vec<u8> {
43 let mut buf = Vec::new();
44 encode_varint_field(&mut buf, 1, u64::from(self.lang.to_wire_byte()));
45 encode_bytes_field(&mut buf, 2, self.path.as_bytes());
46 encode_bytes_field(&mut buf, 3, &self.content);
47 if let Some((start, end)) = self.line_range {
48 encode_varint_field(&mut buf, 4, u64::from(start));
49 encode_varint_field(&mut buf, 5, u64::from(end));
50 }
51 buf
52 }
53
54 pub fn decode_body(mut buf: &[u8]) -> Result<Self, TypeError> {
58 let mut lang: Option<Lang> = None;
59 let mut path: Option<String> = None;
60 let mut content: Option<Vec<u8>> = None;
61 let mut line_start: Option<u32> = None;
62 let mut line_end: Option<u32> = None;
63
64 while !buf.is_empty() {
65 let (header, n) = decode_field_header(buf)?;
66 buf = &buf[n..];
67
68 match header.field_id {
69 1 => {
70 let (v, n) = decode_varint_value(buf)?;
71 buf = &buf[n..];
72 lang = Some(Lang::from_wire_byte(v as u8));
73 }
74 2 => {
75 let (data, n) = decode_bytes_value(buf)?;
76 buf = &buf[n..];
77 path = Some(String::from_utf8_lossy(data).into_owned());
78 }
79 3 => {
80 let (data, n) = decode_bytes_value(buf)?;
81 buf = &buf[n..];
82 content = Some(data.to_vec());
83 }
84 4 => {
85 let (v, n) = decode_varint_value(buf)?;
86 buf = &buf[n..];
87 line_start = Some(v as u32);
88 }
89 5 => {
90 let (v, n) = decode_varint_value(buf)?;
91 buf = &buf[n..];
92 line_end = Some(v as u32);
93 }
94 _ => {
95 let n = skip_field(buf, header.wire_type)?;
96 buf = &buf[n..];
97 }
98 }
99 }
100
101 Ok(Self {
102 lang: lang.ok_or(TypeError::MissingRequiredField { field: "lang" })?,
103 path: path.ok_or(TypeError::MissingRequiredField { field: "path" })?,
104 content: content.ok_or(TypeError::MissingRequiredField { field: "content" })?,
105 line_range: match (line_start, line_end) {
106 (Some(s), Some(e)) => Some((s, e)),
107 _ => None,
108 },
109 })
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn roundtrip_full_file() {
119 let block = CodeBlock {
120 lang: Lang::Rust,
121 path: "src/main.rs".to_string(),
122 content: b"fn main() {}".to_vec(),
123 line_range: None,
124 };
125 let body = block.encode_body();
126 let decoded = CodeBlock::decode_body(&body).unwrap();
127 assert_eq!(decoded, block);
128 }
129
130 #[test]
131 fn roundtrip_with_line_range() {
132 let block = CodeBlock {
133 lang: Lang::TypeScript,
134 path: "src/index.ts".to_string(),
135 content: b"console.log('hello');".to_vec(),
136 line_range: Some((10, 25)),
137 };
138 let body = block.encode_body();
139 let decoded = CodeBlock::decode_body(&body).unwrap();
140 assert_eq!(decoded, block);
141 }
142
143 #[test]
144 fn roundtrip_unknown_language() {
145 let block = CodeBlock {
146 lang: Lang::Other(0x42),
147 path: "script.xyz".to_string(),
148 content: b"custom code".to_vec(),
149 line_range: None,
150 };
151 let body = block.encode_body();
152 let decoded = CodeBlock::decode_body(&body).unwrap();
153 assert_eq!(decoded.lang, Lang::Other(0x42));
154 }
155
156 #[test]
157 fn missing_content_field() {
158 let mut buf = Vec::new();
160 encode_varint_field(&mut buf, 1, 0x01);
161 encode_bytes_field(&mut buf, 2, b"test.rs");
162
163 let result = CodeBlock::decode_body(&buf);
164 assert!(matches!(
165 result,
166 Err(TypeError::MissingRequiredField { field: "content" })
167 ));
168 }
169}