qubit-value 0.13.1

Type-safe containers for single, multi-valued, and named runtime values
Documentation

Qubit Value

Rust CI Coverage Crates.io Rust License 中文文档

qubit-value gives Rust applications one type-safe boundary for values that are known only at runtime. It is useful when configuration, metadata, protocol fields, or user input can be a boolean, number, string, date, collection, or structured JSON value, but the application still needs explicit types, controlled conversion, and predictable errors.

The problem it solves

Without a shared runtime value model, each key-value subsystem tends to invent its own enum, conversion rules, unset semantics, and serialization format. That creates three recurring problems:

  • a missing value, an explicitly empty collection, and JSON null are easily confused;
  • a one-item collection can be accidentally treated as a scalar;
  • values crossing a process or storage boundary lose their runtime type, or accept conversions that were never intended.

Value stores one typed scalar, MultiValues stores one homogeneous collection, and ValueContainer preserves the explicit scalar-or-collection shape. Unset(DataType) retains the declared type without pretending that a concrete value exists.

Quick start: a small runtime configuration map

This is a small configuration-like map: each key stores a different Value, then the reader chooses strict access, explicit conversion, or a typed default. The snippet assumes it is inside a function that returns a compatible Result, so ? can propagate value errors.

use std::collections::HashMap;
use std::time::Duration;

use qubit_datatype::DataType;
use qubit_value::Value;

let config = HashMap::from([
    ("host".to_owned(), Value::new("localhost".to_owned())),
    ("port".to_owned(), Value::new("8080".to_owned())),
    ("debug".to_owned(), Value::new(false)),
    (
        "timeout".to_owned(),
        Value::new_unset(DataType::Duration),
    ),
]);

let host: String = config["host"].get()?;
assert_eq!(host, "localhost");

let port: u16 = config["port"].to()?;
assert_eq!(port, 8080);

let debug: bool = config["debug"].get()?;
assert!(!debug);

let timeout: Duration = config["timeout"].get_or(Duration::from_secs(30))?;
assert_eq!(timeout, Duration::from_secs(30));

If you need a complete, general-purpose configuration object instead of assembling a map yourself, use Config from rs-config. It builds on Value and adds higher-level capabilities such as property management, typed and multi-value reads, defaults, sections, conversion policies, interpolation, and pluggable file/environment configuration sources.

get() is a strict type read: it does not silently convert. to() uses the shared conversion rules from qubit-datatype; failed conversions remain errors. The converter feature is required for to() and to_or(); get_or() only supplies a fallback for an unset value and does not convert.

Use to_with when the boundary needs an explicit policy and limits. Every to_with call creates a fresh ConversionSession, so independent reads do not share cumulative consumption:

To share limits across related reads, use the _in APIs with one ConversionSession; see Shared conversion session.

use qubit_datatype::ConversionLimits;
use qubit_datatype::ConversionPolicy;
use qubit_value::Value;

let policy = ConversionPolicy::env_friendly();
let limits = ConversionLimits::default();
let first = Value::new(" 8080 ").to_with::<u16>(&policy, &limits)?;
let second = Value::new(" 8081 ").to_with::<u16>(&policy, &limits)?;
assert_eq!((first, second), (8080, 8081));

Installation

Add the core crate and its type vocabulary to Cargo.toml:

[dependencies]
qubit-value = { version = "0.13", default-features = false, features = ["converter"] }
qubit-datatype = { version = "0.15", default-features = false }

The quick-start example uses Value::to, so it enables converter. The default feature set is empty; enable only the families you use:

Feature Additional DataType or capability
converter Cross-type conversion APIs such as Value::to
chrono Date, Time, DateTime, and Instant
big-integer BigInteger backed by num_bigint::BigInt
big-decimal BigDecimal backed by bigdecimal::BigDecimal
big-number Compatibility alias for both big-number features
url Url backed by url::Url
json Json backed by serde_json::Value and bounded versioned JSON Wire encoding/decoding
natural-json Convenience alias for converter + json; their combination also enables Natural JSON
redact Policy-aware redacted views through qubit-redact
all converter, chrono, big-number, url, json, natural-json, and redact

