Skip to content

常量与错误 ​

库定义的常量、错误类型、哨兵错误和预定义变量。

安全限制常量 ​

默认限制 ​

go
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() 会自动检查配置是否超过这些限制。

常量值说明
HardMaxFileSize100 MB文件大小硬性上限
HardMaxLineLength64 KB行长度硬性上限
HardMaxKeyLength1024键长度硬性上限
HardMaxValueLength1 MB值长度硬性上限
HardMaxVariables10000变量数硬性上限
HardMaxExpansionDepth20展开深度硬性上限

配置验证会检查是否超过硬性限制:

go
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 提取结构化错误;两种方式互补,并非二选一。详见各错误类型章节。

文件错误 ​

go
var ErrFileNotFound = errors.New("file not found")
var ErrFileTooLarge = errors.New("file exceeds maximum size limit")

检查方式:

go
err := loader.LoadFiles(".env")
if errors.Is(err, env.ErrFileNotFound) {
    // 文件不存在
}
if errors.Is(err, env.ErrFileTooLarge) {
    // 文件过大
}

解析错误 ​

go
var ErrLineTooLong = errors.New("line exceeds maximum length limit")
var ErrInvalidKey = errors.New("invalid key format")
var ErrDuplicateKey = errors.New("duplicate key encountered")

保留哨兵

ErrDuplicateKey 为保留哨兵:当前重复键按跳过处理而非报错,暂无代码路径返回该错误。

安全错误 ​

go
var ErrForbiddenKey = errors.New("key is forbidden for security reasons")
var ErrSecurityViolation = errors.New("security policy violation")
var ErrInvalidValue = errors.New("invalid value content")

检查禁止键:

go
err := loader.Set("PATH", "value")
if errors.Is(err, env.ErrSecurityViolation) {
    // 尝试设置禁止键返回 *SecurityError
}

展开错误 ​

go
var ErrExpansionDepth = errors.New("variable expansion depth exceeded")

限制错误 ​

go
var ErrMaxVariables = errors.New("maximum number of variables exceeded")

状态错误 ​

go
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")

检查方式:

go
// 检查加载器是否已关闭
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 提供详细定位
}

适配器错误 ​

go
var ErrValidateRequiredUnsupported = errors.New(
    "custom validator does not implement ValidateRequired; " +
    "implement Validator interface for required key validation",
)

当自定义验证器仅实现 KeyValidator 接口而未实现完整 Validator 接口时,调用 ValidateRequired 会返回此错误。

检查方式:

go
if errors.Is(err, env.ErrValidateRequiredUnsupported) {
    // 自定义验证器不支持必需键验证
    // 需要实现完整的 Validator 接口
}

解决方法

实现 Validator 接口(包含 ValidateKey、ValidateValue、ValidateRequired 三个方法)而非仅实现 KeyValidator。

错误类型 ​

ParseError ​

解析错误,包含位置信息:

go
type ParseError struct {
    File    string  // 文件名
    Line    int     // 行号
    Content string  // 错误内容(已掩码)
    Err     error   // 原始错误
}

方法:

方法签名说明
Errorfunc (e *ParseError) Error() string实现 error 接口,输出含文件名与行号(无文件名时省略文件段)
Unwrapfunc (e *ParseError) Unwrap() error返回底层 Err,供 errors.Is / errors.As 穿透

使用示例:

go
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 ​

验证错误:

go
type ValidationError struct {
    Field   string  // 字段名
    Value   string  // 值(已掩码)
    Rule    string  // 规则
    Message string  // 消息
}

方法:

