Compare commits

...

8 Commits
v1.0.4 ... main

Author SHA1 Message Date
AI Engineer
d840ccee1f chore: infrastructure alignment and doc sync (by AICoder) 2026-05-16 01:20:19 +08:00
AI Engineer
caa01a61f5 对齐 Tag v1.3.0 (By AI) 2026-05-10 15:48:23 +08:00
AI Engineer
50d6a00918 chore: infrastructure alignment 2026-05-10 13:02:18 +08:00
AI Engineer
8875212dba docs: 补充 SortJoin 文档并更新版本至 v1.1.1 (by AI) 2026-05-08 22:29:43 +08:00
AI Engineer
47a62a3c16 feat: add Base64 Raw support and smart decoding upgrade 2026-05-06 01:11:39 +08:00
AI Engineer
5b4d5a8f3a Refactor: remove Must functions, align with cast.As, add UnUrlBase64FromString (by AI) 2026-05-06 00:11:50 +08:00
AI Engineer
9936ef4437 chore: remove .gitignore 2026-05-05 21:57:26 +08:00
AI Engineer
5a4ad45f0e chore: remove AI.md and align version to v1.0.4 2026-05-01 21:11:27 +08:00
9 changed files with 244 additions and 162 deletions

5
.gitignore vendored
View File

@ -1 +1,4 @@
go.sum .ai/
.geminiignore
.gemini
/CODE-FULL.md

66
AI.md
View File

@ -1,66 +0,0 @@
# AI Coding Context: @go/encoding
本索引供 AI 模型理解 `@go/encoding` 的设计规范,以生成符合本项目“语义优先、安全第一”哲学的代码。
## 🤖 AI 行为准则
1. **区分数据与文本**:二进制数据 (Hex/Base64/Int) 必须基于 `[]byte`;文本格式 (URL/HTML) 优先使用 `string`
2. **默认开启静默模式**:在业务逻辑中,优先推荐使用 `MustUnXxx` 系列 API。一旦解码失败它们返回零值无需额外 `if err != nil`,保持逻辑流平滑。
3. **IntEncoder 集成**:所有整数进制编码、填充、混淆、哈希操作均通过 `IntEncoder` 实例或其导出的封装函数进行。
4. **安全链路**:优先使用基于字节切片的 API减少不必要的内存分配与 string 转换。
## 🛠 API Reference
### 基础编解码 (Hex/Base64)
- `func Hex(data []byte) []byte`:将数据转换为 Hex 编码的字节切片。
- `func HexToString(data []byte) string`:将数据转换为 Hex 编码的字符串。
- `func MustUnHex(data []byte) []byte`:解码 Hex出错时返回空字节切片。
- `func MustUnHexFromString(data string) []byte`:解码 Hex 字符串,出错时返回空字节切片。
- `func UnHex(data []byte) ([]byte, error)`:解码 Hex。
- `func Base64(data []byte) []byte`:将数据转换为 Base64 编码的字节切片。
- `func Base64ToString(data []byte) string`:将数据转换为 Base64 编码的字符串。
- `func UrlBase64(data []byte) []byte`:将数据转换为 URL 安全的 Base64 编码的字节切片。
- `func UrlBase64ToString(data []byte) string`:将数据转换为 URL 安全的 Base64 编码的字符串。
- `func MustUnBase64(data []byte) []byte`:解码 Base64出错时返回空字节切片。
- `func MustUnBase64FromString(data string) []byte`:解码 Base64 字符串,出错时返回空字节切片。
- `func MustUnUrlBase64(data []byte) []byte`:解码 URL Base64出错时返回空字节切片。
- `func MustUnUrlBase64FromString(data string) []byte`:解码 URL Base64 字符串,出错时返回空字节切片。
- `func UnBase64(data []byte) ([]byte, error)`:解码 Base64。
- `func UnUrlBase64(data []byte) ([]byte, error)`:解码 URL Base64。
### Web 编码 (URL/HTML)
- `func UrlEncode(data []byte) string`URL 编码。
- `func MustUnUrlEncode(data string) []byte`URL 解码,出错时返回空字节切片。
- `func HtmlEscape(data []byte) string`HTML 转义。
- `func MustUnHtmlEscape(data string) string`HTML 反转义。
- `func Utf8Valid(data []byte) bool`:校验 UTF-8。
### 整数编码 (IntEncoder)
- `func NewIntEncoder(digits string, radix uint8) (*IntEncoder, error)`:创建自定义编码器。
- `func EncodeInt(u uint64) []byte`:默认编码整数。
- `func AppendInt(buf []byte, u uint64) []byte`:默认追加整数。
- `func DecodeInt(buf []byte) uint64`:默认解码整数。
- `func ExchangeInt(buf []byte) []byte`:默认置换混淆。
- `func HashInt(data []byte, key []byte) []byte`:默认 HMAC-SHA512 哈希。
- `func (enc *IntEncoder) EncodeInt(u uint64) []byte`
- `func (enc *IntEncoder) AppendInt(buf []byte, u uint64) []byte`
- `func (enc *IntEncoder) FillInt(buf []byte, length int) []byte`:填充至指定长度。
- `func (enc *IntEncoder) ExchangeInt(buf []byte) []byte`
- `func (enc *IntEncoder) HashInt(data []byte, key []byte) []byte`
- `func (enc *IntEncoder) DecodeInt(buf []byte) uint64`
## 🧩 典型模式 (Best Practices)
* **✅ 推荐:业务逻辑流 (Silence Mode)**:
```go
// 解码失败即视为空,业务无需中断
raw := encoding.MustUnHexFromString(config.Data)
process(raw)
```
* **✅ 推荐:整数编解码 (IntEncoder)**:
```go
// 使用默认 62 进制编码器,支持追加与混淆
buf := encoding.EncodeInt(userID)
buf = encoding.DefaultIntEncoder.ExchangeInt(buf)
```

