use crate::MigrationContext;
use crate::store::{StaticPath, StorePath};
#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(into = "&'static str", try_from = "String")]
pub enum Role {
Field,
Map,
Node,
}
impl Role {
pub const fn same(self, other: Self) -> bool {
self as u8 == other as u8
}
pub const fn as_str(self) -> &'static str {
match self {
Role::Field => "field",
Role::Map => "map",
Role::Node => "node",
}
}
}
impl From<Role> for &'static str {
fn from(role: Role) -> Self {
role.as_str()
}
}
impl TryFrom<String> for Role {
type Error = String;
fn try_from(name: String) -> Result<Self, Self::Error> {
match name.as_str() {
"field" => Ok(Role::Field),
"map" => Ok(Role::Map),
"node" => Ok(Role::Node),
other => Err(format!("no such role: {other}")),
}
}
}
#[derive(Clone)]
pub struct FieldDescriptor {
pub name: StaticPath,
pub declared: &'static str,
pub type_name: &'static str,
pub role: Role,
pub optional: bool,
pub children: &'static [FieldDescriptor],
pub flattened: bool,
}
impl FieldDescriptor {
pub fn owns(&self, at: &StorePath) -> Option<StorePath> {
match self.role {
Role::Node => None,
Role::Field | Role::Map => Some(at.join(&self.name.path())),
}
}
pub fn below(&self, at: &StorePath) -> StorePath {
match self.flattened {
true => at.clone(),
false => at.join(&self.name.path()),
}
}
pub const fn leaf(
segments: &'static [&'static str],
joined: &'static str,
type_name: &'static str,
) -> Self {
Self {
name: StaticPath::new(segments, joined),
declared: joined,
type_name,
role: Role::Field,
optional: false,
children: &[],
flattened: false,
}
}
}
const fn holds(outer: &[&str], inner: &[&str]) -> bool {
if outer.len() > inner.len() {
return false;
}
let mut at = 0;
while at < outer.len() {
if !same_text(outer[at], inner[at]) {
return false;
}
at += 1;
}
true
}
const fn meet(one: &FieldDescriptor, other: &FieldDescriptor) -> bool {
let (mine, theirs) = (one.name.segments(), other.name.segments());
match (one.role, other.role) {
(Role::Node, Role::Node) => mine.len() == theirs.len() && holds(mine, theirs),
_ => holds(mine, theirs) || holds(theirs, mine),
}
}
pub const fn brings(fields: &[FieldDescriptor], one: &FieldDescriptor) -> bool {
let mut i = 0;
while i < fields.len() {
let met = match fields[i].flattened {
true => brings(fields[i].children, one),
false => meet(&fields[i], one),
};
if met {
return true;
}
i += 1;
}
false
}
pub const fn overlap(a: &[FieldDescriptor], b: &[FieldDescriptor]) -> bool {
let mut i = 0;
while i < a.len() {
let met = match a[i].flattened {
true => overlap(a[i].children, b),
false => brings(b, &a[i]),
};
if met {
return true;
}
i += 1;
}
false
}
pub const fn crosses(fields: &[FieldDescriptor], at: usize) -> bool {
let mut i = 0;
while i < fields.len() {
if i != at && !fields[i].flattened && brings(fields[at].children, &fields[i]) {
return true;
}
i += 1;
}
false
}
pub const fn same_line(one: Option<&str>, other: Option<&str>) -> bool {
match (one, other) {
(None, None) => true,
(Some(one), Some(other)) => same_text(one, other),
_ => false,
}
}
pub const fn same_prefix(one: StaticPath, other: StaticPath) -> bool {
same_text(one.as_str(), other.as_str())
}
const fn same_text(one: &str, other: &str) -> bool {
let (one, other) = (one.as_bytes(), other.as_bytes());
if one.len() != other.len() {
return false;
}
let mut at = 0;
while at < one.len() {
if one[at] != other[at] {
return false;
}
at += 1;
}
true
}
pub trait AmeStateFields: Sized {
const FIELDS: &'static [FieldDescriptor];
const VERSION: u32;
const ID: Option<&'static str>;
const PARENT_PREFIX: StaticPath;
fn load_struct(ctx: &mut MigrationContext) -> crate::migration::StepResult<Self>;
fn save_struct(&self, ctx: &mut MigrationContext) -> crate::migration::StepResult<()>;
}