qubit_metadata/lib.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//! # qubit-metadata
9//!
10//! A general-purpose, typed-value, extensible metadata model for Rust.
11//!
12//! This crate provides a [`Metadata`] type — a structured key-value store
13//! designed for any domain that needs to attach typed annotations to its data
14//! models. It is not a plain `HashMap` — it is a structured extensibility point
15//! with strict typed access, explicit conversion helpers,
16//! [`qubit_value::Value`] backing, and first-class `serde` support.
17//!
18//! ## Design Goals
19//!
20//! - **Typed Values**: Strict typed reads, explicit conversion helpers,
21//! chainable set, and replacement-aware insert APIs backed by
22//! [`qubit_value::Value`]
23//! - **Generality**: No domain-specific assumptions — usable in any Rust
24//! project
25//! - **Schema Support**: Optional schema validation for metadata and filters
26//! through the `schema` feature
27//! - **Serialization**: First-class `serde` support for JSON interchange
28//! - **Filtering**: Composable query conditions through the `filter` feature
29//!
30//! ## Features
31//!
32//! - Core type: [`Metadata`] — an ordered key-value store with typed accessors
33//! - Enable `filter` for composable filter expressions and their public types
34//! - Enable `schema` (which includes `filter`) for field definitions and
35//! validation APIs
36//! - Enable `json` for bounded JSON-slice decoding and buffered encoding; JSON
37//! APIs for schemas and filters also require their respective features
38//! - Error type: [`MetadataError`] — structured failure reporting for reads,
39//! conversions, validation, and wire boundaries
40//! - `schema` also provides aggregate validation errors
41//!
42//! ## Example
43//!
44//! ```rust
45//! use qubit_metadata::Metadata;
46//!
47//! let meta = Metadata::new()
48//! .with("author", "alice")
49//! .with("priority", 3_i64);
50//!
51//! // Optional strict reads distinguish absence from conversion/type failures.
52//! let author = meta.get_optional::<String>("author").unwrap();
53//! assert_eq!(author.as_deref(), Some("alice"));
54//!
55//! // Explicit API: preserve failure reasons for diagnostics.
56//! let priority = meta.convert::<i64>("priority").unwrap();
57//! assert_eq!(priority, 3);
58//! ```
59//!
60//! Comparison predicates in the `filter` feature use three-valued logic: a
61//! missing or unset value is unknown, remains unknown through negation, and
62//! does not match at the public API boundary. Existence predicates are an
63//! exception: `exists` and `not_exists` always return definite Booleans, and
64//! `exists(...).not()` is true when no concrete value is stored.
65//!
66//! ## Bounded JSON
67//!
68//! Enable the `json` feature to use the bounded JSON helpers. The default
69//! helpers use the crate's default decode or encode budget profile; use the
70//! corresponding `_with_limits` helper to configure a profile for that
71//! operation. Add `features = ["json"]` to the crate dependency. Decode and
72//! encode budgets are independent.
73//!
74//! ```rust
75//! # #[cfg(feature = "json")]
76//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
77//! use qubit_metadata::Metadata;
78//!
79//! let metadata = Metadata::new().with("tenant_id", "acme");
80//! let encoded = metadata.to_json_vec()?;
81//! let decoded = Metadata::decode_json_slice(&encoded)?;
82//! assert_eq!(decoded.get_ref::<str>("tenant_id")?, "acme");
83//! # Ok(())
84//! # }
85//! # #[cfg(not(feature = "json"))]
86//! # fn main() {}
87//! ```
88//!
89//! For complete untrusted JSON input, use the bounded slice decoder. Ordinary
90//! Serde paths still check the domain wire contract, but do not automatically
91//! provide the dedicated helper's complete-input byte admission and unified
92//! JSON traversal budget. See the [bounded JSON decoding guide](https://github.com/qubit-ltd/rs-metadata/blob/main/doc/user_guide.md#bounded-json-decoding)
93//! and [bounded JSON encoding guide](https://github.com/qubit-ltd/rs-metadata/blob/main/doc/user_guide.md#bounded-json-encoding)
94//! for configuration and writer behavior. Writer helpers buffer the complete
95//! document; a destination I/O error may leave a written prefix.
96//!
97//! ```rust
98//! # #[cfg(feature = "filter")]
99//! # fn main() -> qubit_metadata::MetadataResult<()> {
100//! use qubit_metadata::FilterExpression;
101//! use qubit_metadata::Metadata;
102//! use qubit_metadata::MetadataFilter;
103//!
104//! let metadata = Metadata::new();
105//! let comparison = MetadataFilter::builder()
106//! .expression(FilterExpression::builder().eq("tenant", "acme").not().build()?)
107//! .build()?;
108//! let absent = MetadataFilter::builder()
109//! .expression(FilterExpression::builder().not_exists("tenant").build()?)
110//! .build()?;
111//! let negated_exists = MetadataFilter::builder()
112//! .expression(FilterExpression::builder().exists("tenant").not().build()?)
113//! .build()?;
114//! assert!(!comparison.matches(&metadata));
115//! assert!(absent.matches(&metadata));
116//! assert!(negated_exists.matches(&metadata));
117//! # Ok(())
118//! # }
119//! # #[cfg(not(feature = "filter"))]
120//! # fn main() {}
121//! ```
122//!
123//! `Metadata`'s `Debug` and `Display` implementations are bounded and rendered
124//! through the default redaction policy. They are useful diagnostic views, not
125//! a confidentiality boundary for arbitrary user-defined keys or error text.
126
127#![deny(missing_docs)]
128
129mod constants;
130#[cfg(feature = "filter")]
131mod filter;
132mod internal;
133mod metadata;
134mod metadata_error;
135#[cfg(feature = "json")]
136mod metadata_limits;
137#[cfg(feature = "json")]
138mod metadata_limits_builder;
139mod metadata_result;
140#[cfg(feature = "schema")]
141mod metadata_validation_error;
142#[cfg(feature = "schema")]
143mod metadata_validation_result;
144#[cfg(feature = "json")]
145mod metadata_wire_decode_error;
146#[cfg(feature = "json")]
147mod metadata_wire_encode_error;
148mod metadata_wire_limit_kind;
149#[cfg(feature = "schema")]
150mod schema;
151mod wire;
152
153#[cfg(test)]
154mod tests;
155
156#[cfg(feature = "filter")]
157pub use filter::Condition;
158#[cfg(feature = "filter")]
159pub use filter::FilterExpression;
160#[cfg(feature = "filter")]
161pub use filter::FilterExpressionBuilder;
162#[cfg(feature = "filter")]
163pub use filter::FilterExpressionView;
164#[cfg(feature = "filter")]
165pub use filter::FilterLimitKind;
166#[cfg(feature = "filter")]
167pub use filter::FilterLimits;
168#[cfg(feature = "filter")]
169pub use filter::FilterLimitsBuilder;
170#[cfg(feature = "filter")]
171pub use filter::FilterMatchOptions;
172#[cfg(feature = "filter")]
173pub use filter::FilterMatchOptionsBuilder;
174#[cfg(feature = "filter")]
175pub use filter::MetadataFilter;
176#[cfg(feature = "filter")]
177pub use filter::MetadataFilterBuilder;
178pub use metadata::Metadata;
179pub use metadata_error::MetadataError;
180#[cfg(feature = "json")]
181pub use metadata_limits::DEFAULT_MAX_JSON_BYTES;
182#[cfg(feature = "json")]
183pub use metadata_limits::DEFAULT_MAX_KEY_BYTES;
184#[cfg(feature = "json")]
185pub use metadata_limits::DEFAULT_MAX_METADATA_ENTRIES;
186#[cfg(feature = "json")]
187pub use metadata_limits::DEFAULT_MAX_SCHEMA_FIELDS;
188#[cfg(feature = "json")]
189pub use metadata_limits::MetadataLimits;
190#[cfg(feature = "json")]
191pub use metadata_limits::default_json_decode_limits;
192#[cfg(feature = "json")]
193pub use metadata_limits::default_json_encode_limits;
194#[cfg(feature = "json")]
195pub use metadata_limits::default_json_value_limits;
196#[cfg(feature = "json")]
197pub use metadata_limits_builder::MetadataLimitsBuilder;
198pub use metadata_result::MetadataResult;
199#[cfg(feature = "schema")]
200pub use metadata_validation_error::MetadataValidationError;
201#[cfg(feature = "schema")]
202pub use metadata_validation_result::MetadataValidationResult;
203#[cfg(feature = "json")]
204pub use metadata_wire_decode_error::MetadataWireDecodeError;
205#[cfg(feature = "json")]
206pub use metadata_wire_encode_error::MetadataWireEncodeError;
207pub use metadata_wire_limit_kind::MetadataWireLimitKind;
208#[cfg(feature = "schema")]
209pub use schema::MetadataField;
210#[cfg(feature = "schema")]
211pub use schema::MetadataSchema;
212#[cfg(feature = "schema")]
213pub use schema::MetadataSchemaBuilder;
214#[cfg(feature = "schema")]
215pub use schema::UnknownFilterFieldPolicy;
216#[cfg(feature = "schema")]
217pub use schema::UnknownMetadataFieldPolicy;