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 same(a: &str, b: &str) -> bool {
let (a, b) = (a.as_bytes(), b.as_bytes());
if a.len() != b.len() {
return false;
}
let mut i = 0;
while i < a.len() {
if a[i] != b[i] {
return false;
}
i += 1;
}
true
}
pub const fn brings(fields: &[FieldDescriptor], name: &str) -> bool {
let mut i = 0;
while i < fields.len() {
if fields[i].flattened {
if brings(fields[i].children, name) {
return true;
}
} else if same(fields[i].name.as_str(), name) {
return true;
}
i += 1;
}
false
}
pub const fn overlap(a: &[FieldDescriptor], b: &[FieldDescriptor]) -> bool {
let mut i = 0;
while i < a.len() {
if a[i].flattened {
if overlap(a[i].children, b) {
return true;
}
} else if brings(b, a[i].name.as_str()) {
return true;
}
i += 1;
}
false
}
pub const fn brings_any(fields: &[FieldDescriptor], names: &[&str]) -> bool {
let mut i = 0;
while i < names.len() {
if brings(fields, names[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<()>;
}