# Разбор TOML: какое семейство отказов несёт значения из файла Отвечает на вопрос, возникший по ходу задачи `telegram-enabled-flag`: можно ли пересказывать отказ библиотеки разбора в журнал, если в файле настроек лежат секреты. Наблюдение понадобилось потому, что ревью дизайна назвало этот путь утечкой, а чинить его без разреза пришлось бы выбрасыванием всего текста отказа — то есть платой разборчивостью на каждой опечатке. ## Как снималось Не замером, а **чтением исходников** зависимости, зафиксированной в `go.mod`: `github.com/BurntSushi/toml` версии **v1.5.0**. Смотрел `error.go`, `parse.go`, `decode.go`, `meta.go`, `lex.go` в кэше модулей. Дополнительно прогонял `toml.Decode` на правдоподобных опечатках — в каталоге вне репозитория, чтобы не править код проекта. ## Что выяснилось - **Значения из файла несёт ровно одно семейство отказов — `toml.ParseError`.** Его поле `Message` собирается из разбираемого куска: `Invalid float value: %q` (`parse.go:341`), `invalid duration: %q`, `%v is out of range`, `Invalid integer %q`. Туда же лексер отдаёт свои отказы через `panicItemf` (`parse.go:134`). - **Прочие отказы декодера значений не содержат вовсе.** Их строит `md.e` (`decode.go:577`) и `md.badtype` — из имён ключей, имён типов (`%T` через `fmtType`) и длин. Обойдены все места: `decode.go:282,288,297,329,348,385,388,399,428,437,467,487,518,552,561`. - **`LastKey` секрета нести не может.** Текущий ключ присваивается только после `itemKeyEnd`, то есть после `=` (`parse.go:200`), а лексер ключа до `=` не доходит (`lex.go:481-501`). Посторонняя строка со значением ключом не станет. - **Поле `Line` у `ParseError` врёт, а `Position.Line` — нет.** `panicErr` и `panicItemf` кладут в устаревшее поле `Line` значение `it.pos.Len`, то есть **длину**, а не номер строки (`parse.go:97,106`). Брать надо `Position.Line`. - **`ParseError` возвращается значением, не указателем** (`decode.go:564`, `parse.go` целиком), поэтому `errors.As` берёт целью `toml.ParseError`, а не `*toml.ParseError`. `Unwrap` у типа нет. - **Ветка без последнего ключа достижима обычной опечаткой.** Незакрытая скобка секции даёт `LastKey=""`: ``` вход "[telegram\nenabled = true\n" → LastKey="" err=toml: line 2: expected '.' or ']' to end table name, but got '\n' instead ``` ## Что из этого следует для кода Разрез по семейству отказа: `ParseError` пересобирается своими словами — путь, строка, столбец, последний ключ, — а его `Message` не берётся; прочие отказы проходят как есть. Так инвариант «Секрет не покидает конфиг» держится, а несовпадение типов по-прежнему называет ключ и типы. **Наблюдение привязано к версии.** Версия, переложившая значение в другое семейство или сменившая возврат на указатель, вернёт утечку молча. Держат это проверки поломанного файла настроек в `internal/config/config_test.go`; при подъёме версии библиотеки их отказ читается как сигнал перечитать эту записку, а не как случайный шум.