stepflow_json/
lib.rs

1//! Parse [StepFlow](https://stepflow.dev) Flow definitions in JSON for a [`Session`](stepflow::Session)
2//!
3//! The main function to use is [`parse_session_json`]
4//!
5//! # Examples
6//! ```
7//! # use stepflow::{ Session, SessionId };
8//! # use stepflow_json::parse_session_json;
9//! const JSON: &str = r#"
10//! {
11//!     "vars": {
12//!         "name": "String",
13//!         "email": "Email"
14//!     },
15//!     "steps": {
16//!        "$root": {
17//!            "substeps": ["nameStep", "emailStep"],
18//!            "outputs": ["name","email"]
19//!        },
20//!        "nameStep": {
21//!            "outputs": ["name"]
22//!        },
23//!        "emailStep": {
24//!            "outputs": ["email"]
25//!        }
26//!     },
27//!     "actions": {
28//!         "$all": { "type": "HtmlForm" }
29//!     }
30//! }"#;
31//!
32//! // Parse JSON to a Session
33//! let mut session = Session::new(SessionId::new(0));
34//! parse_session_json(&mut session, JSON, false).unwrap();
35//! ```
36
37mod session;
38pub use session::parse_session_json;
39
40mod data;
41pub use data::{parse_statedata_json, statedata_from_jsonval_obj, json_value_from_statedata, json_value_from_val};
42
43mod error;
44pub use error::StepFlowParseError;
45
46mod json;
47mod step;
48mod action;
49
50
51#[cfg(test)]
52mod tests {
53    use std::collections::HashMap;
54    use tinyjson::JsonValue;
55    use stepflow_test_util::test_id;
56    use stepflow::data::{StringVar, StringValue};
57    use stepflow::{Session, SessionId, AdvanceBlockedOn};
58    use stepflow::prelude::*;
59    use super::{ parse_session_json, statedata_from_jsonval_obj, StepFlowParseError };
60
61    const JSON: &str = r#"
62    {
63        "vars": {
64            "first_name": "String",
65            "last_name": "String",
66            "email": "Email",
67            "email_waited": "True",
68            "nothing": "Bool"
69        },
70        "steps": {
71           "$root": {
72               "substeps": ["name", "email"],
73               "outputs": ["first_name","last_name","email", "email_waited"]
74           },
75           "name": {
76               "outputs": ["first_name","last_name"]
77           },
78           "email": {
79               "outputs": ["email", "email_waited"]
80           }
81        },
82        "actions": {
83            "$all": {
84                "type": "UriStringTemplate",
85                "template": "/base-path/{{step}}"
86            },
87            "email": {
88                "type": "SetData",
89                "stateData": {
90                    "email_waited": "true"
91                },
92                "afterAttempt": 2
93            }
94        }
95    }"#;
96
97    pub fn create_session(json: &str, allow_implicit_var: bool) -> Result<Session, StepFlowParseError> {
98        let mut session = Session::new(test_id!(SessionId));
99        parse_session_json(&mut session, json, allow_implicit_var)?;
100        Ok(session)
101    }
102
103    #[test]
104    fn deserialize() {
105        let mut session = create_session(JSON, false).unwrap();
106        let name_stepid = session.step_store().get_by_name("name").unwrap().id().clone();
107        let email_stepid = session.step_store().get_by_name("email").unwrap().id().clone();
108        let _firstname_var_id = session.var_store().get_by_name("first_name").unwrap().id().clone();
109        let _email_waited_varid = session.var_store().get_by_name("email_waited").unwrap().id().clone();
110        let uri_action_id = session.action_store().id_from_name("$all").unwrap().clone();
111
112        // advance to first step (name)
113        let name_advance = session.advance(None).unwrap();
114        assert_eq!(name_advance, AdvanceBlockedOn::ActionStartWith(uri_action_id.clone(), "/base-path/name".parse::<StringValue>().unwrap().boxed()));
115
116        // try advancing without setting name and fail
117        let name_advance_fail = session.advance(None).unwrap();
118        assert_eq!(
119            name_advance_fail, 
120            AdvanceBlockedOn::ActionStartWith(uri_action_id.clone(), "/base-path/name".parse::<StringValue>().unwrap().boxed()));
121
122        // advance to next step (email) - fail setdata (attempt #1) so get URI action result
123        let mut data_name = HashMap::new();
124        data_name.insert("first_name".to_owned(), JsonValue::String("billy".to_owned()));
125        data_name.insert("last_name".to_owned(), JsonValue::String("bob".to_owned()));
126        let statedata_name = statedata_from_jsonval_obj(&data_name, session.var_store()).unwrap();
127        let name_advance_success = session.advance(Some((&name_stepid,  statedata_name))).unwrap();
128        assert_eq!(name_advance_success, AdvanceBlockedOn::ActionStartWith(uri_action_id.clone(), "/base-path/email".parse::<StringValue>().unwrap().boxed()));
129
130        // put in email and try advancing -- fail setdata (attempt #2) because email waited setdata action hasn't fired so get URI action result
131        let mut data_email = HashMap::new();
132        data_email.insert("email".to_owned(), JsonValue::String("a@b.com".to_owned()));
133        let statedata_email = statedata_from_jsonval_obj(&data_email, session.var_store()).unwrap();
134        let name_advance_success = session.advance(Some((&email_stepid,  statedata_email))).unwrap();
135        assert_eq!(name_advance_success, AdvanceBlockedOn::ActionStartWith(uri_action_id.clone(), "/base-path/email".parse::<StringValue>().unwrap().boxed()));
136
137        // try advancing again -- success with setdata firing and we're finished
138        let name_advance_success = session.advance(None).unwrap();
139        assert_eq!(name_advance_success, AdvanceBlockedOn::FinishedAdvancing);
140    }
141
142    #[test]
143    fn session_ids() {
144        let session1 = create_session(JSON, false).unwrap();
145        let session2 = create_session(JSON, false).unwrap();
146        assert_ne!(session1.id(), session2.id());
147    }
148
149    #[test]
150    fn implicit_vars() {
151        let json = r#"
152        {
153            "steps": {
154                "$root": {
155                    "substeps": ["step1"],
156                    "outputs": ["test_output"]
157                },
158                "step1": { "inputs": ["test_input"], "outputs": ["test_output"] }
159            },
160            "actions": {
161                "$all": { "type": "HtmlForm" }
162            }
163        }
164        "#;
165        let json = json.to_string();
166
167        // expect error when we don't allow implicit var
168        assert!(matches!(create_session(&json[..], false), Err(_)));
169
170        // create session
171        let session = create_session(&json[..], true).unwrap();
172
173        assert_eq!(session.var_store().iter_names().count(), 2);
174        let input_var = session.var_store().get_by_name("test_input").unwrap();
175        assert!(input_var.is::<StringVar>());
176        let output_var = session.var_store().get_by_name("test_output").unwrap();
177        assert!(output_var.is::<StringVar>());
178    }
179}