Skip to content

Commit

Permalink
Added Schema::json_schema_from_entity
Browse files Browse the repository at this point in the history
  • Loading branch information
tyt2y3 committed Jan 10, 2025
1 parent 1e496a2 commit e896e0c
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 8 deletions.
6 changes: 3 additions & 3 deletions src/entity/base_entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,23 +70,23 @@ pub trait EntityTrait: EntityName {
#[allow(missing_docs)]
type PrimaryKey: PrimaryKeyTrait + PrimaryKeyToColumn<Column = Self::Column>;

/// Check if the relation belongs to an Entity
/// Construct a belongs to relation
fn belongs_to<R>(related: R) -> RelationBuilder<Self, R>
where
R: EntityTrait,
{
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>(_: R) -> RelationBuilder<Self, R>
where
R: EntityTrait + Related<Self>,
{
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>(_: R) -> RelationBuilder<Self, R>
where
R: EntityTrait + Related<Self>,
Expand Down
7 changes: 2 additions & 5 deletions src/schema/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> = variants.iter().map(|v| v.to_string()).collect();
ColumnType::custom(format!("ENUM('{}')", variants.join("', '")).as_str())
Expand Down
184 changes: 184 additions & 0 deletions src/schema/json.rs
Original file line number Diff line number Diff line change
@@ -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<E>(&self, entity: E) -> Value
where
E: EntityTrait,
{
json_schema_from_entity(entity)
}
}

pub(crate) fn json_schema_from_entity<E>(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::<E>(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<E>(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<Value> = 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::<Value>(
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::<Value>(
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()
);
}
}
2 changes: 2 additions & 0 deletions src/schema/mod.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down

0 comments on commit e896e0c

Please sign in to comment.