platform_value/btreemap_extensions/
btreemap_field_replacement.rs

1use crate::value_map::ValueMapHelper;
2use crate::{Error, Value};
3use std::collections::BTreeMap;
4
5use base64::prelude::BASE64_STANDARD;
6use base64::Engine;
7use std::iter::Peekable;
8use std::vec::IntoIter;
9
10#[derive(Debug, Clone, Copy)]
11pub enum IntegerReplacementType {
12    U128,
13    I128,
14    U64,
15    I64,
16    U32,
17    I32,
18    U16,
19    I16,
20    U8,
21    I8,
22}
23
24impl IntegerReplacementType {
25    pub fn replace_for_value(&self, value: Value) -> Result<Value, Error> {
26        Ok(match self {
27            IntegerReplacementType::U128 => Value::U128(value.try_into()?),
28            IntegerReplacementType::I128 => Value::I128(value.try_into()?),
29            IntegerReplacementType::U64 => Value::U64(value.try_into()?),
30            IntegerReplacementType::I64 => Value::I64(value.try_into()?),
31            IntegerReplacementType::U32 => Value::U32(value.try_into()?),
32            IntegerReplacementType::I32 => Value::I32(value.try_into()?),
33            IntegerReplacementType::U16 => Value::U16(value.try_into()?),
34            IntegerReplacementType::I16 => Value::I16(value.try_into()?),
35            IntegerReplacementType::U8 => Value::U8(value.try_into()?),
36            IntegerReplacementType::I8 => Value::I8(value.try_into()?),
37        })
38    }
39}
40
41#[derive(Debug, Clone, Copy)]
42pub enum ReplacementType {
43    Identifier,
44    BinaryBytes,
45    TextBase58,
46    TextBase64,
47}
48
49impl ReplacementType {
50    pub fn replace_for_bytes(&self, bytes: Vec<u8>) -> Result<Value, Error> {
51        match self {
52            ReplacementType::Identifier => {
53                Ok(Value::Identifier(bytes.try_into().map_err(|_| {
54                    Error::ByteLengthNot32BytesError(String::from(
55                        "Trying to replace into an identifier, but not 32 bytes long",
56                    ))
57                })?))
58            }
59            ReplacementType::BinaryBytes => Ok(Value::Bytes(bytes)),
60            ReplacementType::TextBase58 => Ok(Value::Text(bs58::encode(bytes).into_string())),
61            ReplacementType::TextBase64 => Ok(Value::Text(BASE64_STANDARD.encode(bytes))),
62        }
63    }
64
65    pub fn replace_for_bytes_20(&self, bytes: [u8; 20]) -> Result<Value, Error> {
66        match self {
67            ReplacementType::BinaryBytes => Ok(Value::Bytes20(bytes)),
68            ReplacementType::TextBase58 => Ok(Value::Text(bs58::encode(bytes).into_string())),
69            ReplacementType::TextBase64 => Ok(Value::Text(BASE64_STANDARD.encode(bytes))),
70            _ => Err(Error::ByteLengthNot36BytesError(
71                "trying to replace 36 bytes into an identifier".to_string(),
72            )),
73        }
74    }
75
76    pub fn replace_for_bytes_32(&self, bytes: [u8; 32]) -> Result<Value, Error> {
77        match self {
78            ReplacementType::Identifier => Ok(Value::Identifier(bytes)),
79            ReplacementType::BinaryBytes => Ok(Value::Bytes32(bytes)),
80            ReplacementType::TextBase58 => Ok(Value::Text(bs58::encode(bytes).into_string())),
81            ReplacementType::TextBase64 => Ok(Value::Text(BASE64_STANDARD.encode(bytes))),
82        }
83    }
84
85    pub fn replace_for_bytes_36(&self, bytes: [u8; 36]) -> Result<Value, Error> {
86        match self {
87            ReplacementType::BinaryBytes => Ok(Value::Bytes36(bytes)),
88            ReplacementType::TextBase58 => Ok(Value::Text(bs58::encode(bytes).into_string())),
89            ReplacementType::TextBase64 => Ok(Value::Text(BASE64_STANDARD.encode(bytes))),
90            _ => Err(Error::ByteLengthNot36BytesError(
91                "trying to replace 36 bytes into an identifier".to_string(),
92            )),
93        }
94    }
95
96    pub fn replace_consume_value(&self, value: Value) -> Result<Value, Error> {
97        let bytes = value.into_identifier_bytes()?;
98        self.replace_for_bytes(bytes)
99    }
100
101    pub fn replace_value_in_place(&self, value: &mut Value) -> Result<(), Error> {
102        let bytes = value.take().into_identifier_bytes()?;
103        *value = self.replace_for_bytes(bytes)?;
104        Ok(())
105    }
106}
107
108pub trait BTreeValueMapReplacementPathHelper {
109    fn replace_at_path(
110        &mut self,
111        path: &str,
112        replacement_type: ReplacementType,
113    ) -> Result<(), Error>;
114    fn replace_at_paths<'a, I: IntoIterator<Item = &'a String>>(
115        &mut self,
116        paths: I,
117        replacement_type: ReplacementType,
118    ) -> Result<(), Error>;
119}
120
121fn replace_down(
122    mut current_values: Vec<&mut Value>,
123    mut split: Peekable<IntoIter<&str>>,
124    replacement_type: ReplacementType,
125) -> Result<(), Error> {
126    if let Some(path_component) = split.next() {
127        let next_values = current_values
128            .iter_mut()
129            .map(|current_value| {
130                if current_value.is_map() {
131                    let map = current_value.as_map_mut_ref()?;
132                    let Some(new_value) = map.get_optional_key_mut(path_component) else {
133                        return Ok(None);
134                    };
135                    if split.peek().is_none() {
136                        match new_value {
137                            Value::Bytes20(bytes) => {
138                                *new_value = replacement_type.replace_for_bytes_20(*bytes)?;
139                            }
140                            Value::Bytes32(bytes) => {
141                                *new_value = replacement_type.replace_for_bytes_32(*bytes)?;
142                            }
143                            Value::Bytes36(bytes) => {
144                                *new_value = replacement_type.replace_for_bytes_36(*bytes)?;
145                            }
146                            _ => {
147                                let bytes = match replacement_type {
148                                    ReplacementType::Identifier | ReplacementType::TextBase58 => {
149                                        new_value.to_identifier_bytes()
150                                    }
151                                    ReplacementType::BinaryBytes | ReplacementType::TextBase64 => {
152                                        new_value.to_binary_bytes()
153                                    }
154                                }?;
155                                *new_value = replacement_type.replace_for_bytes(bytes)?;
156                            }
157                        }
158                        Ok(None)
159                    } else {
160                        Ok(Some(vec![new_value]))
161                    }
162                } else if current_value.is_array() {
163                    // if it's an array we apply to all members
164                    let array = current_value.to_array_mut()?.iter_mut().collect();
165                    Ok(Some(array))
166                } else {
167                    Err(Error::PathError("path was not an array or map".to_string()))
168                }
169            })
170            .collect::<Result<Vec<_>, Error>>()?
171            .into_iter()
172            .flatten()
173            .flatten()
174            .collect();
175        replace_down(next_values, split, replacement_type)
176    } else {
177        Ok(())
178    }
179}
180
181impl BTreeValueMapReplacementPathHelper for BTreeMap<String, Value> {
182    fn replace_at_path(
183        &mut self,
184        path: &str,
185        replacement_type: ReplacementType,
186    ) -> Result<(), Error> {
187        let mut split: Vec<_> = path.split('.').collect();
188        let first = split.first();
189        let Some(first_path_component) = first else {
190            return Err(Error::PathError("path was empty".to_string()));
191        };
192        let Some(current_value) = self.get_mut(first_path_component.to_owned()) else {
193            return Ok(());
194        };
195        if split.len() == 1 {
196            match current_value {
197                Value::Bytes20(bytes) => {
198                    *current_value = replacement_type.replace_for_bytes_20(*bytes)?;
199                }
200                Value::Bytes32(bytes) => {
201                    *current_value = replacement_type.replace_for_bytes_32(*bytes)?;
202                }
203                Value::Bytes36(bytes) => {
204                    *current_value = replacement_type.replace_for_bytes_36(*bytes)?;
205                }
206                _ => {
207                    let bytes = match replacement_type {
208                        ReplacementType::Identifier | ReplacementType::TextBase58 => {
209                            current_value.to_identifier_bytes()
210                        }
211                        ReplacementType::BinaryBytes | ReplacementType::TextBase64 => {
212                            current_value.to_binary_bytes()
213                        }
214                    }?;
215                    *current_value = replacement_type.replace_for_bytes(bytes)?;
216                }
217            }
218            Ok(())
219        } else {
220            split.remove(0);
221            let current_values = vec![current_value];
222            //todo: make this non recursive
223            replace_down(
224                current_values,
225                split.into_iter().peekable(),
226                replacement_type,
227            )
228        }
229    }
230
231    fn replace_at_paths<'a, I: IntoIterator<Item = &'a String>>(
232        &mut self,
233        paths: I,
234        replacement_type: ReplacementType,
235    ) -> Result<(), Error> {
236        paths
237            .into_iter()
238            .try_for_each(|path| self.replace_at_path(path.as_str(), replacement_type))
239    }
240}