use crate::core::{inventory, FieldType, ModelEntry, ModelSchema, Relation};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SchemaSnapshot {
pub tables: Vec<TableSnapshot>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TableSnapshot {
pub name: String,
pub model: String,
pub fields: Vec<FieldSnapshot>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FieldSnapshot {
pub name: String,
pub column: String,
pub ty: String,
pub nullable: bool,
pub primary_key: bool,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub max_length: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub min: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub max: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub default: Option<String>,
#[serde(skip_serializing_if = "is_false", default)]
pub auto: bool,
#[serde(skip_serializing_if = "is_false", default)]
pub unique: bool,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub fk: Option<RelationSnapshot>,
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_false(v: &bool) -> bool {
!*v
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RelationSnapshot {
pub kind: String,
pub to: String,
pub on: String,
}
impl SchemaSnapshot {
#[must_use]
pub fn from_registry() -> Self {
let mut tables: Vec<TableSnapshot> = inventory::iter::<ModelEntry>
.into_iter()
.map(|e| TableSnapshot::from_schema(e.schema))
.collect();
tables.sort_by(|a, b| a.name.cmp(&b.name));
Self { tables }
}
#[must_use]
pub fn from_registry_for_app(app: &str) -> Self {
let mut tables: Vec<TableSnapshot> = inventory::iter::<ModelEntry>
.into_iter()
.filter(|e| e.resolved_app_label() == Some(app))
.map(|e| TableSnapshot::from_schema(e.schema))
.collect();
tables.sort_by(|a, b| a.name.cmp(&b.name));
Self { tables }
}
#[must_use]
pub fn from_models(models: &[&ModelSchema]) -> Self {
let mut tables: Vec<TableSnapshot> =
models.iter().map(|s| TableSnapshot::from_schema(s)).collect();
tables.sort_by(|a, b| a.name.cmp(&b.name));
Self { tables }
}
#[must_use]
pub fn table(&self, name: &str) -> Option<&TableSnapshot> {
self.tables.iter().find(|t| t.name == name)
}
}
impl TableSnapshot {
#[must_use]
pub fn from_schema(s: &ModelSchema) -> Self {
let mut fields: Vec<FieldSnapshot> =
s.scalar_fields().map(FieldSnapshot::from_schema).collect();
fields.sort_by(|a, b| a.column.cmp(&b.column));
Self {
name: s.table.to_owned(),
model: s.name.to_owned(),
fields,
}
}
#[must_use]
pub fn field(&self, column: &str) -> Option<&FieldSnapshot> {
self.fields.iter().find(|f| f.column == column)
}
}
impl FieldSnapshot {
fn from_schema(f: &crate::core::FieldSchema) -> Self {
let fk = f.relation.and_then(|r| match r {
Relation::Fk { to, on } => Some(RelationSnapshot {
kind: "fk".into(),
to: to.to_owned(),
on: on.to_owned(),
}),
Relation::O2O { to, on } => Some(RelationSnapshot {
kind: "o2o".into(),
to: to.to_owned(),
on: on.to_owned(),
}),
Relation::M2M { .. } => None,
});
Self {
name: f.name.to_owned(),
column: f.column.to_owned(),
ty: field_type_name(f.ty).to_owned(),
nullable: f.nullable,
primary_key: f.primary_key,
max_length: f.max_length,
min: f.min,
max: f.max,
default: f.default.map(str::to_owned),
auto: f.auto,
unique: f.unique,
fk,
}
}
}
fn field_type_name(ty: FieldType) -> &'static str {
match ty {
FieldType::I32 => "i32",
FieldType::I64 => "i64",
FieldType::F32 => "f32",
FieldType::F64 => "f64",
FieldType::Bool => "bool",
FieldType::String => "string",
FieldType::DateTime => "datetime",
FieldType::Date => "date",
FieldType::Uuid => "uuid",
FieldType::Json => "json",
}
}