shopify_function 2.1.0

Crate to write Shopify Functions in Rust.
Documentation
use shopify_function_wasm_api::{
    read::Error as ReadError, write::Error as WriteError, Context, Deserialize, Serialize, Value,
};
use std::collections::BTreeMap;

#[derive(Debug, PartialEq, Clone)]
pub enum JsonValue {
    Null,
    String(String),
    Number(f64),
    Boolean(bool),
    Object(BTreeMap<String, JsonValue>),
    Array(Vec<JsonValue>),
}

impl Deserialize for JsonValue {
    fn deserialize(value: &Value) -> Result<Self, ReadError> {
        if value.is_null() {
            Ok(Self::Null)
        } else if let Some(b) = value.as_bool() {
            Ok(Self::Boolean(b))
        } else if let Some(n) = value.as_number() {
            Ok(Self::Number(n))
        } else if let Some(s) = value.as_string() {
            Ok(Self::String(s))
        } else if let Some(array_len) = value.array_len() {
            let mut array = Vec::with_capacity(array_len);
            for i in 0..array_len {
                let item = value.get_at_index(i);
                array.push(Self::deserialize(&item)?);
            }
            Ok(Self::Array(array))
        } else if let Some(object_len) = value.obj_len() {
            let mut object = BTreeMap::new();
            for i in 0..object_len {
                let key = value
                    .get_obj_key_at_index(i)
                    .ok_or(ReadError::InvalidType)?;
                let value = value.get_at_index(i);
                object.insert(key.to_string(), Self::deserialize(&value)?);
            }
            Ok(Self::Object(object))
        } else {
            Err(ReadError::InvalidType)
        }
    }
}

impl Serialize for JsonValue {
    fn serialize(&self, context: &mut Context) -> Result<(), WriteError> {
        match self {
            Self::Null => context.write_null(),
            Self::String(s) => context.write_utf8_str(s),
            Self::Number(n) => context.write_f64(*n),
            Self::Boolean(b) => context.write_bool(*b),
            Self::Object(o) => context.write_object(
                |ctx| {
                    for (key, value) in o {
                        ctx.write_utf8_str(key)?;
                        value.serialize(ctx)?;
                    }
                    Ok(())
                },
                o.len(),
            ),
            Self::Array(a) => context.write_array(
                |ctx| {
                    for value in a {
                        value.serialize(ctx)?;
                    }
                    Ok(())
                },
                a.len(),
            ),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_deserialize() {
        let json = serde_json::json!({
            "null": null,
            "string": "string",
            "number": 123,
            "boolean": true,
            "object": {
                "key": "value"
            },
            "array": [1, 2, 3]
        });
        let context = Context::new_with_input(json);
        let value = context.input_get().unwrap();
        let json_value = JsonValue::deserialize(&value).unwrap();
        assert_eq!(
            json_value,
            JsonValue::Object(BTreeMap::from([
                ("null".to_string(), JsonValue::Null),
                (
                    "string".to_string(),
                    JsonValue::String("string".to_string())
                ),
                ("number".to_string(), JsonValue::Number(123.0)),
                ("boolean".to_string(), JsonValue::Boolean(true)),
                (
                    "object".to_string(),
                    JsonValue::Object(BTreeMap::from([(
                        "key".to_string(),
                        JsonValue::String("value".to_string())
                    )]))
                ),
                (
                    "array".to_string(),
                    JsonValue::Array(vec![
                        JsonValue::Number(1.0),
                        JsonValue::Number(2.0),
                        JsonValue::Number(3.0)
                    ])
                )
            ]))
        );
    }

    #[test]
    fn test_serialize() {
        let kitchen_sink_value = JsonValue::Object(BTreeMap::from([
            ("null".to_string(), JsonValue::Null),
            (
                "string".to_string(),
                JsonValue::String("string".to_string()),
            ),
            ("number".to_string(), JsonValue::Number(123.0)),
            ("boolean".to_string(), JsonValue::Boolean(true)),
            (
                "object".to_string(),
                JsonValue::Object(BTreeMap::from([(
                    "key".to_string(),
                    JsonValue::String("value".to_string()),
                )])),
            ),
            (
                "array".to_string(),
                JsonValue::Array(vec![
                    JsonValue::Number(1.0),
                    JsonValue::Number(2.0),
                    JsonValue::Number(3.0),
                ]),
            ),
        ]));

        let mut context = Context::new_with_input(serde_json::json!({}));
        kitchen_sink_value.serialize(&mut context).unwrap();
        let output = context.finalize_output_and_return().unwrap();
        let expected = serde_json::json!({
            "null": null,
            "string": "string",
            "number": 123.0,
            "boolean": true,
            "object": {
                "key": "value"
            },
            "array": [1.0, 2.0, 3.0]
        });
        assert_eq!(output, expected);
    }
}