Skip to main content

qubit_metadata/schema/
definition.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9//! [`MetadataSchema`] — schema validation for metadata and filters.
10
11use std::collections::BTreeMap;
12
13use qubit_common::DataType;
14use qubit_value::Value;
15use serde::{Deserialize, Serialize};
16
17use crate::schema::{MetadataField, MetadataSchemaBuilder, UnknownFieldPolicy};
18use crate::{Metadata, MetadataError, MetadataResult};
19
20/// Schema for metadata fields.
21///
22/// A schema declares valid keys, their concrete [`DataType`], and whether they
23/// are required. It can validate actual [`Metadata`] values and validate that a
24/// [`crate::MetadataFilter`] references known fields with compatible operators.
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26pub struct MetadataSchema {
27    /// Field definitions keyed by metadata key.
28    fields: BTreeMap<String, MetadataField>,
29    /// How validation handles unknown metadata keys.
30    unknown_field_policy: UnknownFieldPolicy,
31}
32
33impl MetadataSchema {
34    /// Creates a schema builder.
35    #[inline]
36    #[must_use]
37    pub fn builder() -> MetadataSchemaBuilder {
38        MetadataSchemaBuilder::default()
39    }
40
41    /// Creates a schema from field definitions and unknown-field policy.
42    #[inline]
43    pub(crate) fn new(
44        fields: BTreeMap<String, MetadataField>,
45        unknown_field_policy: UnknownFieldPolicy,
46    ) -> Self {
47        Self {
48            fields,
49            unknown_field_policy,
50        }
51    }
52
53    /// Returns the field definition for `key`.
54    #[inline]
55    #[must_use]
56    pub fn field(&self, key: &str) -> Option<&MetadataField> {
57        self.fields.get(key)
58    }
59
60    /// Returns the declared data type for `key`.
61    #[inline]
62    #[must_use]
63    pub fn field_type(&self, key: &str) -> Option<DataType> {
64        self.field(key).map(MetadataField::data_type)
65    }
66
67    /// Returns the unknown-field policy.
68    #[inline]
69    #[must_use]
70    pub fn unknown_field_policy(&self) -> UnknownFieldPolicy {
71        self.unknown_field_policy
72    }
73
74    /// Returns an iterator over schema fields in key-sorted order.
75    #[inline]
76    pub fn fields(&self) -> impl Iterator<Item = (&str, &MetadataField)> {
77        self.fields.iter().map(|(key, field)| (key.as_str(), field))
78    }
79
80    /// Validates a metadata object against this schema.
81    ///
82    /// # Errors
83    ///
84    /// Returns an error when a required field is missing, a declared field has a
85    /// different concrete type, or an unknown field is present while the schema
86    /// rejects unknown fields.
87    pub fn validate(&self, meta: &Metadata) -> MetadataResult<()> {
88        for (key, field) in &self.fields {
89            if field.is_required() && !meta.contains_key(key) {
90                return Err(MetadataError::MissingRequiredField {
91                    key: key.clone(),
92                    expected: field.data_type(),
93                });
94            }
95        }
96
97        for (key, value) in meta.iter() {
98            self.validate_entry(key, value)?;
99        }
100        Ok(())
101    }
102
103    /// Validates one metadata entry against this schema.
104    pub(crate) fn validate_entry(&self, key: &str, value: &Value) -> MetadataResult<()> {
105        match self.field(key) {
106            Some(field) if field.data_type() != value.data_type() => Err(
107                MetadataError::type_mismatch(key, field.data_type(), value.data_type()),
108            ),
109            Some(_) => Ok(()),
110            None if matches!(self.unknown_field_policy, UnknownFieldPolicy::Reject) => {
111                Err(MetadataError::UnknownField {
112                    key: key.to_string(),
113                })
114            }
115            None => Ok(()),
116        }
117    }
118}
119
120impl Default for MetadataSchema {
121    #[inline]
122    fn default() -> Self {
123        Self {
124            fields: BTreeMap::new(),
125            unknown_field_policy: UnknownFieldPolicy::Reject,
126        }
127    }
128}