use std::{borrow::Cow, num::NonZeroU64};
use crate::BinaryKey;
pub fn key_bytes<K: BinaryKey + ?Sized>(key: &K) -> Vec<u8> {
concat_keys!(key)
}
const SEPARATOR_CHAR: u8 = 0;
const MIGRATION_CHAR: u8 = b'^';
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
pub struct IndexAddress {
pub(super) name: String,
pub(super) id_in_group: Option<Vec<u8>>,
pub(super) in_migration: bool,
}
impl IndexAddress {
pub fn from_root<S: Into<String>>(root: S) -> Self {
Self {
name: root.into(),
id_in_group: None,
in_migration: false,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub(super) fn namespace(&self) -> &str {
if self.in_migration {
let dot_position = self.name.find('.');
if let Some(pos) = dot_position {
&self.name[..pos]
} else {
&self.name
}
} else {
""
}
}
pub fn id_in_group(&self) -> Option<&[u8]> {
self.id_in_group.as_deref()
}
pub fn prepend_name(self, prefix: &str) -> Self {
let name = if self.name.is_empty() {
prefix.to_owned()
} else {
[prefix, ".", self.name()].concat()
};
Self { name, ..self }
}
pub fn append_name(self, suffix: &str) -> Self {
let name = if self.name.is_empty() {
suffix.to_owned()
} else {
[self.name(), ".", suffix].concat()
};
Self { name, ..self }
}
pub fn append_key<K: BinaryKey + ?Sized>(self, suffix: &K) -> Self {
let bytes = if let Some(ref bytes) = self.id_in_group {
concat_keys!(bytes, suffix)
} else {
concat_keys!(suffix)
};
Self {
id_in_group: Some(bytes),
..self
}
}
pub(crate) fn set_in_migration(&mut self) {
self.in_migration = true;
}
pub(crate) fn fully_qualified_name(&self) -> Vec<u8> {
const INDEX_NAME_SEPARATOR: &[u8] = &[SEPARATOR_CHAR];
const MIGRATION_PREFIX: &[u8] = &[MIGRATION_CHAR];
match (self.in_migration, self.id_in_group()) {
(true, Some(bytes)) => {
concat_keys!(MIGRATION_PREFIX, self.name(), INDEX_NAME_SEPARATOR, bytes)
}
(false, Some(bytes)) => concat_keys!(self.name(), INDEX_NAME_SEPARATOR, bytes),
(true, None) => concat_keys!(MIGRATION_PREFIX, self.name()),
(false, None) => self.name.as_bytes().to_vec(),
}
}
pub(crate) fn qualified_prefix(&self) -> Vec<u8> {
let mut prefix = self.fully_qualified_name();
if self.id_in_group.is_none() {
prefix.push(SEPARATOR_CHAR);
}
prefix
}
#[allow(unsafe_code)]
pub(super) fn parse_fully_qualified_name(
qualified_name: &[u8],
min_name_len: usize,
) -> (String, bool) {
let (cutoff_index, is_in_group) = qualified_name[min_name_len..]
.iter()
.position(|&byte| byte == SEPARATOR_CHAR)
.map_or_else(
|| (qualified_name.len(), false),
|pos| (pos + min_name_len, true),
);
let name = if qualified_name[0] == MIGRATION_CHAR {
qualified_name[1..cutoff_index].to_vec()
} else {
qualified_name[..cutoff_index].to_vec()
};
debug_assert!(
name.is_ascii(),
"BUG: non-ASCII chars in the name part of a fully qualified index name \
(qualified name = {:?}, inferred name part = {})",
qualified_name,
String::from_utf8_lossy(&name)
);
let name = unsafe {
String::from_utf8_unchecked(name)
};
(name, is_in_group)
}
#[inline]
pub(super) fn qualify_migration_namespace(namespace: &str) -> Vec<u8> {
["^", namespace, "."].concat().into_bytes()
}
#[inline]
pub(super) fn migrate_qualified_name(qualified_name: &[u8]) -> &[u8] {
debug_assert_eq!(
qualified_name[0], MIGRATION_CHAR,
"Qualified name {:?} is not in migration",
qualified_name
);
&qualified_name[1..]
}
}
impl From<&str> for IndexAddress {
fn from(name: &str) -> Self {
Self::from_root(name)
}
}
impl From<String> for IndexAddress {
fn from(name: String) -> Self {
Self::from_root(name)
}
}
impl<'a, K: BinaryKey + ?Sized> From<(&'a str, &'a K)> for IndexAddress {
fn from((name, key): (&'a str, &'a K)) -> Self {
Self {
name: name.to_owned(),
id_in_group: Some(key_bytes(key)),
in_migration: false,
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct ResolvedAddress {
pub name: String,
pub id: Option<NonZeroU64>,
}
impl ResolvedAddress {
pub(crate) fn new(name: impl Into<String>, id: Option<NonZeroU64>) -> Self {
Self {
name: name.into(),
id,
}
}
pub(crate) fn system(name: impl Into<String>) -> Self {
Self::new(name, None)
}
pub(crate) fn id_to_bytes(&self) -> Option<[u8; 8]> {
self.id.map(|id| id.get().to_le_bytes())
}
pub(crate) fn keyed<'k>(&self, key: &'k [u8]) -> Cow<'k, [u8]> {
match self.id {
None => Cow::Borrowed(key),
Some(id) => {
let mut bytes = Vec::with_capacity(8 + key.len());
bytes.extend_from_slice(&id.get().to_le_bytes());
bytes.extend_from_slice(key);
bytes.into()
}
}
}
}
#[cfg(test)]
impl From<&str> for ResolvedAddress {
fn from(name: &str) -> Self {
Self::system(name)
}
}
#[test]
fn address_resolution() {
{
let (name, is_in_group) = IndexAddress::parse_fully_qualified_name(b"some.list", 0);
assert_eq!(name, "some.list");
assert!(!is_in_group);
}
{
let (name, is_in_group) = IndexAddress::parse_fully_qualified_name(b"some.list", 9);
assert_eq!(name, "some.list");
assert!(!is_in_group);
}
{
let (name, is_in_group) =
IndexAddress::parse_fully_qualified_name(b"some.list\0key\xa0", 9);
assert_eq!(name, "some.list");
assert!(is_in_group);
}
}