flowstdlib 1.0.0

The standard library of functions and flows for 'flow' programs
Documentation
use serde_json::{json, Value};

use flowcore::errors::Result;
use flowcore::flow_output;
use flowcore::model::datatype::{
    ARRAY_TYPE, BOOLEAN_TYPE, NULL_TYPE, NUMBER_TYPE, OBJECT_TYPE, STRING_TYPE,
};
use flowcore::RunAgain;
use flowmacro::flow_function;

fn type_string(value: &Value) -> Result<String> {
    match value {
        Value::String(_) => Ok(STRING_TYPE.into()),
        Value::Bool(_) => Ok(BOOLEAN_TYPE.into()),
        Value::Number(_) => Ok(NUMBER_TYPE.into()),
        Value::Array(array) => Ok(format!(
            "{ARRAY_TYPE}/{}",
            type_string(array.first().ok_or("Could not get array")?)?
        )),
        Value::Object(map) => {
            if let Some(value) = &map.values().next().cloned() {
                Ok(format!("{OBJECT_TYPE}/{}", type_string(value)?))
            } else {
                Ok(format!("{OBJECT_TYPE}/Unknown"))
            }
        }
        Value::Null => Ok(NULL_TYPE.into()),
    }
}

#[flow_function]
fn inner_info(input: &Value) -> Result<(Option<Value>, RunAgain)> {
    let (rows, cols) = match input {
        Value::String(string) => (1, string.len()),
        Value::Bool(_boolean) => (1, 1),
        Value::Number(_number) => (1, 1),
        Value::Array(vec) => match vec.first().ok_or("Could not get array")? {
            Value::Array(row) => (vec.len(), row.len()), // Array of Arrays
            _ => (1, vec.len()),
        },
        Value::Object(map) => (map.len(), 2),
        Value::Null => (0, 0),
    };

    let data_type = type_string(input)?;
    flow_output!(
        "rows" => json!(rows),
        "columns" => json!(cols),
        "type" => json!(data_type),
    )
}

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod test {
    use serde_json::json;
    use serde_json::{Map, Value};

    use flowcore::model::datatype::ARRAY_TYPE;
    use flowcore::model::datatype::BOOLEAN_TYPE;
    use flowcore::model::datatype::NULL_TYPE;
    use flowcore::model::datatype::STRING_TYPE;
    use flowcore::model::datatype::{NUMBER_TYPE, OBJECT_TYPE};

    use super::inner_info;

    #[test]
    fn info_on_number() {
        let (result, _) = inner_info(&json!(1)).expect("_info() failed");
        let output_map = result.expect("Could not get the Value from the output");

        assert_eq!(
            output_map
                .pointer("/type")
                .expect("Could not get the /type from the output"),
            &json!(NUMBER_TYPE)
        );
        assert_eq!(
            output_map
                .pointer("/rows")
                .expect("Could not get the /rows from the output"),
            &json!(1)
        );
        assert_eq!(
            output_map
                .pointer("/columns")
                .expect("Could not get the /columns from the output"),
            &json!(1)
        );
    }

    #[test]
    fn info_on_boolean() {
        let (result, _) = inner_info(&json!(true)).expect("_info() failed");
        let output_map = result.expect("Could not get the Value from the output");

        assert_eq!(
            output_map
                .pointer("/type")
                .expect("Could not get the /type from the output"),
            &json!(BOOLEAN_TYPE)
        );
        assert_eq!(
            output_map
                .pointer("/rows")
                .expect("Could not get the /row from the output"),
            &json!(1)
        );
        assert_eq!(
            output_map
                .pointer("/columns")
                .expect("Could not get the /columns from the output"),
            &json!(1)
        );
    }

    #[test]
    fn info_on_string() {
        let string = json!("Hello");
        let (result, _) = inner_info(&string).expect("_info() failed");
        let output_map = result.expect("Could not get the Value from the output");

        assert_eq!(
            output_map
                .pointer("/type")
                .expect("Could not get the /type from the output"),
            &json!(STRING_TYPE)
        );
        assert_eq!(
            output_map
                .pointer("/rows")
                .expect("Could not get the /row from the output"),
            &json!(1)
        );
        assert_eq!(
            output_map
                .pointer("/columns")
                .expect("Could not get the /columns from the output"),
            &json!(5)
        );
    }

    #[test]
    fn info_on_null() {
        let (result, _) = inner_info(&Value::Null).expect("_info() failed");
        let output_map = result.expect("Could not get the Value from the output");

        assert_eq!(
            output_map
                .pointer("/rows")
                .expect("Could not get the /rows from the output"),
            &json!(0)
        );
        assert_eq!(
            output_map
                .pointer("/type")
                .expect("Could not get the /type from the output"),
            &json!(NULL_TYPE)
        );
        assert_eq!(
            output_map
                .pointer("/columns")
                .expect("Could not get the /column from the output"),
            &json!(0)
        );
    }

    #[test]
    fn info_on_array_of_number() {
        let (result, _) = inner_info(&json!([1, 2, 3])).expect("_info() failed");
        let output_map = result.expect("Could not get the Value from the output");

        assert_eq!(
            output_map
                .pointer("/type")
                .expect("Could not get the /type from the output"),
            &json!(format!("{ARRAY_TYPE}/{NUMBER_TYPE}"))
        );
        assert_eq!(
            output_map
                .pointer("/rows")
                .expect("Could not get the /rows from the output"),
            &json!(1)
        );
        assert_eq!(
            output_map
                .pointer("/columns")
                .expect("Could not get the /column from the output"),
            &json!(3)
        );
    }

    #[test]
    fn info_on_array_of_array_of_number() {
        let array_array_numbers = json!([[1, 2, 3], [4, 5, 6]]);
        let (result, _) = inner_info(&array_array_numbers).expect("_info() failed");
        let output_map = result.expect("Could not get the Value from the output");

        assert_eq!(
            output_map
                .pointer("/type")
                .expect("Could not get the /type from the output"),
            &json!(format!("{ARRAY_TYPE}/{ARRAY_TYPE}/{NUMBER_TYPE}"))
        );
        assert_eq!(
            output_map
                .pointer("/rows")
                .expect("Could not get the /rows from the output"),
            &json!(2)
        );
        assert_eq!(
            output_map
                .pointer("/columns")
                .expect("Could not get the /columns from the output"),
            &json!(3)
        );
    }

    #[test]
    fn info_on_map_of_number() {
        let mut map = Map::new();
        map.insert("0".into(), json!(1));
        map.insert("1".into(), json!(2));
        let input = Value::Object(map);
        let (result, _) = inner_info(&input).expect("_info() failed");
        let output_map = result.expect("Could not get the Value from the output");

        assert_eq!(
            output_map
                .pointer("/type")
                .expect("Could not get the /type from the output"),
            &json!(format!("{OBJECT_TYPE}/{NUMBER_TYPE}"))
        );
        assert_eq!(
            output_map
                .pointer("/rows")
                .expect("Could not get the /rows from the output"),
            &json!(2)
        );
        assert_eq!(
            output_map
                .pointer("/columns")
                .expect("Could not get the /columns from the output"),
            &json!(2)
        );
    }

    #[test]
    fn info_on_map_of_arrays_of_number() {
        let mut map = Map::new();
        map.insert("0".into(), json!([1, 2]));
        map.insert("1".into(), json!([3, 4]));
        let input = Value::Object(map);
        let (result, _) = inner_info(&input).expect("_info() failed");
        let output_map = result.expect("Could not get the Value from the output");

        assert_eq!(
            output_map
                .pointer("/type")
                .expect("Could not get the /type from the output"),
            &json!(format!("{OBJECT_TYPE}/{ARRAY_TYPE}/{NUMBER_TYPE}"))
        );
        assert_eq!(
            output_map
                .pointer("/rows")
                .expect("Could not get the /rows from the output"),
            &json!(2)
        );
        assert_eq!(
            output_map
                .pointer("/columns")
                .expect("Could not get the /columns from the output"),
            &json!(2)
        );
    }
}