use crate::{Attribute, AttributeIndex, AttributeMap, Channel, ChannelMap, Stream};
#[derive(Clone, Debug, Default)]
pub struct PersistenceSchema {
definitions: Vec<PersistenceDefinition>,
}
impl PartialEq for PersistenceSchema {
fn eq(&self, other: &Self) -> bool {
self.definitions.len() == other.definitions.len()
&& self.definitions.iter().zip(&other.definitions).all(
|(definition, other_definition)| {
definition.is_equal_ignoring_value_type(other_definition)
},
)
}
}
impl Eq for PersistenceSchema {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct PersistenceDefinition {
pub(crate) name: String,
pub(crate) kind: PersistenceKind,
pub(crate) index: Option<AttributeIndex>,
pub(crate) sync_to_attribute_store: bool,
pub(crate) is_hard_delete_on_null: bool,
pub(crate) numeric_or_boolean_type_name: Option<&'static str>,
pub(crate) value_type_name: Option<&'static str>,
pub(crate) stream_identity: Option<usize>,
pub(crate) stream_capacity_bytes: Option<i64>,
}
impl PersistenceDefinition {
pub(crate) fn new(name: &str, kind: PersistenceKind) -> Self {
Self {
name: name.to_string(),
kind,
index: None,
sync_to_attribute_store: false,
is_hard_delete_on_null: false,
numeric_or_boolean_type_name: None,
value_type_name: None,
stream_identity: None,
stream_capacity_bytes: None,
}
}
fn is_equal_ignoring_value_type(&self, other: &Self) -> bool {
let Self {
name,
kind,
index,
sync_to_attribute_store,
is_hard_delete_on_null,
numeric_or_boolean_type_name,
value_type_name: _,
stream_identity,
stream_capacity_bytes,
} = self;
*name == other.name
&& *kind == other.kind
&& *index == other.index
&& *sync_to_attribute_store == other.sync_to_attribute_store
&& *is_hard_delete_on_null == other.is_hard_delete_on_null
&& *numeric_or_boolean_type_name == other.numeric_or_boolean_type_name
&& *stream_identity == other.stream_identity
&& *stream_capacity_bytes == other.stream_capacity_bytes
}
pub(crate) fn for_attribute<T>(attribute: &Attribute<T>) -> Self {
Self {
index: attribute.index().cloned(),
sync_to_attribute_store: attribute.is_sync_to_attribute_store(),
is_hard_delete_on_null: attribute.is_hard_delete_on_null(),
numeric_or_boolean_type_name: attribute.numeric_or_boolean_type_name(),
value_type_name: Some(std::any::type_name::<T>()),
..Self::new(attribute.name(), PersistenceKind::Attribute)
}
}
pub(crate) fn for_attribute_map<T>(attribute_map: &AttributeMap<T>) -> Self {
Self {
index: attribute_map.index().cloned(),
sync_to_attribute_store: attribute_map.is_sync_to_attribute_store(),
is_hard_delete_on_null: attribute_map.is_hard_delete_on_null(),
numeric_or_boolean_type_name: attribute_map.numeric_or_boolean_type_name(),
value_type_name: Some(std::any::type_name::<T>()),
..Self::new(attribute_map.name(), PersistenceKind::AttributeMap)
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum PersistenceKind {
Attribute,
AttributeMap,
Channel,
ChannelMap,
Stream,
}
impl PersistenceSchema {
pub fn new() -> Self {
Self::default()
}
pub fn attribute<T>(mut self, attribute: &Attribute<T>) -> Self {
self.definitions
.push(PersistenceDefinition::for_attribute(attribute));
self
}
pub fn attribute_map<T>(mut self, attribute: &AttributeMap<T>) -> Self {
self.definitions
.push(PersistenceDefinition::for_attribute_map(attribute));
self
}
pub fn channel<T>(mut self, channel: &Channel<T>) -> Self {
self.definitions.push(PersistenceDefinition::new(
channel.name(),
PersistenceKind::Channel,
));
self
}
pub fn channel_map<T>(mut self, channel: &ChannelMap<T>) -> Self {
self.definitions.push(PersistenceDefinition::new(
channel.name(),
PersistenceKind::ChannelMap,
));
self
}
pub fn stream<T>(mut self, stream: &Stream<T>) -> Self {
self.definitions.push(PersistenceDefinition {
stream_identity: Some(stream.identity()),
stream_capacity_bytes: Some(stream.stream_capacity_bytes()),
..PersistenceDefinition::new(stream.name(), PersistenceKind::Stream)
});
self
}
pub(crate) fn definitions(&self) -> &[PersistenceDefinition] {
&self.definitions
}
}
#[cfg(test)]
mod tests {
use super::PersistenceSchema;
use crate::{Attribute, AttributeMap, Stream};
#[test]
fn registration_metadata_retains_sync_configuration() {
let attribute = Attribute::<String>::new("plain");
let attribute_map = AttributeMap::<String>::new("map").sync_to_attribute_store();
let schema = PersistenceSchema::new()
.attribute(&attribute)
.attribute_map(&attribute_map);
assert!(!schema.definitions[0].sync_to_attribute_store);
assert!(schema.definitions[1].sync_to_attribute_store);
}
#[test]
fn registration_metadata_retains_hard_delete_configuration() {
let plain = Attribute::<String>::new("plain");
let secret = Attribute::<String>::new("secret").hard_delete_on_null();
let counter = Attribute::<Option<u64>>::new("counter").hard_delete_on_null();
let secrets = AttributeMap::<Vec<u8>>::new("secrets").hard_delete_on_null();
let schema = PersistenceSchema::new()
.attribute(&plain)
.attribute(&secret)
.attribute(&counter)
.attribute_map(&secrets.clone());
assert!(!schema.definitions[0].is_hard_delete_on_null);
assert!(schema.definitions[1].is_hard_delete_on_null);
assert_eq!(schema.definitions[1].numeric_or_boolean_type_name, None);
assert!(
schema.definitions[2]
.numeric_or_boolean_type_name
.is_some_and(|type_name| type_name.contains("u64"))
);
assert!(schema.definitions[3].is_hard_delete_on_null);
assert_eq!(schema.definitions[3].numeric_or_boolean_type_name, None);
}
#[test]
fn schema_equality_ignores_attribute_value_types() {
let text = PersistenceSchema::new().attribute(&Attribute::<String>::new("status"));
let number = PersistenceSchema::new().attribute(&Attribute::<i64>::new("status"));
let other_name = PersistenceSchema::new().attribute(&Attribute::<String>::new("state"));
assert_eq!(text, number);
assert_ne!(text, other_name);
}
#[test]
fn registration_metadata_retains_stream_identity_and_capacity() {
let stream = Stream::<String>::new("thinking", 1_048_576);
let schema = PersistenceSchema::new().stream(&stream);
assert_eq!(
schema.definitions[0].stream_identity,
Some(stream.identity())
);
assert_eq!(schema.definitions[0].stream_capacity_bytes, Some(1_048_576));
}
}