use crate::migration::fields::{FieldDescriptor, Role};
use crate::store::meta::StoredFieldEntry;
use amethystate_core::path::StorePath;
use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Holds {
Value,
Level,
}
#[derive(Debug, Default, Clone)]
pub struct Declared {
places: Vec<(StorePath, Role)>,
}
impl Declared {
pub fn compiled_in() -> &'static Declared {
static COMPILED: OnceLock<Declared> = OnceLock::new();
COMPILED.get_or_init(|| {
let mut places = Vec::new();
for entry in crate::schema::current() {
from_fields(&entry.prefix, entry.fields, &mut places);
}
Declared { places }
})
}
pub fn record(&mut self, prefix: &StorePath, fields: &[StoredFieldEntry]) {
from_stored(prefix, fields, &mut self.places);
}
pub fn under(&self, prefix: &StorePath) -> Declared {
Declared {
places: self
.places
.iter()
.filter(|(at, _)| at.overlaps(prefix))
.cloned()
.collect(),
}
}
pub fn covers(&self, path: &StorePath) -> bool {
self.places
.iter()
.any(|(at, role)| at.starts_with(path) || (role.same(Role::Map) && entry_of(at, path)))
}
pub fn owns_level(&self, path: &StorePath) -> bool {
self.places
.iter()
.any(|(at, role)| role.same(Role::Map) && at == path)
}
pub fn levels_under(&self, prefix: &StorePath) -> Vec<StorePath> {
self.places
.iter()
.filter(|(at, role)| role.same(Role::Map) && at.starts_with(prefix))
.map(|(at, _)| at.clone())
.collect()
}
pub fn holds(&self, path: &StorePath) -> Holds {
for (at, role) in &self.places {
let inside = match role {
Role::Field => path.starts_with(at),
Role::Map => path.len() > at.len() && path.starts_with(at),
Role::Node => false,
};
if inside {
return Holds::Value;
}
}
Holds::Level
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Collision {
Owned(StorePath),
Holds(StorePath),
}
pub fn schema_collision(path: &StorePath) -> Option<(Collision, &'static str)> {
for entry in crate::schema::declarations() {
let prefix = &entry.prefix;
if !path.starts_with(prefix) && !prefix.starts_with(path) {
continue;
}
if let Some(found) = collision(prefix, entry.fields, path) {
return Some((found, entry.struct_name));
}
}
None
}
fn collision(at: &StorePath, fields: &[FieldDescriptor], path: &StorePath) -> Option<Collision> {
for field in fields {
match field.owns(at) {
Some(owned) => {
if owned.starts_with(path) && owned != *path {
return Some(Collision::Holds(owned));
}
if path.starts_with(&owned) {
return Some(Collision::Owned(owned));
}
}
None => {
let below = field.below(at);
if let Some(found) = collision(&below, field.children, path) {
return Some(found);
}
}
}
}
None
}
pub fn seeded_namespaces_under(at: &StorePath) -> Vec<StorePath> {
let mut found = Vec::new();
for entry in crate::schema::declarations() {
if !entry.prefix.starts_with(at) {
continue;
}
found.push(entry.prefix.clone());
collect_seeded(&entry.prefix, entry.fields, &mut found);
}
found
}
fn collect_seeded(at: &StorePath, fields: &[FieldDescriptor], found: &mut Vec<StorePath>) {
for field in fields {
match field.role {
Role::Node => {
let below = field.below(at);
collect_seeded(&below, field.children, found);
if below != *at {
found.push(below);
}
}
Role::Map => found.push(at.join(&field.name.path())),
Role::Field => {}
}
}
}
fn entry_of(at: &StorePath, path: &StorePath) -> bool {
path.len() == at.len() + 1 && path.starts_with(at)
}
fn from_fields(at: &StorePath, fields: &[FieldDescriptor], into: &mut Vec<(StorePath, Role)>) {
for field in fields {
match field.owns(at) {
Some(owned) => into.push((owned, field.role)),
None => from_fields(&field.below(at), field.children, into),
}
}
}
fn from_stored(at: &StorePath, fields: &[StoredFieldEntry], into: &mut Vec<(StorePath, Role)>) {
for field in fields {
let shape = &field.shape;
match shape.role {
Role::Node => {
let below = match shape.flattened {
true => at.clone(),
false => at.join(&field.name),
};
from_stored(&below, &shape.children, into);
}
role => into.push((at.join(&field.name), role)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn declaring(places: &[(&[&str], Role)]) -> Declared {
Declared {
places: places
.iter()
.map(|(at, role)| (StorePath::from_segments(*at), *role))
.collect(),
}
}
fn at(levels: &[&str]) -> StorePath {
StorePath::from_segments(levels)
}
#[test]
fn a_maps_own_path_is_a_level_and_what_is_under_it_is_a_value() {
let declared = declaring(&[(&["ui", "widths"], Role::Map)]);
assert_eq!(declared.holds(&at(&["ui", "widths"])), Holds::Level);
assert_eq!(declared.holds(&at(&["ui", "widths", "cpu"])), Holds::Value);
assert_eq!(
declared.holds(&at(&["ui", "widths", "cpu", "deeper"])),
Holds::Value
);
}
#[test]
fn a_field_is_a_value_at_its_own_path() {
let declared = declaring(&[(&["ui", "theme"], Role::Field)]);
assert_eq!(declared.holds(&at(&["ui", "theme"])), Holds::Value);
assert_eq!(declared.holds(&at(&["ui", "theme", "inner"])), Holds::Value);
assert_eq!(declared.holds(&at(&["ui"])), Holds::Level);
}
#[test]
fn a_level_on_the_way_to_a_declaration_is_a_level() {
let declared = declaring(&[(&["ui", "widths"], Role::Map)]);
assert_eq!(declared.holds(&at(&["ui"])), Holds::Level);
assert_eq!(declared.holds(&StorePath::root()), Holds::Level);
assert_eq!(declared.holds(&at(&["elsewhere"])), Holds::Level);
}
}