qubit-metadata 0.7.2

Type-safe metadata model with schemas and composable filters
Documentation
// =============================================================================
//    Copyright (c) 2025 - 2026 Haixing Hu.
//
//    SPDX-License-Identifier: Apache-2.0
//
//    Licensed under the Apache License, Version 2.0.
// =============================================================================
//! # qubit-metadata
//!
//! A general-purpose, typed-value, extensible metadata model for Rust.
//!
//! This crate provides a [`Metadata`] type — a structured key-value store
//! designed for any domain that needs to attach typed annotations to its data
//! models. It is not a plain `HashMap` — it is a structured extensibility point
//! with strict typed access, explicit conversion helpers,
//! [`qubit_value::Value`] backing, and first-class `serde` support.
//!
//! ## Design Goals
//!
//! - **Typed Values**: Strict typed reads, explicit conversion helpers,
//!   chainable set, and replacement-aware insert APIs backed by
//!   [`qubit_value::Value`]
//! - **Generality**: No domain-specific assumptions — usable in any Rust
//!   project
//! - **Schema Support**: Optional schema validation for metadata and filters
//!   through the `schema` feature
//! - **Serialization**: First-class `serde` support for JSON interchange
//! - **Filtering**: Composable query conditions through the `filter` feature
//!
//! ## Features
//!
//! - Core type: [`Metadata`] — an ordered key-value store with typed accessors
//! - Enable `filter` for composable filter expressions and their public types
//! - Enable `schema` (which includes `filter`) for field definitions and
//!   validation APIs
//! - Enable `json` for bounded JSON-slice decoding and buffered encoding; JSON
//!   APIs for schemas and filters also require their respective features
//! - Error type: [`MetadataError`] — structured failure reporting for reads,
//!   conversions, validation, and wire boundaries
//! - `schema` also provides aggregate validation errors
//!
//! ## Example
//!
//! ```rust
//! use qubit_metadata::Metadata;
//!
//! let meta = Metadata::new()
//!     .with("author", "alice")
//!     .with("priority", 3_i64);
//!
//! // Optional strict reads distinguish absence from conversion/type failures.
//! let author = meta.get_optional::<String>("author").unwrap();
//! assert_eq!(author.as_deref(), Some("alice"));
//!
//! // Explicit API: preserve failure reasons for diagnostics.
//! let priority = meta.convert::<i64>("priority").unwrap();
//! assert_eq!(priority, 3);
//! ```
//!
//! Comparison predicates in the `filter` feature use three-valued logic: a
//! missing or unset value is unknown, remains unknown through negation, and
//! does not match at the public API boundary. Existence predicates are an
//! exception: `exists` and `not_exists` always return definite Booleans, and
//! `exists(...).not()` is true when no concrete value is stored.
//!
//! ## Bounded JSON
//!
//! Enable the `json` feature to use the bounded JSON helpers. The default
//! helpers use the crate's default decode or encode budget profile; use the
//! corresponding `_with_limits` helper to configure a profile for that
//! operation. Add `features = ["json"]` to the crate dependency. Decode and
//! encode budgets are independent.
//!
//! ```rust
//! # #[cfg(feature = "json")]
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use qubit_metadata::Metadata;
//!
//! let metadata = Metadata::new().with("tenant_id", "acme");
//! let encoded = metadata.to_json_vec()?;
//! let decoded = Metadata::decode_json_slice(&encoded)?;
//! assert_eq!(decoded.get_ref::<str>("tenant_id")?, "acme");
//! # Ok(())
//! # }
//! # #[cfg(not(feature = "json"))]
//! # fn main() {}
//! ```
//!
//! For complete untrusted JSON input, use the bounded slice decoder. Ordinary
//! Serde paths still check the domain wire contract, but do not automatically
//! provide the dedicated helper's complete-input byte admission and unified
//! 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)
//! and [bounded JSON encoding guide](https://github.com/qubit-ltd/rs-metadata/blob/main/doc/user_guide.md#bounded-json-encoding)
//! for configuration and writer behavior. Writer helpers buffer the complete
//! document; a destination I/O error may leave a written prefix.
//!
//! ```rust
//! # #[cfg(feature = "filter")]
//! # fn main() -> qubit_metadata::MetadataResult<()> {
//! use qubit_metadata::FilterExpression;
//! use qubit_metadata::Metadata;
//! use qubit_metadata::MetadataFilter;
//!
//! let metadata = Metadata::new();
//! let comparison = MetadataFilter::builder()
//!     .expression(FilterExpression::builder().eq("tenant", "acme").not().build()?)
//!     .build()?;
//! let absent = MetadataFilter::builder()
//!     .expression(FilterExpression::builder().not_exists("tenant").build()?)
//!     .build()?;
//! let negated_exists = MetadataFilter::builder()
//!     .expression(FilterExpression::builder().exists("tenant").not().build()?)
//!     .build()?;
//! assert!(!comparison.matches(&metadata));
//! assert!(absent.matches(&metadata));
//! assert!(negated_exists.matches(&metadata));
//! # Ok(())
//! # }
//! # #[cfg(not(feature = "filter"))]
//! # fn main() {}
//! ```
//!
//! `Metadata`'s `Debug` and `Display` implementations are bounded and rendered
//! through the default redaction policy. They are useful diagnostic views, not
//! a confidentiality boundary for arbitrary user-defined keys or error text.

