Skip to content

feat(cli): Comprehensive erst.toml configuration parsing#646

Open
daveades wants to merge 3 commits intodotandev:mainfrom
daveades:feat/cli-issue-93
Open

feat(cli): Comprehensive erst.toml configuration parsing#646
daveades wants to merge 3 commits intodotandev:mainfrom
daveades:feat/cli-issue-93

Conversation

@daveades
Copy link
Contributor

@daveades daveades commented Feb 25, 2026

==============================================================================
TITLE:
feat(cli): Comprehensive erst.toml configuration parsing - Issue #409

=============================================================================
DESCRIPTION:

Overview

Consolidates CLI configuration so .erst.toml (system, home, local) is loaded first, then environment variables
override it. This unifies configuration precedence for CLI commands.

Changes

Core Implementation

  • Config precedence: /etc/erst/config.toml → ~/.erst.toml → ./.erst.toml, then env overrides.
  • Env overrides: Added support for ERST_SIM_PATH and ERST_CACHE_DIR aliases.
  • Parsing: Handles inline comments and boolean values in TOML, improves list parsing.
  • CLI usage: Debug/explain now read tokens via unified config loader.

Testing

  • Added precedence test for local overriding home config.
  • Updated config priority test to use TOML config file.

Verification

  • Tests not run in environment (go: command not found).

Related Issues

Closes #409

Type of Change

  • New feature
  • Bug fix
  • Breaking change
  • Documentation update

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Tests added/updated
  • Documentation updated
  • No new linting issues
  • Changes verified locally (Go toolchain unavailable)

================================================================================

