qubit_metadata/schema/
metadata_schema.rs1mod filter_validation;
12
13#[cfg(feature = "json")]
14use std::cell::RefCell;
15use std::collections::BTreeMap;
16#[cfg(feature = "json")]
17use std::io::Write;
18#[cfg(feature = "json")]
19use std::rc::Rc;
20
21#[cfg(feature = "json")]
22use qubit_budget::json::JsonDecodeSession;
23#[cfg(feature = "json")]
24use qubit_budget::json::JsonEncodeLimits;
25#[cfg(feature = "json")]
26use qubit_budget::json::JsonEncodeSession;
27use qubit_datatype::DataType;
28#[cfg(feature = "json")]
29use qubit_json::decode::JsonDecoder;
30#[cfg(feature = "json")]
31use qubit_json::encode::JsonEncoder;
32use qubit_value::Value;
33use serde::Deserialize;
34use serde::Deserializer;
35use serde::Serialize;
36use serde::Serializer;
37use serde::de;
38use serde::ser::Error as SerError;
39
40use crate::Metadata;
41use crate::MetadataError;
42use crate::MetadataResult;
43use crate::MetadataValidationError;
44use crate::MetadataValidationResult;
45use crate::constants::STRICT_STRING_MAP_MAX_ENTRIES;
46use crate::constants::STRICT_STRING_MAP_MAX_KEY_BYTES;
47#[cfg(feature = "json")]
48use crate::metadata_limits::MetadataLimits;
49use crate::schema::MetadataField;
50use crate::schema::MetadataSchemaBuilder;
51use crate::schema::UnknownFilterFieldPolicy;
52use crate::schema::UnknownMetadataFieldPolicy;
53use crate::wire::METADATA_SCHEMA_WIRE_VERSION_V1;
54use crate::wire::MetadataSchemaWireV1;
55#[cfg(feature = "json")]
56use crate::wire::MetadataSchemaWireV1Seed;
57use crate::wire::StrictStringMap;
58#[cfg(feature = "json")]
59use crate::wire::StrictStringMapSeed;
60
61#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct MetadataSchema {
85 fields: BTreeMap<String, MetadataField>,
87 unknown_metadata_field_policy: UnknownMetadataFieldPolicy,
89 unknown_filter_field_policy: UnknownFilterFieldPolicy,
91}
92
93impl MetadataSchema {
94 #[inline]
100 #[must_use]
101 pub fn builder() -> MetadataSchemaBuilder {
102 MetadataSchemaBuilder::default()
103 }
104
105 #[cfg(feature = "json")]
121 #[inline]
122 pub fn decode_json_slice(input: &[u8]) -> Result<Self, crate::MetadataWireDecodeError> {
123 Self::decode_json_slice_with_limits(input, MetadataLimits::default())
124 }
125
126 #[cfg(feature = "json")]
144 pub fn decode_json_slice_with_limits(
145 input: &[u8],
146 limits: MetadataLimits,
147 ) -> Result<Self, crate::MetadataWireDecodeError> {
148 limits
149 .validate()
150 .map_err(crate::MetadataWireDecodeError::InvalidLimits)?;
151 let mut decoder = JsonDecoder::new(JsonDecodeSession::from_limits(limits.json_decode()));
152 let error_slot = Rc::new(RefCell::new(None));
153 let wire = decoder
154 .decode_seed_utf8(
155 MetadataSchemaWireV1Seed::new(
156 StrictStringMapSeed::new(limits.max_schema_fields(), limits.max_key_bytes())
157 .with_error_slot(Rc::clone(&error_slot)),
158 ),
159 input,
160 )
161 .map_err(|error| {
162 error_slot.borrow_mut().take().map_or_else(
163 || Into::<crate::MetadataWireDecodeError>::into(error),
164 crate::MetadataWireDecodeError::Domain,
165 )
166 })?;
167 if wire.version != METADATA_SCHEMA_WIRE_VERSION_V1 {
168 return Err(crate::MetadataWireDecodeError::UnsupportedVersion {
169 expected: METADATA_SCHEMA_WIRE_VERSION_V1,
170 actual: wire.version,
171 });
172 }
173 Ok(Self::new(
174 wire.fields.into_inner(),
175 wire.unknown_metadata_field_policy,
176 wire.unknown_filter_field_policy,
177 ))
178 }
179
180 #[cfg(feature = "json")]
182 pub fn to_json_vec(&self) -> Result<Vec<u8>, crate::MetadataWireEncodeError> {
183 self.to_json_vec_with_limits(crate::metadata_limits::default_json_encode_limits())
184 }
185
186 #[cfg(feature = "json")]
197 pub fn to_json_vec_with_limits(&self, limits: JsonEncodeLimits) -> Result<Vec<u8>, crate::MetadataWireEncodeError> {
198 let session = JsonEncodeSession::from_limits(limits);
199 JsonEncoder::new(session).to_vec(self).map_err(Into::into)
200 }
201
202 #[cfg(feature = "json")]
204 pub fn to_json_writer<W>(&self, writer: W) -> Result<(), crate::MetadataWireEncodeError>
205 where
206 W: Write,
207 {
208 self.to_json_writer_with_limits(writer, crate::metadata_limits::default_json_encode_limits())
209 }
210
211 #[cfg(feature = "json")]
223 pub fn to_json_writer_with_limits<W>(
224 &self,
225 writer: W,
226 limits: JsonEncodeLimits,
227 ) -> Result<(), crate::MetadataWireEncodeError>
228 where
229 W: Write,
230 {
231 let session = JsonEncodeSession::from_limits(limits);
232 JsonEncoder::new(session)
233 .write_buffered(writer, self)
234 .map_err(Into::into)
235 }
236
237 #[inline]
249 pub(crate) fn new(
250 fields: BTreeMap<String, MetadataField>,
251 unknown_metadata_field_policy: UnknownMetadataFieldPolicy,
252 unknown_filter_field_policy: UnknownFilterFieldPolicy,
253 ) -> Self {
254 Self {
255 fields,
256 unknown_metadata_field_policy,
257 unknown_filter_field_policy,
258 }
259 }
260
261 #[inline]
271 #[must_use]
272 pub fn field(&self, key: &str) -> Option<&MetadataField> {
273 self.fields.get(key)
274 }
275
276 #[inline]
286 #[must_use]
287 pub fn field_type(&self, key: &str) -> Option<DataType> {
288 self.field(key).map(MetadataField::data_type)
289 }
290
291 #[inline]
297 #[must_use]
298 pub fn unknown_metadata_field_policy(&self) -> UnknownMetadataFieldPolicy {
299 self.unknown_metadata_field_policy
300 }
301
302 #[inline]
308 #[must_use]
309 pub fn unknown_filter_field_policy(&self) -> UnknownFilterFieldPolicy {
310 self.unknown_filter_field_policy
311 }
312
313 #[inline]
319 #[must_use = "the schema field iterator must be consumed to inspect fields"]
320 pub fn fields(&self) -> impl Iterator<Item = (&str, &MetadataField)> {
321 self.fields.iter().map(|(key, field)| (key.as_str(), field))
322 }
323
324 pub fn validate(&self, meta: &Metadata) -> MetadataValidationResult<()> {
340 let mut issues = Vec::new();
341 for (key, field) in &self.fields {
342 if field.is_required() && meta.get_raw(key).is_none_or(Value::is_unset) {
343 issues.push(MetadataError::MissingRequiredField {
344 key: key.clone(),
345 expected: field.data_type(),
346 });
347 }
348 }
349
350 for (key, value) in meta.iter() {
351 if self
352 .field(key)
353 .is_some_and(|field| field.is_required() && value.is_unset())
354 {
355 continue;
356 }
357 if let Err(error) = self.validate_entry(key, value) {
358 issues.push(error);
359 }
360 }
361 if let Some(error) = MetadataValidationError::from_issues(issues) {
362 Err(error)
363 } else {
364 Ok(())
365 }
366 }
367
368 pub(crate) fn validate_entry(&self, key: &str, value: &Value) -> MetadataResult<()> {
384 match self.field(key) {
385 Some(field) if field.is_required() && value.is_unset() => Err(MetadataError::MissingRequiredField {
386 key: key.to_string(),
387 expected: field.data_type(),
388 }),
389 Some(field) if field.data_type() != value.data_type() => {
390 Err(MetadataError::type_mismatch(key, field.data_type(), value.data_type()))
391 }
392 Some(_) => Ok(()),
393 None if matches!(self.unknown_metadata_field_policy, UnknownMetadataFieldPolicy::Reject) => {
394 Err(MetadataError::UnknownField { key: key.to_string() })
395 }
396 None => Ok(()),
397 }
398 }
399
400 #[inline]
415 pub fn validate_wire_contract(&self) -> MetadataResult<()> {
416 if self.fields.len() > STRICT_STRING_MAP_MAX_ENTRIES {
417 return Err(MetadataError::WireLimitExceeded {
418 kind: crate::MetadataWireLimitKind::Entries,
419 value: self.fields.len(),
420 maximum: STRICT_STRING_MAP_MAX_ENTRIES,
421 });
422 }
423 if let Some(key) = self
424 .fields
425 .keys()
426 .find(|key| key.len() > STRICT_STRING_MAP_MAX_KEY_BYTES)
427 {
428 return Err(MetadataError::WireLimitExceeded {
429 kind: crate::MetadataWireLimitKind::KeyBytes,
430 value: key.len(),
431 maximum: STRICT_STRING_MAP_MAX_KEY_BYTES,
432 });
433 }
434 Ok(())
435 }
436}
437
438impl Serialize for MetadataSchema {
439 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
441 where
442 S: Serializer,
443 {
444 self.validate_wire_contract().map_err(<S::Error as SerError>::custom)?;
445 MetadataSchemaWireV1 {
446 version: METADATA_SCHEMA_WIRE_VERSION_V1,
447 fields: &self.fields,
448 unknown_metadata_field_policy: self.unknown_metadata_field_policy,
449 unknown_filter_field_policy: self.unknown_filter_field_policy,
450 }
451 .serialize(serializer)
452 }
453}
454
455impl<'de> Deserialize<'de> for MetadataSchema {
456 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
458 where
459 D: Deserializer<'de>,
460 {
461 let wire: MetadataSchemaWireV1<StrictStringMap<MetadataField>> =
462 MetadataSchemaWireV1::deserialize(deserializer)?;
463 if wire.version != METADATA_SCHEMA_WIRE_VERSION_V1 {
464 return Err(de::Error::custom("unsupported MetadataSchema wire format version"));
465 }
466 Ok(Self::new(
467 wire.fields.into_inner(),
468 wire.unknown_metadata_field_policy,
469 wire.unknown_filter_field_policy,
470 ))
471 }
472}
473
474impl Default for MetadataSchema {
475 #[inline]
476 fn default() -> Self {
477 Self {
478 fields: BTreeMap::new(),
479 unknown_metadata_field_policy: UnknownMetadataFieldPolicy::Reject,
480 unknown_filter_field_policy: UnknownFilterFieldPolicy::Reject,
481 }
482 }
483}