shelly-data 0.4.0

Data-layer primitives for Shelly LiveView (schemas, changesets, repo, migrations).
Documentation
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FieldType {
    String,
    Text,
    Integer,
    Float,
    Boolean,
    Date,
    DateTime,
    Json,
    Uuid,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Field {
    pub name: String,
    pub field_type: FieldType,
    pub required: bool,
}

impl Field {
    pub fn new(name: impl Into<String>, field_type: FieldType) -> Self {
        Self {
            name: name.into(),
            field_type,
            required: false,
        }
    }

    pub fn required(mut self) -> Self {
        self.required = true;
        self
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Schema {
    pub name: String,
    pub table: String,
    pub fields: Vec<Field>,
}

impl Schema {
    pub fn new(name: impl Into<String>, table: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            table: table.into(),
            fields: Vec::new(),
        }
    }

    pub fn with_field(mut self, field: Field) -> Self {
        self.fields.push(field);
        self
    }
}

pub trait SchemaDefinition {
    fn schema() -> Schema;
}

#[cfg(test)]
mod tests {
    use super::{Field, FieldType, Schema, SchemaDefinition};

    #[test]
    fn required_marks_field_as_required() {
        let field = Field::new("title", FieldType::String).required();
        assert_eq!(field.name, "title");
        assert_eq!(field.field_type, FieldType::String);
        assert!(field.required);
    }

    #[test]
    fn schema_builder_collects_fields() {
        let schema = Schema::new("Post", "posts")
            .with_field(Field::new("id", FieldType::Integer).required())
            .with_field(Field::new("title", FieldType::String));

        assert_eq!(schema.name, "Post");
        assert_eq!(schema.table, "posts");
        assert_eq!(schema.fields.len(), 2);
        assert_eq!(schema.fields[0].name, "id");
        assert!(schema.fields[0].required);
    }

    #[test]
    fn schema_definition_trait_can_expose_schema() {
        struct PostSchema;

        impl SchemaDefinition for PostSchema {
            fn schema() -> Schema {
                Schema::new("Post", "posts")
                    .with_field(Field::new("id", FieldType::Integer).required())
                    .with_field(Field::new("title", FieldType::String).required())
            }
        }

        let schema = PostSchema::schema();
        assert_eq!(schema.name, "Post");
        assert_eq!(schema.fields.len(), 2);
        assert!(schema.fields.iter().all(|field| field.required));
    }
}