エラー処理
env ライブラリは構造化されたエラー処理メカニズムを提供し、errors.Is と errors.As パターンをサポートしています。
センチネルエラー
ファイルエラー
go
var (
ErrFileNotFound = errors.New("file not found")
ErrFileTooLarge = errors.New("file exceeds maximum size limit")
)使用例:
go
err := loader.LoadFiles(".env")
if errors.Is(err, env.ErrFileNotFound) {
log.Println("設定ファイルが存在しません")
}
if errors.Is(err, env.ErrFileTooLarge) {
log.Println("設定ファイルが大きすぎます")
}解析エラー
go
var (
ErrLineTooLong = errors.New("line exceeds maximum length limit")
ErrInvalidKey = errors.New("invalid key format")
ErrDuplicateKey = errors.New("duplicate key encountered")
)セキュリティエラー
go
var (
ErrForbiddenKey = errors.New("key is forbidden for security reasons")
ErrSecurityViolation = errors.New("security policy violation")
ErrInvalidValue = errors.New("invalid value content")
)禁止キーのチェック(実際は *SecurityError を返し、ErrSecurityViolation に一致):
go
err := loader.Set("PATH", "/malicious")
if errors.Is(err, env.ErrSecurityViolation) {
log.Println("禁止キーの設定を試みました")
}展開エラー
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")
ErrInvalidConfig = errors.New("invalid configuration")
ErrAlreadyInitialized = errors.New("default loader already initialized")
ErrNotInitialized = errors.New("default loader not initialized; call Load() first")
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、Rule=="required")
var valErr *env.ValidationError
if errors.As(err, &valErr) && valErr.Rule == "required" {
// 必須キーが不足:valErr.Message に不足キーのリストを含む
}アダプターエラー
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 インターフェースを実装する必要がある
}解決方法
KeyValidator のみではなく、Validator インターフェース(ValidateKey、ValidateValue、ValidateRequired の 3 つのメソッドを含む)を実装してください。
構造化エラー型
ParseError
解析エラー、位置情報を含む:
go
type ParseError struct {
File string // ファイル名
Line int // 行番号
Content string // エラー内容
Err error // 元のエラー
}使用例:
go
err := loader.LoadFiles(".env")
var parseErr *env.ParseError
if errors.As(err, &parseErr) {
log.Printf("解析エラー %s:%d - %s\n",
parseErr.File, parseErr.Line, parseErr.Err)
// 出力:解析エラー .env:15 - invalid key format
}FileError
ファイル操作エラー:
go
type FileError struct {
Path string // ファイルパス
Op string // 操作
Err error // 元のエラー
Size int64 // ファイルサイズ
Limit int64 // 制限
}使用例:
go
var fileErr *env.FileError
if errors.As(err, &fileErr) {
if fileErr.Size > 0 {
log.Printf("ファイル %s のサイズ %d が制限 %d を超過\n",
fileErr.Path, fileErr.Size, fileErr.Limit)
}
}SecurityError
セキュリティエラー:
go
type SecurityError struct {
Action string // 操作
Reason string // 原因
Key string // キー名
Details string // 詳細
}使用例:
go
var secErr *env.SecurityError
if errors.As(err, &secErr) {
log.Printf("セキュリティエラー: %s - %s (キー: %s)\n",
secErr.Action, secErr.Reason, secErr.Key)
}ValidationError
検証エラー:
go
type ValidationError struct {
Field string // フィールド名
Value string // 値
Rule string // ルール
Message string // メッセージ
}使用例:
go
var valErr *env.ValidationError
if errors.As(err, &valErr) {
log.Printf("検証失敗: フィールド %s - %s\n", valErr.Field, valErr.Message)
}ExpansionError
変数展開エラー:
go
type ExpansionError struct {
Key string // キー名
Depth int // 現在の深度
Limit int // 制限
Chain string // 展開チェーン
Kind ExpansionErrorKind // エラー原因の分類(ゼロ値 = 深度/循環)
}使用例:
go
var expErr *env.ExpansionError
if errors.As(err, &expErr) {
log.Printf("展開深度超過: %s (チェーン: %s)\n", expErr.Key, expErr.Chain)
}JSONError
JSON 解析エラー:
go
type JSONError struct {
Path string // ファイルパス
Message string // エラーメッセージ
Err error // 元のエラー
}使用例:
go
var jsonErr *env.JSONError
if errors.As(err, &jsonErr) {
log.Printf("JSON エラー %s: %s\n", jsonErr.Path, jsonErr.Message)
}YAMLError
YAML 解析エラー:
go
type YAMLError struct {
Path string // ファイルパス
Line int // 行番号
Column int // 列番号
Message string // エラーメッセージ
Err error // 元のエラー
}使用例:
go
var yamlErr *env.YAMLError
if errors.As(err, &yamlErr) {
log.Printf("YAML エラー %s:%d:%d - %s\n",
yamlErr.Path, yamlErr.Line, yamlErr.Column, yamlErr.Message)
}MarshalError
シリアライズ/デシリアライズエラー:
go
type MarshalError struct {
Field string // フィールド名
Message string // エラーメッセージ
}使用例:
go
_, err := env.MarshalStruct(invalidData)
if err != nil && env.IsMarshalError(err) {
var marshalErr *env.MarshalError
if errors.As(err, &marshalErr) {
log.Printf("シリアライズエラー: フィールド %s - %s\n", marshalErr.Field, marshalErr.Message)
}
}エラー処理模式
errors.Is パターン
センチネルエラーのチェック:
go
err := loader.LoadFiles(".env")
switch {
case errors.Is(err, env.ErrFileNotFound):
// ファイルが存在しない
log.Println("設定ファイルが存在しません。デフォルト値を使用します")
case errors.Is(err, env.ErrFileTooLarge):
// ファイルが大きすぎます
log.Fatal("設定ファイルが大きすぎます")
case errors.Is(err, env.ErrSecurityViolation):
// 禁止キー(実際は *SecurityError を返す)
log.Fatal("禁止キーを検出")
case err != nil:
// その他のエラー
log.Fatalf("読み込み失敗: %v", err)
}
// キー形式が不正(実際は *ValidationError、Field=="key")
var valErr *env.ValidationError
if errors.As(err, &valErr) && valErr.Field == "key" {
log.Fatalf("無効なキーを検出: %s", valErr.Message)
}errors.As パターン
詳細なエラー情報の抽出:
go
err := loader.LoadFiles(".env")
if err == nil {
return
}
// 解析エラーの抽出を試行
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)
}
// その他のエラー
log.Fatalf("不明なエラー: %v", err)組み合わせ処理
go
func handleLoadError(err error) {
if err == nil {
return
}
// まずセンチネルエラーをチェック
switch {
case errors.Is(err, env.ErrFileNotFound):
log.Println("警告:設定ファイルが存在しません")
return
case errors.Is(err, env.ErrFileTooLarge):
var fileErr *env.FileError
errors.As(err, &fileErr)
log.Fatalf("ファイル %s が大きすぎます (%d > %d)",
fileErr.Path, fileErr.Size, fileErr.Limit)
}
// 次に構造化エラーをチェック
var parseErr *env.ParseError
if errors.As(err, &parseErr) {
log.Fatalf("解析エラー %s:%d - %v",
parseErr.File, parseErr.Line, parseErr.Err)
}
var secErr *env.SecurityError
if errors.As(err, &secErr) {
log.Fatalf("セキュリティエラー: %s", secErr.Reason)
}
// 不明なエラー
log.Fatalf("エラー: %v", err)
}復旧パターン
グレースフルデグラデーション
go
func loadConfig() *Config {
cfg := env.ProductionConfig()
cfg.Filenames = nil
loader, err := env.New(cfg)
if err != nil {
log.Printf("設定エラー: %v,デフォルト設定を使用", err)
return defaultConfig()
}
defer loader.Close()
err = loader.LoadFiles(".env")
if err != nil {
if errors.Is(err, env.ErrFileNotFound) {
log.Println("設定ファイルが存在しません。デフォルト値を使用します")
return defaultConfig()
}
log.Fatalf("読み込み失敗: %v", err)
}
if err := loader.Validate(); err != nil {
log.Fatalf("検証失敗: %v", err)
}
return parseConfig(loader)
}リトライパターン
go
func loadWithRetry(filenames []string, maxRetries int) error {
cfg := env.DefaultConfig()
cfg.Filenames = nil
loader, err := env.New(cfg)
if err != nil {
return err
}
defer loader.Close()
for i := 0; i < maxRetries; i++ {
err := loader.LoadFiles(filenames...)
if err == nil {
return nil
}
if errors.Is(err, env.ErrFileNotFound) {
time.Sleep(time.Second * time.Duration(i+1))
continue
}
return err
}
return errors.New("max retries exceeded")
}完全な例
go
package main
import (
"errors"
"log"
"github.com/cybergodev/env"
)
func main() {
cfg := env.ProductionConfig()
cfg.Filenames = nil
cfg.FailOnMissingFile = true
cfg.RequiredKeys = []string{"DB_HOST", "API_KEY"}
loader, err := env.New(cfg)
if err != nil {
log.Fatal(err)
}
defer loader.Close()
err = loader.LoadFiles(".env")
if err != nil {
handleLoadError(err)
}
if err := loader.Validate(); err != nil {
handleValidationError(err)
}
log.Println("設定の読み込みに成功")
}
func handleLoadError(err error) {
switch {
case errors.Is(err, env.ErrFileNotFound):
log.Fatal("設定ファイルが存在しません")
case errors.Is(err, env.ErrFileTooLarge):
var fileErr *env.FileError
errors.As(err, &fileErr)
log.Fatalf("ファイルが大きすぎます: %s (%d bytes)", fileErr.Path, fileErr.Size)
case errors.Is(err, env.ErrSecurityViolation):
log.Fatal("禁止キーを検出")
}
// 構造化エラー
var parseErr *env.ParseError
if errors.As(err, &parseErr) {
log.Fatalf("解析エラー %s:%d - %v",
parseErr.File, parseErr.Line, parseErr.Err)
}
var secErr *env.SecurityError
if errors.As(err, &secErr) {
log.Fatalf("セキュリティエラー: %s - %s", secErr.Action, secErr.Reason)
}
log.Fatalf("読み込み失敗: %v", err)
}
func handleValidationError(err error) {
var valErr *env.ValidationError
if errors.As(err, &valErr) {
if valErr.Rule == "required" {
// 必須キーが不足:valErr.Message に不足キーのリストを含む
log.Fatalf("必須キーが不足: %s", valErr.Message)
}
log.Fatalf("検証失敗: %s - %s", valErr.Field, valErr.Message)
}
log.Fatalf("検証失敗: %v", err)
}関連ドキュメント
- 定数とエラー - 完全なエラーリスト
- Config API - 制限設定
- セキュリティ概要 - セキュリティエラー処理