rjango 0.1.1

A full-stack Rust backend framework inspired by Django
Documentation
pub trait DatabaseIntrospection {
    fn get_table_list(&self) -> Vec<TableInfo>;
    fn get_table_description(&self, table_name: &str) -> Vec<ColumnInfo>;
    fn get_constraints(&self, table_name: &str) -> Vec<ConstraintInfo>;
    fn get_indexes(&self, table_name: &str) -> Vec<IndexInfo>;
    fn get_primary_key_column(&self, table_name: &str) -> Option<String>;
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TableInfo {
    pub name: String,
    pub table_type: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ColumnInfo {
    pub name: String,
    pub data_type: String,
    pub nullable: bool,
    pub default: Option<String>,
    pub primary_key: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConstraintInfo {
    pub name: String,
    pub constraint_type: String,
    pub columns: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IndexInfo {
    pub name: String,
    pub columns: Vec<String>,
    pub unique: bool,
}

#[cfg(test)]
mod tests {
    use super::{ColumnInfo, ConstraintInfo, DatabaseIntrospection, IndexInfo, TableInfo};

    struct FakeIntrospection;

    impl DatabaseIntrospection for FakeIntrospection {
        fn get_table_list(&self) -> Vec<TableInfo> {
            vec![
                TableInfo {
                    name: "widgets".to_string(),
                    table_type: "table".to_string(),
                },
                TableInfo {
                    name: "widgets_view".to_string(),
                    table_type: "view".to_string(),
                },
            ]
        }

        fn get_table_description(&self, table_name: &str) -> Vec<ColumnInfo> {
            match table_name {
                "widgets" => vec![
                    ColumnInfo {
                        name: "id".to_string(),
                        data_type: "INTEGER".to_string(),
                        nullable: false,
                        default: None,
                        primary_key: true,
                    },
                    ColumnInfo {
                        name: "slug".to_string(),
                        data_type: "TEXT".to_string(),
                        nullable: false,
                        default: Some("''".to_string()),
                        primary_key: false,
                    },
                ],
                _ => Vec::new(),
            }
        }

        fn get_constraints(&self, table_name: &str) -> Vec<ConstraintInfo> {
            match table_name {
                "widgets" => vec![ConstraintInfo {
                    name: "widgets_slug_key".to_string(),
                    constraint_type: "unique".to_string(),
                    columns: vec!["slug".to_string()],
                }],
                _ => Vec::new(),
            }
        }

        fn get_indexes(&self, table_name: &str) -> Vec<IndexInfo> {
            match table_name {
                "widgets" => vec![IndexInfo {
                    name: "widgets_slug_idx".to_string(),
                    columns: vec!["slug".to_string()],
                    unique: false,
                }],
                _ => Vec::new(),
            }
        }

        fn get_primary_key_column(&self, table_name: &str) -> Option<String> {
            (table_name == "widgets").then(|| "id".to_string())
        }
    }

    #[test]
    fn introspection_lists_tables() {
        let introspection = FakeIntrospection;
        let tables = introspection.get_table_list();

        assert_eq!(tables.len(), 2);
        assert_eq!(tables[0].name, "widgets");
        assert_eq!(tables[1].table_type, "view");
    }

    #[test]
    fn introspection_describes_table_columns() {
        let introspection = FakeIntrospection;
        let columns = introspection.get_table_description("widgets");

        assert_eq!(columns.len(), 2);
        assert!(columns[0].primary_key);
        assert_eq!(columns[1].default.as_deref(), Some("''"));
    }

    #[test]
    fn introspection_reports_constraints() {
        let introspection = FakeIntrospection;
        let constraints = introspection.get_constraints("widgets");

        assert_eq!(constraints.len(), 1);
        assert_eq!(constraints[0].constraint_type, "unique");
        assert_eq!(constraints[0].columns, vec!["slug"]);
    }

    #[test]
    fn introspection_reports_indexes() {
        let introspection = FakeIntrospection;
        let indexes = introspection.get_indexes("widgets");

        assert_eq!(indexes.len(), 1);
        assert_eq!(indexes[0].name, "widgets_slug_idx");
        assert!(!indexes[0].unique);
    }

    #[test]
    fn introspection_reports_primary_key_column() {
        let introspection = FakeIntrospection;

        assert_eq!(
            introspection.get_primary_key_column("widgets").as_deref(),
            Some("id")
        );
        assert_eq!(introspection.get_primary_key_column("missing"), None);
    }

    #[test]
    fn info_structs_are_cloneable_and_comparable() {
        let table = TableInfo {
            name: "widgets".to_string(),
            table_type: "table".to_string(),
        };
        let cloned = table.clone();

        assert_eq!(table, cloned);
    }
}