platform_value/replace.rs
1use crate::btreemap_extensions::btreemap_field_replacement::IntegerReplacementType;
2use crate::inner_value_at_path::is_array_path;
3use crate::{Error, ReplacementType, Value, ValueMapHelper};
4use std::collections::HashSet;
5
6impl Value {
7 /// If the `Value` is a `Map`, replaces the value at the path inside the map.
8 /// This is used to set inner values as Identifiers or BinaryData, or from Identifiers or
9 /// BinaryData to base58 or base64 strings.
10 /// Either returns `Err(Error::Structure("reason"))` or `Err(Error::ByteLengthNot32BytesError))`
11 /// if the replacement can not happen.
12 ///
13 /// ```
14 /// # use platform_value::{Error, Identifier, ReplacementType, Value};
15 /// #
16 /// let mut inner_value = Value::Map(
17 /// vec![
18 /// (Value::Text(String::from("food_id")), Value::Text("6oFRdsUNiAtXscRn52atKYCiF8RBnH9vbUzhtzY3d83e".to_string())),
19 /// ]
20 /// );
21 /// let mut value = Value::Map(
22 /// vec![
23 /// (Value::Text(String::from("foods")), inner_value),
24 /// ]
25 /// );
26 ///
27 /// value.replace_at_path("foods.food_id", ReplacementType::Identifier).expect("expected to replace at path with identifier");
28 ///
29 /// assert_eq!(value.get_value_at_path("foods.food_id"), Ok(&Value::Identifier([86, 35, 118, 67, 167, 43, 101, 109, 72, 97, 35, 99, 0, 254, 108, 154, 254, 154, 190, 40, 237, 25, 58, 246, 111, 19, 44, 215, 141, 140, 156, 117])));
30 ///
31 /// let mut tangerine_value = Value::Map(
32 /// vec![
33 /// (Value::Text(String::from("food_id")), Value::Text("6oFRdsUNiAtXscRn52atKYCiF8RBnH9vbUzhtzY3d83e".to_string())),
34 /// ]
35 /// );
36 /// let mut mandarin_value = Value::Map(
37 /// vec![
38 /// (Value::Text(String::from("food_id")), Value::Text("6oFRdsUNiAtXscRn52atKYCiF8RBnH9vbUzhtzY3d83e".to_string())),
39 /// ]
40 /// );
41 /// let mut oranges_value = Value::Array(
42 /// vec![
43 /// tangerine_value,
44 /// mandarin_value
45 /// ]
46 /// );
47 /// let mut value = Value::Map(
48 /// vec![
49 /// (Value::Text(String::from("foods")), oranges_value),
50 /// ]
51 /// );
52 ///
53 /// value.replace_at_path("foods[].food_id", ReplacementType::Identifier).expect("expected to replace at path with identifier");
54 ///
55 /// assert_eq!(value.get_value_at_path("foods[0].food_id"), Ok(&Value::Identifier([86, 35, 118, 67, 167, 43, 101, 109, 72, 97, 35, 99, 0, 254, 108, 154, 254, 154, 190, 40, 237, 25, 58, 246, 111, 19, 44, 215, 141, 140, 156, 117])));
56 ///
57 /// ```
58 pub fn replace_at_path(
59 &mut self,
60 path: &str,
61 replacement_type: ReplacementType,
62 ) -> Result<(), Error> {
63 let mut split = path.split('.').peekable();
64 let mut current_values = vec![self];
65 while let Some(path_component) = split.next() {
66 if let Some((string_part, number_part)) = is_array_path(path_component)? {
67 current_values = current_values
68 .into_iter()
69 .map(|current_value| {
70 let map = current_value.to_map_mut()?;
71 let array_value = map.get_key_mut(string_part)?;
72 let array = array_value.to_array_mut()?;
73 if let Some(number_part) = number_part {
74 if array.len() < number_part {
75 //this already exists
76 Ok(vec![array.get_mut(number_part).unwrap()])
77 } else {
78 Err(Error::StructureError(format!(
79 "element at position {number_part} in array does not exist"
80 )))
81 }
82 } else {
83 // we are replacing all members in array
84 Ok(array.iter_mut().collect())
85 }
86 })
87 .collect::<Result<Vec<Vec<&mut Value>>, Error>>()?
88 .into_iter()
89 .flatten()
90 .collect()
91 } else {
92 current_values = current_values
93 .into_iter()
94 .filter_map(|current_value| {
95 let map = match current_value.as_map_mut_ref() {
96 Ok(map) => map,
97 Err(err) => return Some(Err(err)),
98 };
99
100 let new_value = map.get_optional_key_mut(path_component)?;
101
102 if split.peek().is_none() {
103 let bytes_result = match replacement_type {
104 ReplacementType::Identifier | ReplacementType::TextBase58 => {
105 new_value.to_identifier_bytes()
106 }
107 ReplacementType::BinaryBytes | ReplacementType::TextBase64 => {
108 new_value.to_binary_bytes()
109 }
110 };
111 let bytes = match bytes_result {
112 Ok(bytes) => bytes,
113 Err(err) => return Some(Err(err)),
114 };
115 *new_value = match replacement_type.replace_for_bytes(bytes) {
116 Ok(value) => value,
117 Err(err) => return Some(Err(err)),
118 };
119 return None;
120 }
121 Some(Ok(new_value))
122 })
123 .collect::<Result<Vec<&mut Value>, Error>>()?;
124 }
125 }
126 Ok(())
127 }
128
129 /// Calls replace_at_path for every path in a given array.
130 /// Either returns `Err(Error::Structure("reason"))` or `Err(Error::ByteLengthNot32BytesError))`
131 /// if the replacement can not happen.
132 ///
133 /// ```
134 /// # use platform_value::{Error, Identifier, ReplacementType, Value};
135 /// #
136 /// let mut inner_value = Value::Map(
137 /// vec![
138 /// (Value::Text(String::from("grapes")), Value::Text("6oFRdsUNiAtXscRn52atKYCiF8RBnH9vbUzhtzY3d83e".to_string())),
139 /// (Value::Text(String::from("oranges")), Value::Array(vec![Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101)])),
140 /// ]
141 /// );
142 /// let mut value = Value::Map(
143 /// vec![
144 /// (Value::Text(String::from("foods")), inner_value),
145 /// ]
146 /// );
147 ///
148 /// let paths = vec!["foods.grapes", "foods.oranges"];
149 ///
150 /// value.replace_at_paths(paths, ReplacementType::Identifier).expect("expected to replace at paths with identifier");
151 ///
152 /// assert_eq!(value.get_value_at_path("foods.grapes"), Ok(&Value::Identifier([86, 35, 118, 67, 167, 43, 101, 109, 72, 97, 35, 99, 0, 254, 108, 154, 254, 154, 190, 40, 237, 25, 58, 246, 111, 19, 44, 215, 141, 140, 156, 117])));
153 /// assert_eq!(value.get_value_at_path("foods.oranges"), Ok(&Value::Identifier([104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101])));
154 ///
155 /// ```
156 pub fn replace_at_paths<'a, I: IntoIterator<Item = &'a str>>(
157 &mut self,
158 paths: I,
159 replacement_type: ReplacementType,
160 ) -> Result<(), Error> {
161 paths
162 .into_iter()
163 .try_for_each(|path| self.replace_at_path(path, replacement_type))
164 }
165
166 /// If the `Value` is a `Map`, replaces the value at the path inside the map.
167 /// This is used to set inner values as Identifiers or BinaryData, or from Identifiers or
168 /// BinaryData to base58 or base64 strings.
169 /// Either returns `Err(Error::Structure("reason"))` or `Err(Error::ByteLengthNot32BytesError))`
170 /// if the replacement can not happen.
171 ///
172 /// ```
173 /// # use platform_value::{Error, Identifier, IntegerReplacementType, Value};
174 /// #
175 /// let mut inner_value = Value::Map(
176 /// vec![
177 /// (Value::Text(String::from("food_id")), Value::U8(5)),
178 /// ]
179 /// );
180 /// let mut value = Value::Map(
181 /// vec![
182 /// (Value::Text(String::from("foods")), inner_value),
183 /// ]
184 /// );
185 ///
186 /// value.replace_integer_type_at_path("foods.food_id", IntegerReplacementType::U32).expect("expected to replace at path with identifier");
187 ///
188 /// assert_eq!(value.get_value_at_path("foods.food_id"), Ok(&Value::U32(5)));
189 ///
190 /// let mut tangerine_value = Value::Map(
191 /// vec![
192 /// (Value::Text(String::from("food_id")), Value::U128(8)),
193 /// ]
194 /// );
195 /// let mut mandarin_value = Value::Map(
196 /// vec![
197 /// (Value::Text(String::from("food_id")), Value::U32(2)),
198 /// ]
199 /// );
200 /// let mut oranges_value = Value::Array(
201 /// vec![
202 /// tangerine_value,
203 /// mandarin_value
204 /// ]
205 /// );
206 /// let mut value = Value::Map(
207 /// vec![
208 /// (Value::Text(String::from("foods")), oranges_value),
209 /// ]
210 /// );
211 ///
212 /// value.replace_integer_type_at_path("foods[].food_id", IntegerReplacementType::U16).expect("expected to replace at path with identifier");
213 ///
214 /// assert_eq!(value.get_value_at_path("foods[0].food_id"), Ok(&Value::U16(8)));
215 ///
216 /// ```
217 pub fn replace_integer_type_at_path(
218 &mut self,
219 path: &str,
220 replacement_type: IntegerReplacementType,
221 ) -> Result<(), Error> {
222 let mut split = path.split('.').peekable();
223 let mut current_values = vec![self];
224 while let Some(path_component) = split.next() {
225 if let Some((string_part, number_part)) = is_array_path(path_component)? {
226 current_values = current_values
227 .into_iter()
228 .map(|current_value| {
229 let map = current_value.to_map_mut()?;
230 let array_value = map.get_key_mut(string_part)?;
231 let array = array_value.to_array_mut()?;
232 if let Some(number_part) = number_part {
233 if array.len() < number_part {
234 //this already exists
235 Ok(vec![array.get_mut(number_part).unwrap()])
236 } else {
237 Err(Error::StructureError(format!(
238 "element at position {number_part} in array does not exist"
239 )))
240 }
241 } else {
242 // we are replacing all members in array
243 Ok(array.iter_mut().collect())
244 }
245 })
246 .collect::<Result<Vec<Vec<&mut Value>>, Error>>()?
247 .into_iter()
248 .flatten()
249 .collect()
250 } else {
251 current_values = current_values
252 .into_iter()
253 .filter_map(|current_value| {
254 let map = match current_value.as_map_mut_ref() {
255 Ok(map) => map,
256 Err(err) => return Some(Err(err)),
257 };
258
259 let new_value = map.get_optional_key_mut(path_component)?;
260
261 if split.peek().is_none() {
262 *new_value = match replacement_type.replace_for_value(new_value.clone())
263 {
264 Ok(value) => value,
265 Err(err) => return Some(Err(err)),
266 };
267 return None;
268 }
269 Some(Ok(new_value))
270 })
271 .collect::<Result<Vec<&mut Value>, Error>>()?;
272 }
273 }
274 Ok(())
275 }
276
277 /// Calls replace_at_path for every path in a given array.
278 /// Either returns `Err(Error::Structure("reason"))` or `Err(Error::ByteLengthNot32BytesError))`
279 /// if the replacement can not happen.
280 ///
281 /// ```
282 /// # use platform_value::{Error, Identifier, IntegerReplacementType, ReplacementType, Value};
283 /// #
284 /// let mut inner_value = Value::Map(
285 /// vec![
286 /// (Value::Text(String::from("grapes")), Value::U16(5)),
287 /// (Value::Text(String::from("oranges")), Value::I32(6)),
288 /// ]
289 /// );
290 /// let mut value = Value::Map(
291 /// vec![
292 /// (Value::Text(String::from("foods")), inner_value),
293 /// ]
294 /// );
295 ///
296 /// let paths = vec!["foods.grapes", "foods.oranges"];
297 ///
298 /// value.replace_integer_type_at_paths(paths, IntegerReplacementType::U32).expect("expected to replace at paths with identifier");
299 ///
300 /// assert_eq!(value.get_value_at_path("foods.grapes"), Ok(&Value::U32(5)));
301 /// assert_eq!(value.get_value_at_path("foods.oranges"), Ok(&Value::U32(6)));
302 ///
303 /// ```
304 pub fn replace_integer_type_at_paths<'a, I: IntoIterator<Item = &'a str>>(
305 &mut self,
306 paths: I,
307 replacement_type: IntegerReplacementType,
308 ) -> Result<(), Error> {
309 paths
310 .into_iter()
311 .try_for_each(|path| self.replace_integer_type_at_path(path, replacement_type))
312 }
313
314 /// `replace_to_binary_types_when_setting_with_path` will replace a value with a corresponding
315 /// binary type (Identifier or Binary Data) if that data is in one of the given paths.
316 /// Paths can either be terminal, or can represent an object or an array (with values) where
317 /// all subvalues must be set to the binary type.
318 /// Either returns `Err(Error::Structure("reason"))` or `Err(Error::ByteLengthNot32BytesError))`
319 /// if the replacement can not happen.
320 ///
321 /// ```
322 /// # use std::collections::HashSet;
323 /// use platform_value::{Error, Identifier, ReplacementType, Value};
324 /// #
325 /// let mut inner_inner_value = Value::Map(
326 /// vec![
327 /// (Value::Text(String::from("mandarins")), Value::Text("6oFRdsUNiAtXscRn52atKYCiF8RBnH9vbUzhtzY3d83e".to_string())),
328 /// (Value::Text(String::from("tangerines")), Value::Array(vec![Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101)])),
329 /// ]
330 /// );
331 /// let mut inner_value = Value::Map(
332 /// vec![
333 /// (Value::Text(String::from("grapes")), Value::Text("6oFRdsUNiAtXscRn52atKYCiF8RBnH9vbUzhtzY3d83e".to_string())),
334 /// (Value::Text(String::from("oranges")), inner_inner_value),
335 /// ]
336 /// );
337 /// let mut value = Value::Map(
338 /// vec![
339 /// (Value::Text(String::from("foods")), inner_value),
340 /// ]
341 /// );
342 ///
343 ///
344 /// let identifier_paths = HashSet::from(["foods.oranges.tangerines"]);
345 ///
346 /// value.replace_to_binary_types_of_root_value_when_setting_at_path("foods.oranges", identifier_paths, HashSet::new()).expect("expected to replace at paths with identifier");
347 ///
348 /// assert_eq!(value.get_value_at_path("foods.oranges.tangerines"), Ok(&Value::Identifier([104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101])));
349 ///
350 /// ```
351 pub fn replace_to_binary_types_of_root_value_when_setting_at_path(
352 &mut self,
353 path: &str,
354 identifier_paths: HashSet<&str>,
355 binary_paths: HashSet<&str>,
356 ) -> Result<(), Error> {
357 if identifier_paths.contains(path) {
358 ReplacementType::Identifier.replace_value_in_place(self)?;
359 } else if binary_paths.contains(path) {
360 ReplacementType::BinaryBytes.replace_value_in_place(self)?;
361 } else {
362 identifier_paths
363 .into_iter()
364 .try_for_each(|identifier_path| {
365 if identifier_path.starts_with(path) {
366 self.replace_at_path(identifier_path, ReplacementType::Identifier)
367 .map(|_| ())
368 } else {
369 Ok(())
370 }
371 })?;
372
373 binary_paths.into_iter().try_for_each(|binary_path| {
374 if binary_path.starts_with(path) {
375 self.replace_at_path(binary_path, ReplacementType::BinaryBytes)
376 .map(|_| ())
377 } else {
378 Ok(())
379 }
380 })?;
381 }
382 Ok(())
383 }
384
385 /// `replace_to_binary_types_when_setting_with_path` will replace a value with a corresponding
386 /// binary type (Identifier or Binary Data) if that data is in one of the given paths.
387 /// Paths can either be terminal, or can represent an object or an array (with values) where
388 /// all subvalues must be set to the binary type.
389 /// Either returns `Err(Error::Structure("reason"))` or `Err(Error::ByteLengthNot32BytesError))`
390 /// if the replacement can not happen.
391 ///
392 /// ```
393 /// # use std::collections::HashSet;
394 /// use platform_value::{Error, Identifier, ReplacementType, Value};
395 /// #
396 /// let mut inner_inner_value = Value::Map(
397 /// vec![
398 /// (Value::Text(String::from("mandarins")), Value::Text("6oFRdsUNiAtXscRn52atKYCiF8RBnH9vbUzhtzY3d83e".to_string())),
399 /// (Value::Text(String::from("tangerines")), Value::Array(vec![Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101)])),
400 /// ]
401 /// );
402 /// let mut inner_value = Value::Map(
403 /// vec![
404 /// (Value::Text(String::from("grapes")), Value::Text("6oFRdsUNiAtXscRn52atKYCiF8RBnH9vbUzhtzY3d83e".to_string())),
405 /// (Value::Text(String::from("oranges")), inner_inner_value),
406 /// ]
407 /// );
408 /// let mut value = Value::Map(
409 /// vec![
410 /// (Value::Text(String::from("foods")), inner_value),
411 /// ]
412 /// );
413 ///
414 ///
415 /// let identifier_paths = HashSet::from(["foods.oranges.tangerines"]);
416 ///
417 /// let oranges = value.get_mut_value_at_path("foods.oranges").unwrap();
418 /// oranges.replace_to_binary_types_when_setting_with_path("foods.oranges", identifier_paths, HashSet::new()).expect("expected to replace at paths with identifier");
419 ///
420 /// assert_eq!(value.get_value_at_path("foods.oranges.tangerines"), Ok(&Value::Identifier([104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101])));
421 ///
422 /// ```
423 pub fn replace_to_binary_types_when_setting_with_path(
424 &mut self,
425 path: &str,
426 identifier_paths: HashSet<&str>,
427 binary_paths: HashSet<&str>,
428 ) -> Result<(), Error> {
429 if identifier_paths.contains(path) {
430 ReplacementType::Identifier.replace_value_in_place(self)?;
431 } else if binary_paths.contains(path) {
432 ReplacementType::BinaryBytes.replace_value_in_place(self)?;
433 } else {
434 let mut path = path.to_string();
435 path.push('.');
436 identifier_paths
437 .into_iter()
438 .try_for_each(|identifier_path| {
439 if let Some(suffix) = identifier_path.strip_prefix(path.as_str()) {
440 self.replace_at_path(suffix, ReplacementType::Identifier)
441 .map(|_| ())
442 } else {
443 Ok(())
444 }
445 })?;
446 binary_paths.into_iter().try_for_each(|binary_path| {
447 if let Some(suffix) = binary_path.strip_prefix(path.as_str()) {
448 self.replace_at_path(suffix, ReplacementType::BinaryBytes)
449 .map(|_| ())
450 } else {
451 Ok(())
452 }
453 })?;
454 }
455 Ok(())
456 }
457
458 /// Cleans all values and removes null inner values at any depth.
459 /// if the replacement can not happen.
460 ///
461 /// ```
462 /// # use platform_value::{Error, Identifier, IntegerReplacementType, ReplacementType, Value};
463 /// #
464 /// let mut inner_value = Value::Map(
465 /// vec![
466 /// (Value::Text(String::from("grapes")), Value::Null),
467 /// (Value::Text(String::from("oranges")), Value::I32(6)),
468 /// ]
469 /// );
470 /// let mut value = Value::Map(
471 /// vec![
472 /// (Value::Text(String::from("foods")), inner_value),
473 /// ]
474 /// );
475 ///
476 /// value = value.clean_recursive().unwrap();
477 ///
478 /// assert_eq!(value.get_optional_value_at_path("foods.grapes"), Ok(None));
479 ///
480 pub fn clean_recursive(self) -> Result<Value, Error> {
481 Ok(Value::Map(
482 self.into_map()?
483 .into_iter()
484 .filter_map(|(key, value)| {
485 if value.is_null() {
486 None
487 } else if value.is_map() {
488 match value.clean_recursive() {
489 Ok(value) => Some(Ok((key, value))),
490 Err(e) => Some(Err(e)),
491 }
492 } else {
493 Some(Ok((key, value)))
494 }
495 })
496 .collect::<Result<Vec<_>, Error>>()?,
497 ))
498 }
499}
500
501#[cfg(test)]
502mod tests {
503 use super::*;
504 use crate::{IntegerReplacementType, ReplacementType, Value};
505
506 // ---------------------------------------------------------------
507 // Helper: builds a 32-byte base58-encoded string from a seed byte
508 // ---------------------------------------------------------------
509 fn base58_of_32_bytes(seed: u8) -> String {
510 bs58::encode([seed; 32]).into_string()
511 }
512
513 fn make_32_u8_array(seed: u8) -> Vec<Value> {
514 vec![Value::U8(seed); 32]
515 }
516
517 // ===============================================================
518 // replace_at_path — single segment, ReplacementType::Identifier
519 // ===============================================================
520
521 #[test]
522 fn replace_at_path_single_segment_identifier_from_text() {
523 let b58 = base58_of_32_bytes(1);
524 let mut value = Value::Map(vec![(Value::Text("id".into()), Value::Text(b58))]);
525 value
526 .replace_at_path("id", ReplacementType::Identifier)
527 .unwrap();
528 assert_eq!(
529 value.get_value_at_path("id").unwrap(),
530 &Value::Identifier([1u8; 32])
531 );
532 }
533
534 #[test]
535 fn replace_at_path_single_segment_identifier_from_u8_array() {
536 let mut value = Value::Map(vec![(
537 Value::Text("id".into()),
538 Value::Array(make_32_u8_array(7)),
539 )]);
540 value
541 .replace_at_path("id", ReplacementType::Identifier)
542 .unwrap();
543 assert_eq!(
544 value.get_value_at_path("id").unwrap(),
545 &Value::Identifier([7u8; 32])
546 );
547 }
548
549 // ===============================================================
550 // replace_at_path — single segment, ReplacementType::BinaryBytes
551 // ===============================================================
552
553 #[test]
554 fn replace_at_path_single_segment_binary_bytes_from_base64() {
555 use base64::prelude::*;
556 let raw = vec![10u8, 20, 30];
557 let b64 = BASE64_STANDARD.encode(&raw);
558 let mut value = Value::Map(vec![(Value::Text("data".into()), Value::Text(b64))]);
559 value
560 .replace_at_path("data", ReplacementType::BinaryBytes)
561 .unwrap();
562 assert_eq!(value.get_value_at_path("data").unwrap(), &Value::Bytes(raw));
563 }
564
565 // ===============================================================
566 // replace_at_path — single segment, ReplacementType::TextBase58
567 // ===============================================================
568
569 #[test]
570 fn replace_at_path_single_segment_text_base58() {
571 let mut value = Value::Map(vec![(
572 Value::Text("id".into()),
573 Value::Bytes(vec![1, 2, 3]),
574 )]);
575 value
576 .replace_at_path("id", ReplacementType::TextBase58)
577 .unwrap();
578 let expected_b58 = bs58::encode(vec![1u8, 2, 3]).into_string();
579 assert_eq!(
580 value.get_value_at_path("id").unwrap(),
581 &Value::Text(expected_b58)
582 );
583 }
584
585 // ===============================================================
586 // replace_at_path — single segment, ReplacementType::TextBase64
587 // ===============================================================
588
589 #[test]
590 fn replace_at_path_single_segment_text_base64() {
591 use base64::prelude::*;
592 let raw = vec![1u8, 2, 3];
593 let mut value = Value::Map(vec![(Value::Text("bin".into()), Value::Bytes(raw.clone()))]);
594 value
595 .replace_at_path("bin", ReplacementType::TextBase64)
596 .unwrap();
597 let expected_b64 = BASE64_STANDARD.encode(&raw);
598 assert_eq!(
599 value.get_value_at_path("bin").unwrap(),
600 &Value::Text(expected_b64)
601 );
602 }
603
604 // ===============================================================
605 // replace_at_path — multi-segment nested path
606 // ===============================================================
607
608 #[test]
609 fn replace_at_path_multi_segment_nested() {
610 let b58 = base58_of_32_bytes(5);
611 let inner = Value::Map(vec![(Value::Text("owner_id".into()), Value::Text(b58))]);
612 let mut value = Value::Map(vec![(Value::Text("doc".into()), inner)]);
613
614 value
615 .replace_at_path("doc.owner_id", ReplacementType::Identifier)
616 .unwrap();
617 assert_eq!(
618 value.get_value_at_path("doc.owner_id").unwrap(),
619 &Value::Identifier([5u8; 32])
620 );
621 }
622
623 // ===============================================================
624 // replace_at_path — array path with [] (all members)
625 // ===============================================================
626
627 #[test]
628 fn replace_at_path_array_all_members() {
629 let b58 = base58_of_32_bytes(3);
630 let elem1 = Value::Map(vec![(Value::Text("id".into()), Value::Text(b58.clone()))]);
631 let elem2 = Value::Map(vec![(Value::Text("id".into()), Value::Text(b58))]);
632 let arr = Value::Array(vec![elem1, elem2]);
633 let mut value = Value::Map(vec![(Value::Text("items".into()), arr)]);
634
635 value
636 .replace_at_path("items[].id", ReplacementType::Identifier)
637 .unwrap();
638 assert_eq!(
639 value.get_value_at_path("items[0].id").unwrap(),
640 &Value::Identifier([3u8; 32])
641 );
642 assert_eq!(
643 value.get_value_at_path("items[1].id").unwrap(),
644 &Value::Identifier([3u8; 32])
645 );
646 }
647
648 // ===============================================================
649 // replace_at_path — optional key missing returns Ok
650 // ===============================================================
651
652 #[test]
653 fn replace_at_path_missing_key_returns_ok() {
654 let mut value = Value::Map(vec![(Value::Text("a".into()), Value::U32(42))]);
655 // "b" does not exist — filter_map filters it out
656 let result = value.replace_at_path("b", ReplacementType::Identifier);
657 assert!(result.is_ok());
658 }
659
660 // ===============================================================
661 // replace_at_path — non-map at root gives error
662 // ===============================================================
663
664 #[test]
665 fn replace_at_path_on_non_map_errors() {
666 let mut value = Value::U32(42);
667 let result = value.replace_at_path("key", ReplacementType::Identifier);
668 assert!(result.is_err());
669 }
670
671 // ===============================================================
672 // replace_at_path — non-32-byte data for Identifier errors
673 // ===============================================================
674
675 #[test]
676 fn replace_at_path_identifier_wrong_length_errors() {
677 // base58-encode only 10 bytes, not 32
678 let b58 = bs58::encode([1u8; 10]).into_string();
679 let mut value = Value::Map(vec![(Value::Text("id".into()), Value::Text(b58))]);
680 let result = value.replace_at_path("id", ReplacementType::Identifier);
681 assert!(result.is_err());
682 }
683
684 // ===============================================================
685 // replace_at_paths — multiple paths
686 // ===============================================================
687
688 #[test]
689 fn replace_at_paths_replaces_multiple() {
690 let b58 = base58_of_32_bytes(9);
691 let inner = Value::Map(vec![
692 (Value::Text("a".into()), Value::Text(b58.clone())),
693 (Value::Text("b".into()), Value::Text(b58)),
694 ]);
695 let mut value = Value::Map(vec![(Value::Text("root".into()), inner)]);
696 value
697 .replace_at_paths(vec!["root.a", "root.b"], ReplacementType::Identifier)
698 .unwrap();
699 assert_eq!(
700 value.get_value_at_path("root.a").unwrap(),
701 &Value::Identifier([9u8; 32])
702 );
703 assert_eq!(
704 value.get_value_at_path("root.b").unwrap(),
705 &Value::Identifier([9u8; 32])
706 );
707 }
708
709 // ===============================================================
710 // replace_integer_type_at_path — single segment, U32
711 // ===============================================================
712
713 #[test]
714 fn replace_integer_type_single_segment_u32() {
715 let mut value = Value::Map(vec![(Value::Text("count".into()), Value::U8(5))]);
716 value
717 .replace_integer_type_at_path("count", IntegerReplacementType::U32)
718 .unwrap();
719 assert_eq!(value.get_value_at_path("count").unwrap(), &Value::U32(5));
720 }
721
722 // ===============================================================
723 // replace_integer_type_at_path — single segment, various types
724 // ===============================================================
725
726 #[test]
727 fn replace_integer_type_single_segment_u16() {
728 let mut value = Value::Map(vec![(Value::Text("v".into()), Value::U128(100))]);
729 value
730 .replace_integer_type_at_path("v", IntegerReplacementType::U16)
731 .unwrap();
732 assert_eq!(value.get_value_at_path("v").unwrap(), &Value::U16(100));
733 }
734
735 #[test]
736 fn replace_integer_type_single_segment_u64() {
737 let mut value = Value::Map(vec![(Value::Text("v".into()), Value::U8(42))]);
738 value
739 .replace_integer_type_at_path("v", IntegerReplacementType::U64)
740 .unwrap();
741 assert_eq!(value.get_value_at_path("v").unwrap(), &Value::U64(42));
742 }
743
744 #[test]
745 fn replace_integer_type_single_segment_i32() {
746 let mut value = Value::Map(vec![(Value::Text("v".into()), Value::I8(-3))]);
747 value
748 .replace_integer_type_at_path("v", IntegerReplacementType::I32)
749 .unwrap();
750 assert_eq!(value.get_value_at_path("v").unwrap(), &Value::I32(-3));
751 }
752
753 #[test]
754 fn replace_integer_type_single_segment_u128() {
755 let mut value = Value::Map(vec![(Value::Text("v".into()), Value::U64(999))]);
756 value
757 .replace_integer_type_at_path("v", IntegerReplacementType::U128)
758 .unwrap();
759 assert_eq!(value.get_value_at_path("v").unwrap(), &Value::U128(999));
760 }
761
762 #[test]
763 fn replace_integer_type_single_segment_i128() {
764 let mut value = Value::Map(vec![(Value::Text("v".into()), Value::I64(-500))]);
765 value
766 .replace_integer_type_at_path("v", IntegerReplacementType::I128)
767 .unwrap();
768 assert_eq!(value.get_value_at_path("v").unwrap(), &Value::I128(-500));
769 }
770
771 #[test]
772 fn replace_integer_type_single_segment_u8() {
773 let mut value = Value::Map(vec![(Value::Text("v".into()), Value::U16(7))]);
774 value
775 .replace_integer_type_at_path("v", IntegerReplacementType::U8)
776 .unwrap();
777 assert_eq!(value.get_value_at_path("v").unwrap(), &Value::U8(7));
778 }
779
780 #[test]
781 fn replace_integer_type_single_segment_i8() {
782 let mut value = Value::Map(vec![(Value::Text("v".into()), Value::I16(-2))]);
783 value
784 .replace_integer_type_at_path("v", IntegerReplacementType::I8)
785 .unwrap();
786 assert_eq!(value.get_value_at_path("v").unwrap(), &Value::I8(-2));
787 }
788
789 #[test]
790 fn replace_integer_type_single_segment_i16() {
791 let mut value = Value::Map(vec![(Value::Text("v".into()), Value::I32(-100))]);
792 value
793 .replace_integer_type_at_path("v", IntegerReplacementType::I16)
794 .unwrap();
795 assert_eq!(value.get_value_at_path("v").unwrap(), &Value::I16(-100));
796 }
797
798 #[test]
799 fn replace_integer_type_single_segment_i64() {
800 let mut value = Value::Map(vec![(Value::Text("v".into()), Value::I128(-9999))]);
801 value
802 .replace_integer_type_at_path("v", IntegerReplacementType::I64)
803 .unwrap();
804 assert_eq!(value.get_value_at_path("v").unwrap(), &Value::I64(-9999));
805 }
806
807 // ===============================================================
808 // replace_integer_type_at_path — multi-segment
809 // ===============================================================
810
811 #[test]
812 fn replace_integer_type_multi_segment() {
813 let inner = Value::Map(vec![(Value::Text("level".into()), Value::U8(10))]);
814 let mut value = Value::Map(vec![(Value::Text("nested".into()), inner)]);
815 value
816 .replace_integer_type_at_path("nested.level", IntegerReplacementType::U32)
817 .unwrap();
818 assert_eq!(
819 value.get_value_at_path("nested.level").unwrap(),
820 &Value::U32(10)
821 );
822 }
823
824 // ===============================================================
825 // replace_integer_type_at_path — array path
826 // ===============================================================
827
828 #[test]
829 fn replace_integer_type_array_all_members() {
830 let elem1 = Value::Map(vec![(Value::Text("n".into()), Value::U128(8))]);
831 let elem2 = Value::Map(vec![(Value::Text("n".into()), Value::U32(2))]);
832 let arr = Value::Array(vec![elem1, elem2]);
833 let mut value = Value::Map(vec![(Value::Text("items".into()), arr)]);
834 value
835 .replace_integer_type_at_path("items[].n", IntegerReplacementType::U16)
836 .unwrap();
837 assert_eq!(
838 value.get_value_at_path("items[0].n").unwrap(),
839 &Value::U16(8)
840 );
841 assert_eq!(
842 value.get_value_at_path("items[1].n").unwrap(),
843 &Value::U16(2)
844 );
845 }
846
847 // ===============================================================
848 // replace_integer_type_at_path — missing key returns Ok
849 // ===============================================================
850
851 #[test]
852 fn replace_integer_type_missing_key_returns_ok() {
853 let mut value = Value::Map(vec![(Value::Text("a".into()), Value::U32(1))]);
854 let result = value.replace_integer_type_at_path("missing", IntegerReplacementType::U32);
855 assert!(result.is_ok());
856 }
857
858 // ===============================================================
859 // replace_integer_type_at_path — non-map errors
860 // ===============================================================
861
862 #[test]
863 fn replace_integer_type_on_non_map_errors() {
864 let mut value = Value::U32(42);
865 let result = value.replace_integer_type_at_path("key", IntegerReplacementType::U32);
866 assert!(result.is_err());
867 }
868
869 // ===============================================================
870 // replace_integer_type_at_paths — multiple paths
871 // ===============================================================
872
873 #[test]
874 fn replace_integer_type_at_paths_replaces_multiple() {
875 let inner = Value::Map(vec![
876 (Value::Text("x".into()), Value::U16(5)),
877 (Value::Text("y".into()), Value::I32(6)),
878 ]);
879 let mut value = Value::Map(vec![(Value::Text("root".into()), inner)]);
880 value
881 .replace_integer_type_at_paths(vec!["root.x", "root.y"], IntegerReplacementType::U32)
882 .unwrap();
883 assert_eq!(value.get_value_at_path("root.x").unwrap(), &Value::U32(5));
884 assert_eq!(value.get_value_at_path("root.y").unwrap(), &Value::U32(6));
885 }
886
887 // ===============================================================
888 // replace_to_binary_types_of_root_value_when_setting_at_path
889 // — identifier match (exact path in identifier_paths)
890 // ===============================================================
891
892 #[test]
893 fn replace_root_binary_types_identifier_exact_match() {
894 let b58 = base58_of_32_bytes(2);
895 let mut value = Value::Text(b58);
896 let identifier_paths = HashSet::from(["my_id"]);
897 value
898 .replace_to_binary_types_of_root_value_when_setting_at_path(
899 "my_id",
900 identifier_paths,
901 HashSet::new(),
902 )
903 .unwrap();
904 assert_eq!(value, Value::Identifier([2u8; 32]));
905 }
906
907 // ===============================================================
908 // replace_to_binary_types_of_root_value_when_setting_at_path
909 // — binary match (exact path in binary_paths)
910 // ===============================================================
911
912 #[test]
913 fn replace_root_binary_types_binary_exact_match() {
914 let b58 = base58_of_32_bytes(4);
915 let mut value = Value::Text(b58);
916 let binary_paths = HashSet::from(["my_data"]);
917 value
918 .replace_to_binary_types_of_root_value_when_setting_at_path(
919 "my_data",
920 HashSet::new(),
921 binary_paths,
922 )
923 .unwrap();
924 // BinaryBytes uses into_identifier_bytes (base58 decode) then replace_for_bytes -> Value::Bytes
925 assert_eq!(value, Value::Bytes([4u8; 32].to_vec()));
926 }
927
928 // ===============================================================
929 // replace_to_binary_types_of_root_value_when_setting_at_path
930 // — prefix-based partial replacement (path starts_with)
931 // ===============================================================
932
933 #[test]
934 fn replace_root_binary_types_prefix_based() {
935 let b58 = base58_of_32_bytes(6);
936 let inner = Value::Map(vec![(Value::Text("sub_id".into()), Value::Text(b58))]);
937 let mut value = Value::Map(vec![(Value::Text("nested".into()), inner)]);
938 let identifier_paths = HashSet::from(["root.nested.sub_id"]);
939 value
940 .replace_to_binary_types_of_root_value_when_setting_at_path(
941 "root",
942 identifier_paths,
943 HashSet::new(),
944 )
945 .unwrap();
946 // The identifier_path "root.nested.sub_id" starts_with "root", so
947 // replace_at_path("root.nested.sub_id", Identifier) is called on self.
948 // But self is the map starting at "nested", so the full path from self's
949 // perspective is "root.nested.sub_id" which includes the "root" prefix --
950 // this means it tries to find "root" key in self. Since our value doesn't
951 // have a "root" key, the replacement is silently skipped.
952 // This is the actual behavior of the method.
953 }
954
955 #[test]
956 fn replace_root_binary_types_prefix_replaces_sub_path() {
957 let b58 = base58_of_32_bytes(6);
958 let inner = Value::Map(vec![(Value::Text("sub_id".into()), Value::Text(b58))]);
959 let mut value = Value::Map(vec![(Value::Text("nested".into()), inner)]);
960 // The identifier path starts with the path prefix
961 let identifier_paths = HashSet::from(["doc.nested.sub_id"]);
962 value
963 .replace_to_binary_types_of_root_value_when_setting_at_path(
964 "doc",
965 identifier_paths,
966 HashSet::new(),
967 )
968 .unwrap();
969 // "doc.nested.sub_id".starts_with("doc") is true, so
970 // self.replace_at_path("doc.nested.sub_id", Identifier) is called.
971 // self doesn't have key "doc", so the filter_map returns empty vec.
972 // This is the expected silent skip behavior.
973 }
974
975 // ===============================================================
976 // replace_to_binary_types_of_root_value_when_setting_at_path
977 // — no match at all, returns Ok
978 // ===============================================================
979
980 #[test]
981 fn replace_root_binary_types_no_match_returns_ok() {
982 let mut value = Value::Map(vec![(Value::Text("a".into()), Value::U32(1))]);
983 let result = value.replace_to_binary_types_of_root_value_when_setting_at_path(
984 "unrelated",
985 HashSet::from(["other.path"]),
986 HashSet::new(),
987 );
988 assert!(result.is_ok());
989 }
990
991 // ===============================================================
992 // replace_to_binary_types_when_setting_with_path — identifier
993 // ===============================================================
994
995 #[test]
996 fn replace_when_setting_with_path_identifier_exact() {
997 let b58 = base58_of_32_bytes(11);
998 let mut value = Value::Text(b58);
999 let identifier_paths = HashSet::from(["doc.owner"]);
1000 value
1001 .replace_to_binary_types_when_setting_with_path(
1002 "doc.owner",
1003 identifier_paths,
1004 HashSet::new(),
1005 )
1006 .unwrap();
1007 assert_eq!(value, Value::Identifier([11u8; 32]));
1008 }
1009
1010 // ===============================================================
1011 // replace_to_binary_types_when_setting_with_path — strip prefix
1012 // ===============================================================
1013
1014 #[test]
1015 fn replace_when_setting_with_path_strip_prefix() {
1016 let b58 = base58_of_32_bytes(15);
1017 let mut value = Value::Map(vec![(Value::Text("sub_id".into()), Value::Text(b58))]);
1018 let identifier_paths = HashSet::from(["container.sub_id"]);
1019 value
1020 .replace_to_binary_types_when_setting_with_path(
1021 "container",
1022 identifier_paths,
1023 HashSet::new(),
1024 )
1025 .unwrap();
1026 assert_eq!(
1027 value.get_value_at_path("sub_id").unwrap(),
1028 &Value::Identifier([15u8; 32])
1029 );
1030 }
1031
1032 // ===============================================================
1033 // replace_to_binary_types_when_setting_with_path — binary strip prefix
1034 // ===============================================================
1035
1036 #[test]
1037 fn replace_when_setting_with_path_binary_strip_prefix() {
1038 use base64::prelude::*;
1039 let raw = vec![1u8, 2, 3, 4, 5];
1040 let b64 = BASE64_STANDARD.encode(&raw);
1041 let mut value = Value::Map(vec![(Value::Text("blob".into()), Value::Text(b64))]);
1042 let binary_paths = HashSet::from(["container.blob"]);
1043 value
1044 .replace_to_binary_types_when_setting_with_path(
1045 "container",
1046 HashSet::new(),
1047 binary_paths,
1048 )
1049 .unwrap();
1050 assert_eq!(value.get_value_at_path("blob").unwrap(), &Value::Bytes(raw));
1051 }
1052
1053 // ===============================================================
1054 // replace_to_binary_types_when_setting_with_path — no match
1055 // ===============================================================
1056
1057 #[test]
1058 fn replace_when_setting_with_path_no_match_ok() {
1059 let mut value = Value::Map(vec![(Value::Text("a".into()), Value::U32(1))]);
1060 let result = value.replace_to_binary_types_when_setting_with_path(
1061 "container",
1062 HashSet::from(["other.path"]),
1063 HashSet::new(),
1064 );
1065 assert!(result.is_ok());
1066 }
1067
1068 // ===============================================================
1069 // clean_recursive — removes nulls from nested maps
1070 // ===============================================================
1071
1072 #[test]
1073 fn clean_recursive_removes_nulls() {
1074 let inner = Value::Map(vec![
1075 (Value::Text("keep".into()), Value::U32(1)),
1076 (Value::Text("drop".into()), Value::Null),
1077 ]);
1078 let value = Value::Map(vec![
1079 (Value::Text("inner".into()), inner),
1080 (Value::Text("also_drop".into()), Value::Null),
1081 ]);
1082 let cleaned = value.clean_recursive().unwrap();
1083 let map = cleaned.to_map().unwrap();
1084 assert_eq!(map.len(), 1);
1085 let inner_map = map.get_key("inner").unwrap().to_map().unwrap();
1086 assert_eq!(inner_map.len(), 1);
1087 assert!(inner_map.get_optional_key("keep").is_some());
1088 assert!(inner_map.get_optional_key("drop").is_none());
1089 }
1090
1091 #[test]
1092 fn clean_recursive_deeply_nested() {
1093 let deep = Value::Map(vec![
1094 (Value::Text("a".into()), Value::Null),
1095 (Value::Text("b".into()), Value::U8(1)),
1096 ]);
1097 let mid = Value::Map(vec![
1098 (Value::Text("deep".into()), deep),
1099 (Value::Text("c".into()), Value::Null),
1100 ]);
1101 let value = Value::Map(vec![(Value::Text("mid".into()), mid)]);
1102 let cleaned = value.clean_recursive().unwrap();
1103 let mid_map = cleaned.get_value_at_path("mid").unwrap().to_map().unwrap();
1104 assert_eq!(mid_map.len(), 1); // only "deep" remains
1105 let deep_map = cleaned
1106 .get_value_at_path("mid.deep")
1107 .unwrap()
1108 .to_map()
1109 .unwrap();
1110 assert_eq!(deep_map.len(), 1); // only "b" remains
1111 }
1112
1113 #[test]
1114 fn clean_recursive_preserves_non_null_non_map_values() {
1115 let value = Value::Map(vec![
1116 (Value::Text("num".into()), Value::U64(42)),
1117 (Value::Text("text".into()), Value::Text("hello".into())),
1118 (
1119 Value::Text("arr".into()),
1120 Value::Array(vec![Value::Null, Value::U8(1)]),
1121 ),
1122 ]);
1123 let cleaned = value.clean_recursive().unwrap();
1124 let map = cleaned.to_map().unwrap();
1125 assert_eq!(map.len(), 3);
1126 // Arrays with Null inside are NOT cleaned (clean_recursive only filters map entries)
1127 let arr = map.get_key("arr").unwrap().to_array_ref().unwrap();
1128 assert_eq!(arr.len(), 2);
1129 }
1130
1131 #[test]
1132 fn clean_recursive_all_null() {
1133 let value = Value::Map(vec![
1134 (Value::Text("a".into()), Value::Null),
1135 (Value::Text("b".into()), Value::Null),
1136 ]);
1137 let cleaned = value.clean_recursive().unwrap();
1138 let map = cleaned.to_map().unwrap();
1139 assert_eq!(map.len(), 0);
1140 }
1141
1142 #[test]
1143 fn clean_recursive_on_non_map_errors() {
1144 let value = Value::U32(42);
1145 let result = value.clean_recursive();
1146 assert!(result.is_err());
1147 }
1148
1149 // ===============================================================
1150 // replace_at_path — intermediate non-map value errors
1151 // ===============================================================
1152
1153 #[test]
1154 fn replace_at_path_intermediate_non_map_errors() {
1155 let mut value = Value::Map(vec![(Value::Text("a".into()), Value::U32(42))]);
1156 // "a" is U32, not a map, so traversing "a.b" should error
1157 let result = value.replace_at_path("a.b", ReplacementType::Identifier);
1158 assert!(result.is_err());
1159 }
1160}