Copilot AI review requested due to automatic review settings February 25, 2026 17:40
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements comprehensive TOML configuration parsing for the erst CLI tool (Issue #409), establishing a hierarchical configuration system that loads settings from multiple sources with proper precedence. The PR description incorrectly includes extensive AWS KMS signing content (Issue #393), which is not implemented in this PR.

Changes:

  • Implemented TOML configuration file loading with precedence: /etc/erst/config.toml~/.erst.toml./.erst.toml → environment variables
  • Added support for environment variable aliases (ERST_SIM_PATH for ERST_SIMULATOR_PATH, ERST_CACHE_DIR for ERST_CACHE_PATH)
  • Migrated debug and explain commands from config.LoadConfig() to unified config.Load() function

Reviewed changes

Copilot reviewed 19 out of 21 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
internal/config/config.go Refactored configuration loading to support TOML files with hierarchical precedence, added helper functions for parsing booleans and stripping inline comments, implemented environment variable override system
internal/config/config_test.go Added test for local config overriding home config to verify precedence behavior
internal/cmd/debug.go Updated to use new unified config.Load() function for token resolution
internal/cmd/explain.go Updated to use new unified config.Load() function for token resolution
tests/config_priority_test.go Updated test to use TOML config format instead of JSON

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

paths := []string{
".erst.toml",
filepath.Join(os.ExpandEnv("$HOME"), ".erst.toml"),
"/etc/erst/config.toml",
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded path "/etc/erst/config.toml" is Unix-specific and will not work correctly on Windows. On Windows, this path would be invalid and could cause issues. Consider using a platform-appropriate system config path or making this path configurable, or at least document that system-level configuration is only supported on Unix-like systems.

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +38
// Create a dummy TOML config file
configFile := filepath.Join(tmpDir, ".erst.toml")
configData := []byte(`rpc_token = "CONFIG_TOKEN"`)
err := os.WriteFile(configFile, configData, 0600)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description focuses heavily on AWS KMS signing features (Issue #393), but the actual code changes only implement TOML configuration parsing (Issue #409). The PR title correctly mentions "erst.toml configuration parsing", but the description should be updated to accurately reflect what this PR actually implements. The AWS KMS content appears to be copied from a different PR.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +331 to +334

for _, key := range []string{"ERST_RPC_URL", "ERST_RPC_URLS", "STELLAR_RPC_URLS"} {
os.Unsetenv(key)
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test should restore the original values of the unset environment variables after the test completes. Currently, it only unsets them (line 332-334) but doesn't restore them in a defer block. This could cause test pollution if other tests depend on these environment variables. Consider saving the original values and restoring them in a defer function, similar to how HOME is handled on lines 320-322.

Suggested change
for _, key := range []string{"ERST_RPC_URL", "ERST_RPC_URLS", "STELLAR_RPC_URLS"} {
os.Unsetenv(key)
}
origEnv := make(map[string]struct {
value string
set bool
})
for _, key := range []string{"ERST_RPC_URL", "ERST_RPC_URLS", "STELLAR_RPC_URLS"} {
val, ok := os.LookupEnv(key)
origEnv[key] = struct {
value string
set bool
}{value: val, set: ok}
os.Unsetenv(key)
}
defer func() {
for key, state := range origEnv {
if state.set {
_ = os.Setenv(key, state.value)
} else {
_ = os.Unsetenv(key)
}
}
}()

Copilot uses AI. Check for mistakes.
Comment on lines 331 to 338
func parseBool(value string) bool {
switch strings.ToLower(strings.TrimSpace(value)) {
case "1", "true", "yes", "on":
return true
default:
return false
}
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new helper function parseBool has been introduced but lacks unit test coverage. Given that it handles multiple boolean representations (1, true, yes, on), it would be beneficial to add explicit tests for this function to ensure all cases are properly handled, especially edge cases like "TRUE", "Yes", "ON" (uppercase variants) and invalid inputs like "false", "0", "no", "off".

Copilot uses AI. Check for mistakes.
Comment on lines 340 to 357
func stripInlineComment(line string) string {
inSingle := false
inDouble := false
for i, ch := range line {
if ch == '"' && !inSingle {
inDouble = !inDouble
continue
}
if ch == '\'' && !inDouble {
inSingle = !inSingle
continue
}
if ch == '#' && !inSingle && !inDouble {
return strings.TrimSpace(line[:i])
}
}
return defaultValue
return line
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stripInlineComment function lacks unit test coverage. This function handles complex quote-aware comment parsing logic, including nested quote handling for single and double quotes. Given its complexity and importance for correct TOML parsing, dedicated unit tests should be added to verify behavior with edge cases like unmatched quotes, escaped quotes, and comments within strings.

Copilot uses AI. Check for mistakes.
Comment on lines 340 to 357
func stripInlineComment(line string) string {
inSingle := false
inDouble := false
for i, ch := range line {
if ch == '"' && !inSingle {
inDouble = !inDouble
continue
}
if ch == '\'' && !inDouble {
inSingle = !inSingle
continue
}
if ch == '#' && !inSingle && !inDouble {
return strings.TrimSpace(line[:i])
}
}
return defaultValue
return line
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stripInlineComment function does not handle escaped quotes. For example, a string like key = "value with \" quote" would incorrectly toggle the inDouble state when encountering the escaped quote, potentially causing comments to be parsed incorrectly. Consider adding escape character handling by checking if the previous character is a backslash before toggling quote states.

Copilot uses AI. Check for mistakes.
lines := strings.Split(content, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
line = strings.TrimSpace(stripInlineComment(line))
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stripInlineComment function is called on every line of the TOML file, but the result is only used after TrimSpace. This means that if stripInlineComment returns a line with trailing spaces before a comment, those spaces will be trimmed anyway. Consider calling stripInlineComment before the initial TrimSpace to avoid unnecessary whitespace processing, or ensuring stripInlineComment returns an already-trimmed result for better performance.

Suggested change
line = strings.TrimSpace(stripInlineComment(line))
line = stripInlineComment(strings.TrimSpace(line))

Copilot uses AI. Check for mistakes.
Comment on lines +332 to +334
for _, key := range []string{"ERST_RPC_URL", "ERST_RPC_URLS", "STELLAR_RPC_URLS"} {
os.Unsetenv(key)
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test should also unset or save/restore other environment variables that could affect the configuration loading, such as ERST_NETWORK, ERST_LOG_LEVEL, ERST_CACHE_PATH, etc. Currently, if any of these are set in the test environment, they could interfere with the test's ability to verify that local config properly overrides home config. Consider either unsetting all ERST_* environment variables or saving and restoring them similar to how HOME is handled.

Suggested change
for _, key := range []string{"ERST_RPC_URL", "ERST_RPC_URLS", "STELLAR_RPC_URLS"} {
os.Unsetenv(key)
}
envKeys := []string{
"ERST_RPC_URL",
"ERST_RPC_URLS",
"STELLAR_RPC_URLS",
"ERST_NETWORK",
"ERST_LOG_LEVEL",
"ERST_CACHE_PATH",
}
origEnv := make(map[string]string, len(envKeys))
for _, key := range envKeys {
if val, ok := os.LookupEnv(key); ok {
origEnv[key] = val
}
os.Unsetenv(key)
}
defer func() {
for _, key := range envKeys {
if val, ok := origEnv[key]; ok {
_ = os.Setenv(key, val)
} else {
_ = os.Unsetenv(key)
}
}
}()

Copilot uses AI. Check for mistakes.
@dotandev
Copy link
Owner

fix conflicts, please.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CLI] Comprehensive erst.toml configuration parsing #193

3 participants