Skip to content

Commit a7b390d

Browse files
authored
Improve TOML configuration error handling with position tracking and typo detection (#813)
Configuration parse errors lacked position context and silently ignored typos in field names, leading to debugging friction and silent misconfigurations. ## Changes ### Precise error locations Extract line and column from `ParseError.Position` for pinpoint debugging: ```go // Before Error: failed to parse TOML: toml: line 2 (last key "gateway"): expected '.' or '='... // After Error: failed to parse TOML at line 2, column 6: expected '.' or '=', but got '3' instead ``` ### Unknown key detection Use `MetaData.Undecoded()` to catch typos and deprecated fields: ```toml [gateway] prot = 3000 # Typo detected startup_timout = 30 # Typo detected ``` Generates warnings: ``` [WARN] [config] Unknown configuration key 'gateway.prot' - check for typos [WARN] [config] Unknown configuration key 'gateway.startup_timout' - check for typos ``` ### Streaming decoder Replace `os.ReadFile` + `toml.Decode` with `toml.NewDecoder` for memory efficiency with large config files. ### Test coverage - Parse errors with column numbers - Unknown key detection in gateway and server sections - Multiple simultaneous typos - Large file handling (100+ servers) - TOML 1.1 duplicate key detection ### Documentation Added "Configuration Validation and Error Handling" section to README with examples and best practices. > [!WARNING] > > <details> > <summary>Firewall rules blocked me from connecting to one or more addresses (expand for details)</summary> > > #### I tried to connect to the following addresses, but was blocked by firewall rules: > > - `example.com` > - Triggering command: `/tmp/go-build4210791917/b271/launcher.test /tmp/go-build4210791917/b271/launcher.test -test.testlogfile=/tmp/go-build4210791917/b271/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 64/src/net -trimpath 64/pkg/tool/linux_amd64/vet -p github.com/davec--norc -lang=go1.16 64/pkg/tool/linux_amd64/vet -I 519236/b216/_pkg_.a cfg fig.test --gdwarf-5 b/gh-aw-mcpg/intdocker-cli-plugin-metadata -o fig.test` (dns block) > - Triggering command: `/tmp/go-build3777671178/b275/launcher.test /tmp/go-build3777671178/b275/launcher.test -test.testlogfile=/tmp/go-build3777671178/b275/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true t n for TOML configuration validation and error handling Co-autho--format=format:%H %ct %D ache/go/1.25.6/x64/pkg/tool/linux_amd64/vet ache/go/1.25.6/x/opt/hostedtoolcache/go/1.25.6/x64/pkg/tool/linux_amd64/compile -I` (dns block) > - `invalid-host-that-does-not-exist-12345.com` > - Triggering command: `/tmp/go-build3314539425/b001/config.test /tmp/go-build3314539425/b001/config.test -test.testlogfile=/tmp/go-build3314539425/b001/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true go k1BRF76vb x_amd64/vet credential.helpe/opt/hostedtoolcache/go/1.25.6/x64/pkg/tool/linux_amd64/compile` (dns block) > - Triggering command: `/tmp/go-build4210791917/b259/config.test /tmp/go-build4210791917/b259/config.test -test.testlogfile=/tmp/go-build4210791917/b259/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 64/src/net ache/go/1.25.6/x64/src/go/scanner/errors.go bin/as -I /tmp/go-build962-o -I 1.o -o 64/src/net -trimpath 64/pkg/tool/linu-lang=go1.25 -p crypto/sha256 -lang=go1.25 64/pkg/tool/linu-goversion` (dns block) > - Triggering command: `/tmp/go-build3777671178/b263/config.test /tmp/go-build3777671178/b263/config.test -test.testlogfile=/tmp/go-build3777671178/b263/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true .cfg by/8ae1b12e94aa80f9eaed17e6ee8f6f9b1e1c0807ed03bea0920b88cdb129e6eb 64/pkg/tool/linux_amd64/vet by/8ae1b12e94aa8git 920b88cdb129e6ebrev-parse rgo/bin/as 64/pkg/tool/linux_amd64/vet dock�� -o n for TOML configuration validation and error handling Co-autho-buildid=HBV11mEKIlvfULJyOluM/u0/tmp/go-build673569813/b262/vet.cfg ache/go/1.25.6/x64/pkg/tool/linux_amd64/vet ache/go/1.25.6/xbash -I ache/go/1.25.6/x--noprofile ache/go/1.25.6/x64/pkg/tool/linux_amd64/vet` (dns block) > - `nonexistent.local` > - Triggering command: `/tmp/go-build4210791917/b271/launcher.test /tmp/go-build4210791917/b271/launcher.test -test.testlogfile=/tmp/go-build4210791917/b271/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 64/src/net -trimpath 64/pkg/tool/linux_amd64/vet -p github.com/davec--norc -lang=go1.16 64/pkg/tool/linux_amd64/vet -I 519236/b216/_pkg_.a cfg fig.test --gdwarf-5 b/gh-aw-mcpg/intdocker-cli-plugin-metadata -o fig.test` (dns block) > - Triggering command: `/tmp/go-build3777671178/b275/launcher.test /tmp/go-build3777671178/b275/launcher.test -test.testlogfile=/tmp/go-build3777671178/b275/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true t n for TOML configuration validation and error handling Co-autho--format=format:%H %ct %D ache/go/1.25.6/x64/pkg/tool/linux_amd64/vet ache/go/1.25.6/x/opt/hostedtoolcache/go/1.25.6/x64/pkg/tool/linux_amd64/compile -I` (dns block) > - `slow.example.com` > - Triggering command: `/tmp/go-build4210791917/b271/launcher.test /tmp/go-build4210791917/b271/launcher.test -test.testlogfile=/tmp/go-build4210791917/b271/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 64/src/net -trimpath 64/pkg/tool/linux_amd64/vet -p github.com/davec--norc -lang=go1.16 64/pkg/tool/linux_amd64/vet -I 519236/b216/_pkg_.a cfg fig.test --gdwarf-5 b/gh-aw-mcpg/intdocker-cli-plugin-metadata -o fig.test` (dns block) > - Triggering command: `/tmp/go-build3777671178/b275/launcher.test /tmp/go-build3777671178/b275/launcher.test -test.testlogfile=/tmp/go-build3777671178/b275/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true t n for TOML configuration validation and error handling Co-autho--format=format:%H %ct %D ache/go/1.25.6/x64/pkg/tool/linux_amd64/vet ache/go/1.25.6/x/opt/hostedtoolcache/go/1.25.6/x64/pkg/tool/linux_amd64/compile -I` (dns block) > - `this-host-does-not-exist-12345.com` > - Triggering command: `/tmp/go-build4210791917/b280/mcp.test /tmp/go-build4210791917/b280/mcp.test -test.testlogfile=/tmp/go-build4210791917/b280/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true /opt/hostedtoolcache/go/1.25.6/x64/src/runtime/cgo 519236/b184/ 64/bin/go --gdwarf-5 --64 ut-2370224496.c as 5192�� ache/go/1.25.6/x64/src/net -I cfg --gdwarf-5 --64 -o RL1q3u8/2td8KcPQCBPBYpO4RxDG` (dns block) > > If you need me to access, download, or install something from one of these locations, you can either: > > - Configure [Actions setup steps](https://gh.io/copilot/actions-setup-steps) to set up my environment, which run before the firewall is enabled > - Add the appropriate URLs or hosts to the custom allowlist in this repository's [Copilot coding agent settings](https://github.com/github/gh-aw-mcpg/settings/copilot/coding_agent) (admins only) > > </details> <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> ---- *This section details on the original issue you should resolve* <issue_title>[go-fan] Go Module Review: BurntSushi/toml</issue_title> <issue_description># 🐹 Go Fan Report: github.com/BurntSushi/toml ## Module Overview BurntSushi/toml is **the** de-facto standard TOML parser for Go with 4,893 stars and active maintenance. This library provides a reflection-based API for encoding and decoding TOML configuration files, implementing the TOML specification with excellent compliance. The project is currently using **v1.6.0** (released December 18, 2025). ## Current Usage in gh-aw-mcpg The TOML library has a focused, minimal footprint in the gateway: - **Files**: 1 file (`internal/config/config_core.go`) - **Import Count**: 1 import - **Key APIs Used**: - `toml.Decode()` - Single usage for parsing TOML configuration into Go structs **Current implementation** (lines 97-98 of `config_core.go`): ```````go if _, err := toml.Decode(string(data), &cfg); err != nil { return nil, fmt.Errorf("failed to parse TOML: %w", err) } `````` ### Usage Pattern Analysis ✅ **Strengths**: Clean, simple, effective for basic parsing ⚠️ **Limitations**: - Generic error wrapping loses valuable position information - Reads entire file into memory (inefficient for large configs) - No validation of unknown/misspelled keys - Doesn't leverage advanced features added in recent versions ## Research Findings ### Recent Updates (We're on Latest! 🎉) The project is already using v1.6.0, which includes: **v1.6.0 (December 2025)** - Current Version: - ✅ **TOML 1.1 enabled by default** - Allows newlines in inline tables for better readability - ✅ **Fixed duplicate array key validation** - Catches configuration errors like: ``````toml arr = [1] arr = [2] # Now correctly errors: "Key 'arr' has already been defined" `````` - ✅ **Improved float encoding** - Large floats now use exponent syntax (e.g., `5e+22`) **v1.5.0 (March 2025)** - Available features we're not using: - 📍 **`Position.Col`** - Column-level error reporting for pinpoint accuracy - 🔑 **Custom string types as map keys** - More flexible type handling - 🐛 **Better nested inline table encoding** - Fixes edge cases **v1.4.0 (May 2024)** - Available features we're not using: - 📦 **`toml.Marshal()`** - Convenient encoding API for generating TOML - 🎯 **Position wrapping** - Better error context from `UnmarshalTOML()` ### Best Practices from Maintainers 1. **Leverage ParseError for detailed diagnostics** ``````go if perr, ok := err.(*toml.ParseError); ok { // Access: perr.Line, perr.Col, perr.Message } `````` 2. **Use Meta API to detect typos/unknown keys** ``````go md, err := toml.Decode(string(data), &cfg) for _, key := range md.Undecoded() { log.Printf("WARNING: Unknown key: %s", key) } `````` 3. **Stream large files with NewDecoder** ``````go decoder := toml.NewDecoder(file) decoder.Strict(true) // Reject unknown fields `````` 4. **Implement UnmarshalTOML for custom validation** - Validate fields during unmarshaling - Return descriptive errors with field names ## Improvement Opportunities ### 🏃 Quick Wins #### 1. **Leverage Position Information for Better Error Messages** (5 minutes) **Impact**: High - Users get actionable error messages **Effort**: Minimal - Simple type assertion **Current problem**: `````` Error: failed to parse TOML: near line 15: expected key separator '=' ``````` **Improved version**: ```````go if _, err := toml.Decode(string(data), &cfg); err != nil { if perr, ok := err.(*toml.ParseError); ok { return nil, fmt.Errorf("failed to parse TOML at line %d, column %d: %s", perr.Line, perr.Col, perr.Message) } return nil, fmt.Errorf("failed to parse TOML: %w", err) } `````` **Result**: `````` Error: failed to parse TOML at line 15, column 8: expected key separator '=' ^ Exact location! ``````` #### 2. **Detect Configuration Typos with Meta API** (10 minutes) **Impact**: High - Catch common mistakes like `prot` instead of `port` **Effort**: Low - Add 5 lines of code ``````go var cfg Config md, err := toml.Decode(string(data), &cfg) if err != nil { return nil, err } // Warn about unknown keys (typos, deprecated options) undecoded := md.Undecoded() if len(undecoded) > 0 { for _, key := range undecoded { logConfig.Printf("WARNING: Unknown configuration key '%s' - check for typos", key) } } `````` **Catches errors like**: - `prot = 3000` → warns about `prot`, suggests `port` - `startup_timout = 60` → warns about `startup_timout`, suggests `startup_timeout` #### 3. **Use Streaming Decoder** (10 minutes) **Impact**: Medium - Better memory efficiency **Effort**: Low - Replace file read with decoder ``````go // Before: Load entire file data, err := os.ReadFile(path) if _, err := toml.Decode(string(data), &cfg); err != nil { ... } // After: Strea... </details> > **Custom agent used: agentic-workflows** > GitHub Agentic Workflows (gh-aw) - Create, debug, and upgrade AI-powered workflows with intelligent prompt routing <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes #749 <!-- START COPILOT CODING AGENT TIPS --> --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
2 parents d4dc29c + 4c69d30 commit a7b390d

4 files changed

Lines changed: 234 additions & 7 deletions

File tree

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,51 @@ See **[Configuration Specification](https://github.com/github/gh-aw/blob/main/do
211211
- **Passthrough**: Set value to empty string (`""`) to pass through from host
212212
- **Expansion**: Use `${VAR_NAME}` syntax for dynamic substitution (fails if undefined)
213213

214+
### Configuration Validation and Error Handling
215+
216+
MCP Gateway provides detailed error messages and validation to help catch configuration issues early:
217+
218+
#### Parse Errors with Precise Location
219+
220+
When there's a syntax error in your TOML configuration, the gateway reports the exact line and column:
221+
222+
```bash
223+
$ awmg --config config.toml
224+
Error: failed to parse TOML at line 2, column 6: expected '.' or '=', but got '3' instead
225+
```
226+
227+
This helps you quickly identify and fix syntax issues.
228+
229+
#### Unknown Key Detection (Typo Detection)
230+
231+
The gateway detects and warns about unknown configuration keys, helping catch typos and deprecated options:
232+
233+
```toml
234+
[gateway]
235+
prot = 3000 # Typo: should be 'port'
236+
startup_timout = 30 # Typo: should be 'startup_timeout'
237+
```
238+
239+
When you run the gateway with these typos, you'll see warnings in the log file:
240+
241+
```
242+
[2026-02-07T17:46:51Z] [WARN] [config] Unknown configuration key 'gateway.prot' - check for typos or deprecated options
243+
[2026-02-07T17:46:51Z] [WARN] [config] Unknown configuration key 'gateway.startup_timout' - check for typos or deprecated options
244+
```
245+
246+
The gateway will use default values for unrecognized keys, so it will still start, but the warnings help you identify and fix configuration issues.
247+
248+
#### Memory-Efficient Parsing
249+
250+
The gateway uses streaming parsing for configuration files, making it efficient even with large configuration files containing many servers.
251+
252+
#### Best Practices
253+
254+
1. **Check logs for warnings**: After starting the gateway, check the log file for any warnings about unknown keys
255+
2. **Use precise error messages**: When you see a parse error, the line and column numbers point exactly to the problem
256+
3. **Validate configuration**: Test your configuration changes by running the gateway and checking for warnings
257+
4. **Keep configuration clean**: Remove any deprecated or unused configuration options
258+
214259
## Usage
215260

216261
```

config.example.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,14 @@ args = [
139139
#
140140
# Configuration Validation:
141141
# - Required fields: command, args (for stdio servers)
142-
# - Unknown keys will generate warnings in logs
143-
# - Parse errors will show line numbers for easy debugging
142+
# - Unknown keys will generate warnings in logs (helps catch typos!)
143+
# - Parse errors show exact line and column numbers for easy debugging
144+
# - Example: "failed to parse TOML at line 2, column 6: expected '=' ..."
145+
#
146+
# Common Typos Detected:
147+
# - prot → port
148+
# - startup_timout → startup_timeout
149+
# - tool_timout → tool_timeout
144150
#
145151
# For more information, see:
146152
# - MCP Protocol: https://github.com/modelcontextprotocol

internal/config/config_core.go

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010

1111
"github.com/BurntSushi/toml"
12+
"github.com/github/gh-aw-mcpg/internal/logger"
1213
)
1314

1415
// Core constants for configuration defaults
@@ -91,20 +92,48 @@ type ServerConfig struct {
9192
// LoadFromFile loads configuration from a TOML file.
9293
func LoadFromFile(path string) (*Config, error) {
9394
logConfig.Printf("Loading configuration from file: %s", path)
94-
data, err := os.ReadFile(path)
95+
96+
// Open file for streaming
97+
file, err := os.Open(path)
9598
if err != nil {
96-
return nil, fmt.Errorf("failed to read config file: %w", err)
99+
return nil, fmt.Errorf("failed to open config file: %w", err)
97100
}
101+
defer file.Close()
98102

99-
logConfig.Printf("Read %d bytes from config file", len(data))
100-
103+
// Use streaming decoder for better memory efficiency
101104
var cfg Config
102-
if _, err := toml.Decode(string(data), &cfg); err != nil {
105+
decoder := toml.NewDecoder(file)
106+
md, err := decoder.Decode(&cfg)
107+
if err != nil {
108+
// Extract position information from ParseError for better error messages
109+
// Note: We use Position.Line, Position.Col, and Message separately to provide
110+
// a consistent, precise error format. perr.Error() includes line info but not
111+
// column, so we construct our own message with both for better UX.
112+
// Try pointer type first (for compatibility)
113+
if perr, ok := err.(*toml.ParseError); ok {
114+
return nil, fmt.Errorf("failed to parse TOML at line %d, column %d: %s",
115+
perr.Position.Line, perr.Position.Col, perr.Message)
116+
}
117+
// Try value type (used by toml.Decode)
118+
if perr, ok := err.(toml.ParseError); ok {
119+
return nil, fmt.Errorf("failed to parse TOML at line %d, column %d: %s",
120+
perr.Position.Line, perr.Position.Col, perr.Message)
121+
}
103122
return nil, fmt.Errorf("failed to parse TOML: %w", err)
104123
}
105124

106125
logConfig.Printf("Parsed TOML config with %d servers", len(cfg.Servers))
107126

127+
// Detect and warn about unknown configuration keys (typos, deprecated options)
128+
undecoded := md.Undecoded()
129+
if len(undecoded) > 0 {
130+
for _, key := range undecoded {
131+
// Log to both debug logger and file logger for visibility
132+
logConfig.Printf("WARNING: Unknown configuration key '%s' - check for typos or deprecated options", key)
133+
logger.LogWarn("config", "Unknown configuration key '%s' - check for typos or deprecated options", key)
134+
}
135+
}
136+
108137
// Validate required fields
109138
if len(cfg.Servers) == 0 {
110139
return nil, fmt.Errorf("no servers defined in configuration")

internal/config/config_test.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,3 +1096,150 @@ args = ["run", "--rm", "-i", "mcp/memory"]
10961096
_, ok = cfg.Servers["memory"]
10971097
assert.True(t, ok, "Server 'memory' not found")
10981098
}
1099+
1100+
// TestLoadFromFile_ParseErrorWithColumnNumber tests that parse errors include column information
1101+
func TestLoadFromFile_ParseErrorWithColumnNumber(t *testing.T) {
1102+
tmpDir := t.TempDir()
1103+
tmpFile := filepath.Join(tmpDir, "config.toml")
1104+
1105+
// Invalid TOML: missing equals sign
1106+
tomlContent := `[gateway]
1107+
port 3000
1108+
`
1109+
1110+
err := os.WriteFile(tmpFile, []byte(tomlContent), 0644)
1111+
require.NoError(t, err, "Failed to write temp TOML file")
1112+
1113+
cfg, err := LoadFromFile(tmpFile)
1114+
require.Error(t, err, "Expected error for invalid TOML")
1115+
assert.Nil(t, cfg, "Config should be nil on error")
1116+
1117+
// Error should contain line and column information from our improved error format
1118+
errMsg := err.Error()
1119+
assert.Contains(t, errMsg, "line", "Error should mention line number")
1120+
// Our improved format includes "column" explicitly when ParseError is detected
1121+
assert.True(t, strings.Contains(errMsg, "column") || strings.Contains(errMsg, "line 2"),
1122+
"Error should mention column or line position, got: %s", errMsg)
1123+
}
1124+
1125+
// TestLoadFromFile_UnknownKeysInGateway tests detection of unknown keys in gateway section
1126+
func TestLoadFromFile_UnknownKeysInGateway(t *testing.T) {
1127+
tmpDir := t.TempDir()
1128+
tmpFile := filepath.Join(tmpDir, "config.toml")
1129+
1130+
// TOML with typo in gateway field: "prot" instead of "port"
1131+
tomlContent := `
1132+
[gateway]
1133+
prot = 3000
1134+
api_key = "test-key"
1135+
1136+
[servers.test]
1137+
command = "docker"
1138+
args = ["run", "--rm", "-i", "test/container:latest"]
1139+
`
1140+
1141+
err := os.WriteFile(tmpFile, []byte(tomlContent), 0644)
1142+
require.NoError(t, err, "Failed to write temp TOML file")
1143+
1144+
// Enable debug logging to capture warning about unknown key
1145+
SetDebug(true)
1146+
defer SetDebug(false)
1147+
1148+
// Should still load successfully, but warning will be logged
1149+
cfg, err := LoadFromFile(tmpFile)
1150+
require.NoError(t, err, "LoadFromFile() should succeed even with unknown keys")
1151+
require.NotNil(t, cfg, "Config should not be nil")
1152+
1153+
// Port should be default since "prot" was not recognized
1154+
assert.Equal(t, DefaultPort, cfg.Gateway.Port, "Port should be default since 'prot' is unknown")
1155+
assert.Equal(t, "test-key", cfg.Gateway.APIKey, "API key should be set correctly")
1156+
}
1157+
1158+
// TestLoadFromFile_MultipleUnknownKeys tests detection of multiple typos
1159+
func TestLoadFromFile_MultipleUnknownKeys(t *testing.T) {
1160+
tmpDir := t.TempDir()
1161+
tmpFile := filepath.Join(tmpDir, "config.toml")
1162+
1163+
// TOML with multiple typos
1164+
tomlContent := `
1165+
[gateway]
1166+
port = 8080
1167+
startup_timout = 30
1168+
tool_timout = 60
1169+
1170+
[servers.test]
1171+
command = "docker"
1172+
args = ["run", "--rm", "-i", "test/container:latest"]
1173+
typ = "stdio"
1174+
`
1175+
1176+
err := os.WriteFile(tmpFile, []byte(tomlContent), 0644)
1177+
require.NoError(t, err, "Failed to write temp TOML file")
1178+
1179+
// Enable debug logging to capture warnings
1180+
SetDebug(true)
1181+
defer SetDebug(false)
1182+
1183+
// Should still load successfully
1184+
cfg, err := LoadFromFile(tmpFile)
1185+
require.NoError(t, err, "LoadFromFile() should succeed even with multiple unknown keys")
1186+
require.NotNil(t, cfg, "Config should not be nil")
1187+
1188+
// Correctly spelled fields should work
1189+
assert.Equal(t, 8080, cfg.Gateway.Port, "Port should be set correctly")
1190+
// Misspelled fields should use defaults
1191+
assert.Equal(t, DefaultStartupTimeout, cfg.Gateway.StartupTimeout, "StartupTimeout should be default")
1192+
assert.Equal(t, DefaultToolTimeout, cfg.Gateway.ToolTimeout, "ToolTimeout should be default")
1193+
}
1194+
1195+
// TestLoadFromFile_StreamingLargeFile tests that streaming decoder works efficiently
1196+
func TestLoadFromFile_StreamingLargeFile(t *testing.T) {
1197+
tmpDir := t.TempDir()
1198+
tmpFile := filepath.Join(tmpDir, "large-config.toml")
1199+
1200+
// Create a TOML file with many servers
1201+
var tomlContent strings.Builder
1202+
tomlContent.WriteString("[gateway]\nport = 3000\n\n")
1203+
1204+
for i := 1; i <= 100; i++ {
1205+
tomlContent.WriteString(fmt.Sprintf("[servers.server%d]\n", i))
1206+
tomlContent.WriteString("command = \"docker\"\n")
1207+
tomlContent.WriteString(fmt.Sprintf("args = [\"run\", \"--rm\", \"-i\", \"test/server%d:latest\"]\n\n", i))
1208+
}
1209+
1210+
err := os.WriteFile(tmpFile, []byte(tomlContent.String()), 0644)
1211+
require.NoError(t, err, "Failed to write temp TOML file")
1212+
1213+
// Should load successfully using streaming decoder
1214+
cfg, err := LoadFromFile(tmpFile)
1215+
require.NoError(t, err, "LoadFromFile() should handle large files")
1216+
require.NotNil(t, cfg, "Config should not be nil")
1217+
assert.Len(t, cfg.Servers, 100, "Expected 100 servers")
1218+
}
1219+
1220+
// TestLoadFromFile_InvalidTOMLDuplicateKey tests handling of duplicate keys
1221+
func TestLoadFromFile_InvalidTOMLDuplicateKey(t *testing.T) {
1222+
tmpDir := t.TempDir()
1223+
tmpFile := filepath.Join(tmpDir, "config.toml")
1224+
1225+
// TOML 1.1+ should detect duplicate keys (available in v1.6.0)
1226+
tomlContent := `
1227+
[gateway]
1228+
port = 3000
1229+
port = 8080
1230+
1231+
[servers.test]
1232+
command = "docker"
1233+
args = ["run", "--rm", "-i", "test/container:latest"]
1234+
`
1235+
1236+
err := os.WriteFile(tmpFile, []byte(tomlContent), 0644)
1237+
require.NoError(t, err, "Failed to write temp TOML file")
1238+
1239+
cfg, err := LoadFromFile(tmpFile)
1240+
require.Error(t, err, "Expected error for duplicate key")
1241+
assert.Nil(t, cfg, "Config should be nil on error")
1242+
1243+
// Error should mention the duplicate key
1244+
assert.Contains(t, err.Error(), "line", "Error should mention line number")
1245+
}

0 commit comments

Comments
 (0)