diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index 0f534f531..441a73160 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -70,7 +70,7 @@ pub trait EntityTrait: EntityName { #[allow(missing_docs)] type PrimaryKey: PrimaryKeyTrait + PrimaryKeyToColumn; - /// Check if the relation belongs to an Entity + /// Construct a belongs to relation fn belongs_to(related: R) -> RelationBuilder where R: EntityTrait, @@ -78,7 +78,7 @@ pub trait EntityTrait: EntityName { RelationBuilder::new(RelationType::HasOne, Self::default(), related, false) } - /// Check if the entity has at least one relation + /// Construct a has one relation fn has_one(_: R) -> RelationBuilder where R: EntityTrait + Related, @@ -86,7 +86,7 @@ pub trait EntityTrait: EntityName { RelationBuilder::from_rel(RelationType::HasOne, R::to().rev(), true) } - /// Check if the Entity has many relations + /// Construct a has many relation fn has_many(_: R) -> RelationBuilder where R: EntityTrait + Related, diff --git a/src/schema/entity.rs b/src/schema/entity.rs index dd2da733a..940f93b2e 100644 --- a/src/schema/entity.rs +++ b/src/schema/entity.rs @@ -192,11 +192,8 @@ where E: EntityTrait, { let orm_column_def = column.def(); - let types = match orm_column_def.col_type { - ColumnType::Enum { - ref name, - ref variants, - } => match backend { + let types = match &orm_column_def.col_type { + ColumnType::Enum { name, variants } => match backend { DbBackend::MySql => { let variants: Vec = variants.iter().map(|v| v.to_string()).collect(); ColumnType::custom(format!("ENUM('{}')", variants.join("', '")).as_str()) diff --git a/src/schema/json.rs b/src/schema/json.rs new file mode 100644 index 000000000..8f5e0f7e9 --- /dev/null +++ b/src/schema/json.rs @@ -0,0 +1,184 @@ +use crate::{ColumnTrait, ColumnType, EntityTrait, Iden, Iterable, Schema}; +use serde_json::{Map, Value}; + +impl Schema { + /// Construct a schema description in json for the given Entity. + pub fn json_schema_from_entity(&self, entity: E) -> Value + where + E: EntityTrait, + { + json_schema_from_entity(entity) + } +} + +pub(crate) fn json_schema_from_entity(entity: E) -> Value +where + E: EntityTrait, +{ + let mut obj = Map::new(); + let mut cols = Vec::new(); + + if let Some(comment) = entity.comment() { + obj.insert("comment".to_owned(), Value::String(comment.to_owned())); + } + + for column in E::Column::iter() { + let col = json_schema_from_entity_column::(column); + cols.push(col); + } + obj.insert("columns".to_owned(), Value::Array(cols)); + + let mut pk = Vec::new(); + for col in E::PrimaryKey::iter() { + pk.push(Value::String(col.to_string())); + } + obj.insert("primary_key".to_owned(), Value::Array(pk)); + + Value::Object(obj) +} + +fn json_schema_from_entity_column(column: E::Column) -> Value +where + E: EntityTrait, +{ + let mut obj = Map::new(); + + let column_def = column.def(); + obj.insert("name".to_owned(), Value::String(column.to_string())); + obj.insert( + "type".to_owned(), + type_def_from_column_def(&column_def.col_type), + ); + obj.insert("nullable".to_owned(), Value::Bool(column_def.null)); + if column_def.unique { + obj.insert("unique".to_owned(), Value::Bool(true)); + } + if let Some(comment) = column_def.comment { + obj.insert("comment".to_owned(), Value::String(comment)); + } + + Value::Object(obj) +} + +fn type_def_from_column_def(column_type: &ColumnType) -> Value { + match column_type { + ColumnType::Char(_) | ColumnType::String(_) | ColumnType::Text => { + Value::String("string".to_owned()) + } + ColumnType::TinyInteger + | ColumnType::SmallInteger + | ColumnType::Integer + | ColumnType::BigInteger + | ColumnType::TinyUnsigned + | ColumnType::SmallUnsigned + | ColumnType::Unsigned + | ColumnType::BigUnsigned => Value::String("integer".to_owned()), + ColumnType::Float | ColumnType::Double => Value::String("real".to_owned()), + ColumnType::Decimal(_) | ColumnType::Money(_) => Value::String("decimal".to_owned()), + ColumnType::DateTime | ColumnType::Timestamp | ColumnType::TimestampWithTimeZone => { + Value::String("datetime".to_owned()) + } + ColumnType::Time => Value::String("time".to_owned()), + ColumnType::Date => Value::String("date".to_owned()), + ColumnType::Year => Value::String("year".to_owned()), + ColumnType::Binary(_) + | ColumnType::VarBinary(_) + | ColumnType::Bit(_) + | ColumnType::VarBit(_) => Value::String("binary".to_owned()), + ColumnType::Boolean => Value::String("bool".to_owned()), + ColumnType::Json | ColumnType::JsonBinary => Value::String("json".to_owned()), + ColumnType::Uuid => Value::String("uuid".to_owned()), + ColumnType::Custom(typename) => Value::String(typename.to_string()), + ColumnType::Enum { name, variants } => { + let mut enum_def = Map::new(); + enum_def.insert("name".to_owned(), Value::String(name.to_string())); + let variants: Vec = variants + .iter() + .map(|v| Value::String(v.to_string())) + .collect(); + enum_def.insert("variants".to_owned(), Value::Array(variants)); + Value::Object(enum_def) + } + ColumnType::Array(inner) => { + let mut obj = Map::new(); + obj.insert("array".to_owned(), type_def_from_column_def(inner)); + Value::Object(obj) + } + _ => Value::String("other".to_owned()), + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + tests_cfg::{cake, lunch_set}, + DbBackend, + }; + + #[test] + fn test_json_schema_from_entity() { + let json = Schema::new(DbBackend::MySql).json_schema_from_entity(cake::Entity); + println!("{}", serde_json::to_string_pretty(&json).unwrap()); + assert_eq!( + json, + serde_json::from_str::( + r#"{ + "columns": [ + { + "name": "id", + "nullable": false, + "type": "integer" + }, + { + "name": "name", + "nullable": false, + "type": "string" + } + ], + "primary_key": [ + "id" + ] + }"# + ) + .unwrap() + ); + + let json = Schema::new(DbBackend::MySql).json_schema_from_entity(lunch_set::Entity); + println!("{}", serde_json::to_string_pretty(&json).unwrap()); + assert_eq!( + json, + serde_json::from_str::( + r#"{ + "columns": [ + { + "name": "id", + "nullable": false, + "type": "integer" + }, + { + "name": "name", + "nullable": false, + "type": "string" + }, + { + "name": "tea", + "nullable": false, + "type": { + "name": "tea", + "variants": [ + "EverydayTea", + "BreakfastTea" + ] + } + } + ], + "primary_key": [ + "id" + ] + }"# + ) + .unwrap() + ); + } +} diff --git a/src/schema/mod.rs b/src/schema/mod.rs index e89d9cc5e..d9333b5b3 100644 --- a/src/schema/mod.rs +++ b/src/schema/mod.rs @@ -1,6 +1,8 @@ use crate::DbBackend; mod entity; +#[cfg(feature = "serde_json")] +mod json; /// This is a helper struct to convert [`EntityTrait`](crate::EntityTrait) /// into different [`sea_query`](crate::sea_query) statements.