stepflow-json 0.0.3

StepFlow flow definitions using JSON
Documentation
//! Parse [StepFlow](https://stepflow.dev) Flow definitions in JSON for a [`Session`](stepflow::Session)
//!
//! The main function to use is [`parse_session_json`]
//!
//! # Examples
//! ```
//! # use stepflow::{ Session, SessionId };
//! # use stepflow_json::parse_session_json;
//! const JSON: &str = r#"
//! {
//!     "vars": {
//!         "name": "String",
//!         "email": "Email"
//!     },
//!     "steps": {
//!        "$root": {
//!            "substeps": ["nameStep", "emailStep"],
//!            "outputs": ["name","email"]
//!        },
//!        "nameStep": {
//!            "outputs": ["name"]
//!        },
//!        "emailStep": {
//!            "outputs": ["email"]
//!        }
//!     },
//!     "actions": {
//!         "$all": { "type": "HtmlForm" }
//!     }
//! }"#;
//!
//! // Parse JSON to a Session
//! let mut session = Session::new(SessionId::new(0));
//! parse_session_json(&mut session, JSON, false).unwrap();
//! ```

mod session;
pub use session::parse_session_json;

mod data;
pub use data::{parse_statedata_json, statedata_from_jsonval_obj, json_value_from_statedata, json_value_from_val};

mod error;
pub use error::StepFlowParseError;

mod json;
mod step;
mod action;


#[cfg(test)]
mod tests {
    use std::collections::HashMap;
    use tinyjson::JsonValue;
    use stepflow_test_util::test_id;
    use stepflow::data::{StringVar, StringValue};
    use stepflow::{Session, SessionId, AdvanceBlockedOn};
    use stepflow::prelude::*;
    use super::{ parse_session_json, statedata_from_jsonval_obj, StepFlowParseError };

    const JSON: &str = r#"
    {
        "vars": {
            "first_name": "String",
            "last_name": "String",
            "email": "Email",
            "email_waited": "True",
            "nothing": "Bool"
        },
        "steps": {
           "$root": {
               "substeps": ["name", "email"],
               "outputs": ["first_name","last_name","email", "email_waited"]
           },
           "name": {
               "outputs": ["first_name","last_name"]
           },
           "email": {
               "outputs": ["email", "email_waited"]
           }
        },
        "actions": {
            "$all": {
                "type": "UriStringTemplate",
                "template": "/base-path/{{step}}"
            },
            "email": {
                "type": "SetData",
                "stateData": {
                    "email_waited": "true"
                },
                "afterAttempt": 2
            }
        }
    }"#;

    pub fn create_session(json: &str, allow_implicit_var: bool) -> Result<Session, StepFlowParseError> {
        let mut session = Session::new(test_id!(SessionId));
        parse_session_json(&mut session, json, allow_implicit_var)?;
        Ok(session)
    }

    #[test]
    fn deserialize() {
        let mut session = create_session(JSON, false).unwrap();
        let name_stepid = session.step_store().get_by_name("name").unwrap().id().clone();
        let email_stepid = session.step_store().get_by_name("email").unwrap().id().clone();
        let _firstname_var_id = session.var_store().get_by_name("first_name").unwrap().id().clone();
        let _email_waited_varid = session.var_store().get_by_name("email_waited").unwrap().id().clone();
        let uri_action_id = session.action_store().id_from_name("$all").unwrap().clone();

        // advance to first step (name)
        let name_advance = session.advance(None).unwrap();
        assert_eq!(name_advance, AdvanceBlockedOn::ActionStartWith(uri_action_id.clone(), "/base-path/name".parse::<StringValue>().unwrap().boxed()));

        // try advancing without setting name and fail
        let name_advance_fail = session.advance(None).unwrap();
        assert_eq!(
            name_advance_fail, 
            AdvanceBlockedOn::ActionStartWith(uri_action_id.clone(), "/base-path/name".parse::<StringValue>().unwrap().boxed()));

        // advance to next step (email) - fail setdata (attempt #1) so get URI action result
        let mut data_name = HashMap::new();
        data_name.insert("first_name".to_owned(), JsonValue::String("billy".to_owned()));
        data_name.insert("last_name".to_owned(), JsonValue::String("bob".to_owned()));
        let statedata_name = statedata_from_jsonval_obj(&data_name, session.var_store()).unwrap();
        let name_advance_success = session.advance(Some((&name_stepid,  statedata_name))).unwrap();
        assert_eq!(name_advance_success, AdvanceBlockedOn::ActionStartWith(uri_action_id.clone(), "/base-path/email".parse::<StringValue>().unwrap().boxed()));

        // put in email and try advancing -- fail setdata (attempt #2) because email waited setdata action hasn't fired so get URI action result
        let mut data_email = HashMap::new();
        data_email.insert("email".to_owned(), JsonValue::String("a@b.com".to_owned()));
        let statedata_email = statedata_from_jsonval_obj(&data_email, session.var_store()).unwrap();
        let name_advance_success = session.advance(Some((&email_stepid,  statedata_email))).unwrap();
        assert_eq!(name_advance_success, AdvanceBlockedOn::ActionStartWith(uri_action_id.clone(), "/base-path/email".parse::<StringValue>().unwrap().boxed()));

        // try advancing again -- success with setdata firing and we're finished
        let name_advance_success = session.advance(None).unwrap();
        assert_eq!(name_advance_success, AdvanceBlockedOn::FinishedAdvancing);
    }

    #[test]
    fn session_ids() {
        let session1 = create_session(JSON, false).unwrap();
        let session2 = create_session(JSON, false).unwrap();
        assert_ne!(session1.id(), session2.id());
    }

    #[test]
    fn implicit_vars() {
        let json = r#"
        {
            "steps": {
                "$root": {
                    "substeps": ["step1"],
                    "outputs": ["test_output"]
                },
                "step1": { "inputs": ["test_input"], "outputs": ["test_output"] }
            },
            "actions": {
                "$all": { "type": "HtmlForm" }
            }
        }
        "#;
        let json = json.to_string();

        // expect error when we don't allow implicit var
        assert!(matches!(create_session(&json[..], false), Err(_)));

        // create session
        let session = create_session(&json[..], true).unwrap();

        assert_eq!(session.var_store().iter_names().count(), 2);
        let input_var = session.var_store().get_by_name("test_input").unwrap();
        assert!(input_var.is::<StringVar>());
        let output_var = session.var_store().get_by_name("test_output").unwrap();
        assert!(output_var.is::<StringVar>());
    }
}