Skip to main content

txtx_core/std/functions/
json.rs

1use jaq_interpret::{Ctx, FilterT, ParseCtx, RcIter, Val};
2use serde_json::Value as JsonValue;
3use txtx_addon_kit::types::AuthorizationContext;
4use txtx_addon_kit::{
5    define_function, indoc,
6    types::{
7        diagnostics::Diagnostic,
8        functions::{FunctionImplementation, FunctionSpecification},
9        types::{Type, Value},
10    },
11};
12
13use crate::std::functions::{arg_checker, to_diag};
14
15lazy_static! {
16    pub static ref JSON_FUNCTIONS: Vec<FunctionSpecification> = vec![define_function! {
17        JsonQuery => {
18            name: "jq",
19            documentation: indoc!{r#"
20            The `jq` function allows slicing, filtering, and mapping JSON data. 
21            See the [jq](https://jqlang.github.io/jq/manual/) documentation for more details.
22            "#},                
23            example: indoc!{r#"
24              output "message" { 
25                  value = jq("{ \"message\": \"Hello world!\" }", ".message")
26              }
27              > message: Hello world!
28            "#},
29            inputs: [
30                decoded_json: {
31                    documentation: "A JSON object.",
32                    typing: vec![Type::string(), Type::arbitrary_object()]
33                },
34                query: {
35                    documentation: "A JSON query. See the [jq](https://jqlang.github.io/jq/manual/) documentation.",
36                    typing: vec![Type::string()]
37                }
38            ],
39            output: {
40                documentation: "The result of the `jq` query.",
41                typing: Type::array(Type::string())
42            },
43        }
44    },];
45}
46
47pub struct JsonQuery;
48impl FunctionImplementation for JsonQuery {
49    fn check_instantiability(
50        _fn_spec: &FunctionSpecification,
51        _auth_ctx: &AuthorizationContext,
52        _args: &Vec<Type>,
53    ) -> Result<Type, Diagnostic> {
54        unimplemented!()
55    }
56
57    fn run(
58        fn_spec: &FunctionSpecification,
59        _auth_ctx: &AuthorizationContext,
60        args: &Vec<Value>,
61    ) -> Result<Value, Diagnostic> {
62        arg_checker(fn_spec, args)?;
63        let input_str = args.get(0).unwrap().encode_to_string();
64        let filter = args.get(1).unwrap().as_string().unwrap();
65
66        // try to deserialize the string as is
67        let input: JsonValue = match serde_json::from_str(&input_str) {
68            Ok(json) => json,
69            // if it fails, trim quotes and try again
70            Err(e) => serde_json::from_str(&input_str.trim_matches('"'))
71                .map_err(|_| to_diag(fn_spec, format!("failed to decode input as json: {e}")))?,
72        };
73
74        let mut defs = ParseCtx::new(Vec::new());
75
76        // parse the filter
77        let (f, errs) = jaq_parse::parse(&filter, jaq_parse::main());
78        if !errs.is_empty() {
79            return Err(to_diag(fn_spec, errs.first().unwrap().to_string()));
80        }
81
82        // compile the filter in the context of the given definitions
83        let f = defs.compile(f.unwrap());
84        let errs = defs.errs;
85        if !errs.is_empty() {
86            return Err(to_diag(fn_spec, errs.first().unwrap().0.to_string()));
87        }
88
89        let inputs = RcIter::new(core::iter::empty());
90        // iterator over the output values
91        let result = f
92            .run((Ctx::new([], &inputs), Val::from(input)))
93            .into_iter()
94            // todo: we need to allow other types other than string
95            .map(|o| o.map(|v| Value::from_jaq_value(&v)))
96            .collect::<Result<Result<Vec<Value>, _>, _>>()
97            .map_err(|e| to_diag(fn_spec, e.to_string()))?
98            .map_err(|e| to_diag(fn_spec, e.to_string()))?;
99        if result.len() == 1 {
100            Ok(result.first().unwrap().clone())
101        } else {
102            Ok(Value::array(result))
103        }
104    }
105}