diff options
| author | Fuwn <[email protected]> | 2026-02-28 05:39:38 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-28 05:39:38 -0800 |
| commit | 2525861018ccd4db2f683a8778e5784a9e2942f5 (patch) | |
| tree | 6232a03b8ff0633adf920bafdcb946b2c1b62928 | |
| parent | chore(compose): add thin mode override (diff) | |
| download | plutia-test-2525861018ccd4db2f683a8778e5784a9e2942f5.tar.xz plutia-test-2525861018ccd4db2f683a8778e5784a9e2942f5.zip | |
feat(config): allow optional config file with default fallback
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | config.default.yaml | 2 | ||||
| -rw-r--r-- | docker-compose.yml | 2 | ||||
| -rw-r--r-- | internal/config/config.go | 12 | ||||
| -rw-r--r-- | internal/config/config_load_test.go | 82 |
5 files changed, 93 insertions, 7 deletions
@@ -211,6 +211,8 @@ See [`config.default.yaml`](./config.default.yaml). All supported config keys: - `rate_limit.proof_rps` - `rate_limit.proof_burst` +Config files are optional. If `--config` points to a missing file, Plutia falls back to internal defaults and then applies any `PLUTIA_*` environment overrides. + ### Example `docker-compose.yml` ```yaml diff --git a/config.default.yaml b/config.default.yaml index 9782b06..a9d1da7 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -6,7 +6,7 @@ zstd_level: 9 block_size_mb: 8 checkpoint_interval: 100000 commit_batch_size: 128 -verify_workers: 10 +# verify_workers: 16 export_page_size: 1000 replay_trace: false thin_cache_ttl: 24h diff --git a/docker-compose.yml b/docker-compose.yml index 3fad1b3..5c115ba 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,7 +22,7 @@ services: PLUTIA_THIN_CACHE_MAX_ENTRIES: ${THIN_CACHE_MAX_ENTRIES:-100000} PLUTIA_CHECKPOINT_INTERVAL: ${CHECKPOINT_INTERVAL:-100000} PLUTIA_COMMIT_BATCH_SIZE: ${COMMIT_BATCH_SIZE:-128} - PLUTIA_VERIFY_WORKERS: ${VERIFY_WORKERS:-8} + PLUTIA_VERIFY_WORKERS: ${VERIFY_WORKERS:-} PLUTIA_EXPORT_PAGE_SIZE: "${EXPORT_PAGE_SIZE:-1000}" PLUTIA_REPLAY_TRACE: "${REPLAY_TRACE:-false}" PLUTIA_LISTEN_ADDR: "${LISTEN_ADDR:-:8080}" diff --git a/internal/config/config.go b/internal/config/config.go index 69173fd..47f5842 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -89,11 +89,13 @@ func Load(path string) (Config, error) { b, err := os.ReadFile(path) if err != nil { - return Config{}, fmt.Errorf("read config: %w", err) - } - - if err := yaml.Unmarshal(b, &cfg); err != nil { - return Config{}, fmt.Errorf("parse config: %w", err) + if !errors.Is(err, os.ErrNotExist) { + return Config{}, fmt.Errorf("read config: %w", err) + } + } else { + if err := yaml.Unmarshal(b, &cfg); err != nil { + return Config{}, fmt.Errorf("parse config: %w", err) + } } } diff --git a/internal/config/config_load_test.go b/internal/config/config_load_test.go new file mode 100644 index 0000000..516dd93 --- /dev/null +++ b/internal/config/config_load_test.go @@ -0,0 +1,82 @@ +package config + +import ( + "os" + "path/filepath" + "testing" + "time" +) + +func TestLoadMissingConfigFileFallsBackToDefaults(t *testing.T) { + cfg, err := Load(filepath.Join(t.TempDir(), "missing.yaml")) + if err != nil { + t.Fatalf("load missing config: %v", err) + } + + def := Default() + if cfg.Mode != def.Mode { + t.Fatalf("mode mismatch: got %q want %q", cfg.Mode, def.Mode) + } + if cfg.VerifyPolicy != def.VerifyPolicy { + t.Fatalf("verify policy mismatch: got %q want %q", cfg.VerifyPolicy, def.VerifyPolicy) + } + if cfg.CheckpointInterval != def.CheckpointInterval { + t.Fatalf("checkpoint interval mismatch: got %d want %d", cfg.CheckpointInterval, def.CheckpointInterval) + } + if cfg.VerifyWorkers != def.VerifyWorkers { + t.Fatalf("verify workers mismatch: got %d want %d", cfg.VerifyWorkers, def.VerifyWorkers) + } +} + +func TestLoadPartialConfigRetainsDefaultValues(t *testing.T) { + configPath := filepath.Join(t.TempDir(), "partial.yaml") + content := []byte("mode: thin\nverify: full\n") + + if err := os.WriteFile(configPath, content, 0o644); err != nil { + t.Fatalf("write partial config: %v", err) + } + + cfg, err := Load(configPath) + if err != nil { + t.Fatalf("load partial config: %v", err) + } + + if cfg.Mode != ModeThin { + t.Fatalf("mode mismatch: got %q want %q", cfg.Mode, ModeThin) + } + if cfg.VerifyPolicy != VerifyFull { + t.Fatalf("verify policy mismatch: got %q want %q", cfg.VerifyPolicy, VerifyFull) + } + + def := Default() + if cfg.PLCSource != def.PLCSource { + t.Fatalf("plc_source mismatch: got %q want %q", cfg.PLCSource, def.PLCSource) + } + if cfg.RequestTimeout != 10*time.Second { + t.Fatalf("request_timeout mismatch: got %s want 10s", cfg.RequestTimeout) + } + if cfg.CommitBatchSize != def.CommitBatchSize { + t.Fatalf("commit_batch_size mismatch: got %d want %d", cfg.CommitBatchSize, def.CommitBatchSize) + } +} + +func TestLoadMissingConfigFileAppliesEnvOverrides(t *testing.T) { + t.Setenv("PLUTIA_MODE", ModeThin) + t.Setenv("PLUTIA_VERIFY", VerifyFull) + t.Setenv("PLUTIA_REQUEST_TIMEOUT", "15s") + + cfg, err := Load(filepath.Join(t.TempDir(), "missing.yaml")) + if err != nil { + t.Fatalf("load missing config with env overrides: %v", err) + } + + if cfg.Mode != ModeThin { + t.Fatalf("mode mismatch: got %q want %q", cfg.Mode, ModeThin) + } + if cfg.VerifyPolicy != VerifyFull { + t.Fatalf("verify policy mismatch: got %q want %q", cfg.VerifyPolicy, VerifyFull) + } + if cfg.RequestTimeout != 15*time.Second { + t.Fatalf("request_timeout mismatch: got %s want 15s", cfg.RequestTimeout) + } +} |