duckscriptsdk/sdk/std/json/encode/
mod.rs1use 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}