qubit-value 0.10.0

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.

Installation

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

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

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 JSON Wire decoding; Natural JSON also requires converter
redact Policy-aware redacted views through qubit-redact
all converter, chrono, big-number, url, json, and redact

Supported DataType values

qubit_datatype::DataType is the closed runtime type vocabulary used by Value, MultiValues, and Unset. The same type identifies a scalar and its homogeneous collection form.

DataType Rust value Feature Typical use
Bool bool flags and switches
Char char one Unicode character
Int8 / Int16 / Int32 / Int64 / Int128 i8i128 signed integers
UInt8 / UInt16 / UInt32 / UInt64 / UInt128 u8u128 unsigned integers
Float32 / Float64 f32 / f64 finite or in-memory floating-point values
String String text and text-backed input
Date chrono::NaiveDate chrono calendar date
Time chrono::NaiveTime chrono time of day
DateTime chrono::NaiveDateTime chrono date and local time
Instant chrono::DateTime<chrono::Utc> chrono UTC time point
BigInteger num_bigint::BigInt big-integer arbitrary-precision integer
BigDecimal bigdecimal::BigDecimal big-decimal arbitrary-precision decimal
Duration std::time::Duration elapsed time
Url url::Url url parsed URL
StringMap HashMap<String, String> string-key/string-value properties
Json serde_json::Value json arbitrary JSON structure

The 25 variants above are the complete current DataType enum. A feature-gated variant can still appear in an Unset(DataType) declaration, but a concrete value of that type requires the corresponding feature in the build that stores or reads it.

What it provides

  • Value and MultiValues have typed constructors, typed getters, generic mutation, borrowed reads, and explicit unset state.
  • 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 type mismatches and ordinary conversion failures are still reported.
  • 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. Use it 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 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.

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.

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 closed and versioned; Natural JSON intentionally omits runtime type tags. The user guide contains the full Wire workflow, borrowed payload examples, feature compatibility rules, and resource-limit handling.

For example, Natural JSON produces these exact JSON strings:

use std::collections::HashMap;

use qubit_datatype::DataType;
use qubit_value::{MultiValues, Value};

assert_eq!(Value::new(42i32).to_json_value()?.to_string(), "42");
assert_eq!(
    Value::new("localhost".to_owned())
        .to_json_value()?
        .to_string(),
    r#""localhost""#,
);
assert_eq!(
    Value::new_unset(DataType::String)
        .to_json_value()?
        .to_string(),
    "null",
);
assert_eq!(
    MultiValues::new([8080i32, 8081])
        .to_json_value()?
        .to_string(),
    "[8080,8081]",
);
assert_eq!(
    Value::new(HashMap::from([
        ("z".to_owned(), "26".to_owned()),
        ("a".to_owned(), "1".to_owned()),
    ]))
    .to_json_value()?
    .to_string(),
    r#"{"a":"1","z":"26"}"#,
);

The scalar number becomes 42, the string keeps its JSON quotes, an unset value becomes null, a concrete collection becomes an array, and string-map keys are emitted in dictionary order.

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
./ci-check.sh

# Check code coverage
./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 ./align-ci.sh to format code and ./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