Skip to main content

qubit_metadata/schema/
metadata_schema_builder.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! [`MetadataSchemaBuilder`] — fluent schema construction API.
9
10use std::collections::BTreeMap;
11
12use qubit_datatype::DataType;
13
14use crate::MetadataError;
15use crate::MetadataResult;
16use crate::schema::MetadataField;
17use crate::schema::MetadataSchema;
18use crate::schema::UnknownFilterFieldPolicy;
19use crate::schema::UnknownMetadataFieldPolicy;
20
21/// Builder for [`MetadataSchema`].
22///
23/// # Examples
24///
25/// ```
26/// use qubit_datatype::DataType;
27/// use qubit_metadata::MetadataSchema;
28///
29/// # fn main() -> qubit_metadata::MetadataResult<()> {
30/// let schema = MetadataSchema::builder()
31///     .required("tenant", DataType::String)
32///     .build()?;
33/// assert!(schema.field("tenant").is_some());
34/// # Ok(())
35/// # }
36/// ```
37#[derive(Debug, Clone, PartialEq, Eq, Default)]
38pub struct MetadataSchemaBuilder {
39    /// Field definitions being built.
40    fields: BTreeMap<String, MetadataField>,
41    /// Policy for metadata keys not declared in the schema.
42    unknown_metadata_field_policy: UnknownMetadataFieldPolicy,
43    /// Policy for filter keys not declared in the schema.
44    unknown_filter_field_policy: UnknownFilterFieldPolicy,
45    /// First duplicate declaration detected while building the schema.
46    error: Option<MetadataError>,
47}
48
49impl MetadataSchemaBuilder {
50    /// Adds a required field definition.
51    ///
52    /// # Parameters
53    ///
54    /// * `key` - Metadata key to declare.
55    /// * `data_type` - Concrete data type accepted by the field.
56    ///
57    /// # Returns
58    ///
59    /// The updated builder.
60    #[inline]
61    #[must_use]
62    pub fn required(self, key: &str, data_type: DataType) -> Self {
63        self.declare_field(key, MetadataField::new(data_type, true))
64    }
65
66    /// Adds an optional field definition.
67    ///
68    /// # Parameters
69    ///
70    /// * `key` - Metadata key to declare.
71    /// * `data_type` - Concrete data type accepted by the field.
72    ///
73    /// # Returns
74    ///
75    /// The updated builder.
76    #[inline]
77    #[must_use]
78    pub fn optional(self, key: &str, data_type: DataType) -> Self {
79        self.declare_field(key, MetadataField::new(data_type, false))
80    }
81
82    /// Explicitly replaces a field definition.
83    ///
84    /// # Parameters
85    ///
86    /// * `key` - Metadata key to replace.
87    /// * `field` - Replacement definition.
88    ///
89    /// # Returns
90    ///
91    /// The updated builder. This is the only declaration method that may
92    /// overwrite an existing field.
93    #[inline]
94    #[must_use]
95    pub fn replace_field(mut self, key: &str, field: MetadataField) -> Self {
96        let _ = self.fields.insert(key.to_string(), field);
97        self
98    }
99
100    /// Sets the policy for metadata keys not declared by the schema.
101    ///
102    /// # Parameters
103    ///
104    /// * `policy` - Unknown metadata-field policy to store.
105    ///
106    /// # Returns
107    ///
108    /// The updated builder.
109    #[inline]
110    #[must_use]
111    pub fn unknown_metadata_field_policy(mut self, policy: UnknownMetadataFieldPolicy) -> Self {
112        self.unknown_metadata_field_policy = policy;
113        self
114    }
115
116    /// Sets the policy for filter keys not declared by the schema.
117    ///
118    /// # Parameters
119    ///
120    /// * `policy` - Unknown filter-field policy to store.
121    ///
122    /// # Returns
123    ///
124    /// The updated builder.
125    #[inline]
126    #[must_use]
127    pub fn unknown_filter_field_policy(mut self, policy: UnknownFilterFieldPolicy) -> Self {
128        self.unknown_filter_field_policy = policy;
129        self
130    }
131
132    /// Builds the schema.
133    ///
134    /// # Returns
135    ///
136    /// The immutable schema described by this builder.
137    ///
138    /// # Errors
139    ///
140    /// Returns [`MetadataError::DuplicateSchemaField`] when `required` or
141    /// `optional` declare the same key more than once.
142    #[inline]
143    pub fn build(self) -> MetadataResult<MetadataSchema> {
144        if let Some(error) = self.error {
145            return Err(error);
146        }
147        Ok(MetadataSchema::new(
148            self.fields,
149            self.unknown_metadata_field_policy,
150            self.unknown_filter_field_policy,
151        ))
152    }
153
154    /// Declares a field unless the key has already been declared.
155    fn declare_field(mut self, key: &str, field: MetadataField) -> Self {
156        if self.fields.contains_key(key) {
157            if self.error.is_none() {
158                self.error = Some(MetadataError::DuplicateSchemaField { key: key.to_string() });
159            }
160            return self;
161        }
162        let _ = self.fields.insert(key.to_string(), field);
163        self
164    }
165}