Skip to main content

qubit_metadata/
metadata_limits.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//! Default JSON and metadata-domain limits for metadata wire documents.
9
10use qubit_budget::ResourceLimit;
11use qubit_budget::json::JsonDecodeLimits;
12use qubit_budget::json::JsonEncodeLimits;
13use qubit_budget::json::JsonResource;
14use qubit_budget::json::JsonValueLimits;
15use serde::de::Error as _;
16
17use crate::metadata_limits_builder::MetadataLimitsBuilder;
18
19/// Default maximum number of bytes in a complete metadata JSON document.
20///
21/// This bound applies to both the complete input accepted during decoding and
22/// the complete output produced during encoding. Directional JSON profiles
23/// apply it through their respective input and output byte budgets.
24pub const DEFAULT_MAX_JSON_BYTES: usize = 1_048_576;
25
26/// Default maximum number of entries accepted in a metadata map.
27///
28/// This domain limit is separate from the generic JSON map-entry budget.
29pub const DEFAULT_MAX_METADATA_ENTRIES: usize = 4_096;
30
31/// Default maximum number of fields accepted in a metadata schema.
32///
33/// This domain limit applies to schema fields independently of generic JSON
34/// traversal limits.
35pub const DEFAULT_MAX_SCHEMA_FIELDS: usize = 4_096;
36
37/// Default maximum UTF-8 byte length of one metadata or schema key.
38///
39/// The limit counts encoded bytes, so a multi-byte character consumes more
40/// than one byte.
41pub const DEFAULT_MAX_KEY_BYTES: usize = 256;
42
43/// Domain-specific metadata limits composed with a shared JSON profile.
44///
45/// `MetadataLimits` deliberately keeps metadata-entry, schema-field, and key
46/// bounds separate from generic JSON map/key accounting. This preserves the
47/// protocol's domain limits while the directional JSON limit types handle
48/// document traversal, payload lengths, and complete input/output bytes.
49///
50/// # Examples
51///
52/// ```
53/// use qubit_metadata::MetadataLimits;
54///
55/// # fn main() -> Result<(), serde_json::Error> {
56/// let limits = MetadataLimits::builder().max_key_bytes(128).build()?;
57/// assert_eq!(limits.max_key_bytes(), 128);
58/// # Ok(())
59/// # }
60/// ```
61#[must_use]
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
63pub struct MetadataLimits {
64    /// JSON budgets applied while decoding an untrusted document.
65    json_decode: JsonDecodeLimits,
66    /// JSON budgets applied while encoding a document.
67    json_encode: JsonEncodeLimits,
68    /// Maximum number of entries accepted in one metadata map.
69    max_metadata_entries: usize,
70    /// Maximum number of fields accepted in one metadata schema.
71    max_schema_fields: usize,
72    /// Maximum UTF-8 byte length accepted for a metadata or schema key.
73    max_key_bytes: usize,
74}
75
76impl MetadataLimits {
77    /// Creates a builder with the default JSON profile and domain limits.
78    ///
79    /// # Returns
80    ///
81    /// A builder initialized with the protocol defaults.
82    #[inline(always)]
83    pub fn builder() -> MetadataLimitsBuilder {
84        MetadataLimitsBuilder::default()
85    }
86
87    /// Creates immutable limits from the values held by `builder` without
88    /// validating the domain hard caps.
89    ///
90    /// # Parameters
91    ///
92    /// * `builder` - The builder whose JSON profiles and domain limits are
93    ///   copied into the resulting value.
94    ///
95    /// # Returns
96    ///
97    /// A limits value containing the builder's configured values.
98    #[inline]
99    pub(crate) fn from_builder(builder: MetadataLimitsBuilder) -> Self {
100        Self {
101            json_decode: builder.json_decode,
102            json_encode: builder.json_encode,
103            max_metadata_entries: builder.max_metadata_entries,
104            max_schema_fields: builder.max_schema_fields,
105            max_key_bytes: builder.max_key_bytes,
106        }
107    }
108
109    /// Returns the JSON budgets used to decode an untrusted document.
110    ///
111    /// The returned value is copied and can be used to inspect the profile
112    /// without changing this limits value.
113    ///
114    /// # Returns
115    ///
116    /// The decoding profile stored in this value.
117    #[inline(always)]
118    #[must_use]
119    pub const fn json_decode(&self) -> JsonDecodeLimits {
120        self.json_decode
121    }
122
123    /// Returns the JSON budgets used to encode a document.
124    ///
125    /// The returned value is copied and can be used to inspect the profile
126    /// without changing this limits value.
127    ///
128    /// # Returns
129    ///
130    /// The encoding profile stored in this value.
131    #[inline(always)]
132    #[must_use]
133    pub const fn json_encode(&self) -> JsonEncodeLimits {
134        self.json_encode
135    }
136
137    /// Returns the maximum number of entries accepted in a metadata map.
138    ///
139    /// This domain-specific bound is independent of generic JSON map
140    /// accounting.
141    ///
142    /// # Returns
143    ///
144    /// The metadata-entry limit stored in this value.
145    #[inline(always)]
146    #[must_use]
147    pub const fn max_metadata_entries(&self) -> usize {
148        self.max_metadata_entries
149    }
150
151    /// Returns the maximum number of fields accepted in a metadata schema.
152    ///
153    /// This domain-specific bound is independent of generic JSON traversal
154    /// limits.
155    ///
156    /// # Returns
157    ///
158    /// The schema-field limit stored in this value.
159    #[inline(always)]
160    #[must_use]
161    pub const fn max_schema_fields(&self) -> usize {
162        self.max_schema_fields
163    }
164
165    /// Returns the maximum UTF-8 byte length accepted for one key.
166    ///
167    /// The byte count applies to metadata and schema keys and is distinct from
168    /// the generic JSON key budget.
169    ///
170    /// # Returns
171    ///
172    /// The key-byte limit stored in this value.
173    #[inline(always)]
174    #[must_use]
175    pub const fn max_key_bytes(&self) -> usize {
176        self.max_key_bytes
177    }
178
179    /// Validates receiver-controlled domain limits against protocol hard caps.
180    ///
181    /// # Errors
182    ///
183    /// Returns a JSON error when the metadata-entry, schema-field, or key-byte
184    /// limit exceeds its corresponding protocol maximum.
185    pub fn validate(&self) -> Result<(), serde_json::Error> {
186        if self.max_metadata_entries > DEFAULT_MAX_METADATA_ENTRIES {
187            return Err(serde_json::Error::custom(format!(
188                "metadata entries limit {} exceeds {}",
189                self.max_metadata_entries, DEFAULT_MAX_METADATA_ENTRIES,
190            )));
191        }
192        if self.max_schema_fields > DEFAULT_MAX_SCHEMA_FIELDS {
193            return Err(serde_json::Error::custom(format!(
194                "schema fields limit {} exceeds {}",
195                self.max_schema_fields, DEFAULT_MAX_SCHEMA_FIELDS,
196            )));
197        }
198        if self.max_key_bytes > DEFAULT_MAX_KEY_BYTES {
199            return Err(serde_json::Error::custom(format!(
200                "metadata key limit {} exceeds {}",
201                self.max_key_bytes, DEFAULT_MAX_KEY_BYTES,
202            )));
203        }
204        Ok(())
205    }
206}
207
208impl Default for MetadataLimits {
209    /// Creates the protocol's default metadata and JSON limits.
210    ///
211    /// The defaults satisfy the domain hard caps, so construction cannot fail.
212    ///
213    /// # Returns
214    ///
215    /// A limits value containing the standard decoding, encoding, and domain
216    /// profiles.
217    fn default() -> Self {
218        Self::builder()
219            .build()
220            .expect("default metadata limits satisfy protocol hard caps")
221    }
222}
223
224/// Creates the default direction-independent metadata JSON value profile.
225///
226/// The profile bounds nesting, nodes, sequence items, map entries, keys,
227/// strings, numbers, and payload size. Complete input and output byte limits
228/// are supplied separately by the directional profiles.
229///
230/// # Returns
231///
232/// A value profile configured with the protocol defaults.
233pub fn default_json_value_limits() -> JsonValueLimits {
234    JsonValueLimits::<JsonResource, usize>::builder()
235        .max_depth(64_usize)
236        .max_nodes(100_000_usize)
237        .max_sequence_items(4_096_usize)
238        .max_map_entries(json_quantity(DEFAULT_MAX_METADATA_ENTRIES))
239        .max_key_bytes(256 * 1024_usize)
240        .max_string_bytes(256 * 1024_usize)
241        .max_number_bytes(4_096_usize)
242        .max_payload_bytes(json_quantity(DEFAULT_MAX_JSON_BYTES))
243        .build()
244}
245
246/// Creates the default metadata JSON decoding profile.
247///
248/// The profile applies the protocol's complete-input byte limit together with
249/// the shared metadata JSON value limits.
250///
251/// # Returns
252///
253/// A decoding profile configured with the protocol defaults.
254pub fn default_json_decode_limits() -> JsonDecodeLimits {
255    JsonDecodeLimits::builder()
256        .input_bytes_limit(ResourceLimit::new(
257            JsonResource::InputBytes,
258            json_quantity(DEFAULT_MAX_JSON_BYTES),
259        ))
260        .value_limits(default_json_value_limits())
261        .build()
262}
263
264/// Creates the default metadata JSON encoding profile.
265///
266/// The profile applies the protocol's complete-output byte limit together
267/// with the shared metadata JSON value limits.
268///
269/// # Returns
270///
271/// An encoding profile configured with the protocol defaults.
272pub fn default_json_encode_limits() -> JsonEncodeLimits {
273    JsonEncodeLimits::builder()
274        .output_bytes_limit(ResourceLimit::new(
275            JsonResource::OutputBytes,
276            json_quantity(DEFAULT_MAX_JSON_BYTES),
277        ))
278        .value_limits(default_json_value_limits())
279        .build()
280}
281
282/// Converts protocol JSON limits to the native quantity used by JSON parsing.
283///
284/// The protocol and parser currently represent these quantities with the same
285/// unsigned integer type, so conversion preserves the input exactly.
286///
287/// # Parameters
288///
289/// * `value` - A protocol limit expressed as a `usize`.
290///
291/// # Returns
292///
293/// The unchanged quantity in the parser's native representation.
294fn json_quantity(value: usize) -> usize {
295    value
296}