mod filter_validation;
#[cfg(feature = "json")]
use std::cell::RefCell;
use std::collections::BTreeMap;
#[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::DataType;
#[cfg(feature = "json")]
use qubit_json::decode::JsonDecoder;
#[cfg(feature = "json")]
use qubit_json::encode::JsonEncoder;
use qubit_value::Value;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use serde::Serializer;
use serde::de;
use serde::ser::Error as SerError;
use crate::Metadata;
use crate::MetadataError;
use crate::MetadataResult;
use crate::MetadataValidationError;
use crate::MetadataValidationResult;
use crate::constants::STRICT_STRING_MAP_MAX_ENTRIES;
use crate::constants::STRICT_STRING_MAP_MAX_KEY_BYTES;
#[cfg(feature = "json")]
use crate::metadata_limits::MetadataLimits;
use crate::schema::MetadataField;
use crate::schema::MetadataSchemaBuilder;
use crate::schema::UnknownFilterFieldPolicy;
use crate::schema::UnknownMetadataFieldPolicy;
use crate::wire::METADATA_SCHEMA_WIRE_VERSION_V1;
use crate::wire::MetadataSchemaWireV1;
#[cfg(feature = "json")]
use crate::wire::MetadataSchemaWireV1Seed;
use crate::wire::StrictStringMap;
#[cfg(feature = "json")]
use crate::wire::StrictStringMapSeed;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MetadataSchema {
fields: BTreeMap<String, MetadataField>,
unknown_metadata_field_policy: UnknownMetadataFieldPolicy,
unknown_filter_field_policy: UnknownFilterFieldPolicy,
}
impl MetadataSchema {
#[inline]
#[must_use]
pub fn builder() -> MetadataSchemaBuilder {
MetadataSchemaBuilder::default()
}
#[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(
MetadataSchemaWireV1Seed::new(
StrictStringMapSeed::new(limits.max_schema_fields(), limits.max_key_bytes())
.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_SCHEMA_WIRE_VERSION_V1 {
return Err(crate::MetadataWireDecodeError::UnsupportedVersion {
expected: METADATA_SCHEMA_WIRE_VERSION_V1,
actual: wire.version,
});
}
Ok(Self::new(
wire.fields.into_inner(),
wire.unknown_metadata_field_policy,
wire.unknown_filter_field_policy,
))
}
#[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 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 session = JsonEncodeSession::from_limits(limits);
JsonEncoder::new(session)
.write_buffered(writer, self)
.map_err(Into::into)
}
#[inline]
pub(crate) fn new(
fields: BTreeMap<String, MetadataField>,
unknown_metadata_field_policy: UnknownMetadataFieldPolicy,
unknown_filter_field_policy: UnknownFilterFieldPolicy,
) -> Self {
Self {
fields,
unknown_metadata_field_policy,
unknown_filter_field_policy,
}
}
#[inline]
#[must_use]
pub fn field(&self, key: &str) -> Option<&MetadataField> {
self.fields.get(key)
}
#[inline]
#[must_use]
pub fn field_type(&self, key: &str) -> Option<DataType> {
self.field(key).map(MetadataField::data_type)
}
#[inline]
#[must_use]
pub fn unknown_metadata_field_policy(&self) -> UnknownMetadataFieldPolicy {
self.unknown_metadata_field_policy
}
#[inline]
#[must_use]
pub fn unknown_filter_field_policy(&self) -> UnknownFilterFieldPolicy {
self.unknown_filter_field_policy
}
#[inline]
#[must_use = "the schema field iterator must be consumed to inspect fields"]
pub fn fields(&self) -> impl Iterator<Item = (&str, &MetadataField)> {
self.fields.iter().map(|(key, field)| (key.as_str(), field))
}
pub fn validate(&self, meta: &Metadata) -> MetadataValidationResult<()> {
let mut issues = Vec::new();
for (key, field) in &self.fields {
if field.is_required() && meta.get_raw(key).is_none_or(Value::is_unset) {
issues.push(MetadataError::MissingRequiredField {
key: key.clone(),
expected: field.data_type(),
});
}
}
for (key, value) in meta.iter() {
if self
.field(key)
.is_some_and(|field| field.is_required() && value.is_unset())
{
continue;
}
if let Err(error) = self.validate_entry(key, value) {
issues.push(error);
}
}
if let Some(error) = MetadataValidationError::from_issues(issues) {
Err(error)
} else {
Ok(())
}
}
pub(crate) fn validate_entry(&self, key: &str, value: &Value) -> MetadataResult<()> {
match self.field(key) {
Some(field) if field.is_required() && value.is_unset() => Err(MetadataError::MissingRequiredField {
key: key.to_string(),
expected: field.data_type(),
}),
Some(field) if field.data_type() != value.data_type() => {
Err(MetadataError::type_mismatch(key, field.data_type(), value.data_type()))
}
Some(_) => Ok(()),
None if matches!(self.unknown_metadata_field_policy, UnknownMetadataFieldPolicy::Reject) => {
Err(MetadataError::UnknownField { key: key.to_string() })
}
None => Ok(()),
}
}
#[inline]
pub fn validate_wire_contract(&self) -> MetadataResult<()> {
if self.fields.len() > STRICT_STRING_MAP_MAX_ENTRIES {
return Err(MetadataError::WireLimitExceeded {
kind: crate::MetadataWireLimitKind::Entries,
value: self.fields.len(),
maximum: STRICT_STRING_MAP_MAX_ENTRIES,
});
}
if let Some(key) = self
.fields
.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 Serialize for MetadataSchema {
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)?;
MetadataSchemaWireV1 {
version: METADATA_SCHEMA_WIRE_VERSION_V1,
fields: &self.fields,
unknown_metadata_field_policy: self.unknown_metadata_field_policy,
unknown_filter_field_policy: self.unknown_filter_field_policy,
}
.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for MetadataSchema {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let wire: MetadataSchemaWireV1<StrictStringMap<MetadataField>> =
MetadataSchemaWireV1::deserialize(deserializer)?;
if wire.version != METADATA_SCHEMA_WIRE_VERSION_V1 {
return Err(de::Error::custom("unsupported MetadataSchema wire format version"));
}
Ok(Self::new(
wire.fields.into_inner(),
wire.unknown_metadata_field_policy,
wire.unknown_filter_field_policy,
))
}
}
impl Default for MetadataSchema {
#[inline]
fn default() -> Self {
Self {
fields: BTreeMap::new(),
unknown_metadata_field_policy: UnknownMetadataFieldPolicy::Reject,
unknown_filter_field_policy: UnknownFilterFieldPolicy::Reject,
}
}
}