#![deny(missing_docs)]

mod constants;
#[cfg(feature = "filter")]
mod filter;
mod internal;
mod metadata;
mod metadata_error;
#[cfg(feature = "json")]
mod metadata_limits;
#[cfg(feature = "json")]
mod metadata_limits_builder;
mod metadata_result;
#[cfg(feature = "schema")]
mod metadata_validation_error;
#[cfg(feature = "schema")]
mod metadata_validation_result;
#[cfg(feature = "json")]
mod metadata_wire_decode_error;
#[cfg(feature = "json")]
mod metadata_wire_encode_error;
mod metadata_wire_limit_kind;
#[cfg(feature = "schema")]
mod schema;
mod wire;

#[cfg(test)]
mod tests;

#[cfg(feature = "filter")]
pub use filter::Condition;
#[cfg(feature = "filter")]
pub use filter::FilterExpression;
#[cfg(feature = "filter")]
pub use filter::FilterExpressionBuilder;
#[cfg(feature = "filter")]
pub use filter::FilterExpressionView;
#[cfg(feature = "filter")]
pub use filter::FilterLimitKind;
#[cfg(feature = "filter")]
pub use filter::FilterLimits;
#[cfg(feature = "filter")]
pub use filter::FilterLimitsBuilder;
#[cfg(feature = "filter")]
pub use filter::FilterMatchOptions;
#[cfg(feature = "filter")]
pub use filter::FilterMatchOptionsBuilder;
#[cfg(feature = "filter")]
pub use filter::MetadataFilter;
#[cfg(feature = "filter")]
pub use filter::MetadataFilterBuilder;
pub use metadata::Metadata;
pub use metadata_error::MetadataError;
#[cfg(feature = "json")]
pub use metadata_limits::DEFAULT_MAX_JSON_BYTES;
#[cfg(feature = "json")]
pub use metadata_limits::DEFAULT_MAX_KEY_BYTES;
#[cfg(feature = "json")]
pub use metadata_limits::DEFAULT_MAX_METADATA_ENTRIES;
#[cfg(feature = "json")]
pub use metadata_limits::DEFAULT_MAX_SCHEMA_FIELDS;
#[cfg(feature = "json")]
pub use metadata_limits::MetadataLimits;
#[cfg(feature = "json")]
pub use metadata_limits::default_json_decode_limits;
#[cfg(feature = "json")]
pub use metadata_limits::default_json_encode_limits;
#[cfg(feature = "json")]
pub use metadata_limits::default_json_value_limits;
#[cfg(feature = "json")]
pub use metadata_limits_builder::MetadataLimitsBuilder;
pub use metadata_result::MetadataResult;
#[cfg(feature = "schema")]
pub use metadata_validation_error::MetadataValidationError;
#[cfg(feature = "schema")]
pub use metadata_validation_result::MetadataValidationResult;
#[cfg(feature = "json")]
pub use metadata_wire_decode_error::MetadataWireDecodeError;
#[cfg(feature = "json")]
pub use metadata_wire_encode_error::MetadataWireEncodeError;
pub use metadata_wire_limit_kind::MetadataWireLimitKind;
#[cfg(feature = "schema")]
pub use schema::MetadataField;
#[cfg(feature = "schema")]
pub use schema::MetadataSchema;
#[cfg(feature = "schema")]
pub use schema::MetadataSchemaBuilder;
#[cfg(feature = "schema")]
pub use schema::UnknownFilterFieldPolicy;
#[cfg(feature = "schema")]
pub use schema::UnknownMetadataFieldPolicy;