operaton_task_worker/structures/
process_variables.rs

1use std::collections::HashMap;
2use serde::{Deserialize, Serialize};
3use log::error;
4
5#[derive(Debug, Serialize, Deserialize)]
6#[serde(rename_all = "camelCase")]
7pub struct JsonValue {
8    data_format_name: String,
9
10    value: serde_json::Value,
11
12    string: bool,
13
14    object: bool,
15
16    boolean: bool,
17
18    number: bool,
19
20    array: bool,
21
22    #[serde(rename = "null")]
23    null_val: bool,
24
25    node_type: String,
26}
27
28#[derive(Debug, Serialize, Deserialize)]
29pub struct JsonVar {
30    #[serde(rename = "value")]
31    pub json_value: JsonValue,
32
33    #[serde(rename = "valueInfo")]
34    pub value_info: HashMap<String, serde_json::Value>,
35}
36
37#[derive(Debug, Serialize, Deserialize)]
38pub struct BoolVar {
39    pub value: bool,
40
41    #[serde(rename = "valueInfo")]
42    pub value_info: HashMap<String, serde_json::Value>,
43}
44
45#[derive(Debug, Serialize, Deserialize)]
46pub struct StringVar {
47    pub value: String,
48
49    #[serde(rename = "valueInfo")]
50    pub value_info: HashMap<String, serde_json::Value>,
51}
52
53#[derive(Debug)]
54pub enum ProcessInstanceVariable {
55    Json(JsonVar),
56    Boolean(BoolVar),
57    String(StringVar),
58}
59
60impl ProcessInstanceVariable {
61    pub fn as_bool(&self) -> Option<bool> {
62        match self {
63            ProcessInstanceVariable::Boolean(b) => Some(b.value),
64            _ => None,
65        }
66    }
67    pub fn as_str(&self) -> Option<&str> {
68        match self {
69            ProcessInstanceVariable::String(s) => Some(&s.value),
70            _ => None,
71        }
72    }
73    pub fn as_json(&self) -> Option<&serde_json::Value> {
74        match self {
75            ProcessInstanceVariable::Json(j) => Some(&j.json_value.value),
76            _ => None,
77        }
78    }
79}
80
81/// This represents an entry of the original JSON
82#[derive(Deserialize)]
83pub struct Entry {
84    #[serde(rename = "type")]
85    typ: String,
86
87    #[serde(default)]
88    #[allow(dead_code)]
89    name: String,
90
91    value: serde_json::Value,
92
93    #[serde(rename = "valueInfo")]
94    value_info: HashMap<String, serde_json::Value>,
95}
96
97impl<'de> Deserialize<'de> for ProcessInstanceVariable {
98    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
99    where
100        D: serde::Deserializer<'de>,
101    {
102        let map = HashMap::<String, Entry>::deserialize(deserializer)?;
103
104        // We expect only one entry in practice, but we'll take the first valid one
105        // Or collect all into Vec<Var> if you want multiple
106        for (_, entry) in map {
107            return match entry.typ.as_str() {
108                "Json" => {
109                    let json_var = JsonVar {
110                        json_value: serde_json::from_value(entry.value).map_err(serde::de::Error::custom)?,
111                        value_info: entry.value_info,
112                    };
113                    Ok(ProcessInstanceVariable::Json(json_var))
114                }
115                "Boolean" => {
116                    let bool_var = BoolVar {
117                        value: serde_json::from_value(entry.value).map_err(serde::de::Error::custom)?,
118                        value_info: entry.value_info,
119                    };
120                    Ok(ProcessInstanceVariable::Boolean(bool_var))
121                },
122                "String" => {
123                    let string_var = StringVar {
124                        value: serde_json::from_value(entry.value).map_err(serde::de::Error::custom)?,
125                        value_info: entry.value_info,
126                    };
127                    Ok(ProcessInstanceVariable::String(string_var))
128                },
129                _ => Err(serde::de::Error::custom(format!("unknown type: {}", entry.typ))),
130            };
131        }
132
133        Err(serde::de::Error::custom("no valid entries found"))
134    }
135}
136
137pub fn parse_process_instance_variables(json_str: &str) -> HashMap<String, ProcessInstanceVariable> {
138    // According to Camunda 7/Operaton, the variable endpoint returns an object map of name -> { type, value, valueInfo }
139    let parsed_map: HashMap<String, Entry> = serde_json::from_str(json_str).unwrap_or_else(|_| {
140        error!("Error while parsing \"{}\", ignoring it for now.", json_str);
141        HashMap::new()
142    });
143    let mut result = HashMap::new();
144    for (name, entry) in parsed_map {
145        let parsed_var = match entry.typ.as_str() {
146            "Json" => ProcessInstanceVariable::Json(JsonVar {
147                json_value: serde_json::from_value(entry.value).unwrap(),
148                value_info: entry.value_info,
149            }),
150            "Boolean" => ProcessInstanceVariable::Boolean(BoolVar {
151                value: serde_json::from_value(entry.value).unwrap(),
152                value_info: entry.value_info,
153            }),
154            "String" => ProcessInstanceVariable::String(StringVar {
155                value: serde_json::from_value(entry.value).unwrap(),
156                value_info: entry.value_info,
157            }),
158            _ => continue,
159        };
160        result.insert(name, parsed_var);
161    }
162    result
163}
164
165#[cfg(test)]
166mod test {
167    use crate::structures::process_variables::parse_process_instance_variables;
168
169    #[test]
170    fn test_module_parsing() {
171        let response_string: &str = "{\"checklist_vj3ler\":{\"type\":\"Json\",\"value\":{\"dataFormatName\":\"application/json\",\"value\":false,\"string\":false,\"object\":false,\"boolean\":false,\"number\":false,\"array\":true,\"null\":false,\"nodeType\":\"ARRAY\"},\"valueInfo\":{}},\"checkbox_6ow5yg\":{\"type\":\"Boolean\",\"value\":true,\"valueInfo\":{}}}";
172        let variables = parse_process_instance_variables(response_string);
173        dbg!(&variables);
174        assert!(!variables.is_empty())
175    }
176}