From 5b4d5a8f3a1673225298c6e2c1a09b86f494b5fa Mon Sep 17 00:00:00 2001 From: AI Engineer Date: Wed, 6 May 2026 00:11:50 +0800 Subject: [PATCH] Refactor: remove Must functions, align with cast.As, add UnUrlBase64FromString (by AI) --- CHANGELOG.md | 13 ++++++++ README.md | 28 +++++++++++----- TEST.md | 18 +++++----- encoding.go | 85 ++++++++++++------------------------------------ encoding_test.go | 25 ++++++++------ go.mod | 2 ++ go.sum | 2 ++ 7 files changed, 82 insertions(+), 91 deletions(-) create mode 100644 go.sum diff --git a/CHANGELOG.md b/CHANGELOG.md index 331dc9c..88ca72f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog: @go/encoding +## [v1.0.6] - 2026-05-06 + +### Changed +- **设计哲学对齐**:全面废除 `Must` 前缀函数,改为配合 `go/cast` 的 `As` 函数消除摩擦,提升代码语义化。 +- **API 重命名**:`MustUnHtmlEscape` 重命名为 `HtmlUnescape`,使其符合标准 API 习惯且保持 frictionless。 + +### Added +- **API 补全**:新增 `UnHexFromString`、`UnBase64FromString`、`UnUrlBase64FromString` 与 `UnUrlEncode` 异步错误返回版本。 +- **依赖对齐**:引入 `apigo.cc/go/cast` 依赖以支持消除摩擦能力。 + +## [v1.0.5] - 2026-05-01 +- (同步版本号) + ## [v1.0.4] - 2026-05-01 ### Changed diff --git a/README.md b/README.md index a947ea1..b00cb14 100644 --- a/README.md +++ b/README.md @@ -4,29 +4,29 @@ # @go/encoding -`@go/encoding` 是一个为“极简、安全、无摩擦”业务开发设计的编解码工具库。它统一了二进制数据与文本处理的 API 语义,通过静默处理错误与基于字节的零拷贝设计,极大降低了业务逻辑中的错误处理心智负担。 +`@go/encoding` 是一个为“极简、安全、无摩擦”业务开发设计的编解码工具库。它统一了二进制数据与文本处理的 API 语义,通过与 `go/cast` 结合,极大降低了业务逻辑中的错误处理心智负担。 ## 🎯 设计哲学 * **API 原生直觉**:二进制操作基于 `[]byte`,文本表现类操作(如 HTML/URL)基于 `string`,与 Go 原生习惯保持高度一致。 -* **静默防御 (Must 系列)**:对于不可逆的非法输入,提供 `MustUnXxx` 系列 API,静默返回零值,消除业务代码中无效的错误检查噪声。 +* **消除摩擦 (Frictionless)**:废除 `Must` 前缀函数,推荐配合 `cast.As` 使用以实现更优雅的静默处理,降低业务代码中无效的错误检查噪声。 * **极致纯粹**:废除所有冗余的封装,强制数据链路层以 `[]byte` 形式流转,确保底层安全性与性能。 ## 🛠 API Reference ### 基础编解码 (Hex/Base64) - `func Hex(data []byte) []byte` / `func HexToString(data []byte) string` -- `func MustUnHex(data []byte) []byte` / `func MustUnHexFromString(data string) []byte` - +- `func UnHex(data []byte) ([]byte, error)` / `func UnHexFromString(data string) ([]byte, error)` - `func Base64(data []byte) []byte` / `func Base64ToString(data []byte) string` -- `func MustUnBase64(data []byte) []byte` / `func MustUnBase64FromString(data string) []byte` +- `func UnBase64(data []byte) ([]byte, error)` / `func UnBase64FromString(data string) ([]byte, error)` +- `func UrlBase64(data []byte) []byte` / `func UrlBase64ToString(data []byte) string` +- `func UnUrlBase64(data []byte) ([]byte, error)` / `func UnUrlBase64FromString(data string) ([]byte, error)` ### Web 编码 (URL/HTML) - `func UrlEncode(data []byte) string` -- `func MustUnUrlEncode(data string) []byte` - +- `func UnUrlEncode(data string) ([]byte, error)` - `func HtmlEscape(data []byte) string` -- `func MustUnHtmlEscape(data string) string` +- `func HtmlUnescape(data string) string` ### 整数与自定义进制 (IntEncoder) - `func EncodeInt(u uint64) []byte` @@ -41,3 +41,15 @@ ```bash go get apigo.cc/go/encoding ``` + +## 💡 示例 (配合 cast.As 消除摩擦) + +```go +import ( + "apigo.cc/go/encoding" + "apigo.cc/go/cast" +) + +// 配合 cast.As 替代原有的 MustUnHex 系列 +data := cast.As(encoding.UnHexFromString("68656c6c6f")) +``` diff --git a/TEST.md b/TEST.md index 5a28fb3..2ccdba7 100644 --- a/TEST.md +++ b/TEST.md @@ -1,27 +1,27 @@ # Test Report: @go/encoding ## 📋 测试概览 -- **测试时间**: 2026-05-01 +- **测试时间**: 2026-05-06 - **测试环境**: darwin/amd64 - **Go 版本**: 1.25.0 ## ✅ 功能测试 (Functional Tests) | 场景 | 状态 | 描述 | | :--- | :--- | :--- | -| `TestHex` | PASS | Hex 编解码往返一致性,Must 系列静默处理测试。 | -| `TestBase64` | PASS | Standard/URL Base64 编解码一致性,静默 API 测试。 | +| `TestHex` | PASS | Hex 编解码往返一致性,配合 `cast.As` 消除摩擦测试。 | +| `TestBase64` | PASS | Standard/URL Base64 编解码一致性,`Un...FromString` 补全测试。 | | `TestWebEncoding` | PASS | URL 编解码、HTML 转义反转义往返测试。 | | `TestUtf8` | PASS | UTF-8 校验逻辑测试。 | | `TestIntEncoder` | PASS | 包含正常编解码、FillInt 补齐、ExchangeInt 置换、HashInt 确定性测试。 | ## 🛡️ 鲁棒性防御 (Robustness) -- **静默处理 (Quiet Mode)**:所有 `MustUnXxx` API 对非法数据均返回空切片 `[]byte{}` 或空字符串,有效防止业务逻辑中的非预期中断或 Panic。 -- **参数校验**:`NewIntEncoder` 对字符集重复、长度不足等构造错误进行了防御性校验,强制要求单字节字符集。 -- **性能优化**:Hex 解码优化,减少了不必要的字符串到字节切片的转换和内存分配。 +- **消除摩擦 (Frictionless)**:废除 `Must` 系列 API,通过 `cast.As` 实现静默处理,保持业务逻辑简洁。 +- **API 补全**:补全了所有 `FromString` 版本的解码函数,并提供标准 error 返回。 +- **参数校验**:`NewIntEncoder` 对字符集重复、长度不足等构造错误进行了防御性校验。 ## ⚡ 性能基准 (Benchmarks) | 函数 | 平均耗时 | 性能分析 | | :--- | :--- | :--- | -| `HexEncode` | **44.03 ns/op** | 高效处理二进制数据,优化后性能稳步提升。 | -| `Base64Encode` | **39.73 ns/op** | 吞吐量优异。 | -| `IntEncoder` | **44.63 ns/op** | 整数编码逻辑极简,开销极小。 | +| `HexEncode` | **46.64 ns/op** | 高效处理二进制数据。 | +| `Base64Encode` | **42.24 ns/op** | 吞吐量优异。 | +| `IntEncoder` | **46.66 ns/op** | 整数编码逻辑极简,开销极小。 | diff --git a/encoding.go b/encoding.go index 48773bb..2335445 100644 --- a/encoding.go +++ b/encoding.go @@ -20,26 +20,6 @@ func HexToString(data []byte) string { return hex.EncodeToString(data) } -// MustUnHex 将 Hex 编码的字节切片解码,出错时返回空字节切片 -func MustUnHex(data []byte) []byte { - dst := make([]byte, hex.DecodedLen(len(data))) - n, err := hex.Decode(dst, data) - if err != nil { - return []byte{} - } - return dst[:n] -} - -// MustUnHexFromString 将 Hex 编码的字符串解码,出错时返回空字节切片 -func MustUnHexFromString(data string) []byte { - dst := make([]byte, hex.DecodedLen(len(data))) - n, err := hex.Decode(dst, []byte(data)) - if err != nil { - return []byte{} - } - return dst[:n] -} - // UnHex 将 Hex 编码的字节切片解码 func UnHex(data []byte) ([]byte, error) { dst := make([]byte, hex.DecodedLen(len(data))) @@ -47,6 +27,11 @@ func UnHex(data []byte) ([]byte, error) { return dst[:n], err } +// UnHexFromString 将 Hex 编码的字符串解码 +func UnHexFromString(data string) ([]byte, error) { + return hex.DecodeString(data) +} + // Base64 将数据转换为 Base64 编码的字节切片 func Base64(data []byte) []byte { buf := make([]byte, base64.StdEncoding.EncodedLen(len(data))) @@ -71,44 +56,6 @@ func UrlBase64ToString(data []byte) string { return base64.URLEncoding.EncodeToString(data) } -// MustUnBase64 将 Base64 编码的字节切片解码,出错时返回空字节切片 -func MustUnBase64(data []byte) []byte { - dbuf := make([]byte, base64.StdEncoding.DecodedLen(len(data))) - n, err := base64.StdEncoding.Decode(dbuf, data) - if err != nil { - return []byte{} - } - return dbuf[:n] -} - -// MustUnBase64FromString 将 Base64 编码的字符串解码,出错时返回空字节切片 -func MustUnBase64FromString(data string) []byte { - dbuf, err := base64.StdEncoding.DecodeString(data) - if err != nil { - return []byte{} - } - return dbuf -} - -// MustUnUrlBase64 将 URL 安全的 Base64 编码的字节切片解码,出错时返回空字节切片 -func MustUnUrlBase64(data []byte) []byte { - dbuf := make([]byte, base64.URLEncoding.DecodedLen(len(data))) - n, err := base64.URLEncoding.Decode(dbuf, data) - if err != nil { - return []byte{} - } - return dbuf[:n] -} - -// MustUnUrlBase64FromString 将 URL 安全的 Base64 编码的字符串解码,出错时返回空字节切片 -func MustUnUrlBase64FromString(data string) []byte { - dbuf, err := base64.URLEncoding.DecodeString(data) - if err != nil { - return []byte{} - } - return dbuf -} - // UnBase64 将 Base64 编码的字节切片解码 func UnBase64(data []byte) ([]byte, error) { dbuf := make([]byte, base64.StdEncoding.DecodedLen(len(data))) @@ -116,6 +63,11 @@ func UnBase64(data []byte) ([]byte, error) { return dbuf[:n], err } +// UnBase64FromString 将 Base64 编码的字符串解码 +func UnBase64FromString(data string) ([]byte, error) { + return base64.StdEncoding.DecodeString(data) +} + // UnUrlBase64 将 URL 安全的 Base64 编码的字节切片解码 func UnUrlBase64(data []byte) ([]byte, error) { dbuf := make([]byte, base64.URLEncoding.DecodedLen(len(data))) @@ -123,18 +75,23 @@ func UnUrlBase64(data []byte) ([]byte, error) { return dbuf[:n], err } +// UnUrlBase64FromString 将 URL 安全的 Base64 编码的字符串解码 +func UnUrlBase64FromString(data string) ([]byte, error) { + return base64.URLEncoding.DecodeString(data) +} + // UrlEncode 对数据进行 URL 编码 func UrlEncode(data []byte) string { return url.QueryEscape(string(data)) } -// MustUnUrlEncode 对字符串进行 URL 解码,出错时返回空字节切片 -func MustUnUrlEncode(data string) []byte { +// UnUrlEncode 对字符串进行 URL 解码 +func UnUrlEncode(data string) ([]byte, error) { res, err := url.QueryUnescape(data) if err != nil { - return []byte{} + return nil, err } - return []byte(res) + return []byte(res), nil } // HtmlEscape 对数据进行 HTML 转义 @@ -142,8 +99,8 @@ func HtmlEscape(data []byte) string { return html.EscapeString(string(data)) } -// MustUnHtmlEscape 对 HTML 字符串进行反转义 -func MustUnHtmlEscape(data string) string { +// HtmlUnescape 对 HTML 字符串进行反转义 +func HtmlUnescape(data string) string { return html.UnescapeString(data) } diff --git a/encoding_test.go b/encoding_test.go index 1b041bc..34e12ab 100644 --- a/encoding_test.go +++ b/encoding_test.go @@ -4,6 +4,7 @@ import ( "bytes" "testing" + "apigo.cc/go/cast" "apigo.cc/go/encoding" ) @@ -16,12 +17,12 @@ func TestHex(t *testing.T) { t.Fatal("Hex roundtrip failed") } - if !bytes.Equal(encoding.MustUnHexFromString(encoded), data) { - t.Error("MustUnHexFromString failed") + if !bytes.Equal(cast.As(encoding.UnHexFromString(encoded)), data) { + t.Error("UnHexFromString with cast.As failed") } - if len(encoding.MustUnHexFromString("!@#$")) != 0 { - t.Error("MustUnHexFromString should return empty for invalid hex chars") + if len(cast.As(encoding.UnHexFromString("!@#$"))) != 0 { + t.Error("UnHexFromString should return empty for invalid hex chars with cast.As") } } @@ -36,8 +37,8 @@ func TestBase64(t *testing.T) { t.Error("Base64 roundtrip failed") } - if !bytes.Equal(encoding.MustUnBase64FromString(enc), data) { - t.Error("MustUnBase64FromString failed") + if !bytes.Equal(cast.As(encoding.UnBase64FromString(enc)), data) { + t.Error("UnBase64FromString with cast.As failed") } // URL @@ -46,6 +47,10 @@ func TestBase64(t *testing.T) { if !bytes.Equal([]byte("hello/world+"), uDec) { t.Error("UrlBase64 roundtrip failed") } + + if !bytes.Equal(cast.As(encoding.UnUrlBase64FromString(uEnc)), []byte("hello/world+")) { + t.Error("UnUrlBase64FromString with cast.As failed") + } } // --- Web --- @@ -53,17 +58,17 @@ func TestWebEncoding(t *testing.T) { // URL data := []byte("a b+c") enc := encoding.UrlEncode(data) - if !bytes.Equal(encoding.MustUnUrlEncode(enc), data) { + if !bytes.Equal(cast.As(encoding.UnUrlEncode(enc)), data) { t.Error("UrlEncode roundtrip failed") } - if len(encoding.MustUnUrlEncode("%ZZ")) != 0 { - t.Error("MustUnUrlEncode should return empty for invalid input") + if len(cast.As(encoding.UnUrlEncode("%ZZ"))) != 0 { + t.Error("UnUrlEncode should return empty for invalid input with cast.As") } // HTML htmlData := []byte("