qubit-value 0.12.1

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

[![Rust CI](https://github.com/qubit-ltd/rs-value/actions/workflows/ci.yml/badge.svg)](https://github.com/qubit-ltd/rs-value/actions/workflows/ci.yml)
[![Coverage](https://img.shields.io/endpoint?url=https://qubit-ltd.github.io/rs-value/coverage-badge.json)](https://qubit-ltd.github.io/rs-value/coverage/)
[![Crates.io](https://img.shields.io/crates/v/qubit-value.svg?color=blue)](https://crates.io/crates/qubit-value)
[![Rust](https://img.shields.io/badge/rust-1.94+-blue.svg?logo=rust)](https://www.rust-lang.org)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
[![中文文档](https://img.shields.io/badge/文档-中文版-blue.svg)](README.zh_CN.md)

`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.

<!-- example:quick-start run -->
```rust
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`](https://github.com/qubit-ltd/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:

<!-- example:conversion-policy run -->
```rust
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`:

```toml
[dependencies]
qubit-value = { version = "0.12", features = ["converter"] }
qubit-datatype = { version = "0.14", default-features = false, features = ["converter"] }
```

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.
- `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`]https://github.com/qubit-ltd/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`]https://github.com/qubit-ltd/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:

```json
{"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.

The Wire DTOs implement `Serialize`, but deliberately do not implement generic
`Deserialize`: a general Serde deserializer cannot enforce the raw-input and
structural limits required at an untrusted boundary. Use the bounded
`ValueWireV1::decode_json_slice` helpers for complete JSON documents. For an
embedded value, pass `ValueWireV1Seed::new()` or `ValueWirePayloadV1Seed::new()`
to the surrounding `JsonDecoder::decode_seed_utf8` or `next_value_seed` call so
the outer protocol owns one shared budget.

`NamedValue` and `NamedMultiValues` do implement generic `Deserialize` so they
can be embedded in larger Serde documents. That implementation validates the
V1 schema and payload shape but inherits resource accounting from the supplied
deserializer. Use their bounded `decode_json_slice` helpers for complete,
untrusted JSON input, or a resource-bounded outer decoder when they are nested.

Natural JSON cannot reconstruct `DataType`, unset state, or scalar-versus-
collection shape. Wire V1 rejects non-finite floats and unsupported or malformed
payloads instead of guessing. Use a fresh bounded session for each independent
Wire operation; reuse a session only when several embedded values belong to the
same outer request budget.

For conversion fallbacks, a policy-missing scalar may use the supplied default.
A missing item inside a concrete collection never does, including item zero;
an explicitly empty collection also remains an error for first-item reads.

## Learn more

- [English user guide]doc/user_guide.md
- [Architecture and Wire design]doc/design.md
- [中文用户手册]doc/user_guide.zh_CN.md
- [API documentation]https://docs.rs/qubit-value
- [`qubit-datatype` conversion contract]https://docs.rs/qubit-datatype/latest/qubit_datatype/
- [中文 README]README.zh_CN.md

## Testing

```bash
# 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](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](https://github.com/qubit-ltd/rs-value)