stepflow_serde/
lib.rs

1//! Provides structures for [`Serde`](::serde) to simplify deserialization of a [`Session`](stepflow::Session)
2//!
3//! The main object to use with `Serde` is [`SessionSerde`].
4//!
5//! # Examples
6//! ```
7//! # use stepflow::SessionId;
8//! # use stepflow_serde::SessionSerde;
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 session_serde: SessionSerde = serde_json::from_str(JSON).unwrap();
34//! let session = session_serde.into_session::<serde_json::Error>(SessionId::new(0), false).unwrap();
35//!
36//! ```
37
38mod session;
39pub use session::SessionSerde;
40
41mod data;
42pub use data::StateDataSerde;
43
44mod errors;
45pub use errors::SerdeError;
46
47mod var;
48use var::VarSerde;
49
50mod step;
51use step::StepSerde;
52
53mod action;
54use action::ActionSerde;
55
56
57#[cfg(test)]
58mod tests {
59    use std::collections::HashMap;
60    use serde_json::json;
61    use stepflow_test_util::test_id;
62    use stepflow::data::{StringVar, StringValue};
63    use stepflow::{Session, SessionId, AdvanceBlockedOn};
64    use stepflow::prelude::*;
65    use super::{ SessionSerde, StateDataSerde, SerdeError};
66
67    const JSON: &str = r#"
68    {
69        "vars": {
70            "first_name": "String",
71            "last_name": "String",
72            "email": "Email",
73            "email_waited": "True",
74            "nothing": "Bool"
75        },
76        "steps": {
77           "$root": {
78               "substeps": ["name", "email"],
79               "outputs": ["first_name","last_name","email", "email_waited"]
80           },
81           "name": {
82               "outputs": ["first_name","last_name"]
83           },
84           "email": {
85               "outputs": ["email", "email_waited"]
86           }
87        },
88        "actions": {
89            "$all": {
90                "type": "stringTemplate",
91                "template": "/base-path/{{step}}",
92                "escapeFor": "uri"
93            },
94            "email": {
95                "type": "setData",
96                "stateData": {
97                    "email_waited": "true"
98                },
99                "afterAttempt": 2
100            }
101        }
102    }"#;
103
104    pub fn create_session(json: &str, allow_implicit_var: bool) -> Result<Session, SerdeError<serde_json::Error>> {
105        let session_serde: SessionSerde = serde_json::from_str(json).map_err(|e| SerdeError::InvalidFormat(e))?;
106        let session = session_serde.into_session(test_id!(SessionId), allow_implicit_var)?;
107        Ok(session)
108    }
109
110    #[test]
111    fn deserialize() {
112        let mut session = create_session(JSON, false).unwrap();
113        let name_stepid = session.step_store().get_by_name("name").unwrap().id().clone();
114        let email_stepid = session.step_store().get_by_name("email").unwrap().id().clone();
115        let _firstname_var_id = session.var_store().get_by_name("first_name").unwrap().id().clone();
116        let _email_waited_varid = session.var_store().get_by_name("email_waited").unwrap().id().clone();
117        let uri_action_id = session.action_store().id_from_name("$all").unwrap().clone();
118
119        // advance to first step (name)
120        let name_advance = session.advance(None).unwrap();
121        assert_eq!(name_advance, AdvanceBlockedOn::ActionStartWith(uri_action_id.clone(), "/base-path/name".parse::<StringValue>().unwrap().boxed()));
122
123        // try advancing without setting name and fail
124        let name_advance_fail = session.advance(None).unwrap();
125        assert_eq!(
126            name_advance_fail, 
127            AdvanceBlockedOn::ActionStartWith(uri_action_id.clone(), "/base-path/name".parse::<StringValue>().unwrap().boxed()));
128
129        // advance to next step (email) - fail setdata (attempt #1) so get URI action result
130        let mut data_name = HashMap::new();
131        data_name.insert("first_name".to_owned(), "billy".to_owned());
132        data_name.insert("last_name".to_owned(), "bob".to_owned());
133        let statedata_name = StateDataSerde::new(data_name).to_statedata(session.var_store()).unwrap();
134        let name_advance_success = session.advance(Some((&name_stepid,  statedata_name))).unwrap();
135        assert_eq!(name_advance_success, AdvanceBlockedOn::ActionStartWith(uri_action_id.clone(), "/base-path/email".parse::<StringValue>().unwrap().boxed()));
136
137        // put in email and try advancing -- fail setdata (attempt #2) because email waited setdata action hasn't fired so get URI action result
138        let mut data_email = HashMap::new();
139        data_email.insert("email".to_owned(), "a@b.com".to_owned());
140        let statedata_email = StateDataSerde::new(data_email).to_statedata(session.var_store()).unwrap();
141        let name_advance_success = session.advance(Some((&email_stepid,  statedata_email))).unwrap();
142        assert_eq!(name_advance_success, AdvanceBlockedOn::ActionStartWith(uri_action_id.clone(), "/base-path/email".parse::<StringValue>().unwrap().boxed()));
143
144        // try advancing again -- success with setdata firing and we're finished
145        let name_advance_success = session.advance(None).unwrap();
146        assert_eq!(name_advance_success, AdvanceBlockedOn::FinishedAdvancing);
147    }
148
149    #[test]
150    fn session_ids() {
151        let session1 = create_session(JSON, false).unwrap();
152        let session2 = create_session(JSON, false).unwrap();
153        assert_ne!(session1.id(), session2.id());
154    }
155
156    #[test]
157    fn implicit_vars() {
158        let json = json!({
159            "steps": {
160                "$root": {
161                    "substeps": ["step1"],
162                    "outputs": ["test_output"]
163                },
164                "step1": { "inputs": ["test_input"], "outputs": ["test_output"] }
165            },
166            "actions": {
167                "$all": { "type": "htmlForm" }
168            }
169        });
170        let json = json.to_string();
171
172        // expect error when we don't allow implicit var
173        assert!(matches!(create_session(&json[..], false), Err(_)));
174
175        // create session
176        let session = create_session(&json[..], true).unwrap();
177
178        assert_eq!(session.var_store().iter_names().count(), 2);
179        let input_var = session.var_store().get_by_name("test_input").unwrap();
180        assert!(input_var.is::<StringVar>());
181        let output_var = session.var_store().get_by_name("test_output").unwrap();
182        assert!(output_var.is::<StringVar>());
183    }
184}