1use crate::compiler::prelude::*;
2use std::sync::LazyLock;
3
4static DEFAULT_PRETTY: LazyLock<Value> = LazyLock::new(|| Value::Boolean(false));
5
6static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
7 vec![
8 Parameter::required("value", kind::ANY, "The value to convert to a JSON string."),
9 Parameter::optional(
10 "pretty",
11 kind::BOOLEAN,
12 "Whether to pretty print the JSON string or not.",
13 )
14 .default(&DEFAULT_PRETTY),
15 ]
16});
17
18fn encode_json(value: &Value, pretty: bool) -> Value {
19 let result = if pretty {
22 serde_json::to_string_pretty(&value)
23 } else {
24 serde_json::to_string(&value)
25 };
26
27 match result {
28 Ok(value) => value.into(),
29 Err(error) => unreachable!("unable encode to json: {}", error),
30 }
31}
32
33#[derive(Clone, Copy, Debug)]
34pub struct EncodeJson;
35
36impl Function for EncodeJson {
37 fn identifier(&self) -> &'static str {
38 "encode_json"
39 }
40
41 fn usage(&self) -> &'static str {
42 "Encodes the `value` to JSON."
43 }
44
45 fn category(&self) -> &'static str {
46 Category::Codec.as_ref()
47 }
48
49 fn return_kind(&self) -> u16 {
50 kind::BYTES
51 }
52
53 fn parameters(&self) -> &'static [Parameter] {
54 PARAMETERS.as_slice()
55 }
56
57 fn compile(
58 &self,
59 _state: &state::TypeState,
60 _ctx: &mut FunctionCompileContext,
61 arguments: ArgumentList,
62 ) -> Compiled {
63 let value = arguments.required("value");
64 let pretty = arguments.optional("pretty");
65
66 Ok(EncodeJsonFn { value, pretty }.as_expr())
67 }
68
69 fn examples(&self) -> &'static [Example] {
70 &[
71 example! {
72 title: "Encode object to JSON",
73 source: r#"encode_json({"field": "value", "another": [1,2,3]})"#,
74 result: Ok(r#"s'{"another":[1,2,3],"field":"value"}'"#),
75 },
76 example! {
77 title: "Encode object to as pretty-printed JSON",
78 source: r#"encode_json({"field": "value", "another": [1,2,3]}, true)"#,
79 result: Ok(
80 r#""{\n \"another\": [\n 1,\n 2,\n 3\n ],\n \"field\": \"value\"\n}""#,
81 ),
82 },
83 ]
84 }
85}
86
87#[derive(Clone, Debug)]
88struct EncodeJsonFn {
89 value: Box<dyn Expression>,
90 pretty: Option<Box<dyn Expression>>,
91}
92
93impl FunctionExpression for EncodeJsonFn {
94 fn resolve(&self, ctx: &mut Context) -> Resolved {
95 let value = self.value.resolve(ctx)?;
96 let pretty = self
97 .pretty
98 .map_resolve_with_default(ctx, || DEFAULT_PRETTY.clone())?
99 .try_boolean()?;
100 Ok(encode_json(&value, pretty))
101 }
102
103 fn type_def(&self, _: &state::TypeState) -> TypeDef {
104 TypeDef::bytes().infallible()
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 use chrono::{DateTime, Utc};
113 use regex::Regex;
114
115 test_function![
116 encode_json => EncodeJson;
117
118 bytes {
119 args: func_args![value: "hello"],
120 want: Ok(r#""hello""#),
121 tdef: TypeDef::bytes().infallible(),
122 }
123
124 bytes_pretty {
125 args: func_args![value: "hello", pretty: true],
126 want: Ok(r#""hello""#),
127 tdef: TypeDef::bytes().infallible(),
128 }
129
130 integer {
131 args: func_args![value: 42],
132 want: Ok("42"),
133 tdef: TypeDef::bytes().infallible(),
134 }
135
136 integer_pretty {
137 args: func_args![value: 42, pretty: true],
138 want: Ok("42"),
139 tdef: TypeDef::bytes().infallible(),
140 }
141
142 float {
143 args: func_args![value: 42f64],
144 want: Ok("42.0"),
145 tdef: TypeDef::bytes().infallible(),
146 }
147
148 float_pretty {
149 args: func_args![value: 42f64, pretty: true],
150 want: Ok("42.0"),
151 tdef: TypeDef::bytes().infallible(),
152 }
153
154 boolean {
155 args: func_args![value: false],
156 want: Ok("false"),
157 tdef: TypeDef::bytes().infallible(),
158 }
159
160 boolean_pretty {
161 args: func_args![value: false, pretty: true],
162 want: Ok("false"),
163 tdef: TypeDef::bytes().infallible(),
164 }
165
166 map {
167 args: func_args![value: Value::from_iter([(String::from("field"), Value::from("value"))])],
168 want: Ok(r#"{"field":"value"}"#),
169 tdef: TypeDef::bytes().infallible(),
170 }
171
172 map_pretty {
173 args: func_args![value: Value::from_iter([(String::from("field"), Value::from("value"))]), pretty: true],
174 want: Ok("{\n \"field\": \"value\"\n}"),
175 tdef: TypeDef::bytes().infallible(),
176 }
177
178 array {
179 args: func_args![value: vec![1, 2, 3]],
180 want: Ok("[1,2,3]"),
181 tdef: TypeDef::bytes().infallible(),
182 }
183
184 array_pretty {
185 args: func_args![value: vec![1, 2, 3], pretty: true],
186 want: Ok("[\n 1,\n 2,\n 3\n]"),
187 tdef: TypeDef::bytes().infallible(),
188 }
189
190 timestamp {
191 args: func_args![
192 value: DateTime::parse_from_str("1983 Apr 13 12:09:14.274 +0000", "%Y %b %d %H:%M:%S%.3f %z")
193 .unwrap()
194 .with_timezone(&Utc)
195 ],
196 want: Ok(r#""1983-04-13T12:09:14.274Z""#),
197 tdef: TypeDef::bytes().infallible(),
198 }
199
200 timestamp_pretty {
201 args: func_args![
202 value: DateTime::parse_from_str("1983 Apr 13 12:09:14.274 +0000", "%Y %b %d %H:%M:%S%.3f %z")
203 .unwrap()
204 .with_timezone(&Utc),
205 pretty: true
206 ],
207 want: Ok(r#""1983-04-13T12:09:14.274Z""#),
208 tdef: TypeDef::bytes().infallible(),
209 }
210
211 regex {
212 args: func_args![value: Regex::new("^a\\d+$").unwrap()],
213 want: Ok(r#""^a\\d+$""#),
214 tdef: TypeDef::bytes().infallible(),
215 }
216
217 regex_pretty {
218 args: func_args![value: Regex::new("^a\\d+$").unwrap(), pretty: true],
219 want: Ok(r#""^a\\d+$""#),
220 tdef: TypeDef::bytes().infallible(),
221 }
222
223 null {
224 args: func_args![value: Value::Null],
225 want: Ok("null"),
226 tdef: TypeDef::bytes().infallible(),
227 }
228
229 null_pretty {
230 args: func_args![value: Value::Null, pretty: true],
231 want: Ok("null"),
232 tdef: TypeDef::bytes().infallible(),
233 }
234 ];
235}