1use crate::compiler::prelude::*;
2use crate::stdlib::json_utils::json_type_def::json_type_def;
3use ciborium::de::from_reader;
4use zstd::zstd_safe::WriteBuf;
5
6fn parse_cbor(value: Value) -> Resolved {
7 let bytes = value.try_bytes()?;
8 let value = from_reader(bytes.as_slice()).map_err(|e| format!("unable to parse cbor: {e}"))?;
9 Ok(value)
10}
11
12#[derive(Clone, Copy, Debug)]
13pub struct ParseCbor;
14
15impl Function for ParseCbor {
16 fn identifier(&self) -> &'static str {
17 "parse_cbor"
18 }
19
20 fn summary(&self) -> &'static str {
21 "parse a string to a CBOR type"
22 }
23
24 fn usage(&self) -> &'static str {
25 "Parses the `value` as [CBOR](https://cbor.io)."
26 }
27
28 fn category(&self) -> &'static str {
29 Category::Parse.as_ref()
30 }
31
32 fn internal_failure_reasons(&self) -> &'static [&'static str] {
33 &["`value` is not a valid CBOR-formatted payload."]
34 }
35
36 fn return_kind(&self) -> u16 {
37 kind::BOOLEAN
38 | kind::INTEGER
39 | kind::FLOAT
40 | kind::BYTES
41 | kind::OBJECT
42 | kind::ARRAY
43 | kind::NULL
44 }
45
46 fn notices(&self) -> &'static [&'static str] {
47 &["Only CBOR types are returned."]
48 }
49
50 fn examples(&self) -> &'static [Example] {
51 &[
52 example! {
53 title: "Parse CBOR",
54 source: r#"parse_cbor!(decode_base64!("oWVmaWVsZGV2YWx1ZQ=="))"#,
55 result: Ok(r#"{ "field": "value" }"#),
56 },
57 example! {
58 title: "array",
59 source: r#"parse_cbor!(decode_base64!("gvUA"))"#,
60 result: Ok("[true, 0]"),
61 },
62 example! {
63 title: "string",
64 source: r#"parse_cbor!(decode_base64!("ZWhlbGxv"))"#,
65 result: Ok("hello"),
66 },
67 example! {
68 title: "integer",
69 source: r#"parse_cbor!(decode_base64!("GCo="))"#,
70 result: Ok("42"),
71 },
72 example! {
73 title: "float",
74 source: r#"parse_cbor!(decode_base64!("+0BFEKPXCj1x"))"#,
75 result: Ok("42.13"),
76 },
77 example! {
78 title: "boolean",
79 source: r#"parse_cbor!(decode_base64!("9A=="))"#,
80 result: Ok("false"),
81 },
82 ]
83 }
84
85 fn compile(
86 &self,
87 _state: &state::TypeState,
88 _ctx: &mut FunctionCompileContext,
89 arguments: ArgumentList,
90 ) -> Compiled {
91 let value = arguments.required("value");
92 Ok(ParseCborFn { value }.as_expr())
93 }
94
95 fn parameters(&self) -> &'static [Parameter] {
96 const PARAMETERS: &[Parameter] = &[Parameter::required(
97 "value",
98 kind::BYTES,
99 "The CBOR payload to parse.",
100 )];
101 PARAMETERS
102 }
103}
104
105#[derive(Debug, Clone)]
106struct ParseCborFn {
107 value: Box<dyn Expression>,
108}
109
110impl FunctionExpression for ParseCborFn {
111 fn resolve(&self, ctx: &mut Context) -> Resolved {
112 let value = self.value.resolve(ctx)?;
113 parse_cbor(value)
114 }
115
116 fn type_def(&self, _: &state::TypeState) -> TypeDef {
117 json_type_def()
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use crate::value;
125 use nom::AsBytes;
126 use std::env;
127 use std::fs;
128 use std::path::PathBuf;
129
130 fn test_data_dir() -> PathBuf {
131 PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("tests/data/cbor")
132 }
133
134 fn read_cbor_file(cbor_bin_message_path: &str) -> Vec<u8> {
135 fs::read(test_data_dir().join(cbor_bin_message_path)).unwrap()
136 }
137
138 test_function![
139 parse_cbor => ParseCbor;
140
141 parses {
142 args: func_args![ value: value!(read_cbor_file("simple.cbor").as_bytes()) ],
143 want: Ok(value!({ field: "value" })),
144 tdef: json_type_def(),
145 }
146
147 complex_cbor {
148 args: func_args![ value: value!(read_cbor_file("complex.cbor").as_bytes()) ],
149 want: Ok(value!({ object: {string: "value", number: 42, array: ["hello", "world"], boolean: false} })),
150 tdef: json_type_def(),
151 }
152 ];
153}