use std::borrow::Cow;
use crate::error::{DbError, SchemaError};
pub use crate::schema_compat::classify_schema_update;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CollectionId(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SchemaVersion(pub u32);
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FieldPath(pub Vec<Cow<'static, str>>);
impl FieldPath {
pub fn new(parts: impl IntoIterator<Item = Cow<'static, str>>) -> Result<Self, DbError> {
let parts: Vec<Cow<'static, str>> = parts.into_iter().collect();
if parts.is_empty() | parts.iter().any(|p| p.is_empty()) {
return Err(DbError::Schema(SchemaError::InvalidFieldPath));
}
Ok(Self(parts))
}
}
pub(crate) fn validate_field_defs(fields: &[FieldDef]) -> Result<(), DbError> {
for f in fields {
if f.path.0.is_empty() | f.path.0.iter().any(|s| s.is_empty()) {
return Err(DbError::Schema(SchemaError::InvalidFieldPath));
}
}
let mut seen: std::collections::HashSet<&FieldPath> = std::collections::HashSet::new();
for f in fields {
if !seen.insert(&f.path) {
return Err(DbError::Schema(SchemaError::InvalidFieldPath));
}
}
for (i, a) in fields.iter().enumerate() {
for b in fields.iter().skip(i + 1) {
let pa = &a.path.0;
let pb = &b.path.0;
let min = pa.len().min(pb.len());
if (pa.len() != pb.len()) & (pa[..min] == pb[..min]) {
return Err(DbError::Schema(SchemaError::InvalidFieldPath));
}
}
}
Ok(())
}
#[derive(Debug, Clone, PartialEq)]
pub enum Type {
Bool,
Int64,
Uint64,
Float64,
String,
Bytes,
Uuid,
Timestamp,
Optional(Box<Type>),
List(Box<Type>),
Object(Vec<FieldDef>),
Enum(Vec<String>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum Constraint {
MinI64(i64),
MaxI64(i64),
MinU64(u64),
MaxU64(u64),
MinF64(f64),
MaxF64(f64),
MinLength(u64),
MaxLength(u64),
Regex(String),
Email,
Url,
NonEmpty,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FieldDef {
pub path: FieldPath,
pub ty: Type,
pub constraints: Vec<Constraint>,
}
impl FieldDef {
pub fn new(path: FieldPath, ty: Type) -> Self {
Self {
path,
ty,
constraints: Vec::new(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IndexKind {
Unique,
NonUnique,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IndexDef {
pub name: String,
pub path: FieldPath,
pub kind: IndexKind,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CollectionSchema {
pub name: String,
pub version: SchemaVersion,
pub fields: Vec<FieldDef>,
pub id: Option<CollectionId>,
}
pub trait DbModel {
fn collection_name() -> &'static str;
fn fields() -> Vec<FieldDef>;
fn primary_field() -> &'static str;
fn indexes() -> Vec<IndexDef> {
Vec::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SchemaChange {
Safe,
NeedsMigration {
reason: String,
backfill_top_level_field: Option<String>,
backfill_field_path: Option<FieldPath>,
},
Breaking { reason: String },
}
#[cfg(test)]
mod tests {
include!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/unit/src_schema_tests.rs"
));
}