View File

@ -1,5 +1,31 @@
# Changelog: @go/encoding # Changelog: @go/encoding
## [v1.1.1] - 2026-05-08
### Added
- **文档补全**:在 README 中补充了 `SortJoin` 接口的详细说明,该接口用于 Map 或 Struct 的排序拼接(签名场景)。
## [v1.1.0] - 2026-05-06
### Added
- **Base64 无填充支持**:新增 `Base64Raw``Base64RawToString``UrlBase64Raw``UrlBase64RawToString` 接口,支持生成不带填充符(`=`)的编码。
### Changed
- **智能解码升级**:升级了 `UnBase64``UnUrlBase64` 系列函数,通过 O(1) 零分配检测自动兼容“带填充”与“无填充”的输入数据,无需额外调用 Raw 解码接口。
## [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 ## [v1.0.4] - 2026-05-01
### Changed ### Changed

View File

@ -4,29 +4,36 @@
# @go/encoding # @go/encoding
`@go/encoding` 是一个为“极简、安全、无摩擦”业务开发设计的编解码工具库。它统一了二进制数据与文本处理的 API 语义,通过静默处理错误与基于字节的零拷贝设计,极大降低了业务逻辑中的错误处理心智负担。 `@go/encoding` 是一个为“极简、安全、无摩擦”业务开发设计的编解码工具库。它统一了二进制数据与文本处理的 API 语义,通过`go/cast` 结合,极大降低了业务逻辑中的错误处理心智负担。
## 🎯 设计哲学 ## 🎯 设计哲学
* **API 原生直觉**:二进制操作基于 `[]byte`,文本表现类操作(如 HTML/URL基于 `string`,与 Go 原生习惯保持高度一致。 * **API 原生直觉**:二进制操作基于 `[]byte`,文本表现类操作(如 HTML/URL基于 `string`,与 Go 原生习惯保持高度一致。
* **静默防御 (Must 系列)**:对于不可逆的非法输入,提供 `MustUnXxx` 系列 API静默返回零值消除业务代码中无效的错误检查噪声。 * **消除摩擦 (Frictionless)**:废除 `Must` 前缀函数,推荐配合 `cast.As` 使用以实现更优雅的静默处理,降低业务代码中无效的错误检查噪声。
* **极致纯粹**:废除所有冗余的封装,强制数据链路层以 `[]byte` 形式流转,确保底层安全性与性能。 * **极致纯粹**:废除所有冗余的封装,强制数据链路层以 `[]byte` 形式流转,确保底层安全性与性能。
## 🛠 API Reference ## 🛠 API Reference
### 基础编解码 (Hex/Base64) ### 基础编解码 (Hex/Base64)
- `func Hex(data []byte) []byte` / `func HexToString(data []byte) string` - `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 Base64(data []byte) []byte` / `func Base64ToString(data []byte) string`
- `func MustUnBase64(data []byte) []byte` / `func MustUnBase64FromString(data string) []byte` - `func Base64Raw(data []byte) []byte` / `func Base64RawToString(data []byte) string` (无填充版本)
- `func UnBase64(data []byte) ([]byte, error)` / `func UnBase64FromString(data string) ([]byte, error)` (智能兼容填充与无填充)
- `func UrlBase64(data []byte) []byte` / `func UrlBase64ToString(data []byte) string`
- `func UrlBase64Raw(data []byte) []byte` / `func UrlBase64RawToString(data []byte) string` (无填充版本)
- `func UnUrlBase64(data []byte) ([]byte, error)` / `func UnUrlBase64FromString(data string) ([]byte, error)` (智能兼容填充与无填充)
### Web 编码 (URL/HTML) ### Web 编码 (URL/HTML)
- `func UrlEncode(data []byte) string` - `func UrlEncode(data []byte) string`
- `func MustUnUrlEncode(data string) []byte` - `func UnUrlEncode(data string) ([]byte, error)`
- `func HtmlEscape(data []byte) string` - `func HtmlEscape(data []byte) string`
- `func MustUnHtmlEscape(data string) string` - `func HtmlUnescape(data string) string`
- `func Utf8Valid(data []byte) bool`
### 签名工具 (Signature Utils)
- `func SortJoin(v any, separator, connector string, urlEncode bool) string`
将 Map 或 Struct 转换为排序并拼接后的字符串。常用于生成签名原串。支持自动 URL 编码。
### 整数与自定义进制 (IntEncoder) ### 整数与自定义进制 (IntEncoder)
- `func EncodeInt(u uint64) []byte` - `func EncodeInt(u uint64) []byte`
@ -41,3 +48,15 @@
```bash ```bash
go get apigo.cc/go/encoding 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"))
```

19
TEST.md
View File

@ -1,27 +1,28 @@
# Test Report: @go/encoding # Test Report: @go/encoding
## 📋 测试概览 ## 📋 测试概览
- **测试时间**: 2026-05-01 - **测试时间**: 2026-05-08
- **测试环境**: darwin/amd64 - **测试环境**: darwin/amd64
- **Go 版本**: 1.25.0 - **Go 版本**: 1.25.0
## ✅ 功能测试 (Functional Tests) ## ✅ 功能测试 (Functional Tests)
| 场景 | 状态 | 描述 | | 场景 | 状态 | 描述 |
| :--- | :--- | :--- | | :--- | :--- | :--- |
| `TestHex` | PASS | Hex 编解码往返一致性,Must 系列静默处理测试。 | | `TestHex` | PASS | Hex 编解码往返一致性,配合 `cast.As` 消除摩擦测试。 |
| `TestBase64` | PASS | Standard/URL Base64 编解码一致性,静默 API 测试。 | | `TestBase64` | PASS | Standard/URL Base64 编解码一致性,`Un...FromString` 补全测试。 |
| `TestWebEncoding` | PASS | URL 编解码、HTML 转义反转义往返测试。 | | `TestWebEncoding` | PASS | URL 编解码、HTML 转义反转义往返测试。 |
| `TestSortJoin` | PASS | Map 与 Struct 的排序拼接测试,验证签名场景。 |
| `TestUtf8` | PASS | UTF-8 校验逻辑测试。 | | `TestUtf8` | PASS | UTF-8 校验逻辑测试。 |
| `TestIntEncoder` | PASS | 包含正常编解码、FillInt 补齐、ExchangeInt 置换、HashInt 确定性测试。 | | `TestIntEncoder` | PASS | 包含正常编解码、FillInt 补齐、ExchangeInt 置换、HashInt 确定性测试。 |
## 🛡️ 鲁棒性防御 (Robustness) ## 🛡️ 鲁棒性防御 (Robustness)
- **静默处理 (Quiet Mode)**:所有 `MustUnXxx` API 对非法数据均返回空切片 `[]byte{}` 或空字符串,有效防止业务逻辑中的非预期中断或 Panic - **消除摩擦 (Frictionless)**:废除 `Must` 系列 API通过 `cast.As` 实现静默处理,保持业务逻辑简洁
- **参数校验**`NewIntEncoder` 对字符集重复、长度不足等构造错误进行了防御性校验,强制要求单字节字符集 - **API 补全**:补全了所有 `FromString` 版本的解码函数,并提供标准 error 返回
- **性能优化**Hex 解码优化,减少了不必要的字符串到字节切片的转换和内存分配 - **参数校验**`NewIntEncoder` 对字符集重复、长度不足等构造错误进行了防御性校验
## ⚡ 性能基准 (Benchmarks) ## ⚡ 性能基准 (Benchmarks)
| 函数 | 平均耗时 | 性能分析 | | 函数 | 平均耗时 | 性能分析 |
| :--- | :--- | :--- | | :--- | :--- | :--- |
| `HexEncode` | **44.03 ns/op** | 高效处理二进制数据,优化后性能稳步提升。 | | `HexEncode` | **46.64 ns/op** | 高效处理二进制数据。 |
| `Base64Encode` | **39.73 ns/op** | 吞吐量优异。 | | `Base64Encode` | **42.24 ns/op** | 吞吐量优异。 |
| `IntEncoder` | **44.63 ns/op** | 整数编码逻辑极简,开销极小。 | | `IntEncoder` | **46.66 ns/op** | 整数编码逻辑极简,开销极小。 |

View File

@ -5,7 +5,11 @@ import (
"encoding/hex" "encoding/hex"
"html" "html"
"net/url" "net/url"
"sort"
"strings"
"unicode/utf8" "unicode/utf8"
"apigo.cc/go/cast"
) )
// Hex 将数据转换为 Hex 编码的字节切片 // Hex 将数据转换为 Hex 编码的字节切片
@ -20,26 +24,6 @@ func HexToString(data []byte) string {
return hex.EncodeToString(data) 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 编码的字节切片解码 // UnHex 将 Hex 编码的字节切片解码
func UnHex(data []byte) ([]byte, error) { func UnHex(data []byte) ([]byte, error) {
dst := make([]byte, hex.DecodedLen(len(data))) dst := make([]byte, hex.DecodedLen(len(data)))
@ -47,6 +31,11 @@ func UnHex(data []byte) ([]byte, error) {
return dst[:n], err return dst[:n], err
} }
// UnHexFromString 将 Hex 编码的字符串解码
func UnHexFromString(data string) ([]byte, error) {
return hex.DecodeString(data)
}
// Base64 将数据转换为 Base64 编码的字节切片 // Base64 将数据转换为 Base64 编码的字节切片
func Base64(data []byte) []byte { func Base64(data []byte) []byte {
buf := make([]byte, base64.StdEncoding.EncodedLen(len(data))) buf := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
@ -59,6 +48,18 @@ func Base64ToString(data []byte) string {
return base64.StdEncoding.EncodeToString(data) return base64.StdEncoding.EncodeToString(data)
} }
// Base64Raw 将数据转换为无填充的 Base64 编码的字节切片
func Base64Raw(data []byte) []byte {
buf := make([]byte, base64.RawStdEncoding.EncodedLen(len(data)))
base64.RawStdEncoding.Encode(buf, data)
return buf
}
// Base64RawToString 将数据转换为无填充的 Base64 编码的字符串
func Base64RawToString(data []byte) string {
return base64.RawStdEncoding.EncodeToString(data)
}
// UrlBase64 将数据转换为 URL 安全的 Base64 编码的字节切片 // UrlBase64 将数据转换为 URL 安全的 Base64 编码的字节切片
func UrlBase64(data []byte) []byte { func UrlBase64(data []byte) []byte {
buf := make([]byte, base64.URLEncoding.EncodedLen(len(data))) buf := make([]byte, base64.URLEncoding.EncodedLen(len(data)))
@ -71,70 +72,70 @@ func UrlBase64ToString(data []byte) string {
return base64.URLEncoding.EncodeToString(data) return base64.URLEncoding.EncodeToString(data)
} }
// MustUnBase64 将 Base64 编码的字节切片解码,出错时返回空字节切片 // UrlBase64Raw 将数据转换为 URL 安全且无填充的 Base64 编码的字节切片
func MustUnBase64(data []byte) []byte { func UrlBase64Raw(data []byte) []byte {
dbuf := make([]byte, base64.StdEncoding.DecodedLen(len(data))) buf := make([]byte, base64.RawURLEncoding.EncodedLen(len(data)))
n, err := base64.StdEncoding.Decode(dbuf, data) base64.RawURLEncoding.Encode(buf, data)
if err != nil { return buf
return []byte{}
}
return dbuf[:n]
} }
// MustUnBase64FromString 将 Base64 编码的字符串解码,出错时返回空字节切片 // UrlBase64RawToString 将数据转换为 URL 安全且无填充的 Base64 编码的字符串
func MustUnBase64FromString(data string) []byte { func UrlBase64RawToString(data []byte) string {
dbuf, err := base64.StdEncoding.DecodeString(data) return base64.RawURLEncoding.EncodeToString(data)
if err != nil {
return []byte{}
}
return dbuf
} }
// MustUnUrlBase64 将 URL 安全的 Base64 编码的字节切片解码,出错时返回空字节切片 // UnBase64 将 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) { func UnBase64(data []byte) ([]byte, error) {
if len(data) > 0 && data[len(data)-1] == '=' {
dbuf := make([]byte, base64.StdEncoding.DecodedLen(len(data))) dbuf := make([]byte, base64.StdEncoding.DecodedLen(len(data)))
n, err := base64.StdEncoding.Decode(dbuf, data) n, err := base64.StdEncoding.Decode(dbuf, data)
return dbuf[:n], err return dbuf[:n], err
} }
dbuf := make([]byte, base64.RawStdEncoding.DecodedLen(len(data)))
n, err := base64.RawStdEncoding.Decode(dbuf, data)
return dbuf[:n], err
}
// UnUrlBase64 将 URL 安全的 Base64 编码的字节切片解码 // UnBase64FromString 将 Base64 编码的字符串解码(自动兼容有无填充)
func UnBase64FromString(data string) ([]byte, error) {
if len(data) > 0 && data[len(data)-1] == '=' {
return base64.StdEncoding.DecodeString(data)
}
return base64.RawStdEncoding.DecodeString(data)
}
// UnUrlBase64 将 URL 安全的 Base64 编码的字节切片解码(自动兼容有无填充)
func UnUrlBase64(data []byte) ([]byte, error) { func UnUrlBase64(data []byte) ([]byte, error) {
if len(data) > 0 && data[len(data)-1] == '=' {
dbuf := make([]byte, base64.URLEncoding.DecodedLen(len(data))) dbuf := make([]byte, base64.URLEncoding.DecodedLen(len(data)))
n, err := base64.URLEncoding.Decode(dbuf, data) n, err := base64.URLEncoding.Decode(dbuf, data)
return dbuf[:n], err return dbuf[:n], err
} }
dbuf := make([]byte, base64.RawURLEncoding.DecodedLen(len(data)))
n, err := base64.RawURLEncoding.Decode(dbuf, data)
return dbuf[:n], err
}
// UnUrlBase64FromString 将 URL 安全的 Base64 编码的字符串解码(自动兼容有无填充)
func UnUrlBase64FromString(data string) ([]byte, error) {
if len(data) > 0 && data[len(data)-1] == '=' {
return base64.URLEncoding.DecodeString(data)
}
return base64.RawURLEncoding.DecodeString(data)
}
// UrlEncode 对数据进行 URL 编码 // UrlEncode 对数据进行 URL 编码
func UrlEncode(data []byte) string { func UrlEncode(data []byte) string {
return url.QueryEscape(string(data)) return url.QueryEscape(string(data))
} }
// MustUnUrlEncode 对字符串进行 URL 解码,出错时返回空字节切片 // UnUrlEncode 对字符串进行 URL 解码
func MustUnUrlEncode(data string) []byte { func UnUrlEncode(data string) ([]byte, error) {
res, err := url.QueryUnescape(data) res, err := url.QueryUnescape(data)
if err != nil { if err != nil {
return []byte{} return nil, err
} }
return []byte(res) return []byte(res), nil
} }
// HtmlEscape 对数据进行 HTML 转义 // HtmlEscape 对数据进行 HTML 转义
@ -142,8 +143,8 @@ func HtmlEscape(data []byte) string {
return html.EscapeString(string(data)) return html.EscapeString(string(data))
} }
// MustUnHtmlEscape 对 HTML 字符串进行反转义 // HtmlUnescape 对 HTML 字符串进行反转义
func MustUnHtmlEscape(data string) string { func HtmlUnescape(data string) string {
return html.UnescapeString(data) return html.UnescapeString(data)
} }
@ -151,3 +152,41 @@ func MustUnHtmlEscape(data string) string {
func Utf8Valid(data []byte) bool { func Utf8Valid(data []byte) bool {
return utf8.Valid(data) return utf8.Valid(data)
} }
// SortJoin 将 Map 或 Struct 转换为排序并拼接后的字符串 (常用于签名)
func SortJoin(v any, separator, connector string, urlEncode bool) string {
var m map[string]any
if vm, ok := v.(map[string]any); ok {
m = vm
} else if v != nil {
cast.Convert(&m, v)
}
if len(m) == 0 {
return ""
}
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
var builder strings.Builder
for i, k := range keys {
if i > 0 {
builder.WriteString(separator)
}
val := cast.String(m[k])
if urlEncode {
builder.WriteString(url.QueryEscape(k))
builder.WriteString(connector)
builder.WriteString(url.QueryEscape(val))
} else {
builder.WriteString(k)
builder.WriteString(connector)
builder.WriteString(val)
}
}
return builder.String()
}

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"testing" "testing"
"apigo.cc/go/cast"
"apigo.cc/go/encoding" "apigo.cc/go/encoding"
) )
@ -16,12 +17,12 @@ func TestHex(t *testing.T) {
t.Fatal("Hex roundtrip failed") t.Fatal("Hex roundtrip failed")
} }
if !bytes.Equal(encoding.MustUnHexFromString(encoded), data) { if !bytes.Equal(cast.As(encoding.UnHexFromString(encoded)), data) {
t.Error("MustUnHexFromString failed") t.Error("UnHexFromString with cast.As failed")
} }
if len(encoding.MustUnHexFromString("!@#$")) != 0 { if len(cast.As(encoding.UnHexFromString("!@#$"))) != 0 {
t.Error("MustUnHexFromString should return empty for invalid hex chars") t.Error("UnHexFromString should return empty for invalid hex chars with cast.As")
} }
} }
@ -36,16 +37,41 @@ func TestBase64(t *testing.T) {
t.Error("Base64 roundtrip failed") t.Error("Base64 roundtrip failed")
} }
if !bytes.Equal(encoding.MustUnBase64FromString(enc), data) { if !bytes.Equal(cast.As(encoding.UnBase64FromString(enc)), data) {
t.Error("MustUnBase64FromString failed") t.Error("UnBase64FromString with cast.As failed")
}
// Raw (Unpadded)
rawEnc := encoding.Base64RawToString(data)
if bytes.HasSuffix([]byte(rawEnc), []byte("=")) {
t.Error("Base64Raw should not have padding")
}
rawDec, err := encoding.UnBase64FromString(rawEnc)
if err != nil || !bytes.Equal(data, rawDec) {
t.Error("Base64Raw smart decoding failed")
} }
// URL // URL
uEnc := encoding.UrlBase64ToString([]byte("hello/world+")) uData := []byte("hello/world+")
uEnc := encoding.UrlBase64ToString(uData)
uDec, _ := encoding.UnUrlBase64([]byte(uEnc)) uDec, _ := encoding.UnUrlBase64([]byte(uEnc))
if !bytes.Equal([]byte("hello/world+"), uDec) { if !bytes.Equal(uData, uDec) {
t.Error("UrlBase64 roundtrip failed") t.Error("UrlBase64 roundtrip failed")
} }
if !bytes.Equal(cast.As(encoding.UnUrlBase64FromString(uEnc)), uData) {
t.Error("UnUrlBase64FromString with cast.As failed")
}
// URL Raw (Unpadded)
uRawEnc := encoding.UrlBase64RawToString(uData)
if bytes.HasSuffix([]byte(uRawEnc), []byte("=")) {
t.Error("UrlBase64Raw should not have padding")
}
uRawDec, err := encoding.UnUrlBase64FromString(uRawEnc)
if err != nil || !bytes.Equal(uData, uRawDec) {
t.Error("UrlBase64Raw smart decoding failed")
}
} }
// --- Web --- // --- Web ---
@ -53,17 +79,17 @@ func TestWebEncoding(t *testing.T) {
// URL // URL
data := []byte("a b+c") data := []byte("a b+c")
enc := encoding.UrlEncode(data) 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") t.Error("UrlEncode roundtrip failed")
} }
if len(encoding.MustUnUrlEncode("%ZZ")) != 0 { if len(cast.As(encoding.UnUrlEncode("%ZZ"))) != 0 {
t.Error("MustUnUrlEncode should return empty for invalid input") t.Error("UnUrlEncode should return empty for invalid input with cast.As")
} }
// HTML // HTML
htmlData := []byte("<script>") htmlData := []byte("<script>")
escaped := encoding.HtmlEscape(htmlData) escaped := encoding.HtmlEscape(htmlData)
if encoding.MustUnHtmlEscape(escaped) != string(htmlData) { if encoding.HtmlUnescape(escaped) != string(htmlData) {
t.Error("HtmlEscape roundtrip failed") t.Error("HtmlEscape roundtrip failed")
} }
} }
@ -77,3 +103,33 @@ func TestUtf8(t *testing.T) {
t.Error("Invalid UTF-8 should fail") t.Error("Invalid UTF-8 should fail")
} }
} }
// --- SortJoin ---
func TestSortJoin(t *testing.T) {
m := map[string]any{
"B": 2,
"A": 1,
"C": "hello world",
}
// Test Map
res := encoding.SortJoin(m, "&", "=", true)
expected := "A=1&B=2&C=hello+world"
if res != expected {
t.Errorf("SortJoin Map failed: expected %s, got %s", expected, res)
}
// Test Struct
type User struct {
Name string `json:"name"`
Age int `json:"age"`
ID string
}
u := User{Name: "Alice", Age: 30, ID: "123"}
resStruct := encoding.SortJoin(u, ";", ":", false)
// Keys: age, name, ID. Sorted: ID, age, name
expectedStruct := "ID:123;age:30;name:Alice"
if resStruct != expectedStruct {
t.Errorf("SortJoin Struct failed: expected %s, got %s", expectedStruct, resStruct)
}
}

2
go.mod
View File

@ -1,3 +1,5 @@
module apigo.cc/go/encoding module apigo.cc/go/encoding
go 1.25.0 go 1.25.0
require apigo.cc/go/cast v1.3.3

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
apigo.cc/go/cast v1.3.3 h1:aln5eDR5DZVWVzZ/y5SJh1gQNgWv2sT82I25NaO9g34=
apigo.cc/go/cast v1.3.3/go.mod h1:lGlwImiOvHxG7buyMWhFzcdvQzmSaoKbmr7bcDfUpHk=