1use bytes::Bytes;
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
8pub struct Patch {
9 pub unit: String,
11 pub range: String,
13 pub content: Bytes,
15 pub content_length: Option<usize>,
17}
18
19#[cfg(feature = "fuzzing")]
20impl<'a> arbitrary::Arbitrary<'a> for Patch {
21 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
22 Ok(Patch {
23 unit: u.arbitrary()?,
24 range: u.arbitrary()?,
25 content: bytes::Bytes::from(u.arbitrary::<Vec<u8>>()?),
26 content_length: u.arbitrary()?,
27 })
28 }
29}
30
31impl Patch {
32 #[must_use]
34 pub fn new(
35 unit: impl Into<String>,
36 range: impl Into<String>,
37 content: impl Into<Bytes>,
38 ) -> Self {
39 let content_bytes = content.into();
40 let content_length = content_bytes.len();
41 Patch {
42 unit: unit.into(),
43 range: range.into(),
44 content: content_bytes,
45 content_length: Some(content_length),
46 }
47 }
48
49 #[inline]
50 #[must_use]
51 pub fn json(range: impl Into<String>, content: impl Into<Bytes>) -> Self {
52 Self::new("json", range, content)
53 }
54
55 #[inline]
56 #[must_use]
57 pub fn bytes(range: impl Into<String>, content: impl Into<Bytes>) -> Self {
58 Self::new("bytes", range, content)
59 }
60
61 #[inline]
62 #[must_use]
63 pub fn text(range: impl Into<String>, content: impl Into<String>) -> Self {
64 let content_str = content.into();
65 Self::new("text", range, Bytes::from(content_str))
66 }
67
68 #[inline]
69 #[must_use]
70 pub fn lines(range: impl Into<String>, content: impl Into<String>) -> Self {
71 let content_str = content.into();
72 Self::new("lines", range, Bytes::from(content_str))
73 }
74
75 #[must_use]
76 pub fn with_length(
77 unit: impl Into<String>,
78 range: impl Into<String>,
79 content: impl Into<Bytes>,
80 length: usize,
81 ) -> Self {
82 Patch {
83 unit: unit.into(),
84 range: range.into(),
85 content: content.into(),
86 content_length: Some(length),
87 }
88 }
89
90 #[inline]
91 #[must_use]
92 pub fn is_json(&self) -> bool {
93 self.unit == "json"
94 }
95
96 #[inline]
97 #[must_use]
98 pub fn is_bytes(&self) -> bool {
99 self.unit == "bytes"
100 }
101
102 #[inline]
103 #[must_use]
104 pub fn is_text(&self) -> bool {
105 self.unit == "text"
106 }
107
108 #[inline]
109 #[must_use]
110 pub fn is_lines(&self) -> bool {
111 self.unit == "lines"
112 }
113
114 #[inline]
115 #[must_use]
116 pub fn content_str(&self) -> Option<&str> {
117 std::str::from_utf8(&self.content).ok()
118 }
119
120 #[inline]
121 #[must_use]
122 pub fn content_text(&self) -> Option<&str> {
123 self.content_str()
124 }
125
126 #[inline]
127 #[must_use]
128 pub fn len(&self) -> usize {
129 self.content_length.unwrap_or_else(|| self.content.len())
130 }
131
132 #[inline]
133 #[must_use]
134 pub fn is_empty(&self) -> bool {
135 self.len() == 0
136 }
137
138 #[must_use]
139 pub fn content_range_header(&self) -> String {
140 format!("{} {}", self.unit, self.range)
141 }
142
143 pub fn validate(&self) -> crate::error::Result<()> {
144 if self.unit.is_empty() {
145 return Err(crate::error::BraidError::Protocol(
146 "Patch unit cannot be empty".into(),
147 ));
148 }
149 if self.range.is_empty() {
150 return Err(crate::error::BraidError::Protocol(
151 "Patch range cannot be empty".into(),
152 ));
153 }
154 Ok(())
155 }
156}
157
158impl Default for Patch {
159 fn default() -> Self {
160 Patch {
161 unit: "bytes".to_string(),
162 range: String::new(),
163 content: Bytes::new(),
164 content_length: Some(0),
165 }
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn test_patch_new() {
175 let patch = Patch::new("custom", "range", "content");
176 assert_eq!(patch.unit, "custom");
177 assert_eq!(patch.range, "range");
178 assert_eq!(patch.content_length, Some(7));
179 }
180
181 #[test]
182 fn test_patch_json() {
183 let patch = Patch::json(".field", "value");
184 assert_eq!(patch.unit, "json");
185 assert_eq!(patch.range, ".field");
186 assert!(patch.is_json());
187 }
188
189 #[test]
190 fn test_patch_bytes() {
191 let patch = Patch::bytes("0:100", &b"content"[..]);
192 assert_eq!(patch.unit, "bytes");
193 assert_eq!(patch.range, "0:100");
194 assert!(patch.is_bytes());
195 }
196
197 #[test]
198 fn test_patch_text() {
199 let patch = Patch::text(".title", "New Title");
200 assert_eq!(patch.unit, "text");
201 assert_eq!(patch.range, ".title");
202 assert!(patch.is_text());
203 }
204
205 #[test]
206 fn test_patch_lines() {
207 let patch = Patch::lines("10:20", "new lines\n");
208 assert_eq!(patch.unit, "lines");
209 assert_eq!(patch.range, "10:20");
210 assert!(patch.is_lines());
211 }
212
213 #[test]
214 fn test_content_str() {
215 let patch = Patch::json(".field", "value");
216 assert_eq!(patch.content_str(), Some("value"));
217 }
218
219 #[test]
220 fn test_len_and_is_empty() {
221 let patch = Patch::json(".field", "value");
222 assert_eq!(patch.len(), 5);
223 assert!(!patch.is_empty());
224
225 let empty = Patch::json(".field", "");
226 assert_eq!(empty.len(), 0);
227 assert!(empty.is_empty());
228 }
229
230 #[test]
231 fn test_content_range_header() {
232 let patch = Patch::json(".users[0]", "{}");
233 assert_eq!(patch.content_range_header(), "json .users[0]");
234 }
235
236 #[test]
237 fn test_default() {
238 let patch = Patch::default();
239 assert_eq!(patch.unit, "bytes");
240 assert!(patch.range.is_empty());
241 assert!(patch.is_empty());
242 }
243
244 #[test]
245 fn test_validate_valid_patch() {
246 let patch = Patch::json(".field", "value");
247 assert!(patch.validate().is_ok());
248 }
249
250 #[test]
251 fn test_validate_empty_unit() {
252 let patch = Patch {
253 unit: String::new(),
254 range: ".field".to_string(),
255 content: Bytes::from("value"),
256 content_length: Some(5),
257 };
258 assert!(patch.validate().is_err());
259 }
260
261 #[test]
262 fn test_validate_empty_range() {
263 let patch = Patch {
264 unit: "json".to_string(),
265 range: String::new(),
266 content: Bytes::from("value"),
267 content_length: Some(5),
268 };
269 assert!(patch.validate().is_err());
270 }
271
272 #[test]
273 fn test_validate_all_types() {
274 assert!(Patch::json(".f", "v").validate().is_ok());
275 assert!(Patch::bytes("0:10", &b"data"[..]).validate().is_ok());
276 assert!(Patch::text(".t", "text").validate().is_ok());
277 assert!(Patch::lines("1:5", "lines").validate().is_ok());
278 }
279}