#[cfg(feature = "json")]
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::fmt;
#[cfg(feature = "json")]
use std::io::Write;
#[cfg(feature = "json")]
use std::rc::Rc;
#[cfg(feature = "json")]
use qubit_budget::json::JsonDecodeSession;
#[cfg(feature = "json")]
use qubit_budget::json::JsonEncodeLimits;
#[cfg(feature = "json")]
use qubit_budget::json::JsonEncodeSession;
use qubit_datatype::ConversionLimits;
use qubit_datatype::ConversionPolicy;
use qubit_datatype::DataConversionTarget;
use qubit_datatype::DataType;
#[cfg(feature = "json")]
use qubit_json::decode::JsonDecoder;
#[cfg(feature = "json")]
use qubit_json::encode::JsonEncoder;
use qubit_redact::Redact;
use qubit_redact::RedactionWriter;
use qubit_redact::Redactor;
use qubit_value::IntoValueDefault;
use qubit_value::StrictValueRead;
use qubit_value::Value;
use qubit_value::ValueError;
#[cfg(feature = "json")]
use qubit_value::ValueWireEncodePreflight;
use qubit_value::ValueWirePayloadV1;
use qubit_value::ValueWirePayloadV1Seed;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use serde::Serializer;
use serde::de;
use serde::de::DeserializeSeed;
#[cfg(feature = "json")]
use serde::de::Error as DeError;
use serde::ser::Error as SerError;
use crate::MetadataError;
use crate::MetadataResult;
#[cfg(feature = "schema")]
use crate::MetadataSchema;
use crate::constants::STRICT_STRING_MAP_MAX_ENTRIES;
use crate::constants::STRICT_STRING_MAP_MAX_KEY_BYTES;
use crate::internal::MetadataValues;
#[cfg(feature = "json")]
use crate::metadata_limits::MetadataLimits;
use crate::wire::METADATA_WIRE_VERSION_V1;
use crate::wire::MetadataWireV1;
use crate::wire::MetadataWireV1Seed;
use crate::wire::MetadataWireValuesRef;
use crate::wire::StrictStringMap;
use crate::wire::StrictStringMapValueSeed;
#[derive(Clone, PartialEq, Default)]
pub struct Metadata(
BTreeMap<String, Value>,
);
impl Metadata {
#[inline]
#[must_use]
pub fn new() -> Self {
Self(BTreeMap::new())
}
#[cfg(feature = "json")]
#[inline]
pub fn decode_json_slice(input: &[u8]) -> Result<Self, crate::MetadataWireDecodeError> {
Self::decode_json_slice_with_limits(input, MetadataLimits::default())
}
#[cfg(feature = "json")]
pub fn decode_json_slice_with_limits(
input: &[u8],
limits: MetadataLimits,
) -> Result<Self, crate::MetadataWireDecodeError> {
limits
.validate()
.map_err(crate::MetadataWireDecodeError::InvalidLimits)?;
let mut decoder = JsonDecoder::new(JsonDecodeSession::from_limits(limits.json_decode()));
let error_slot = Rc::new(RefCell::new(None));
let wire = decoder
.decode_seed_utf8(
MetadataWireV1Seed::new(
StrictStringMapValueSeed::new(
limits.max_metadata_entries(),
limits.max_key_bytes(),
ValueWirePayloadV1Seed::new(),
)
.with_error_slot(Rc::clone(&error_slot)),
),
input,
)
.map_err(|error| {
error_slot.borrow_mut().take().map_or_else(
|| Into::<crate::MetadataWireDecodeError>::into(error),
crate::MetadataWireDecodeError::Domain,
)
})?;
if wire.version != METADATA_WIRE_VERSION_V1 {
return Err(crate::MetadataWireDecodeError::UnsupportedVersion {
expected: METADATA_WIRE_VERSION_V1,
actual: wire.version,
});
}
let metadata = Self(Self::from_wire(wire).map_err(|error| {
crate::MetadataWireDecodeError::InvalidJson(<serde_json::Error as DeError>::custom(error))
})?);
Ok(metadata)
}
#[cfg(feature = "json")]
pub fn to_json_vec(&self) -> Result<Vec<u8>, crate::MetadataWireEncodeError> {
self.to_json_vec_with_limits(crate::metadata_limits::default_json_encode_limits())
}
#[cfg(feature = "json")]
pub fn to_json_vec_with_limits(&self, limits: JsonEncodeLimits) -> Result<Vec<u8>, crate::MetadataWireEncodeError> {
let mut preflight = ValueWireEncodePreflight::new_value_limits(*limits.value_limits());
for value in self.0.values() {
preflight
.check_value(value)
.map_err(crate::MetadataWireEncodeError::from)?;
}
let session = JsonEncodeSession::from_limits(limits);
JsonEncoder::new(session).to_vec(self).map_err(Into::into)
}
#[cfg(feature = "json")]
pub fn to_json_writer<W>(&self, writer: W) -> Result<(), crate::MetadataWireEncodeError>
where
W: Write,
{
self.to_json_writer_with_limits(writer, crate::metadata_limits::default_json_encode_limits())
}
#[cfg(feature = "json")]
pub fn to_json_writer_with_limits<W>(
&self,
writer: W,
limits: JsonEncodeLimits,
) -> Result<(), crate::MetadataWireEncodeError>
where
W: Write,
{
let mut preflight = ValueWireEncodePreflight::new_value_limits(*limits.value_limits());
for value in self.0.values() {
preflight
.check_value(value)
.map_err(crate::MetadataWireEncodeError::from)?;
}
let session = JsonEncodeSession::from_limits(limits);
JsonEncoder::new(session)
.write_buffered(writer, self)
.map_err(Into::into)
}
#[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)
}
pub fn get<T: StrictValueRead>(&self, key: &str) -> MetadataResult<T> {
T::read_scalar(self.entry(key)?).map_err(|source| Self::map_access_error(key, source))
}
pub fn get_ref<'a, T: ?Sized>(&'a self, key: &str) -> MetadataResult<&'a T>
where
&'a T: TryFrom<&'a Value, Error = ValueError>,
{
self.entry(key)?
.get_ref::<T>()
.map_err(|source| Self::map_access_error(key, source))
}
pub fn get_optional<T: StrictValueRead>(&self, key: &str) -> MetadataResult<Option<T>> {
match self.get(key) {
Ok(value) => Ok(Some(value)),
Err(error) if Self::is_defaultable(&error, false) => Ok(None),
Err(error) => Err(error),
}
}
pub fn get_or<T: StrictValueRead>(&self, key: &str, default: impl IntoValueDefault<T>) -> MetadataResult<T> {
self.get_optional(key)
.map(|value| value.unwrap_or_else(|| default.into_value_default()))
}
pub fn convert<T: DataConversionTarget>(&self, key: &str) -> MetadataResult<T> {
self.convert_with(key, ConversionPolicy::default_ref(), ConversionLimits::default_ref())
}
pub fn convert_with<T: DataConversionTarget>(
&self,
key: &str,
policy: &ConversionPolicy,
limits: &ConversionLimits,
) -> MetadataResult<T> {
self.entry(key)?
.to_with(policy, limits)
.map_err(|source| Self::map_access_error(key, source))
}
pub fn convert_optional_with<T: DataConversionTarget>(
&self,
key: &str,
policy: &ConversionPolicy,
limits: &ConversionLimits,
) -> MetadataResult<Option<T>> {
match self.convert_with(key, policy, limits) {
Ok(value) => Ok(Some(value)),
Err(error) if Self::is_defaultable(&error, true) => Ok(None),
Err(error) => Err(error),
}
}
pub fn convert_or_with<T: DataConversionTarget>(
&self,
key: &str,
default: impl IntoValueDefault<T>,
policy: &ConversionPolicy,
limits: &ConversionLimits,
) -> MetadataResult<T> {
self.convert_optional_with(key, policy, limits)
.map(|value| value.unwrap_or_else(|| default.into_value_default()))
}
fn entry(&self, key: &str) -> MetadataResult<&Value> {
self.get_raw(key)
.ok_or_else(|| MetadataError::MissingKey(key.to_owned()))
}
fn map_access_error(key: &str, source: ValueError) -> MetadataError {
MetadataError::ValueAccess {
key: key.to_owned(),
source: Box::new(source),
}
}
fn is_defaultable(error: &MetadataError, conversion: bool) -> bool {
match error {
MetadataError::MissingKey(_) => true,
MetadataError::ValueAccess { source, .. } => source.missing().is_some_and(|missing| {
if conversion {
missing.is_defaultable_for_conversion()
} else {
missing.is_defaultable_for_strict_read()
}
}),
_ => false,
}
}
#[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]
pub fn insert<T>(&mut self, key: &str, value: T) -> Option<Value>
where
T: Into<Value>,
{
self.0.insert(key.to_string(), value.into())
}
#[inline]
pub fn set<T>(&mut self, key: &str, value: T) -> &mut Self
where
T: Into<Value>,
{
let _ = self.insert(key, value);
self
}
#[inline]
#[must_use]
pub fn with<T>(mut self, key: &str, value: T) -> Self
where
T: Into<Value>,
{
self.set(key, value);
self
}
#[cfg(feature = "schema")]
#[inline]
pub fn insert_checked<T>(&mut self, schema: &MetadataSchema, key: &str, value: T) -> MetadataResult<Option<Value>>
where
T: Into<Value>,
{
let value = value.into();
schema.validate_entry(key, &value)?;
Ok(self.insert(key, value))
}
#[cfg(feature = "schema")]
#[inline]
pub fn set_checked<T>(&mut self, schema: &MetadataSchema, key: &str, value: T) -> MetadataResult<&mut Self>
where
T: Into<Value>,
{
let _ = self.insert_checked(schema, key, value)?;
Ok(self)
}
#[cfg(feature = "schema")]
#[inline]
pub fn with_checked<T>(mut self, schema: &MetadataSchema, key: &str, value: T) -> MetadataResult<Self>
where
T: Into<Value>,
{
self.set_checked(schema, key, value)?;
Ok(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]
#[must_use = "the metadata iterator must be consumed to inspect entries"]
pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
self.0.iter().map(|(key, value)| (key.as_str(), value))
}
#[inline]
#[must_use = "the metadata key iterator must be consumed to inspect keys"]
pub fn keys(&self) -> impl Iterator<Item = &str> {
self.0.keys().map(String::as_str)
}
#[inline]
#[must_use = "the metadata value iterator must be consumed to inspect values"]
pub fn values(&self) -> impl Iterator<Item = &Value> {
self.0.values()
}
pub fn merge(&mut self, mut other: Metadata) {
self.0.append(&mut other.0);
}
#[must_use]
pub fn merged(&self, other: &Metadata) -> Metadata {
let mut result = self.clone();
let mut right = other.0.clone();
result.0.append(&mut right);
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]
pub fn validate_wire_contract(&self) -> MetadataResult<()> {
if self.0.len() > STRICT_STRING_MAP_MAX_ENTRIES {
return Err(MetadataError::WireLimitExceeded {
kind: crate::MetadataWireLimitKind::Entries,
value: self.0.len(),
maximum: STRICT_STRING_MAP_MAX_ENTRIES,
});
}
if let Some(key) = self.0.keys().find(|key| key.len() > STRICT_STRING_MAP_MAX_KEY_BYTES) {
return Err(MetadataError::WireLimitExceeded {
kind: crate::MetadataWireLimitKind::KeyBytes,
value: key.len(),
maximum: STRICT_STRING_MAP_MAX_KEY_BYTES,
});
}
Ok(())
}
}
impl Redact for Metadata {
fn write_redacted(&self, writer: &mut RedactionWriter<'_>) {
writer.record("Metadata", |fields| {
fields.nested("values", &MetadataValues(&self.0));
});
}
}
impl fmt::Debug for Metadata {
#[inline]
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let output = Redactor::strict().redact_text(self);
let text = output.text_or_marker("<redaction incomplete>");
formatter.write_str(text.as_ref())
}
}
impl fmt::Display for Metadata {
#[inline]
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let output = Redactor::strict().redact_text(self);
let text = output.text_or_marker("<redaction incomplete>");
formatter.write_str(text.as_ref())
}
}
impl Serialize for Metadata {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.validate_wire_contract().map_err(<S::Error as SerError>::custom)?;
MetadataWireV1 {
version: METADATA_WIRE_VERSION_V1,
values: MetadataWireValuesRef(&self.0),
}
.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Metadata {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let wire: MetadataWireV1<StrictStringMap<ValueWirePayloadV1>> =
MetadataWireV1Seed::new(StrictStringMapValueSeed::new(
STRICT_STRING_MAP_MAX_ENTRIES,
STRICT_STRING_MAP_MAX_KEY_BYTES,
ValueWirePayloadV1Seed::new(),
))
.deserialize(deserializer)?;
if wire.version != METADATA_WIRE_VERSION_V1 {
return Err(de::Error::custom("unsupported Metadata wire format version"));
}
let values = Self::from_wire(wire).map_err(de::Error::custom)?;
Ok(Self(values))
}
}
impl Metadata {
fn from_wire(
wire: MetadataWireV1<StrictStringMap<ValueWirePayloadV1>>,
) -> Result<BTreeMap<String, Value>, &'static str> {
wire.values
.into_inner()
.into_iter()
.map(|(key, value)| {
value
.into_container()
.into_scalar()
.map(|value| (key, value))
.map_err(|_| "metadata values must use scalar V1 payloads")
})
.collect()
}
}
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);
}
}