方法签名说明
Errorfunc (e *ValidationError) Error() string实现 error 接口,含字段名与消息
Isfunc (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 ​

安全错误:

go
type SecurityError struct {
    Action  string  // 操作
    Reason  string  // 原因
    Key     string  // 键名(已掩码)
    Details string  // 额外详情
}

方法:

方法签名说明
Errorfunc (e *SecurityError) Error() string实现 error 接口,含被阻止的操作与原因
Isfunc (e *SecurityError) Is(target error) bool匹配 ErrSecurityViolation;禁止键/白名单拒绝(Action 为 key_access)另匹配 ErrForbiddenKey

使用示例:

go
var secErr *env.SecurityError
if errors.As(err, &secErr) {
    fmt.Printf("安全错误: %s - %s\n", secErr.Action, secErr.Reason)
}

FileError ​

文件操作错误:

go
type FileError struct {
    Path  string  // 文件路径
    Op    string  // 失败的操作(open、stat、size_check、parse)
    Err   error   // 原始错误
    Size  int64   // 文件大小(Size 检查时)
    Limit int64   // 限制(Size 检查时)
}

方法:

方法签名说明
Errorfunc (e *FileError) Error() string实现 error 接口;Size 与 Limit 均为正时附带 (size N exceeds limit M) 细节
Unwrapfunc (e *FileError) Unwrap() error返回底层 Err,供 errors.Is / errors.As 穿透

使用示例:

go
var fileErr *env.FileError
if errors.As(err, &fileErr) {
    fmt.Printf("文件 %s 大小 %d 超过限制 %d\n",
        fileErr.Path, fileErr.Size, fileErr.Limit)
}

ExpansionError ​

变量展开错误:

go
type ExpansionError struct {
    Key   string             // 键名
    Depth int                // 当前深度
    Limit int                // 限制
    Chain string             // 展开链(已脱敏)
    Kind  ExpansionErrorKind // 错误原因类别(零值 = 深度/循环)
}

方法:

方法签名说明
Errorfunc (e *ExpansionError) Error() string实现 error 接口;输出中的键名已掩码(仅保留前 2 个字符,后接 ***)
Isfunc (e *ExpansionError) Is(target error) bool仅 Kind != ExpansionRequiredKind 时匹配 ErrExpansionDepth(语义见下方「errors.Is 行为」)

错误分类(Kind 字段):

go
type ExpansionErrorKind int

const (
    // ExpansionDepthKind 表示展开触达递归深度限制或检测到变量循环。
    // 这是零值,因此常见的深度/循环错误无需显式分类。
    // errors.Is(err, ErrExpansionDepth) 可匹配此类错误。
    ExpansionDepthKind ExpansionErrorKind = iota

    // ExpansionRequiredKind 表示必需变量(${VAR:?message})未设置或为空。
    // 这不属于深度溢出,因此不会匹配 ErrExpansionDepth。
    ExpansionRequiredKind
)

errors.Is 行为: *ExpansionError 仅在 Kind != ExpansionRequiredKind 时匹配 ErrExpansionDepth。必需变量错误属于独立的失败模式,不通过 ErrExpansionDepth 匹配。

使用示例:

go
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 解析错误:

go
type JSONError struct {
    Path    string  // JSON 文档内路径(非文件路径)
    Message string  // 错误消息
    Err     error   // 原始错误
}

方法:

方法签名说明
Errorfunc (e *JSONError) Error() string实现 error 接口,含文档内路径
Unwrapfunc (e *JSONError) Unwrap() error返回底层 Err,供 errors.Is / errors.As 穿透

YAMLError ​

YAML 解析错误:

go
type YAMLError struct {
    Path    string  // YAML 文档内路径(非文件路径)
    Line    int     // 行号
    Column  int     // 列号
    Message string  // 错误消息
    Err     error   // 原始错误
}

方法:

方法签名说明
Errorfunc (e *YAMLError) Error() string实现 error 接口,含文档内路径与行列号
Unwrapfunc (e *YAMLError) Unwrap() error返回底层 Err,供 errors.Is / errors.As 穿透

MarshalError ​

序列化错误:

go
type MarshalError struct {
    Field   string  // 字段名
    Message string  // 错误消息
}

func IsMarshalError(err error) bool  // 检查函数

方法:

方法签名说明
Errorfunc (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
macOSDYLD_INSERT_LIBRARIES, DYLD_LIBRARY_PATH
WindowsCOMSPEC, PATHEXT, SYSTEMROOT, WINDIR
ShellSHELL, 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解析攻击修改字段分隔符

使用示例:

go
// 设置禁止键返回 *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
金融与 PIISSN, 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

匹配规则:

  • 不区分大小写
  • 键名包含任一模式即被识别为敏感

使用示例:

go
// 检查键是否敏感
if env.IsSensitiveKey("DB_PASSWORD") {
    // 使用安全方式处理
    secret := env.GetSecure("DB_PASSWORD")
    if secret != nil {
        defer secret.Release()
    }
}

DefaultKeyPattern ​

默认键名验证模式:

go
var DefaultKeyPattern *regexp.Regexp = nil

性能优化

nil 值启用快速字节级验证(约 10 倍性能提升)。 默认验证规则:以字母开头,只包含字母、数字、下划线。

自定义模式:

go
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 常量 ​

文件格式类型:

go
type FileFormat int

const (
    FormatAuto  FileFormat = iota  // 自动检测
    FormatEnv                      // .env 格式
    FormatJSON                     // JSON 格式
    FormatYAML                     // YAML 格式
)

使用示例:

go
// 检测格式
format := env.DetectFormat("config.json")  // FormatJSON

// 指定格式序列化
data, _ := env.Marshal(cfg, env.FormatJSON)

// 格式字符串
fmt.Println(format.String())  // "json"

错误检查模式 ​

errors.Is 模式 ​

检查哨兵错误:

go
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 模式 ​

提取详细错误信息:

go
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)
}

完整错误处理示例 ​

go
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)
	}
}

相关文档 ​