# Qubit Codec

[![CircleCI](https://circleci.com/gh/qubit-ltd/rs-codec.svg?style=shield)](https://circleci.com/gh/qubit-ltd/rs-codec)
[![Coverage Status](https://coveralls.io/repos/github/qubit-ltd/rs-codec/badge.svg?branch=main)](https://coveralls.io/github/qubit-ltd/rs-codec?branch=main)
[![Crates.io](https://img.shields.io/crates/v/qubit-codec.svg?color=blue)](https://crates.io/crates/qubit-codec)
[![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)
[![English Document](https://img.shields.io/badge/Document-English-blue.svg)](README.md)

为 Rust 应用提供可复用的字节与文本编解码器。

## 概述

Qubit Codec 提供小而明确的编解码器，用于 Qubit Rust crate 和应用中常见的稳定字节与文本编码。它保持 Rust API 轻量、类型明确且符合 Rust 习惯：常见场景直接使用具体方法，泛型边界再使用 trait。

本 crate 聚焦具有清晰线格式语义的文本编码：

- 十六进制字节串
- Base64 字节串
- C 字符串字面量字节片段
- percent-encoded UTF-8 文本
- `application/x-www-form-urlencoded` UTF-8 文本片段

它不会替代 Rust 的 `Display`、`FromStr`、`TryFrom` 或 `serde`，这些 API 仍然适合普通对象转换。

## 设计目标

- **语义明确**：每个 codec 都说明字母表、分隔符、填充和解码规则。
- **API 表面小**：优先提供直接的 `encode` 和 `decode` 方法，泛型场景再使用 trait。
- **无隐藏 Panic**：畸形输入返回 `CodecError`，不直接 panic。
- **Trait 可组合**：`Encoder`、`Decoder` 和 `Codec` 支持可复用边界，不强制动态分发。
- **实现可复用**：常用编码集中在一个 crate，避免下游重复实现。
- **依赖最少化**：只在确有价值时依赖维护良好的第三方 crate。

## 特性

### 🔡 **十六进制字节**

- **默认小写**：`HexCodec::new()` 生成连续小写十六进制文本。
- **大写模式**：`HexCodec::upper()` 或 `with_uppercase(true)` 生成大写字符。
- **可选整体前缀**：在整个编码值前添加并要求前缀，例如 `0x`。
- **可选逐字节前缀**：在每个编码字节前添加并要求 byte 前缀，例如 `0x`。
- **可选分隔符**：在字节之间写入并接受分隔符，例如 `:` 或空格。
- **空白处理**：解码时可选择忽略 ASCII 空白字符。
- **前缀大小写处理**：解码匹配已配置前缀时，可选择忽略 ASCII 大小写。
- **缓冲区 API**：`encode_into` 和 `decode_into` 可追加写入已有缓冲区。

### 🔐 **Base64 字节**

- **标准字母表**：支持带 padding 和无 padding 的标准 Base64。
- **URL 安全字母表**：支持带 padding 和无 padding 的 URL-safe Base64。
- **类型化错误**：畸形输入返回 `CodecError::InvalidInput`。

### 🔤 **C 字符串字面量字节**

- **混合文本与转义**：解码 `PK\003\004` 和 `\xd0\xcf` 这样的片段。
- **C 转义支持**：支持简单转义、八进制转义、十六进制转义和 universal byte escape。
- **字节输出**：直接解码为原始字节，不要求 UTF-8。

### 🌐 **Percent-Encoding**

- **UTF-8 文本**：编码和解码 UTF-8 字符串。
- **RFC 3986 unreserved 集合**：ASCII 字母、数字、`-`、`.`、`_` 和 `~` 保持不变。
- **大写转义**：写出 `%2F`、`%E4` 这样的 percent escape。
- **畸形转义检测**：报告截断或非法的 `%XX` 序列。

### 📝 **Form URL Encoding**

- **表单片段 codec**：处理 `application/x-www-form-urlencoded` 文本片段。
- **空格使用加号**：空格编码为 `+`，解码时 `+` 还原为空格。
- **Percent 兼容**：复用与 `PercentCodec` 相同的 UTF-8 和 `%XX` 校验行为。

### 🎯 **聚焦的公开 API**

- **`Encoder<Input>`**：将借用输入编码为关联输出类型。
- **`Decoder<Input>`**：将借用输入解码为关联输出类型。
- **`Codec<EncodeInput, DecodeInput>`**：组合 encoder 和 decoder trait。
- **`CodecError` / `CodecResult`**：内置 codec 的公共错误与结果类型。

## 安装

在 `Cargo.toml` 中添加：

```toml
[dependencies]
qubit-codec = "0.3.0"
```

## 快速开始

### 十六进制字节

```rust
use qubit_codec::HexCodec;

fn main() {
    let codec = HexCodec::upper()
        .with_prefix("0x")
        .with_separator(" ");

    let encoded = codec.encode(&[0x1f, 0x8b, 0x00, 0xff]);
    assert_eq!("0x1F 8B 00 FF", encoded);

    let decoded = codec
        .decode("0x1F 8B 00 FF")
        .expect("hex text should decode");
    assert_eq!(vec![0x1f, 0x8b, 0x00, 0xff], decoded);
}
```

### Base64 字节

```rust
use qubit_codec::Base64Codec;

fn main() {
    let codec = Base64Codec::standard();

    let encoded = codec.encode(b"hello");
    assert_eq!("aGVsbG8=", encoded);

    let decoded = codec
        .decode("aGVsbG8=")
        .expect("Base64 text should decode");
    assert_eq!(b"hello".to_vec(), decoded);
}
```

### 无 Padding 的 URL-Safe Base64

```rust
use qubit_codec::Base64Codec;

fn main() {
    let codec = Base64Codec::url_safe_no_pad();

    let encoded = codec.encode(&[251, 255, 239]);
    assert_eq!("-__v", encoded);

    let decoded = codec
        .decode("-__v")
        .expect("URL-safe Base64 text should decode");
    assert_eq!(vec![251, 255, 239], decoded);
}
```

### C 字符串字面量字节

```rust
use qubit_codec::CStringLiteralCodec;

fn main() {
    let codec = CStringLiteralCodec::new();

    let decoded = codec
        .decode(r"PK\003\004")
        .expect("C string literal should decode");
    assert_eq!(b"PK\x03\x04".to_vec(), decoded);

    let encoded = codec.encode(&[0xd0, 0xcf, 0x11, 0xe0]);
    assert_eq!(r"\xD0\xCF\x11\xE0", encoded);
}
```

### Percent-Encoding UTF-8 文本

```rust
use qubit_codec::PercentCodec;

fn main() {
    let codec = PercentCodec::new();

    let encoded = codec.encode("a b/中");
    assert_eq!("a%20b%2F%E4%B8%AD", encoded);

    let decoded = codec
        .decode("a%20b%2F%E4%B8%AD")
        .expect("percent-encoded text should decode");
    assert_eq!("a b/中", decoded);
}
```

### Form URL Encoding

```rust
use qubit_codec::FormUrlencodedCodec;

fn main() {
    let codec = FormUrlencodedCodec::new();

    let encoded = codec.encode("name=Qubit Codec");
    assert_eq!("name%3DQubit+Codec", encoded);

    let decoded = codec
        .decode("name%3DQubit+Codec")
        .expect("form-url-encoded text should decode");
    assert_eq!("name=Qubit Codec", decoded);
}
```

### 泛型 Trait 用法

当应用代码只依赖“具备某种编码能力”，而不想依赖具体 codec 类型时，可以使用 trait。

```rust
use qubit_codec::{
    CodecError,
    Encoder,
    HexCodec,
};

fn encode_payload<C>(codec: &C, payload: &[u8]) -> Result<String, CodecError>
where
    C: Encoder<[u8], Output = String, Error = CodecError>,
{
    codec.encode(payload)
}

fn main() {
    let text = encode_payload(&HexCodec::new(), &[0xab, 0xcd])
        .expect("hex encoding should not fail");
    assert_eq!("abcd", text);
}
```

## API 参考

### Trait 操作

| Trait | 方法 | 描述 |
|-------|------|------|
| `Encoder<Input>` | `encode(&Input)` | 将借用输入编码为关联输出类型 |
| `Decoder<Input>` | `decode(&Input)` | 将借用输入解码为关联输出类型 |
| `Codec<EncodeInput, DecodeInput>` | - | `Encoder` 与 `Decoder` 的组合 trait |

### `HexCodec` 操作

| 方法 | 描述 |
|------|------|
| `new()` | 创建无前缀、无分隔符的小写 codec |
| `upper()` | 创建无前缀、无分隔符的大写 codec |
| `with_uppercase(enabled)` | 配置字符大小写 |
| `with_prefix(prefix)` | 添加并要求整体前缀，例如 `0x1F8B` |
| `with_byte_prefix(prefix)` | 在每个 byte 前添加并要求前缀，例如 `0x1F 0x8B` |
| `with_separator(separator)` | 在字节之间添加并接受分隔符 |
| `with_ignored_ascii_whitespace(enabled)` | 解码时忽略 ASCII 空白字符 |
| `with_ignore_prefix_case(enabled)` | 解码匹配已配置前缀时忽略 ASCII 大小写 |
| `encode(bytes)` | 将字节编码为十六进制文本 |
| `encode_into(bytes, output)` | 将编码文本追加到已有 `String` |
| `decode(text)` | 将十六进制文本解码为字节 |
| `decode_into(text, output)` | 将解码字节追加到已有 `Vec<u8>` |

### `Base64Codec` 操作

| 方法 | 字母表 | Padding | 描述 |
|------|--------|---------|------|
| `standard()` | 标准 | 有 | 创建标准 Base64 codec |
| `standard_no_pad()` | 标准 | 无 | 创建无 padding 的标准 Base64 codec |
| `url_safe()` | URL-safe | 有 | 创建 URL-safe Base64 codec |
| `url_safe_no_pad()` | URL-safe | 无 | 创建无 padding 的 URL-safe Base64 codec |
| `encode(bytes)` | 已配置 | 已配置 | 将字节编码为 Base64 文本 |
| `decode(text)` | 已配置 | 已配置 | 将 Base64 文本解码为字节 |

### `CStringLiteralCodec` 操作

| 方法 | 描述 |
|------|------|
| `new()` | 创建 C 字符串字面量字节 codec |
| `encode(bytes)` | 将字节编码为 C 字符串字面量片段 |
| `decode(text)` | 将 C 字符串字面量片段解码为字节 |

### 文本 Codec 操作

| 类型 | 方法 | 描述 |
|------|------|------|
| `PercentCodec` | `new()` | 创建 percent codec |
| `PercentCodec` | `encode(text)` | 使用 percent encoding 编码 UTF-8 文本 |
| `PercentCodec` | `decode(text)` | 解码 percent-encoded UTF-8 文本 |
| `FormUrlencodedCodec` | `new()` | 创建 form-url-encoded codec |
| `FormUrlencodedCodec` | `encode(text)` | 编码 UTF-8 文本，并将空格写为 `+` |
| `FormUrlencodedCodec` | `decode(text)` | 解码 UTF-8 文本，并将 `+` 视为空格 |

## 错误处理

内置 decoder 返回 `CodecResult<T>`，也就是 `Result<T, CodecError>`。

| 错误 | 含义 |
|------|------|
| `MissingPrefix` | 配置了整体或逐字节十六进制前缀，但输入缺少该前缀 |
| `InvalidDigit` | 输入包含不符合指定进制的数字字符 |
| `InvalidLength` | 输入长度不满足 codec 要求 |
| `InvalidEscape` | 输入包含畸形或不支持的转义序列 |
| `InvalidCharacter` | 输入包含当前位置不允许的字符 |
| `InvalidInput` | 输入被 codec 专属校验拒绝 |
| `InvalidUtf8` | 解码后的字节不是合法 UTF-8 |

## 测试与代码覆盖率

本项目通过 `tests/` 下的集成测试覆盖 codec 行为。

### 运行测试

```bash
# 运行所有测试
cargo test

# 运行覆盖率报告
./coverage.sh

# 生成文本格式报告
./coverage.sh text

# 对齐 CI 要求
./align-ci.sh

# 运行 CI 检查（格式化、clippy、测试、覆盖率、安全审计）
./ci-check.sh
```

## 依赖项

运行时依赖保持很少：

- `base64` 提供 Base64 engine。
- `thiserror` 提供公共错误类型实现。

## 许可证

Copyright (c) 2026. Haixing Hu.

根据 Apache 许可证 2.0 版（"许可证"）授权；
除非遵守许可证，否则您不得使用此文件。
您可以在以下位置获取许可证副本：

    http://www.apache.org/licenses/LICENSE-2.0

除非适用法律要求或书面同意，否则根据许可证分发的软件
按"原样"分发，不附带任何明示或暗示的担保或条件。
有关许可证下的特定语言管理权限和限制，请参阅许可证。

完整的许可证文本请参阅 [LICENSE](LICENSE)。

## 贡献

欢迎贡献！请随时提交 Pull Request。

### 开发指南

- 遵循 Rust API 指南。
- 保持测试全面且稳定。
- 为公共 API 和行为变化编写文档。
- 提交 PR 前确保所有检查通过。

## 作者

**胡海星**

## 相关项目

Qubit 旗下的更多 Rust 库发布在 GitHub 组织 [qubit-ltd](https://github.com/qubit-ltd)。

---

仓库地址：[https://github.com/qubit-ltd/rs-codec](https://github.com/qubit-ltd/rs-codec)
