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}