Skip to content

[go-fan] Go Module Review: BurntSushi/tomlΒ #1815

@github-actions

Description

@github-actions

🐹 Go Fan Report: BurntSushi/toml

Daily Go module review β€” round-robin cycle, prioritizing recently updated dependencies

Module Overview

github.com/BurntSushi/toml is the canonical TOML parser and encoder for Go. It uses reflection for struct marshaling/unmarshaling, implements the full TOML 1.1 spec, and provides rich parse error reporting with source context (line snippet + column pointer). The project is on v1.6.0 β€” the latest release (2025-12-18). βœ…

Current Usage in gh-aw-mcpg

  • Files: 1 file (internal/config/config_core.go)
  • Import Count: 1 import
  • Key APIs Used:
    • toml.NewDecoder(file) β€” streaming decoder
    • decoder.Decode(&cfg) β€” returns MetaData
    • md.Undecoded() β€” unknown field detection (warns instead of hard errors)
    • toml.ParseError β€” error with .Position.Line, .Position.Col, .Message

The TOML module is used exclusively for loading gateway configuration from .toml files. Usage is focused, purposeful, and already on the latest version.

Research Findings

Recent Updates (v1.6.0 β€” 2025-12-18)

  • TOML 1.1 spec enabled by default: Multi-line inline arrays and improved newline handling are now on by default (previously opt-in via BURNTSUSHI_TOML_110 env var)
  • Duplicate key detection fixed: arr = [1]; arr = [2] now correctly returns a parse error
  • Large float encoding fixed: 5e+22 round-trips correctly using exponent syntax

Best Practices from the Library

  1. ParseError.Error() is the canonical error display method β€” it returns the full formatted error including the problematic TOML source line and a ^ pointer to the exact column. It's designed for end-user display.
  2. ParseError.Message is for programmatic inspection only β€” it's the short description without source context.
  3. toml.Marshal(v) (added v1.4.0) is the idiomatic way to encode Go structs to TOML.

Improvement Opportunities

πŸƒ Quick Wins

1. Remove dead-code pointer assertion for *toml.ParseError (config_core.go:220)

The library's Decode method returns toml.ParseError as a value type (not a pointer). The pointer type assertion err.(*toml.ParseError) will never match and is dead code. Removing it simplifies error handling.

Current code:

if perr, ok := err.(*toml.ParseError); ok {  // ← Dead code: never matches
    return nil, fmt.Errorf("failed to parse TOML at line %d, column %d: %s",
        perr.Position.Line, perr.Position.Col, perr.Message)
}
// Try value type (used by toml.Decode)
if perr, ok := err.(toml.ParseError); ok {
    return nil, fmt.Errorf("failed to parse TOML at line %d, column %d: %s",
        perr.Position.Line, perr.Position.Col, perr.Message)
}

Simplified:

if perr, ok := err.(toml.ParseError); ok {
    return nil, fmt.Errorf("failed to parse TOML at line %d, column %d: %s",
        perr.Position.Line, perr.Position.Col, perr.Message)
}
```

#### 2. Use `perr.Error()` for richer config error messages

The library's `ParseError.Error()` returns a beautifully formatted multi-line message that includes the TOML source snippet and a `^` arrow pointing exactly to the error column β€” dramatically better UX for users debugging mistyped config files. The current code discards this context and manually formats a simpler message using just `perr.Message`.

**Current:**
```
failed to parse TOML at line 5, column 12: expected value but found 'x'
```

**With `perr.Error()` (library's designed output):**
```
failed to parse config.toml:

    5 | port = x
               ^
Error: expected value but found 'x'

Suggested change:

if perr, ok := err.(toml.ParseError); ok {
    return nil, fmt.Errorf("failed to parse TOML: %w", perr)
}

The %w wrapping preserves the structured error for callers that want to inspect it programmatically, while perr.Error() surfaces the full context for display.

✨ Feature Opportunities

  • toml.Marshal in test helpers: Config tests use raw TOML strings (e.g., in config_test.go). Using toml.Marshal(cfg) to generate test TOML from Go structs would be compile-time type-checked, prevent TOML syntax typos in test data, and be more maintainable as the Config struct evolves.

    // Instead of raw strings:
    tomlStr := `[gateway]\nport = 3000\n`
    
    // Use marshal:
    tomlBytes, err := toml.Marshal(struct {
        Gateway GatewayConfig `toml:"gateway"`
    }{Gateway: GatewayConfig{Port: 3000}})
  • toml.Unmarshal for in-memory test configs: For small in-memory config construction in tests, toml.Unmarshal([]byte(s), &cfg) is simpler than creating a decoder from a strings.Reader.

πŸ“ Best Practice Alignment

  • Error wrapping with %w: The current error formatting loses the structured ParseError type for downstream handlers (callers can't use errors.As to inspect parse position). Using fmt.Errorf("failed to parse TOML: %w", perr) preserves the full error chain.

πŸ”§ General Improvements

  • The logConfig variable (config_core.go:282) still uses the old log.New(io.Discard, "[CONFIG] ", log.LstdFlags) + SetDebug() pattern, while the rest of the config package uses logger.New("config:config") from the internal logger. While not directly a TOML usage issue, consolidating to the project's logger convention would be cleaner.

Recommendations

Priority Action Impact
High Use perr.Error() (or %w wrapping) instead of manually formatting perr.Message Better user experience when configs have syntax errors
Medium Remove dead *toml.ParseError pointer assertion Code clarity, removes misleading comment
Low Use toml.Marshal in test helpers Test maintainability

Next Steps

  • Remove dead *toml.ParseError pointer type assertion in LoadFromFile
  • Improve parse error messages to use perr.Error() or fmt.Errorf("...: %w", perr) for full context
  • (Optional) Use toml.Marshal in config package tests for struct-based test fixtures

Generated by Go Fan 🐹
Module summary saved to: specs/mods/toml.md (session artifact)
Run: Β§22991041517

Generated by Go Fan Β· β—·

  • expires on Mar 19, 2026, 7:34 AM UTC

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions