use super::{schema_cache, AdminEntry, AdminField, FieldType};
use crate::schema::Schema;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DynamicAdminEntry {
pub admin_name: String,
pub display_name: String,
pub singular_name: String,
pub table: String,
pub fields: Vec<DynamicAdminField>,
pub core: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DynamicAdminField {
pub name: String,
pub ty: FieldType,
pub editable: bool,
pub nullable: bool,
}
impl DynamicAdminField {
pub fn from_admin(f: &AdminField) -> Self {
Self {
name: f.name.to_string(),
ty: f.ty,
editable: f.editable,
nullable: f.nullable,
}
}
}
impl DynamicAdminEntry {
pub fn from_admin(entry: &AdminEntry) -> Self {
Self {
admin_name: entry.admin_name.to_string(),
display_name: entry.display_name.to_string(),
singular_name: entry.singular_name.to_string(),
table: entry.table.to_string(),
fields: entry
.fields
.iter()
.map(DynamicAdminField::from_admin)
.collect(),
core: entry.core,
}
}
}
pub fn field_type_from_str(ty: &str) -> FieldType {
match ty {
"i32" => FieldType::I32,
"i64" => FieldType::I64,
"String" => FieldType::String,
"bool" => FieldType::Bool,
"DateTime" => FieldType::DateTime,
_ => FieldType::String,
}
}
pub fn build_admin_entries(schema: &Schema) -> Vec<DynamicAdminEntry> {
schema
.models
.iter()
.map(|m| DynamicAdminEntry {
admin_name: m.admin_name.clone(),
display_name: m.display_name.clone(),
singular_name: m.singular_name.clone(),
table: m.table.clone(),
core: m.core,
fields: m
.fields
.iter()
.map(|f| DynamicAdminField {
name: f.name.clone(),
ty: field_type_from_str(&f.ty),
editable: f.editable,
nullable: f.nullable,
})
.collect(),
})
.collect()
}
pub fn entries_effective(compiled: &[AdminEntry]) -> Vec<DynamicAdminEntry> {
if let Some(cached) = schema_cache::snapshot() {
let compiled_by_name: std::collections::HashMap<&str, &AdminEntry> =
compiled.iter().map(|e| (e.admin_name, e)).collect();
return build_admin_entries(&cached.schema)
.into_iter()
.map(|mut dyn_entry| {
if let Some(compiled_e) = compiled_by_name.get(dyn_entry.admin_name.as_str()) {
dyn_entry.core = compiled_e.core;
}
dyn_entry
})
.collect();
}
compiled.iter().map(DynamicAdminEntry::from_admin).collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::schema::{SchemaField, SchemaModel, SCHEMA_VERSION};
fn tiny_schema() -> Schema {
Schema {
version: SCHEMA_VERSION,
rustio_version: env!("CARGO_PKG_VERSION").to_string(),
models: vec![SchemaModel {
name: "Post".into(),
table: "posts".into(),
admin_name: "posts".into(),
display_name: "Posts".into(),
singular_name: "Post".into(),
fields: vec![
SchemaField {
name: "id".into(),
ty: "i64".into(),
nullable: false,
editable: false,
relation: None,
},
SchemaField {
name: "title".into(),
ty: "String".into(),
nullable: false,
editable: true,
relation: None,
},
SchemaField {
name: "odd".into(),
ty: "UnknownType".into(),
nullable: true,
editable: true,
relation: None,
},
],
relations: vec![],
core: false,
}],
}
}
#[test]
fn field_type_fallback_is_string_for_unknown() {
assert!(matches!(field_type_from_str("i32"), FieldType::I32));
assert!(matches!(field_type_from_str("i64"), FieldType::I64));
assert!(matches!(field_type_from_str("bool"), FieldType::Bool));
assert!(matches!(
field_type_from_str("DateTime"),
FieldType::DateTime
));
assert!(matches!(field_type_from_str("String"), FieldType::String));
assert!(matches!(field_type_from_str("Decimal"), FieldType::String));
assert!(matches!(field_type_from_str(""), FieldType::String));
}
#[test]
fn build_admin_entries_mirrors_the_schema() {
let s = tiny_schema();
let entries = build_admin_entries(&s);
assert_eq!(entries.len(), 1);
let posts = &entries[0];
assert_eq!(posts.admin_name, "posts");
assert_eq!(posts.display_name, "Posts");
assert_eq!(posts.table, "posts");
assert_eq!(posts.fields.len(), 3);
assert_eq!(posts.fields[0].name, "id");
assert_eq!(posts.fields[1].name, "title");
assert_eq!(posts.fields[2].name, "odd");
assert!(matches!(posts.fields[2].ty, FieldType::String));
assert!(!posts.fields[0].nullable);
assert!(posts.fields[2].nullable);
assert!(!posts.fields[0].editable);
assert!(posts.fields[1].editable);
}
#[test]
fn entry_from_admin_round_trips_compile_time_shape() {
let af = AdminField {
name: "x",
ty: FieldType::I32,
editable: true,
nullable: false,
relation: None,
};
let fields: &'static [AdminField] = Box::leak(vec![af].into_boxed_slice());
let ae = AdminEntry {
admin_name: "widgets",
display_name: "Widgets",
singular_name: "Widget",
table: "widgets",
fields,
core: false,
};
let de = DynamicAdminEntry::from_admin(&ae);
assert_eq!(de.admin_name, "widgets");
assert_eq!(de.display_name, "Widgets");
assert_eq!(de.table, "widgets");
assert_eq!(de.fields.len(), 1);
assert_eq!(de.fields[0].name, "x");
assert!(matches!(de.fields[0].ty, FieldType::I32));
assert!(de.fields[0].editable);
assert!(!de.fields[0].nullable);
assert!(!de.core);
}
}