metamorphose 0.1.18-alpha.1

Macros collection for converting Structure to Model, for a mango-orm project.
Documentation
use metamorphose::Model;

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

// Simulate forwarding from application settings
const _SERVICE_NAME: &str = "test_service_name";
const _DATABASE_NAME: &str = "test_database_name";

// For Models
// *************************************************************************************************
// Model metadata
#[derive(Default, Deserialize, PartialEq, Debug)]
pub struct Meta {
    pub model_name: String,
    pub service_name: String,
    pub database_name: String,
    pub collection_name: String,
    pub fields_count: usize,
    pub fields_name: Vec<String>,
    pub map_field_type: std::collections::HashMap<String, String>,
    pub map_widget_type: std::collections::HashMap<String, String>,
    pub map_relation_models: HashMap<String, HashMap<String, String>>,
    // List of field names that will not be saved to the database
    pub ignore_fields: Vec<String>,
}

pub trait ToModel {
    // Get Model info
    fn meta() -> Result<Meta, Box<dyn std::error::Error>>;
    // Get map of widgets for model fields
    // <field name, Widget>
    fn widgets() -> Result<HashMap<String, Widget>, Box<dyn std::error::Error>>;
}

// For Widgets
// *************************************************************************************************
#[derive(Deserialize, PartialEq, Debug)]
pub struct Widget {
    pub id: String, // "model-name--field-name" ( The value is determined automatically )
    pub label: String,
    pub widget: String,
    pub input_type: String, // The value is determined automatically
    pub name: String,       // The value is determined automatically
    pub value: String,
    pub placeholder: String,
    pub pattern: String, // Validating a field using a client-side regex
    pub minlength: usize,
    pub maxlength: usize,
    pub required: bool,
    pub checked: bool, // For <input type="checkbox|radio">
    pub unique: bool,
    pub hidden: bool,
    pub disabled: bool,
    pub readonly: bool,
    pub step: String,
    pub min: String,
    pub max: String,
    pub other_attrs: String, // "autofocus multiple size=\"some number\" ..."
    pub css_classes: String, // "class-name class-name ..."
    pub choices: Vec<(String, String)>,
    pub hint: String,
    pub warning: String, // The value is determined automatically
    pub error: String,   // The value is determined automatically
}

impl Default for Widget {
    fn default() -> Self {
        Widget {
            id: String::new(),
            label: String::new(),
            widget: String::from("inputText"),
            input_type: String::from("text"),
            name: String::new(),
            value: String::new(),
            placeholder: String::new(),
            pattern: String::new(),
            minlength: 0_usize,
            maxlength: 256_usize,
            required: false,
            checked: false,
            unique: false,
            hidden: false,
            disabled: false,
            readonly: false,
            step: String::from("0"),
            min: String::from("0"),
            max: String::from("0"),
            other_attrs: String::new(),
            css_classes: String::new(),
            choices: Vec::new(),
            hint: String::new(),
            warning: String::new(),
            error: String::new(),
        }
    }
}

// For transporting of Widgets map to implementation of methods
// <field name, Widget>
#[derive(Deserialize)]
struct TransMapWidgets {
    pub map_widgets: std::collections::HashMap<String, Widget>,
}

// Create Model for Category
// *************************************************************************************************
#[Model]
#[derive(Serialize, Deserialize, Default, Clone, Debug)]
pub struct Category1 {
    #[field_attrs(
        widget = "inputText",
        default = "Category Name",
        maxlength = 60,
        unique = true
    )]
    pub title: String,
}

#[Model]
#[derive(Serialize, Deserialize, Default, Clone, Debug)]
pub struct Category2 {
    #[field_attrs(
        widget = "inputText",
        default = "Category Name",
        maxlength = 60,
        unique = true
    )]
    pub title: String,
}

#[Model]
#[derive(Serialize, Deserialize, Default, Clone, Debug)]
pub struct Category3 {
    #[field_attrs(
        widget = "inputText",
        default = "Category Name",
        maxlength = 60,
        unique = true
    )]
    pub title: String,
}

// Create Model
// *************************************************************************************************
#[Model(
    DatabaseName = "test_database_name_2",
    IgnoreFields = "email_confirm, password_confirm"
)]
#[derive(Serialize, Deserialize, Default, Clone, Debug)]
pub struct UserProfile {
    #[field_attrs(widget = "inputText", maxlength = 40, minlength = 3)]
    pub username: String,
    #[field_attrs(widget = "inputEmail", unique = true, maxlength = 74)]
    pub email: String,
    #[field_attrs(widget = "inputEmail", unique = true, maxlength = 74)]
    pub email_confirm: String,
    #[field_attrs(widget = "inputPassword", minlength = 6)]
    pub password: String,
    #[field_attrs(widget = "inputPassword", minlength = 6)]
    pub password_confirm: String,
    #[field_attrs(widget = "numberU32", default = 1)]
    pub count: u32,
    #[field_attrs(widget = "foreignKey", relation_model = "Category_1")]
    pub category_1: String,
    #[field_attrs(widget = "manyToMany", relation_model = "Category_2")]
    pub category_2: Vec<String>,
    #[field_attrs(widget = "oneToOne", relation_model = "Category_3")]
    pub category_3: String,
}

// Tests
// *************************************************************************************************
#[test]
fn it_work() {
    // Checking map of relation models
    let map_relation_models: HashMap<String, HashMap<String, String>> =
        UserProfile::meta().unwrap().map_relation_models;

    assert!(map_relation_models.get("category_1").is_some());
    assert!(map_relation_models.get("category_2").is_some());
    assert!(map_relation_models.get("category_3").is_some());

    assert!(map_relation_models
        .get("category_1")
        .unwrap()
        .get("relation_model")
        .is_some());
    assert!(map_relation_models
        .get("category_2")
        .unwrap()
        .get("relation_model")
        .is_some());
    assert!(map_relation_models
        .get("category_3")
        .unwrap()
        .get("relation_model")
        .is_some());

    assert_eq!(
        "test_service_name__category_1",
        map_relation_models
            .get("category_1")
            .unwrap()
            .get("relation_model")
            .unwrap()
    );
    assert_eq!(
        "test_service_name__category_2",
        map_relation_models
            .get("category_2")
            .unwrap()
            .get("relation_model")
            .unwrap()
    );
    assert_eq!(
        "test_service_name__category_3",
        map_relation_models
            .get("category_3")
            .unwrap()
            .get("relation_model")
            .unwrap()
    );

    assert!(map_relation_models
        .get("category_1")
        .unwrap()
        .get("relation_type")
        .is_some());
    assert!(map_relation_models
        .get("category_2")
        .unwrap()
        .get("relation_type")
        .is_some());
    assert!(map_relation_models
        .get("category_3")
        .unwrap()
        .get("relation_type")
        .is_some());

    assert_eq!(
        "foreignKey",
        map_relation_models
            .get("category_1")
            .unwrap()
            .get("relation_type")
            .unwrap()
    );
    assert_eq!(
        "manyToMany",
        map_relation_models
            .get("category_2")
            .unwrap()
            .get("relation_type")
            .unwrap()
    );
    assert_eq!(
        "oneToOne",
        map_relation_models
            .get("category_3")
            .unwrap()
            .get("relation_type")
            .unwrap()
    );
}