use std::collections::BTreeMap;
use qubit_common::{DataType, DataTypeOf};
use qubit_value::{Value, ValueConstructor, ValueConverter};
use serde::{Deserialize, Serialize};
use crate::{MetadataError, MetadataResult, MetadataSchema};
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct Metadata(BTreeMap<String, Value>);
impl Metadata {
#[inline]
#[must_use]
pub fn new() -> Self {
Self(BTreeMap::new())
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
#[must_use]
pub fn contains_key(&self, key: &str) -> bool {
self.0.contains_key(key)
}
#[inline]
pub fn get<T>(&self, key: &str) -> Option<T>
where
T: DataTypeOf,
Value: ValueConverter<T>,
{
self.try_get(key).ok()
}
pub fn try_get<T>(&self, key: &str) -> MetadataResult<T>
where
T: DataTypeOf,
Value: ValueConverter<T>,
{
let value = self
.0
.get(key)
.ok_or_else(|| MetadataError::MissingKey(key.to_string()))?;
value
.to::<T>()
.map_err(|error| MetadataError::conversion_error(key, T::DATA_TYPE, value, error))
}
#[inline]
#[must_use]
pub fn get_raw(&self, key: &str) -> Option<&Value> {
self.0.get(key)
}
#[inline]
#[must_use]
pub fn data_type(&self, key: &str) -> Option<DataType> {
self.0.get(key).map(Value::data_type)
}
#[inline]
#[must_use]
pub fn get_or<T>(&self, key: &str, default: T) -> T
where
T: DataTypeOf,
Value: ValueConverter<T>,
{
self.try_get(key).unwrap_or(default)
}
#[inline]
pub fn set<T>(&mut self, key: &str, value: T) -> Option<Value>
where
Value: ValueConstructor<T>,
{
self.0.insert(key.to_string(), to_value(value))
}
#[inline]
pub fn set_checked<T>(
&mut self,
schema: &MetadataSchema,
key: &str,
value: T,
) -> MetadataResult<Option<Value>>
where
Value: ValueConstructor<T>,
{
let value = to_value(value);
schema.validate_entry(key, &value)?;
Ok(self.set_raw(key, value))
}
#[inline]
pub fn with_checked<T>(
mut self,
schema: &MetadataSchema,
key: &str,
value: T,
) -> MetadataResult<Self>
where
Value: ValueConstructor<T>,
{
self.set_checked(schema, key, value)?;
Ok(self)
}
#[inline]
#[must_use]
pub fn with<T>(mut self, key: &str, value: T) -> Self
where
Value: ValueConstructor<T>,
{
self.set(key, value);
self
}
#[inline]
pub fn set_raw(&mut self, key: &str, value: Value) -> Option<Value> {
self.0.insert(key.to_string(), value)
}
#[inline]
#[must_use]
pub fn with_raw(mut self, key: &str, value: Value) -> Self {
self.set_raw(key, value);
self
}
#[inline]
pub fn remove(&mut self, key: &str) -> Option<Value> {
self.0.remove(key)
}
#[inline]
pub fn clear(&mut self) {
self.0.clear();
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
self.0.iter().map(|(key, value)| (key.as_str(), value))
}
#[inline]
pub fn keys(&self) -> impl Iterator<Item = &str> {
self.0.keys().map(String::as_str)
}
#[inline]
pub fn values(&self) -> impl Iterator<Item = &Value> {
self.0.values()
}
pub fn merge(&mut self, other: Metadata) {
for (key, value) in other.0 {
self.0.insert(key, value);
}
}
#[must_use]
pub fn merged(&self, other: &Metadata) -> Metadata {
let mut result = self.clone();
for (key, value) in &other.0 {
result.0.insert(key.clone(), value.clone());
}
result
}
#[inline]
pub fn retain<F>(&mut self, mut predicate: F)
where
F: FnMut(&str, &Value) -> bool,
{
self.0.retain(|key, value| predicate(key.as_str(), value));
}
#[inline]
#[must_use]
pub fn into_inner(self) -> BTreeMap<String, Value> {
self.0
}
}
#[inline]
fn to_value<T>(value: T) -> Value
where
Value: ValueConstructor<T>,
{
<Value as ValueConstructor<T>>::from_type(value)
}
impl From<BTreeMap<String, Value>> for Metadata {
#[inline]
fn from(map: BTreeMap<String, Value>) -> Self {
Self(map)
}
}
impl From<Metadata> for BTreeMap<String, Value> {
#[inline]
fn from(meta: Metadata) -> Self {
meta.0
}
}
impl FromIterator<(String, Value)> for Metadata {
#[inline]
fn from_iter<I: IntoIterator<Item = (String, Value)>>(iter: I) -> Self {
Self(iter.into_iter().collect())
}
}
impl IntoIterator for Metadata {
type IntoIter = std::collections::btree_map::IntoIter<String, Value>;
type Item = (String, Value);
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a> IntoIterator for &'a Metadata {
type IntoIter = std::collections::btree_map::Iter<'a, String, Value>;
type Item = (&'a String, &'a Value);
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl Extend<(String, Value)> for Metadata {
#[inline]
fn extend<I: IntoIterator<Item = (String, Value)>>(&mut self, iter: I) {
self.0.extend(iter);
}
}