use std::{
cmp::Ordering,
collections::{BTreeSet, HashMap},
};
use crate::{metadata::BasicTypes, value::Pointer};
#[derive(Debug, Clone)]
pub(super) enum MountingPoint {
Config,
Param {
is_canonical: bool,
expecting: BasicTypes,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct KvPath(String);
impl From<&str> for KvPath {
fn from(value: &str) -> Self {
Self(value.into())
}
}
impl PartialOrd for KvPath {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl KvPath {
fn is_equivalent(&self, kv_path: &str) -> bool {
if kv_path.len() != self.0.len() {
return false;
}
Self::cmp_with_substitutions(&self.0, kv_path, &mut Ordering::Equal).is_eq()
}
fn cmp_with_substitutions(this: &str, other: &str, total_ordering: &mut Ordering) -> Ordering {
for (&this_byte, &other_byte) in this.as_bytes().iter().zip(other.as_bytes()) {
let (this_byte, other_byte) = match (this_byte, other_byte) {
(b'.', b'.') => (b'_', b'_'),
(b'.', other) => {
*total_ordering = total_ordering.then(Ordering::Less); (b'_', other)
}
(other, b'.') => {
*total_ordering = total_ordering.then(Ordering::Greater);
(other, b'_')
}
_ => (this_byte, other_byte),
};
let compared = this_byte.cmp(&other_byte);
if compared != Ordering::Equal {
return compared;
}
}
this.len().cmp(&other.len())
}
}
impl Ord for KvPath {
fn cmp(&self, other: &Self) -> Ordering {
let mut total_ordering = Ordering::Equal;
Self::cmp_with_substitutions(&self.0, &other.0, &mut total_ordering).then(total_ordering)
}
}
#[derive(Debug, Clone, Default)]
pub(super) struct MountingPoints {
kv_paths: BTreeSet<KvPath>,
inner: HashMap<String, MountingPoint>,
}
impl MountingPoints {
pub(super) fn get(&self, path: &str) -> Option<&MountingPoint> {
self.inner.get(path)
}
pub(super) fn by_kv_path<'s>(
&'s self,
kv_path: &'s str,
) -> impl Iterator<Item = (Pointer<'s>, &'s MountingPoint)> + 's {
let kv_paths = self
.kv_paths
.range(..=KvPath::from(kv_path))
.rev()
.take_while(|&path| path.is_equivalent(kv_path));
kv_paths.map(|path| (Pointer(&path.0), &self.inner[&path.0]))
}
pub(super) fn insert(&mut self, path: String, mount: MountingPoint) {
self.kv_paths.insert(KvPath(path.clone()));
self.inner.insert(path, mount);
}
pub(super) fn extend(&mut self, mut from: Self) {
self.kv_paths.append(&mut from.kv_paths);
self.inner.extend(from.inner);
}
}
#[cfg(test)]
mod tests {
use std::{collections::HashSet, ops};
use super::*;
impl ops::Index<&str> for MountingPoints {
type Output = MountingPoint;
fn index(&self, index: &str) -> &Self::Output {
self.get(index)
.unwrap_or_else(|| panic!("no mounting point at {index:?}"))
}
}
#[test]
fn kv_path_ordering() {
assert_eq!(
KvPath::from("test").cmp(&KvPath::from("test")),
Ordering::Equal
);
assert_eq!(
KvPath::from("test.value").cmp(&KvPath::from("test.value")),
Ordering::Equal
);
assert!(KvPath::from("test") < KvPath::from("test0"));
assert!(KvPath::from("test0") > KvPath::from("test"));
assert!(KvPath::from("test.value") < KvPath::from("test_value"));
assert!(KvPath::from("test_value") > KvPath::from("test.value"));
assert!(KvPath::from("test.value") > KvPath::from("test0value"));
assert!(KvPath::from("test_value") > KvPath::from("test0value"));
}
#[test]
fn kv_path_equivalence() {
assert!(KvPath::from("test").is_equivalent("test"));
assert!(KvPath::from("test.path").is_equivalent("test_path"));
assert!(KvPath::from("test_path").is_equivalent("test_path"));
assert!(!KvPath::from("test_path").is_equivalent("test"));
assert!(!KvPath::from("test.path").is_equivalent("test"));
}
#[test]
fn getting_mounting_points_by_kv_path() {
let mut points = MountingPoints::default();
let mount = MountingPoint::Param {
expecting: BasicTypes::BOOL,
is_canonical: true,
};
points.insert("test_path".into(), mount.clone());
points.insert("test.path".into(), mount.clone());
points.insert("test".into(), mount.clone());
points.insert("path".into(), mount.clone());
points.insert("testpath".into(), mount.clone());
points.insert("test.path_1".into(), mount);
let paths: HashSet<_> = points
.by_kv_path("test_path")
.map(|(path, _)| path)
.collect();
assert_eq!(
paths,
HashSet::from([Pointer("test_path"), Pointer("test.path")])
);
let paths: HashSet<_> = points
.by_kv_path("test_path_1")
.map(|(path, _)| path)
.collect();
assert_eq!(paths, HashSet::from([Pointer("test.path_1")]));
}
}