duckscriptsdk/sdk/std/json/encode/
mod.rs

1use crate::sdk::std::json::OBJECT_VALUE;
2use crate::utils::pckg;
3use crate::utils::state::get_handles_sub_state;
4use duckscript::types::command::{Command, CommandInvocationContext, CommandResult};
5use duckscript::types::runtime::StateValue;
6use serde_json::map::Map;
7use serde_json::{Number, Value};
8use std::collections::{HashMap, HashSet};
9
10#[cfg(test)]
11#[path = "./mod_test.rs"]
12mod mod_test;
13
14fn encode_array(name: &str, values: &HashMap<&str, &str>) -> Result<Value, String> {
15    match values.get(format!("{}.length", name).as_str()) {
16        Some(length_str) => match length_str.parse::<usize>() {
17            Ok(length) => {
18                let mut json_vec = vec![];
19
20                for index in 0..length {
21                    let array_item_name = format!("{}[{}]", name, index);
22                    match encode_any(&array_item_name, values) {
23                        Ok(array_item) => json_vec.push(array_item),
24                        Err(error) => return Err(error),
25                    }
26                }
27
28                Ok(Value::Array(json_vec))
29            }
30            Err(error) => Err(error.to_string()),
31        },
32        None => Err(format!(
33            "{} is not a valid JSON array, missing length attribute.",
34            name
35        )),
36    }
37}
38
39fn encode_object(name: &str, values: &HashMap<&str, &str>) -> Result<Value, String> {
40    let child_prefix = format!("{}.", name);
41    let child_prefix_end = child_prefix.len() - 1;
42    let mut children: HashSet<&str> = HashSet::new();
43
44    for (key, _) in values {
45        if key.starts_with(&child_prefix) {
46            let last_index = key.rfind('.').unwrap();
47
48            if last_index == child_prefix_end {
49                let array_key_end = key.rfind("[").unwrap_or(0);
50                let next_key = if array_key_end > last_index && key.rfind("]").is_some() {
51                    &key[0..array_key_end]
52                } else {
53                    key
54                };
55                children.insert(next_key);
56            }
57        }
58    }
59
60    let mut object = Map::new();
61    let prefix_length = name.len() + 1;
62    for key in children {
63        match encode_any(key, values) {
64            Ok(json_value) => {
65                let child_key = &key[prefix_length..];
66                object.insert(child_key.to_string(), json_value);
67                ()
68            }
69            Err(error) => return Err(error),
70        }
71    }
72
73    Ok(Value::Object(object))
74}
75
76fn encode_any(name: &str, values: &HashMap<&str, &str>) -> Result<Value, String> {
77    match values.get(name) {
78        Some(value) => {
79            if *value == OBJECT_VALUE {
80                encode_object(name, values)
81            } else {
82                Ok(Value::String(value.to_string()))
83            }
84        }
85        None => {
86            if values.contains_key(format!("{}.length", name).as_str()) {
87                encode_array(name, values)
88            } else {
89                Ok(Value::Null)
90            }
91        }
92    }
93}
94
95fn encode_from_variables(
96    name: &str,
97    variables: &HashMap<String, String>,
98) -> Result<String, String> {
99    let mut object_variables: HashMap<&str, &str> = HashMap::new();
100
101    for (key, value) in variables {
102        if key == name || key.starts_with(name) {
103            object_variables.insert(key, value);
104        }
105    }
106
107    if object_variables.is_empty() {
108        Ok("".to_string())
109    } else {
110        match encode_any(name, &object_variables) {
111            Ok(value) => Ok(value.to_string()),
112            Err(error) => Err(error),
113        }
114    }
115}
116
117fn encode_from_state_value(
118    state_value: &StateValue,
119    state: &HashMap<String, StateValue>,
120) -> Result<Value, String> {
121    match state_value {
122        StateValue::Boolean(value) => Ok(Value::Bool(*value)),
123        StateValue::Number(value) => Ok(Value::Number(Number::from(*value))),
124        StateValue::UnsignedNumber(value) => Ok(Value::Number(Number::from(*value))),
125        StateValue::Number32Bit(value) => Ok(Value::Number(Number::from(*value))),
126        StateValue::UnsignedNumber32Bit(value) => Ok(Value::Number(Number::from(*value))),
127        StateValue::Number64Bit(value) => Ok(Value::Number(Number::from(*value))),
128        StateValue::UnsignedNumber64Bit(value) => Ok(Value::Number(Number::from(*value))),
129        StateValue::String(value) => match state.get(value) {
130            Some(sub_state_value) => encode_from_state_value(sub_state_value, state),
131            None => Ok(Value::String(value.to_string())),
132        },
133        StateValue::List(list) => {
134            let mut items = vec![];
135
136            for item in list {
137                match encode_from_state_value(item, state) {
138                    Ok(item_value) => {
139                        items.push(item_value);
140                    }
141                    Err(error) => return Err(error),
142                };
143            }
144
145            Ok(Value::Array(items))
146        }
147        StateValue::SubState(sub_state) => {
148            let mut items = Map::new();
149
150            for (key, value) in sub_state {
151                match encode_from_state_value(value, state) {
152                    Ok(item_value) => {
153                        items.insert(key.to_string(), item_value);
154                    }
155                    Err(error) => return Err(error),
156                }
157            }
158
159            Ok(Value::Object(items))
160        }
161        StateValue::ByteArray(_) => Err("Unsupported value type.".to_string()),
162        StateValue::Set(_) => Err("Unsupported value type.".to_string()),
163        StateValue::Any(_) => Err("Unsupported value type.".to_string()),
164    }
165}
166
167fn encode_from_state(value: &str, state: &HashMap<String, StateValue>) -> Result<String, String> {
168    let json_value = match state.get(value) {
169        Some(state_value) => encode_from_state_value(state_value, state),
170        None => Ok(Value::String(value.to_string())),
171    };
172
173    match json_value {
174        Ok(json_value_obj) => Ok(json_value_obj.to_string()),
175        Err(error) => Err(error.to_string()),
176    }
177}
178
179#[derive(Clone)]
180pub(crate) struct CommandImpl {
181    package: String,
182}
183
184impl Command for CommandImpl {
185    fn name(&self) -> String {
186        pckg::concat(&self.package, "Encode")
187    }
188
189    fn aliases(&self) -> Vec<String> {
190        vec!["json_encode".to_string()]
191    }
192
193    fn help(&self) -> String {
194        include_str!("help.md").to_string()
195    }
196
197    fn clone_and_box(&self) -> Box<dyn Command> {
198        Box::new((*self).clone())
199    }
200
201    fn run(&self, context: CommandInvocationContext) -> CommandResult {
202        if context.arguments.is_empty() {
203            CommandResult::Error("No JSON root variable name provided.".to_string())
204        } else {
205            let (start_index, as_state) =
206                if context.arguments.len() > 1 && context.arguments[0] == "--collection" {
207                    (1, true)
208                } else {
209                    (0, false)
210                };
211
212            if as_state {
213                let state = get_handles_sub_state(context.state);
214
215                match encode_from_state(&context.arguments[start_index], state) {
216                    Ok(output) => CommandResult::Continue(Some(output)),
217                    Err(error) => CommandResult::Error(error),
218                }
219            } else {
220                match encode_from_variables(&context.arguments[start_index], context.variables) {
221                    Ok(output) => CommandResult::Continue(Some(output)),
222                    Err(error) => CommandResult::Error(error),
223                }
224            }
225        }
226    }
227}
228
229pub(crate) fn create(package: &str) -> Box<dyn Command> {
230    Box::new(CommandImpl {
231        package: package.to_string(),
232    })
233}