factorio-prototypes-json 0.1.0

Rust types that parse Factorio's Prototype JSON Format.
Documentation
//! Lower-level types to be used for creating basic fields of other types.

use serde::{Deserialize, Serialize};
use serde_json::Number;

/// A literal has the same format as a [`Type`] that is [`Type::Literal`].
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(untagged)]
pub enum Literal {
    /// A string literal.
    String(String),

    /// A number literal.
    ///
    /// JSON does not distinguish between integers and floats.
    Number(Number),

    /// A boolean literal.
    Boolean(bool),
}

/// A literal value with an optional description.
/// This is often used for enums or default values of properties.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct TypeLiteral {
    /// The value of the literal.
    pub value: Literal,

    /// The text description of the literal, if any.
    pub description: Option<String>,
}

/// A type field can be a string ([`Type::Simple`]), in which case that string is the simple type.
/// Otherwise, a type is an enum and considered a complex type by the documentation.
///
/// The enum variant is determined by the `complex_type` field in the JSON.
/// [`Type`] is internally tagged by the `complex_type` field, which is only present in the JSON and not in the Rust struct.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(tag = "complex_type", rename_all = "snake_case")]
pub enum Type {
    /// An array of other types.
    Array {
        /// The type of the elements of the array.
        value: Box<Type>,
    },

    /// A mapping of keys to values.
    ///
    /// The key is also a [`Type`] but usually is a [`Type::Simple`]. In turn, those types are usually newtypes over strings.
    Dictionary {
        /// The type of the keys of the dictionary.
        key: Box<Type>,

        /// The type of the values of the dictionary.
        value: Box<Type>,
    },

    /// A tuple of multiple types.
    ///
    /// The length of [`Type::Tuple::values`] is the number of elements in the tuple.
    Tuple {
        /// The types of the members of this tuple in order.
        values: Vec<Type>,
    },

    /// A union of multiple types.
    Union {
        /// A list of all compatible types for this type.
        options: Vec<Type>,

        /// Whether the options of this union have a description or not.
        full_format: bool,
    },

    /// A literal value with an optional description.
    Literal(TypeLiteral),

    /// A type with a description.
    ///
    /// This is often used for enums or default values of properties when a description is desired.
    Type {
        /// The actual type.
        ///
        /// This format for types is used when they have descriptions attached to them.
        value: Box<Type>,

        /// The text description of the type.
        description: String,
    },

    /// Special type with additional members listed on the API member's `properties` that used this complex type.
    ///
    /// This includes [`crate::Prototype::properties`] and [`crate::Concept::properties`].
    Struct,

    /// The simple type, such as a builtin or a reference to another [`Type`] or [`crate::Concept`]
    ///
    /// This is untagged and so appears as `"type: "foo"` in the JSON.
    /// Typically, concepts have a `PascalCase` name.
    #[serde(untagged)]
    Simple(String),
}

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

    #[test]
    fn complex_type_array() {
        let json = r#"
{
    "complex_type": "array",
    "value": "foo"
}"#;

        let expected = Type::Array {
            value: Box::new(Type::Simple("foo".into())),
        };

        let actual = serde_json::from_str::<Type>(json).unwrap();
        assert_eq!(actual, expected);
    }

    #[test]
    fn complex_type_dictionary() {
        let json = r#"
{
    "complex_type": "dictionary",
    "key": "AirbornePollutantID",
    "value": "double"
}"#;

        let expected = Type::Dictionary {
            key: Box::new(Type::Simple("AirbornePollutantID".into())),
            value: Box::new(Type::Simple("double".into())),
        };

        let actual = serde_json::from_str::<Type>(json).unwrap();
        assert_eq!(actual, expected);
    }

    #[test]
    fn complex_type_tuple() {
        let json = r#"
{
    "complex_type": "tuple",
    "values": [
        "Vector",
        "Vector",
        "Vector",
        "Vector"
    ]
}"#;

        let expected = Type::Tuple {
            values: vec![
                Type::Simple("Vector".into()),
                Type::Simple("Vector".into()),
                Type::Simple("Vector".into()),
                Type::Simple("Vector".into()),
            ],
        };

        let actual = serde_json::from_str::<Type>(json).unwrap();
        assert_eq!(actual, expected);
    }

    #[test]
    fn complex_type_union() {
        let json = r#"
{
    "complex_type": "union",
    "options": [
        {
            "complex_type": "literal",
            "value": "game-finished"
        },
        {
            "complex_type": "literal",
            "value": "rocket-launched"
        }
    ],
    "full_format": false
}"#;

        let expected = Type::Union {
            options: vec![
                Type::Literal(TypeLiteral {
                    value: Literal::String("game-finished".into()),
                    description: None,
                }),
                Type::Literal(TypeLiteral {
                    value: Literal::String("rocket-launched".into()),
                    description: None,
                }),
            ],
            full_format: false,
        };

        let actual = serde_json::from_str::<Type>(json).unwrap();
        assert_eq!(actual, expected);
    }

    #[test]
    fn complex_type_literal_string() {
        let json = r#"
{
    "complex_type": "literal",
    "value": "foo"
}"#;

        let expected = Type::Literal(TypeLiteral {
            value: Literal::String("foo".into()),
            description: None,
        });

        let actual = serde_json::from_str::<Type>(json).unwrap();
        assert_eq!(actual, expected);
    }

    #[test]
    fn complex_type_literal_number() {
        let json = r#"
{
    "complex_type": "literal",
    "value": 1
}"#;

        let expected = Type::Literal(TypeLiteral {
            value: Literal::Number(1.into()),
            description: None,
        });

        let actual = serde_json::from_str::<Type>(json).unwrap();
        assert_eq!(expected, actual);
    }

    #[test]
    fn complex_type_literal_bool() {
        let json = r#"
{
    "complex_type": "literal",
    "value": true
}"#;

        let expected = Type::Literal(TypeLiteral {
            value: Literal::Boolean(true),
            description: None,
        });

        let actual = serde_json::from_str::<Type>(json).unwrap();
        assert_eq!(expected, actual);
    }

    #[test]
    fn complex_type_literal_description() {
        let json = r#"
{
    "complex_type": "literal",
    "value": true,
    "description": "foo"
}"#;

        let expected = Type::Literal(TypeLiteral {
            value: Literal::Boolean(true),
            description: Some("foo".into()),
        });

        let actual = serde_json::from_str::<Type>(json).unwrap();
        assert_eq!(expected, actual);
    }

    #[test]
    fn complex_type_type() {
        let json = r#"
{
    "complex_type": "type",
    "value": "AccumulatorPrototype",
    "description": "`'accumulator'`"
}"#;

        let expected = Type::Type {
            value: Box::new(Type::Simple("AccumulatorPrototype".into())),
            description: "`'accumulator'`".into(),
        };

        let actual = serde_json::from_str::<Type>(json).unwrap();
        assert_eq!(actual, expected);
    }

    #[test]
    fn complex_type_struct() {
        let json = r#"
{
    "complex_type": "struct"
}"#;

        let expected = Type::Struct;
        let actual = serde_json::from_str::<Type>(json).unwrap();
        assert_eq!(actual, expected);
    }

    #[test]
    fn simple() {
        let json = r#""foo""#;
        let expected = Type::Simple("foo".into());
        let actual = serde_json::from_str::<Type>(json).unwrap();
        assert_eq!(expected, actual);
    }
}