常量与错误
库定义的常量、错误类型、哨兵错误和预定义变量。
安全限制常量
默认限制
const (
// DefaultMaxFileSize - 单文件最大字节数
DefaultMaxFileSize int64 = 2 * 1024 * 1024 // 2 MB
// DefaultMaxLineLength - 单行最大长度
DefaultMaxLineLength int = 1024 // 1 KB
// DefaultMaxKeyLength - 键名最大长度
DefaultMaxKeyLength int = 64
// DefaultMaxValueLength - 值最大长度
DefaultMaxValueLength int = 4096 // 4 KB
// DefaultMaxVariables - 每文件最大变量数
DefaultMaxVariables int = 500
// DefaultMaxExpansionDepth - 变量展开最大深度
DefaultMaxExpansionDepth int = 5
)六个默认限制常量——DefaultMaxFileSize、DefaultMaxLineLength、DefaultMaxKeyLength、DefaultMaxValueLength、DefaultMaxVariables、DefaultMaxExpansionDepth——分别对应单文件大小、单行长度、键名长度、值长度、每文件变量数与变量展开深度,均可通过 Config 的同名字段覆盖。
硬性上限
注意
以下为库内部硬性上限(未导出),用于 Config.Validate() 内部检查。用户无法直接引用这些常量,但 cfg.Validate() 会自动检查配置是否超过这些限制。
| 常量 | 值 | 说明 |
|---|---|---|
| HardMaxFileSize | 100 MB | 文件大小硬性上限 |
| HardMaxLineLength | 64 KB | 行长度硬性上限 |
| HardMaxKeyLength | 1024 | 键长度硬性上限 |
| HardMaxValueLength | 1 MB | 值长度硬性上限 |
| HardMaxVariables | 10000 | 变量数硬性上限 |
| HardMaxExpansionDepth | 20 | 展开深度硬性上限 |
配置验证会检查是否超过硬性限制:
cfg := env.DefaultConfig()
cfg.MaxFileSize = 200 * 1024 * 1024 // 超过 100MB 上限
if err := cfg.Validate(); err != nil {
// 返回错误:MaxFileSize exceeds hard limit
}哨兵错误
匹配方式
上述哨兵均可通过 errors.Is 匹配——*ValidationError 与 *SecurityError 内部实现了 Is() 布线,按 Rule/Action 映射到 ErrInvalidKey、ErrMissingRequired、ErrMaxVariables、ErrInvalidValue、ErrForbiddenKey、ErrSecurityViolation 等哨兵。需要文件/行号/字段等详情时,再用 errors.As 提取结构化错误;两种方式互补,并非二选一。详见各错误类型章节。
文件错误
var ErrFileNotFound = errors.New("file not found")
var ErrFileTooLarge = errors.New("file exceeds maximum size limit")检查方式:
err := loader.LoadFiles(".env")
if errors.Is(err, env.ErrFileNotFound) {
// 文件不存在
}
if errors.Is(err, env.ErrFileTooLarge) {
// 文件过大
}解析错误
var ErrLineTooLong = errors.New("line exceeds maximum length limit")
var ErrInvalidKey = errors.New("invalid key format")
var ErrDuplicateKey = errors.New("duplicate key encountered")保留哨兵
ErrDuplicateKey 为保留哨兵:当前重复键按跳过处理而非报错,暂无代码路径返回该错误。
安全错误
var ErrForbiddenKey = errors.New("key is forbidden for security reasons")
var ErrSecurityViolation = errors.New("security policy violation")
var ErrInvalidValue = errors.New("invalid value content")检查禁止键:
err := loader.Set("PATH", "value")
if errors.Is(err, env.ErrSecurityViolation) {
// 尝试设置禁止键返回 *SecurityError
}展开错误
var ErrExpansionDepth = errors.New("variable expansion depth exceeded")限制错误
var ErrMaxVariables = errors.New("maximum number of variables exceeded")状态错误
var ErrClosed = errors.New("loader has been closed")
var ErrInvalidConfig = errors.New("invalid configuration")
var ErrAlreadyInitialized = errors.New("default loader already initialized")
var ErrNotInitialized = errors.New("default loader not initialized; call Load() first")
var ErrMissingRequired = errors.New("required key is missing")检查方式:
// 检查加载器是否已关闭
if errors.Is(err, env.ErrClosed) {
// 加载器已关闭
}
// 检查默认加载器是否已初始化
if errors.Is(err, env.ErrAlreadyInitialized) {
// 默认加载器已存在,无法重复调用 Load()
}
// 检查默认加载器是否未初始化
if errors.Is(err, env.ErrNotInitialized) {
// 需要先调用 env.Load() 或 env.LoadWithConfig()
}
// 检查键格式是否非法(*ValidationError 经 Is() 布线匹配哨兵)
if errors.Is(err, env.ErrInvalidKey) {
// 键格式非法
}
// 检查必需键是否缺失
if errors.Is(err, env.ErrMissingRequired) {
// 缺少必需键
}
// 可选:需要字段/规则等详情时,用 errors.As 提取结构化错误
var valErr *env.ValidationError
if errors.As(err, &valErr) {
// valErr.Field / valErr.Rule 提供详细定位
}适配器错误
var ErrValidateRequiredUnsupported = errors.New(
"custom validator does not implement ValidateRequired; " +
"implement Validator interface for required key validation",
)当自定义验证器仅实现 KeyValidator 接口而未实现完整 Validator 接口时,调用 ValidateRequired 会返回此错误。
检查方式:
if errors.Is(err, env.ErrValidateRequiredUnsupported) {
// 自定义验证器不支持必需键验证
// 需要实现完整的 Validator 接口
}解决方法
实现 Validator 接口(包含 ValidateKey、ValidateValue、ValidateRequired 三个方法)而非仅实现 KeyValidator。
错误类型
ParseError
解析错误,包含位置信息:
type ParseError struct {
File string // 文件名
Line int // 行号
Content string // 错误内容(已掩码)
Err error // 原始错误
}方法:
| 方法 | 签名 | 说明 |
|---|---|---|
Error | func (e *ParseError) Error() string | 实现 error 接口,输出含文件名与行号(无文件名时省略文件段) |
Unwrap | func (e *ParseError) Unwrap() error | 返回底层 Err,供 errors.Is / errors.As 穿透 |
使用示例:
err := loader.LoadFiles(".env")
var parseErr *env.ParseError
if errors.As(err, &parseErr) {
fmt.Printf("解析错误 %s:%d: %v\n",
parseErr.File, parseErr.Line, parseErr.Err)
}ValidationError
验证错误:
type ValidationError struct {
Field string // 字段名
Value string // 值(已掩码)
Rule string // 规则
Message string // 消息
}方法:
| 方法 | 签名 | 说明 |
|---|---|---|
Error | func (e *ValidationError) Error() string | 实现 error 接口,含字段名与消息 |
Is | func (e *ValidationError) Is(target error) bool | 按 Rule 映射哨兵:值类规则(value/null_byte/control_char/utf8)→ ErrInvalidValue;键类规则(Field 为 key:non_empty/max_length/ascii_only/pattern)→ ErrInvalidKey;required → ErrMissingRequired;max_variables → ErrMaxVariables |
SecurityError
安全错误:
type SecurityError struct {
Action string // 操作
Reason string // 原因
Key string // 键名(已掩码)
Details string // 额外详情
}方法:
| 方法 | 签名 | 说明 |
|---|---|---|
Error | func (e *SecurityError) Error() string | 实现 error 接口,含被阻止的操作与原因 |
Is | func (e *SecurityError) Is(target error) bool | 匹配 ErrSecurityViolation;禁止键/白名单拒绝(Action 为 key_access)另匹配 ErrForbiddenKey |
使用示例:
var secErr *env.SecurityError
if errors.As(err, &secErr) {
fmt.Printf("安全错误: %s - %s\n", secErr.Action, secErr.Reason)
}FileError
文件操作错误:
type FileError struct {
Path string // 文件路径
Op string // 失败的操作(open、stat、size_check、parse)
Err error // 原始错误
Size int64 // 文件大小(Size 检查时)
Limit int64 // 限制(Size 检查时)
}方法:
| 方法 | 签名 | 说明 |
|---|---|---|
Error | func (e *FileError) Error() string | 实现 error 接口;Size 与 Limit 均为正时附带 (size N exceeds limit M) 细节 |
Unwrap | func (e *FileError) Unwrap() error | 返回底层 Err,供 errors.Is / errors.As 穿透 |
使用示例:
var fileErr *env.FileError
if errors.As(err, &fileErr) {
fmt.Printf("文件 %s 大小 %d 超过限制 %d\n",
fileErr.Path, fileErr.Size, fileErr.Limit)
}ExpansionError
变量展开错误:
type ExpansionError struct {
Key string // 键名
Depth int // 当前深度
Limit int // 限制
Chain string // 展开链(已脱敏)
Kind ExpansionErrorKind // 错误原因类别(零值 = 深度/循环)
}方法:
| 方法 | 签名 | 说明 |
|---|---|---|
Error | func (e *ExpansionError) Error() string | 实现 error 接口;输出中的键名已掩码(仅保留前 2 个字符,后接 ***) |
Is | func (e *ExpansionError) Is(target error) bool | 仅 Kind != ExpansionRequiredKind 时匹配 ErrExpansionDepth(语义见下方「errors.Is 行为」) |
错误分类(Kind 字段):
type ExpansionErrorKind int
const (
// ExpansionDepthKind 表示展开触达递归深度限制或检测到变量循环。
// 这是零值,因此常见的深度/循环错误无需显式分类。
// errors.Is(err, ErrExpansionDepth) 可匹配此类错误。
ExpansionDepthKind ExpansionErrorKind = iota
// ExpansionRequiredKind 表示必需变量(${VAR:?message})未设置或为空。
// 这不属于深度溢出,因此不会匹配 ErrExpansionDepth。
ExpansionRequiredKind
)errors.Is 行为: *ExpansionError 仅在 Kind != ExpansionRequiredKind 时匹配 ErrExpansionDepth。必需变量错误属于独立的失败模式,不通过 ErrExpansionDepth 匹配。
使用示例:
var expErr *env.ExpansionError
if errors.As(err, &expErr) {
switch expErr.Kind {
case env.ExpansionDepthKind:
// 深度溢出或循环:errors.Is(err, env.ErrExpansionDepth) == true
fmt.Printf("深度 %d/%d,链:%s\n", expErr.Depth, expErr.Limit, expErr.Chain)
case env.ExpansionRequiredKind:
// 必需变量未设置:errors.Is(err, env.ErrExpansionDepth) == false
fmt.Printf("必需变量 %s 未设置\n", expErr.Key)
}
}JSONError
JSON 解析错误:
type JSONError struct {
Path string // JSON 文档内路径(非文件路径)
Message string // 错误消息
Err error // 原始错误
}方法:
| 方法 | 签名 | 说明 |
|---|---|---|
Error | func (e *JSONError) Error() string | 实现 error 接口,含文档内路径 |
Unwrap | func (e *JSONError) Unwrap() error | 返回底层 Err,供 errors.Is / errors.As 穿透 |
YAMLError
YAML 解析错误:
type YAMLError struct {
Path string // YAML 文档内路径(非文件路径)
Line int // 行号
Column int // 列号
Message string // 错误消息
Err error // 原始错误
}方法:
| 方法 | 签名 | 说明 |
|---|---|---|
Error | func (e *YAMLError) Error() string | 实现 error 接口,含文档内路径与行列号 |
Unwrap | func (e *YAMLError) Unwrap() error | 返回底层 Err,供 errors.Is / errors.As 穿透 |
MarshalError
序列化错误:
type MarshalError struct {
Field string // 字段名
Message string // 错误消息
}
func IsMarshalError(err error) bool // 检查函数方法:
| 方法 | 签名 | 说明 |
|---|---|---|
Error | func (e *MarshalError) Error() string | 实现 error 接口,含字段名与消息 |
MarshalError 的字段均为普通值,没有 Unwrap 方法——不存在可解包的底层错误。
预定义变量
DefaultForbiddenKeys
内置禁止键列表,防止修改系统关键变量:
注意
defaultForbiddenKeys 为库内部变量(未导出),无法直接通过 env.DefaultForbiddenKeys 访问。以下为内部使用的完整列表,供参考。
| 类别 | 禁止键 |
|---|---|
| 系统路径 | PATH |
| 动态链接器 (Linux) | LD_PRELOAD, LD_PRELOAD_32, LD_PRELOAD_64, LD_LIBRARY_PATH, LD_LIBRARY_PATH_32, LD_LIBRARY_PATH_64, LD_AUDIT, LD_DEBUG |
| macOS | DYLD_INSERT_LIBRARIES, DYLD_LIBRARY_PATH |
| Windows | COMSPEC, PATHEXT, SYSTEMROOT, WINDIR |
| Shell | SHELL, ENV, BASH_ENV, IFS |
| 语言运行时 | PYTHONPATH, NODE_PATH, PERL5OPT, RUBYLIB |
风险说明:
| 键 | 风险类型 | 说明 |
|---|---|---|
PATH | 命令劫持 | 修改命令搜索路径 |
LD_PRELOAD | 库注入 | 预加载恶意动态库 |
LD_LIBRARY_PATH | 库劫持 | 修改库搜索路径 |
DYLD_INSERT_LIBRARIES | 库注入 | macOS 库注入 |
COMSPEC | 命令劫持 | Windows 命令解释器路径覆盖 |
PATHEXT | 命令劫持 | Windows 可执行文件扩展名篡改 |
SYSTEMROOT | 系统破坏 | Windows 系统根目录篡改 |
WINDIR | 系统破坏 | Windows 目录篡改 |
PYTHONPATH | 模块劫持 | Python 模块搜索路径 |
IFS | 解析攻击 | 修改字段分隔符 |
使用示例:
// 设置禁止键返回 *SecurityError
err := loader.Set("PATH", "/malicious/path")
if errors.Is(err, env.ErrSecurityViolation) {
// 键被禁止
}
// 添加额外的禁止键
cfg := env.DefaultConfig()
cfg.ForbiddenKeys = []string{"MY_SENSITIVE_VAR"}SensitiveKeyPatterns
敏感键模式列表,用于自动检测敏感配置。键名包含这些模式(不区分大小写)时会被识别为敏感:
注意
敏感模式列表为库内部变量 internal.Patterns(未从 env 包导出),通过 IsSensitiveKey() 函数间接访问。以下为主要敏感模式类别,供参考。
主要敏感模式类别:
| 类别 | 模式示例 |
|---|---|
| 认证与授权 | PASSWORD, SECRET, TOKEN, AUTH, CREDENTIAL, PASSPHRASE, SESSION, COOKIE |
| API 与密钥 | API_KEY, APIKEY, ACCESS_KEY, SECRET_KEY, PRIVATE_KEY, PUBLIC_KEY |
| 加密与安全 | PRIVATE, ENCRYPTION_KEY, ENCRYPT_KEY, DECRYPT_KEY, SIGNING_KEY, SIGN_KEY, VERIFY_KEY |
| 金融与 PII | SSN, SOCIAL_SECURITY, CREDIT_CARD, CARD_NUMBER, CVV, CVC, CCV, PAN |
| 加密货币 | MNEMONIC, SEED, RECOVERY, WALLET, PRIVATE_ADDRESS |
| 数据库 | CONNECTION_STRING, CONN_STRING, DATABASE_URL, DB_PASSWORD |
| 云服务 | AWS_SECRET, AZURE_KEY, GCP_KEY, SERVICE_ACCOUNT |
匹配规则:
- 不区分大小写
- 键名包含任一模式即被识别为敏感
使用示例:
// 检查键是否敏感
if env.IsSensitiveKey("DB_PASSWORD") {
// 使用安全方式处理
secret := env.GetSecure("DB_PASSWORD")
if secret != nil {
defer secret.Release()
}
}DefaultKeyPattern
默认键名验证模式:
var DefaultKeyPattern *regexp.Regexp = nil性能优化
nil 值启用快速字节级验证(约 10 倍性能提升)。 默认验证规则:以字母开头,只包含字母、数字、下划线。
自定义模式:
import "regexp"
cfg := env.DefaultConfig()
// 只允许大写字母开头
cfg.KeyPattern = regexp.MustCompile(`^[A-Z][A-Z0-9_]{1,63}$`)安全工具函数
IsSensitiveKey、MaskValue、MaskKey、MaskSensitiveInString、SanitizeForLog 与 ClearBytes 六个安全工具函数的完整签名、参数与示例统一维护在 SecureValue API 的「安全工具函数」一节。简要分工:IsSensitiveKey 判定键名是否敏感;MaskValue 与 MaskKey 按键名敏感性掩码值与键名;SanitizeForLog 与 MaskSensitiveInString 用于日志脱敏与长字符串截断;ClearBytes 负责字节切片的安全清零。
FileFormat 常量
文件格式类型:
type FileFormat int
const (
FormatAuto FileFormat = iota // 自动检测
FormatEnv // .env 格式
FormatJSON // JSON 格式
FormatYAML // YAML 格式
)使用示例:
// 检测格式
format := env.DetectFormat("config.json") // FormatJSON
// 指定格式序列化
data, _ := env.Marshal(cfg, env.FormatJSON)
// 格式字符串
fmt.Println(format.String()) // "json"错误检查模式
errors.Is 模式
检查哨兵错误:
err := loader.LoadFiles(".env")
switch {
case errors.Is(err, env.ErrFileNotFound):
// 文件不存在
case errors.Is(err, env.ErrFileTooLarge):
// 文件过大
case errors.Is(err, env.ErrSecurityViolation):
// 禁止键
case errors.Is(err, env.ErrClosed):
// 加载器已关闭
}errors.As 模式
提取详细错误信息:
err := loader.LoadFiles(".env")
var parseErr *env.ParseError
if errors.As(err, &parseErr) {
fmt.Printf("解析错误在 %s 第 %d 行\n", parseErr.File, parseErr.Line)
}
var fileErr *env.FileError
if errors.As(err, &fileErr) {
fmt.Printf("文件 %s 大小 %d 超过限制 %d\n",
fileErr.Path, fileErr.Size, fileErr.Limit)
}
var secErr *env.SecurityError
if errors.As(err, &secErr) {
fmt.Printf("安全错误: %s - %s\n", secErr.Action, secErr.Reason)
}完整错误处理示例
package main
import (
"errors"
"log"
"github.com/cybergodev/env"
)
func main() {
cfg := env.ProductionConfig()
cfg.FailOnMissingFile = true
loader, err := env.New(cfg)
if err != nil {
log.Fatal(err)
}
defer loader.Close()
err = loader.LoadFiles(".env")
if err != nil {
switch {
case errors.Is(err, env.ErrFileNotFound):
log.Fatal("配置文件不存在")
case errors.Is(err, env.ErrFileTooLarge):
log.Fatal("配置文件过大")
case errors.Is(err, env.ErrClosed):
log.Fatal("加载器已关闭")
default:
var parseErr *env.ParseError
if errors.As(err, &parseErr) {
log.Fatalf("解析错误 %s:%d - %v",
parseErr.File, parseErr.Line, parseErr.Err)
}
var fileErr *env.FileError
if errors.As(err, &fileErr) {
log.Fatalf("文件错误 %s - %v", fileErr.Path, fileErr.Err)
}
var secErr *env.SecurityError
if errors.As(err, &secErr) {
log.Fatalf("安全错误: %s - %s", secErr.Action, secErr.Reason)
}
var jsonErr *env.JSONError
if errors.As(err, &jsonErr) {
log.Fatalf("JSON 错误 %s: %s", jsonErr.Path, jsonErr.Message)
}
var yamlErr *env.YAMLError
if errors.As(err, &yamlErr) {
log.Fatalf("YAML 错误 %s:%d:%d - %s",
yamlErr.Path, yamlErr.Line, yamlErr.Column, yamlErr.Message)
}
log.Fatal(err)
}
}
// 验证必需键
if err := loader.Validate(); err != nil {
var valErr *env.ValidationError
if errors.As(err, &valErr) {
log.Fatalf("验证失败: %s - %s", valErr.Field, valErr.Message)
}
log.Fatal(err)
}
}相关文档
- SecureValue API - 安全工具函数完整 API
- Config API - 配置选项与限制设置
- 安全概述 - 安全架构与核心特性
- 生产检查清单 - 上线前安全检查