Qubit Codec (rs-codec)
qubit-codec addresses a recurring problem in format and adapter crates: once a
team defines how one logical value maps to encoded units, it still needs owned
one-shot APIs, caller-buffered streaming, and optional policy hooks. Rebuilding
indices, capacity bounds, progress, and reset/finish lifecycle in each layer
produces incompatible public surfaces for the same representation rules. This
crate centralizes those mechanics while the format crate keeps domain encoding
and errors. It does not ship concrete binary formats, character sets, Base64,
hex, percent encoding, or general-purpose std::io extensions.
A binary format crate example
A protocol crate must encode 0x1234 as [0x12, 0x34], decode the reverse,
treat a lone input byte as incomplete instead of entering the unsafe decode
entry, and encode a batch of values into a caller-owned buffer—all from one
Codec implementation. Callers that only need a single owned conversion use
CodecValueEncoder / CodecValueDecoder; callers that own the destination
buffer use CodecTranscodeEncoder without duplicating bound arithmetic.
Installation
[]
= "0.17"
The default feature set is empty. Enable json for generic Serde JSON value
codecs, io for the qubit-io buffered bridges, and registry for global
value codec registration and the register_value_string_codec! /
register_value_bytes_codec! macros. The features are independent:
[]
= "0.19"
= { = "0.17", = ["io"] }
[]
= { = "0.17", = ["registry"] }
[]
= { = "0.17", = ["json"] }
With json, the generic codecs encode and decode complete JSON values as
strings or bytes. For example:
use ;
let mut text = new;
let encoded = text.encode.unwrap;
assert_eq!;
let mut bytes = new;
let encoded = bytes.encode.unwrap;
assert_eq!;
T must implement serde::Serialize for encoding and serde::de::DeserializeOwned
for decoding; both directions return serde_json::Error. These are generic
Serde JSON codecs, not concrete domain formats. With both json and registry,
the built-in IDs register only serde_json::Value; a custom DTO needs its own
descriptor and unique registry ID.
Quick start
Implement the representation rule once, then expose owned encoding:
use ;
use ;
;
let mut encoder = new;
let bytes = encoder.encode.expect;
assert_eq!;
The lifecycle-aware decode example shows when
CodecValueDecoder::decode_lifecycle is required instead of strict one-value
decode. The user guide continues this fixed-width scenario
with owned decode, incomplete input, caller-buffered streaming, policy hooks,
and optional qubit-io integration.
The same U16BeCodec can back both owned helpers and batch encoding into a
caller buffer:
use ;
assert_eq!;
assert_eq!;
assert_eq!;
TranscodeEncodeOutput::transcode keeps this whole-range behavior and reserves
the batch's full output bound before encoding. For large inputs, the io
feature also provides transcode_step, which calls the encoder once and
returns TranscodeProgress so the caller can advance through the input while
handling output backpressure. The stepwise path limits each call's initial
reservation to a single-value bound, but retained buffer capacity does not
shrink and a custom encoder may request more space. Existing buffered writers
do not adopt this path automatically.
Checked adapters establish index and capacity preconditions before the unsafe
Codec entry points; a one-byte input therefore surfaces as incomplete or
transcode failure rather than undefined behavior. See the user guide
for incremental transcode, EOF handling, and engines with hooks.
What it provides
- Core value/unit contract:
Codec,DecodeFailure, and explicit encode/decode bounds for checked adapters. - Owned whole-value facades:
ValueEncoder,ValueDecoder,CodecValueEncoder,CodecValueDecoder, including lifecycle-aware decode when reset or finish values are declared. - Strict caller-buffered conversion:
Transcoder,CodecTranscodeEncoder,CodecTranscodeDecoder,CodecTranscodeConverter, withTranscodeProgressandTranscodeStatusfor backpressure. - Policy-aware engines:
engine::TranscodeEncodeEngine,engine::TranscodeDecodeEngine,engine::TranscodeConvertEngine, and hooks such asDecodeIncompleteActionfor incomplete tails after EOF is known. - Byte order helpers:
ByteOrder,ByteOrderSpec,BigEndian,LittleEndian,NativeEndian. - With
registry: validated stable IDs,ValueStringCodecDescriptor,ValueBytesCodecDescriptor,ValueStringCodecRegistry,ValueBytesCodecRegistry, andregister_value_string_codec!/register_value_bytes_codec!for type-checked erased whole-value codecs (string or bytes wire) fixed at registration time. Registered codec errors must implementError + Send + Sync + 'static; source errors are retained across threads, while decoded values remainBox<dyn Any>and the whole decode result is not thereby transferable across threads. - With
io: sync and partial-I/O async transcode input/output bridges overqubit-io.
The crate does not implement domain formats or character-set tables on its own.
Codec::decode separates incomplete visible prefixes from invalid domain input
through DecodeFailure; an incomplete prefix is not an EOF decision by itself.
In the same codec state, Codec::encode_len must match a successful
Codec::encode, including intentional zero-output buffering. A Transcoder
follows reset -> transcode/transcode_eof -> finish; NeedInput leaves its tail
with the caller, NeedOutput requires more destination capacity, and Complete
means all visible input from the call's start index was consumed. Capacity bounds
must cover every reachable transient state; owned adapters may allocate while
streaming APIs use caller-provided buffers. Registry lookup is by validated ID
and execution checks the erased input type before invoking user code.
When a synchronous input bridge is driven through EOF with output capacity
remaining, it dispatches transcode_eof even if no unread input remains. Async
callers dispatch the same EOF event with transcode_eof_step after EndOfInput.
Learn more
Testing
# Run tests with the default feature set
# Run tests with all declared features
# Project CI checks
# Check code coverage
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-codec