1use std::collections::BTreeMap;
12
13use qubit_common::DataType;
14use qubit_value::Value;
15use serde::{Deserialize, Serialize};
16
17use crate::{
18 Condition, Metadata, MetadataError, MetadataField, MetadataFilter, MetadataResult,
19 MetadataSchemaBuilder, UnknownFieldPolicy,
20};
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub struct MetadataSchema {
29 fields: BTreeMap<String, MetadataField>,
31 unknown_field_policy: UnknownFieldPolicy,
33}
34
35impl MetadataSchema {
36 #[inline]
38 #[must_use]
39 pub fn builder() -> MetadataSchemaBuilder {
40 MetadataSchemaBuilder::default()
41 }
42
43 #[inline]
45 pub(crate) fn new(
46 fields: BTreeMap<String, MetadataField>,
47 unknown_field_policy: UnknownFieldPolicy,
48 ) -> Self {
49 Self {
50 fields,
51 unknown_field_policy,
52 }
53 }
54
55 #[inline]
57 #[must_use]
58 pub fn field(&self, key: &str) -> Option<&MetadataField> {
59 self.fields.get(key)
60 }
61
62 #[inline]
64 #[must_use]
65 pub fn field_type(&self, key: &str) -> Option<DataType> {
66 self.field(key).map(MetadataField::data_type)
67 }
68
69 #[inline]
71 #[must_use]
72 pub fn unknown_field_policy(&self) -> UnknownFieldPolicy {
73 self.unknown_field_policy
74 }
75
76 #[inline]
78 pub fn fields(&self) -> impl Iterator<Item = (&str, &MetadataField)> {
79 self.fields.iter().map(|(key, field)| (key.as_str(), field))
80 }
81
82 pub fn validate(&self, meta: &Metadata) -> MetadataResult<()> {
90 for (key, field) in &self.fields {
91 if field.is_required() && !meta.contains_key(key) {
92 return Err(MetadataError::MissingRequiredField {
93 key: key.clone(),
94 expected: field.data_type(),
95 });
96 }
97 }
98
99 for (key, value) in meta.iter() {
100 match self.field(key) {
101 Some(field) if field.data_type() != value.data_type() => {
102 return Err(MetadataError::type_mismatch(
103 key,
104 field.data_type(),
105 value.data_type(),
106 ));
107 }
108 Some(_) => {}
109 None if matches!(self.unknown_field_policy, UnknownFieldPolicy::Reject) => {
110 return Err(MetadataError::UnknownField {
111 key: key.to_string(),
112 });
113 }
114 None => {}
115 }
116 }
117 Ok(())
118 }
119
120 pub fn validate_filter(&self, filter: &MetadataFilter) -> MetadataResult<()> {
128 filter.visit_conditions(|condition| self.validate_condition(condition))
129 }
130
131 fn validate_condition(&self, condition: &Condition) -> MetadataResult<()> {
133 match condition {
134 Condition::Equal { key, value } | Condition::NotEqual { key, value } => {
135 self.validate_value_condition(key, "eq", value)
136 }
137 Condition::Less { key, value } => self.validate_range_condition(key, "lt", value),
138 Condition::LessEqual { key, value } => self.validate_range_condition(key, "le", value),
139 Condition::Greater { key, value } => self.validate_range_condition(key, "gt", value),
140 Condition::GreaterEqual { key, value } => {
141 self.validate_range_condition(key, "ge", value)
142 }
143 Condition::In { key, values } | Condition::NotIn { key, values } => {
144 for value in values {
145 self.validate_value_condition(key, "in_set", value)?;
146 }
147 Ok(())
148 }
149 Condition::Exists { key } | Condition::NotExists { key } => {
150 self.require_field(key)?;
151 Ok(())
152 }
153 }
154 }
155
156 fn validate_value_condition(
158 &self,
159 key: &str,
160 operator: &'static str,
161 value: &Value,
162 ) -> MetadataResult<()> {
163 let field = self.require_field(key)?;
164 if value_matches_field_type(value, field.data_type()) {
165 return Ok(());
166 }
167 Err(MetadataError::InvalidFilterOperator {
168 key: key.to_string(),
169 operator,
170 data_type: field.data_type(),
171 message: format!(
172 "filter value type {} is not compatible with field type {}",
173 value.data_type(),
174 field.data_type()
175 ),
176 })
177 }
178
179 fn validate_range_condition(
181 &self,
182 key: &str,
183 operator: &'static str,
184 value: &Value,
185 ) -> MetadataResult<()> {
186 let field = self.require_field(key)?;
187 if !is_range_comparable_type(field.data_type()) {
188 return Err(MetadataError::InvalidFilterOperator {
189 key: key.to_string(),
190 operator,
191 data_type: field.data_type(),
192 message: "range operators require a numeric or string field".to_string(),
193 });
194 }
195 if value_matches_field_type(value, field.data_type()) {
196 return Ok(());
197 }
198 Err(MetadataError::InvalidFilterOperator {
199 key: key.to_string(),
200 operator,
201 data_type: field.data_type(),
202 message: format!(
203 "filter value type {} is not compatible with field type {}",
204 value.data_type(),
205 field.data_type()
206 ),
207 })
208 }
209
210 fn require_field(&self, key: &str) -> MetadataResult<&MetadataField> {
212 self.field(key)
213 .ok_or_else(|| MetadataError::UnknownFilterField {
214 key: key.to_string(),
215 })
216 }
217}
218
219impl Default for MetadataSchema {
220 #[inline]
221 fn default() -> Self {
222 Self {
223 fields: BTreeMap::new(),
224 unknown_field_policy: UnknownFieldPolicy::Reject,
225 }
226 }
227}
228
229#[inline]
231fn is_numeric_data_type(data_type: DataType) -> bool {
232 matches!(
233 data_type,
234 DataType::Int8
235 | DataType::Int16
236 | DataType::Int32
237 | DataType::Int64
238 | DataType::Int128
239 | DataType::UInt8
240 | DataType::UInt16
241 | DataType::UInt32
242 | DataType::UInt64
243 | DataType::UInt128
244 | DataType::Float32
245 | DataType::Float64
246 | DataType::BigInteger
247 | DataType::BigDecimal
248 | DataType::IntSize
249 | DataType::UIntSize
250 )
251}
252
253#[inline]
255fn is_range_comparable_type(data_type: DataType) -> bool {
256 is_numeric_data_type(data_type) || matches!(data_type, DataType::String)
257}
258
259#[inline]
261fn value_matches_field_type(value: &Value, field_type: DataType) -> bool {
262 let value_type = value.data_type();
263 value_type == field_type || is_numeric_data_type(value_type) && is_numeric_data_type(field_type)
264}