fusabi_stdlib_ext/
format.rs1use fusabi_host::ExecutionContext;
6use fusabi_host::Value;
7
8pub fn sprintf(
10 args: &[Value],
11 _ctx: &ExecutionContext,
12) -> fusabi_host::Result<Value> {
13 let format_str = args
14 .first()
15 .and_then(|v| v.as_str())
16 .ok_or_else(|| fusabi_host::Error::host_function("format.sprintf: missing format string"))?;
17
18 let format_args = &args[1..];
19 let result = format_string(format_str, format_args)?;
20
21 Ok(Value::String(result))
22}
23
24pub fn template(
26 args: &[Value],
27 _ctx: &ExecutionContext,
28) -> fusabi_host::Result<Value> {
29 let template_str = args
30 .first()
31 .and_then(|v| v.as_str())
32 .ok_or_else(|| fusabi_host::Error::host_function("format.template: missing template string"))?;
33
34 let values = args
35 .get(1)
36 .and_then(|v| v.as_map())
37 .ok_or_else(|| fusabi_host::Error::host_function("format.template: missing values map"))?;
38
39 let mut result = template_str.to_string();
40
41 for (key, value) in values {
42 let placeholder = format!("{{{{{}}}}}", key); let replacement = value_to_string(value);
44 result = result.replace(&placeholder, &replacement);
45 }
46
47 Ok(Value::String(result))
48}
49
50pub fn json_encode(
52 args: &[Value],
53 _ctx: &ExecutionContext,
54) -> fusabi_host::Result<Value> {
55 let value = args
56 .first()
57 .ok_or_else(|| fusabi_host::Error::host_function("format.json_encode: missing value"))?;
58
59 #[cfg(feature = "serde-support")]
60 {
61 let json = value.to_json_string();
62 Ok(Value::String(json))
63 }
64
65 #[cfg(not(feature = "serde-support"))]
66 {
67 let json = value_to_json_simple(value);
69 Ok(Value::String(json))
70 }
71}
72
73pub fn json_decode(
75 args: &[Value],
76 _ctx: &ExecutionContext,
77) -> fusabi_host::Result<Value> {
78 let json_str = args
79 .first()
80 .and_then(|v| v.as_str())
81 .ok_or_else(|| fusabi_host::Error::host_function("format.json_decode: missing JSON string"))?;
82
83 #[cfg(feature = "serde-support")]
84 {
85 Value::from_json_str(json_str)
86 .map_err(|e| fusabi_host::Error::host_function(format!("format.json_decode: {}", e)))
87 }
88
89 #[cfg(not(feature = "serde-support"))]
90 {
91 Err(fusabi_host::Error::host_function("json_decode requires serde-support feature"))
93 }
94}
95
96fn format_string(format_str: &str, args: &[Value]) -> fusabi_host::Result<String> {
99 let mut result = String::new();
100 let mut chars = format_str.chars().peekable();
101 let mut arg_index = 0;
102
103 while let Some(c) = chars.next() {
104 if c == '%' {
105 if let Some(&next) = chars.peek() {
106 match next {
107 '%' => {
108 result.push('%');
109 chars.next();
110 }
111 's' => {
112 chars.next();
113 let arg = args.get(arg_index).ok_or_else(|| {
114 fusabi_host::Error::host_function("format.sprintf: not enough arguments")
115 })?;
116 result.push_str(&value_to_string(arg));
117 arg_index += 1;
118 }
119 'd' | 'i' => {
120 chars.next();
121 let arg = args.get(arg_index).ok_or_else(|| {
122 fusabi_host::Error::host_function("format.sprintf: not enough arguments")
123 })?;
124 if let Some(n) = arg.as_int() {
125 result.push_str(&n.to_string());
126 } else {
127 result.push_str(&value_to_string(arg));
128 }
129 arg_index += 1;
130 }
131 'f' => {
132 chars.next();
133 let arg = args.get(arg_index).ok_or_else(|| {
134 fusabi_host::Error::host_function("format.sprintf: not enough arguments")
135 })?;
136 if let Some(f) = arg.as_float() {
137 result.push_str(&f.to_string());
138 } else {
139 result.push_str(&value_to_string(arg));
140 }
141 arg_index += 1;
142 }
143 _ => {
144 result.push(c);
145 }
146 }
147 } else {
148 result.push(c);
149 }
150 } else {
151 result.push(c);
152 }
153 }
154
155 Ok(result)
156}
157
158fn value_to_string(value: &Value) -> String {
159 match value {
160 Value::Null => "null".to_string(),
161 Value::Bool(b) => b.to_string(),
162 Value::Int(i) => i.to_string(),
163 Value::Float(f) => f.to_string(),
164 Value::String(s) => s.clone(),
165 Value::List(l) => {
166 let items: Vec<String> = l.iter().map(value_to_string).collect();
167 format!("[{}]", items.join(", "))
168 }
169 Value::Map(m) => {
170 let items: Vec<String> = m
171 .iter()
172 .map(|(k, v)| format!("{}: {}", k, value_to_string(v)))
173 .collect();
174 format!("{{{}}}", items.join(", "))
175 }
176 _ => format!("{}", value),
177 }
178}
179
180fn value_to_json_simple(value: &Value) -> String {
181 match value {
182 Value::Null => "null".to_string(),
183 Value::Bool(b) => b.to_string(),
184 Value::Int(i) => i.to_string(),
185 Value::Float(f) => f.to_string(),
186 Value::String(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
187 Value::List(l) => {
188 let items: Vec<String> = l.iter().map(value_to_json_simple).collect();
189 format!("[{}]", items.join(","))
190 }
191 Value::Map(m) => {
192 let items: Vec<String> = m
193 .iter()
194 .map(|(k, v)| format!("\"{}\":{}", k, value_to_json_simple(v)))
195 .collect();
196 format!("{{{}}}", items.join(","))
197 }
198 _ => "null".to_string(),
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use fusabi_host::Capabilities;
206 use fusabi_host::{Sandbox, SandboxConfig};
207 use fusabi_host::Limits;
208
209 fn create_test_ctx() -> ExecutionContext {
210 let sandbox = Sandbox::new(SandboxConfig::default()).unwrap();
211 ExecutionContext::new(1, Capabilities::none(), Limits::default(), sandbox)
212 }
213
214 #[test]
215 fn test_sprintf() {
216 let ctx = create_test_ctx();
217
218 let result = sprintf(&[
219 Value::String("Hello, %s! You have %d messages.".into()),
220 Value::String("Alice".into()),
221 Value::Int(5),
222 ], &ctx).unwrap();
223
224 assert_eq!(result.as_str().unwrap(), "Hello, Alice! You have 5 messages.");
225 }
226
227 #[test]
228 fn test_sprintf_float() {
229 let ctx = create_test_ctx();
230
231 let result = sprintf(&[
232 Value::String("Pi is approximately %f".into()),
233 Value::Float(3.14159),
234 ], &ctx).unwrap();
235
236 assert!(result.as_str().unwrap().contains("3.14"));
237 }
238
239 #[test]
240 fn test_template() {
241 let ctx = create_test_ctx();
242
243 let mut values = std::collections::HashMap::new();
244 values.insert("name".to_string(), Value::String("Bob".into()));
245 values.insert("count".to_string(), Value::Int(3));
246
247 let result = template(&[
248 Value::String("Hello, {{name}}! You have {{count}} items.".into()),
249 Value::Map(values),
250 ], &ctx).unwrap();
251
252 assert_eq!(result.as_str().unwrap(), "Hello, Bob! You have 3 items.");
253 }
254
255 #[test]
256 fn test_json_encode() {
257 let ctx = create_test_ctx();
258
259 let result = json_encode(&[Value::Int(42)], &ctx).unwrap();
260 assert_eq!(result.as_str().unwrap(), "42");
261
262 let result = json_encode(&[Value::String("hello".into())], &ctx).unwrap();
263 assert!(result.as_str().unwrap().contains("hello"));
264 }
265}