Таксономия ошибок
Все ошибки config-stack'а — это ConfigError с семью нормативно зафиксированными reason-значениями. Структура одинакова во всех реализациях:
ConfigError {
path: string # dot-notation (database.host)
reason: ConfigErrorReason # enum
details: string # human-readable message
source_id?: string # yaml:/app-config.yaml, etc.
}
Полный enum ConfigErrorReason
Нормативный источник — config-spec/_meta/error_reasons.yaml.
missing
Путь не существует в конфиге, и default не передан в getter.
- Python
- TypeScript
- Go
config.get_string("nonexistent.path")
# ConfigError(
# path="nonexistent.path",
# reason="missing",
# details="Key 'nonexistent.path' not found in config and no default provided",
# )
config.getString("nonexistent.path");
// Throws ConfigError {
// path: "nonexistent.path",
// reason: "missing",
// details: "Key 'nonexistent.path' not found in config and no default provided",
// }
_, err := cfg.GetString("nonexistent.path")
// err: *config.Error {
// Path: "nonexistent.path",
// Reason: config.ReasonMissing,
// Details: "Key 'nonexistent.path' not found in config and no default provided",
// }
Как обрабатывать: проверьте орфографию пути, добавьте default-аргумент или убедитесь, что ключ есть в app-config.yaml.
type_mismatch
Значение существует, но несовместимо с запрашиваемым типом.
- Python
- TypeScript
- Go
# YAML: pool_size: "twenty"
config.get_int("database.pool_size")
# ConfigError(
# path="database.pool_size",
# reason="type_mismatch",
# details="Expected int, got string 'twenty' (does not match ^-?\\d+$)",
# )
// YAML: pool_size: "twenty"
config.getInt("database.pool_size");
// Throws ConfigError {
// path: "database.pool_size",
// reason: "type_mismatch",
// details: "Expected int, got string 'twenty' (does not match ^-?\\d+$)",
// }
// YAML: pool_size: "twenty"
_, err := cfg.GetInt("database.pool_size")
// err: *config.Error {
// Path: "database.pool_size",
// Reason: config.ReasonTypeMismatch,
// Details: "expected int, got string (does not match ^-?\\d+$)",
// }
Как обрабатывать: исправьте значение в YAML на корректное для ожидаемого типа. Интерполированные env-значения всегда строки — убедитесь, что строковое представление соответствует регулярному выражению типа:
int:^-?\d+$float: стандартный парсер чисел с плавающей точкойbool:true|false|yes|no|1|0(case-insensitive)
env_unresolved
Env-переменная в ${VAR} не задана, и default не указан.
# app-config.yaml:
database:
password: "${DB_PASSWORD}"
Если DB_PASSWORD не задана:
ConfigError(
path="database.password",
reason="env_unresolved",
details="Environment variable DB_PASSWORD is not set and no default provided",
)
Как обрабатывать: установите env-переменную (export DB_PASSWORD=...), или — только для не-секретных значений — добавьте значение по умолчанию в YAML (${LOG_LEVEL:-INFO}). Для секретов без значения по умолчанию это защита: отсутствие env сразу вызывает явную ошибку на старте.
validation_failed
Получение типизированной секции (get_section / getSection / GetSection) не прошло валидацию schema.
- Python
- TypeScript
- Go
class DatabaseConfig(BaseModel):
host: str
password: str = Field(..., min_length=1)
# YAML: password: "" (пустая строка)
config.get_section("database", DatabaseConfig)
# ConfigError(
# path="database.password",
# reason="validation_failed",
# details="String should have at least 1 character",
# )
const DatabaseConfigSchema = z.object({
host: z.string(),
password: z.string().min(1),
});
// YAML: password: "" (пустая строка)
config.getSection("database", DatabaseConfigSchema);
// Throws ConfigError {
// path: "database",
// reason: "validation_failed",
// details: "schema validation failed: String must contain at least 1 character(s)",
// }
type DatabaseConfig struct {
Host string `yaml:"host" validate:"required"`
Password string `yaml:"password" validate:"required,min=1"`
}
// YAML: password: "" (пустая строка)
var dbCfg DatabaseConfig
err := cfg.GetSection("database", &dbCfg)
// err: *config.Error {
// Path: "database.password",
// Reason: config.ReasonValidationFailed,
// Details: "field Password failed 'min' validation",
// }
Как обрабатывать: исправьте значение в YAML; перед деплоем добавьте staging-проверку Config.load() + get_section / getSection / GetSection для всех секций.
parse_error
YAML-файл не парсится (синтаксическая ошибка).
# Невалидный YAML:
database:
host: "localhost
port: 5432
ConfigError(
path="",
reason="parse_error",
details="YAML parse error at line 3: unexpected end of stream",
source_id="yaml:/app/app-config.yaml",
)
Как обрабатывать: проверьте YAML через yamllint или yq, исправьте синтаксис. IDE с YAML-lint показывает ошибки сразу.
source_unavailable
ConfigSource не смог загрузить данные.
- Python
- TypeScript
- Go
# Файл не существует:
Config.load("non-existent.yaml")
# ConfigError(
# path="",
# reason="source_unavailable",
# details="File '/app/non-existent.yaml' does not exist",
# source_id="yaml:/app/non-existent.yaml",
# )
// Файл не существует:
await Config.load("non-existent.yaml");
// Throws ConfigError {
// path: "",
// reason: "source_unavailable",
// details: "cannot read non-existent.yaml: ENOENT: no such file or directory",
// sourceId: "yaml:non-existent.yaml",
// }
// Файл не существует:
_, err := config.Load(context.Background(), "non-existent.yaml")
// err: *config.Error {
// Path: "",
// Reason: config.ReasonSourceUnavailable,
// Details: "open non-existent.yaml: no such file or directory",
// SourceID: "yaml:non-existent.yaml",
// }
Другие примеры для будущих источников Phase 2+:
EtcdSource: connection timeout →source_unavailable.VaultSource: auth-fail →source_unavailable.HttpSource: 5xx →source_unavailable.
Как обрабатывать: проверьте существование файла / доступность сервиса / корректность учётных данных.
reload_rejected
Hot-reload не применён из-за ошибки валидации в новой версии конфига.
ConfigError(
path="database.pool_size",
reason="reload_rejected",
details="New config rejected: database.pool_size expected number, got string 'invalid'",
)
Это логируется, не выбрасывается — операция перезагрузки откатывается атомарно, подписчики не получают уведомления, приложение продолжает работать со старыми значениями. Диагностический warning в лог — для оператора.
Иерархия (в реализациях)
- Python
- TypeScript
- Go
from dagstack.config import ConfigError, ConfigErrorReason
try:
config = Config.load("app-config.yaml")
db_cfg = config.get_section("database", DatabaseConfig)
except ConfigError as exc:
match exc.reason:
case ConfigErrorReason.missing:
log.error(f"Конфиг не содержит {exc.path}")
case ConfigErrorReason.env_unresolved:
log.error(f"Env-переменная для {exc.path} не задана")
case ConfigErrorReason.validation_failed:
log.error(f"Невалидный конфиг {exc.path}: {exc.details}")
case _:
log.error(f"Config-ошибка: {exc}")
raise
import { ConfigError, ConfigErrorReason } from "@dagstack/config";
try {
const config = await Config.load("app-config.yaml");
const dbCfg = config.getSection("database", DatabaseConfig);
} catch (exc) {
if (exc instanceof ConfigError) {
switch (exc.reason) {
case ConfigErrorReason.missing:
log.error(`Конфиг не содержит ${exc.path}`);
break;
case ConfigErrorReason.env_unresolved:
log.error(`Env-переменная для ${exc.path} не задана`);
break;
case ConfigErrorReason.validation_failed:
log.error(`Невалидный конфиг ${exc.path}: ${exc.details}`);
break;
}
}
throw exc;
}
import (
"context"
"errors"
"go.dagstack.dev/config"
)
cfg, err := config.Load(context.Background(), "app-config.yaml")
if err != nil {
var cfgErr *config.Error
if errors.As(err, &cfgErr) {
switch cfgErr.Reason {
case config.ReasonMissing:
log.Errorf("Конфиг не содержит %s", cfgErr.Path)
case config.ReasonEnvUnresolved:
log.Errorf("Env-переменная для %s не задана", cfgErr.Path)
case config.ReasonValidationFailed:
log.Errorf("Невалидный конфиг %s: %s", cfgErr.Path, cfgErr.Details)
}
}
return err
}
Маскирование секретов в сообщениях
Поля из scrub-list (api_key, *_token, *_password, *_secret, ...) автоматически маскируются в details:
ConfigError(
path="database.password",
reason="validation_failed",
details="Password '[MASKED]' does not match required pattern", # реальное значение не раскрыто
)
См. Секреты.
См. также
- ADR-0001 §5 Error model — нормативный контракт.
- Источники (ConfigSource) —
source_unavailableзависит от типа источника. - Hot-reload —
reload_rejectedи atomic rollback.