What it provides

  • Value and MultiValues have typed constructors, typed getters, generic mutation, borrowed reads, and explicit unset state.
  • view() exposes borrowed runtime variants for adapters that inspect values without cloning strings, maps, JSON trees, or collection buffers.
  • ValueContainer::Scalar and ValueContainer::Collection preserve shape; a one-item collection remains a collection.
  • get_or/to_or and collection variants make fallback behavior explicit: unset values can use defaults, while concrete empty first-item reads, missing collection items, type mismatches, and ordinary conversion failures remain errors.
  • MultiValues::get_first reads one scalar item and now requires StrictValueRead; complete collections use get or get_slice. This tightens source compatibility for callers that used first-item methods with Vec<T> or slice targets.
  • NamedValue and NamedMultiValues attach a key to a runtime value without changing the value's type semantics.
  • ValueWireV1 provides a versioned, type-preserving JSON representation with bounded to_json_vec() and to_json_writer() entry points; explicit JsonDecodeLimits and JsonEncodeLimits are accepted by the corresponding directional _with_limits methods. Decode uses a caller-configured JsonDecodeSession; encode uses JsonEncodeSession to enforce structure and output bytes online. Use Wire V1 when the receiver must reconstruct the exact DataType and shape.
  • Natural JSON helpers produce ordinary null, scalar, object, and array values when runtime type tags are not wanted.
  • The runtime vocabulary currently contains 25 DataType variants. Concrete feature-gated values require the corresponding feature, although their unset type declarations remain available.

This crate does not provide a complete configuration store, schema registry, file format, or distributed cache. It provides the typed value layer those systems can build on. Its Eq/Hash implementations are suitable for in-memory Rust collections, not persistent fingerprints or distributed-cache keys. The user guide documents the complete type table, JSON number contract, errors, resource limits, and feature compatibility.

Built on Value

Two sibling crates use this value model for key-value containers:

  • rs-config provides typed configuration properties, configuration-file and environment-oriented access, and policy-controlled reads. Use it when the key-value data describes application configuration.
  • rs-metadata provides typed metadata/property storage and filtering. Use it when values describe resources, records, or searchable application metadata.

Value::numeric_cmp compares numbers across runtime types while keeping type-sensitive identity separate; see the numeric comparison guide.

Wire V1 or Natural JSON?

Choose Wire V1 when a receiver must distinguish Int32(42) from String("42"), preserve scalar versus collection shape, or retain Unset(DataType). A typical document is:

{"version":1,"value":{"scalar":{"int32":42}}}

Choose to_json_value() when the boundary is ordinary application JSON and the receiver only needs JSON semantics. Wire V1 is a closed, versioned format; Natural JSON omits explicit runtime type and shape tags. Concrete collections still project to arrays, including one-item collections, while scalar and collection unset values both project to null. JSON alone cannot uniquely recover the original runtime shape: a scalar Value::Json may itself contain an array. Use Wire V1 when runtime type, shape, and unset declarations must be reconstructed. Wire V1 rejects non-finite floats, unsupported types, malformed payloads, and runtime JSON numbers that cannot round trip with the same Number identity instead of guessing. With serde_json/arbitrary_precision, nonstandard or over-precise number forms may be rejected; use explicit big-number types or an application string schema when exact precision is required.

For complete untrusted JSON documents, use the bounded Wire JSON helpers. For embedded values, pass ValueWireV1Seed or ValueWirePayloadV1Seed to JsonDecoder::decode_unique_seed_utf8 or next_value_seed so values in one request share its budget; create a fresh bounded session for each independent Wire operation. Wire DTOs implement Serialize but not generic Deserialize; NamedValue and NamedMultiValues do implement generic Deserialize, whose resource accounting comes from the supplied deserializer. This is not a budget for every Serde decode. When serde_json/arbitrary_precision is unified, use a bounded JSON helper or a decode_unique_* path. See the Wire V1 guide for type and shape details, Instant and preflight limits, DTO seeds, intermediate-tree tradeoffs, and feature compatibility.

A scalar treated as missing by the conversion policy may use a fallback; a missing item in a concrete collection, including its first item, does not. An explicitly empty collection also remains an error for first-item reads. See the errors and diagnostics guide and default-value troubleshooting guide.

Learn more

Testing

# Run tests with the default feature set
cargo test

# Run tests with all declared features
cargo test --all-features

# Project CI checks
./.infra/bin/ci-check.sh

# Check code coverage
./.infra/bin/coverage.sh

License

Copyright (c) 2025 - 2026. Haixing Hu. All rights reserved.

Licensed under the Apache License, Version 2.0. See LICENSE for the full license text.

Contributing

Contributions are welcome. Please follow the Rust API guidelines, keep public API documentation and tests current, and run ./.infra/bin/align-ci.sh to format code and ./.infra/bin/ci-check.sh to satisfy CI requirements before submitting a pull request.

Author

Haixing Hu - Qubit Co. Ltd.

Repository: https://github.com/qubit-ltd/rs-value