use crate::migration::fields::FieldDescriptor;
use amethystate_core::path::StorePath;
use std::sync::OnceLock;
#[doc(hidden)]
pub struct SchemaEntry {
pub prefix: StorePath,
pub id: Option<&'static str>,
pub struct_name: &'static str,
pub version: u32,
pub fields: &'static [FieldDescriptor],
}
inventory::collect!(SchemaEntry);
pub fn declarations() -> &'static [&'static SchemaEntry] {
static COMPILED: OnceLock<Vec<&'static SchemaEntry>> = OnceLock::new();
COMPILED.get_or_init(|| inventory::iter::<SchemaEntry>.into_iter().collect())
}
pub fn declarations_at(prefix: &StorePath) -> impl Iterator<Item = &'static SchemaEntry> + '_ {
declarations()
.iter()
.copied()
.filter(move |entry| entry.prefix == *prefix)
}
pub fn declarations_of(lineage: &Lineage) -> impl Iterator<Item = &'static SchemaEntry> + '_ {
declarations_at(&lineage.prefix).filter(move |entry| lineage.holds(entry))
}
pub fn current_at(prefix: &StorePath) -> Vec<&'static SchemaEntry> {
current().filter(|entry| entry.prefix == *prefix).collect()
}
pub fn current() -> impl Iterator<Item = &'static SchemaEntry> {
declarations()
.iter()
.copied()
.filter(|one| !superseded(one))
}
pub fn declared_twice() -> Option<(&'static SchemaEntry, &'static SchemaEntry)> {
let all = declarations();
for (index, one) in all.iter().enumerate() {
for other in &all[index + 1..] {
if one.prefix == other.prefix
&& one.id == other.id
&& one.version == other.version
&& !same_shape(one, other)
{
return Some((one, other));
}
}
}
None
}
pub fn claimed_twice() -> Option<(&'static SchemaEntry, &'static SchemaEntry, StorePath)> {
let standing: Vec<&'static SchemaEntry> = current().collect();
for (index, one) in standing.iter().enumerate() {
let mine = crate::store::moved::owned(one.fields);
for other in &standing[index + 1..] {
if other.prefix != one.prefix || other.id == one.id {
continue;
}
let theirs = crate::store::moved::owned(other.fields);
let shared = mine.iter().find(|place| {
theirs
.iter()
.any(|their| their.at.starts_with(&place.at) || place.at.starts_with(&their.at))
});
if let Some(place) = shared {
return Some((one, other, one.prefix.join(&place.at)));
}
}
}
None
}
fn same_shape(one: &SchemaEntry, other: &SchemaEntry) -> bool {
use crate::store::meta::StoredFieldEntry;
one.fields
.iter()
.map(StoredFieldEntry::from)
.eq(other.fields.iter().map(StoredFieldEntry::from))
}
fn superseded(one: &SchemaEntry) -> bool {
declarations_at(&one.prefix).any(|other| other.id == one.id && other.version > one.version)
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Lineage {
pub prefix: StorePath,
pub id: Option<String>,
}
impl Lineage {
pub fn unnamed(prefix: StorePath) -> Self {
Self { prefix, id: None }
}
pub fn named(prefix: StorePath, id: impl Into<String>) -> Self {
Self {
prefix,
id: Some(id.into()),
}
}
pub fn id(&self) -> Option<&str> {
self.id.as_deref()
}
pub fn holds(&self, entry: &SchemaEntry) -> bool {
entry.prefix == self.prefix && entry.id == self.id()
}
}
impl From<StorePath> for Lineage {
fn from(prefix: StorePath) -> Self {
Self::unnamed(prefix)
}
}
impl std::fmt::Display for Lineage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.id {
None => write!(f, "{}", self.prefix),
Some(id) => write!(f, "{} (`{id}`)", self.prefix),
}
}
}
impl SchemaEntry {
pub fn lineage(&self) -> Lineage {
Lineage {
prefix: self.prefix.clone(),
id: self.id.map(str::to_string),
}
}
}