Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"bytes"
"errors"
"fmt"
"io"
"net"
"net/url"
"os"
Expand Down Expand Up @@ -186,9 +187,19 @@ func Load(path string) (*Config, error) {
return nil, fmt.Errorf("read config %q: %w", path, err)
}

if err := yaml.Unmarshal(data, cfg); err != nil {
decoder := yaml.NewDecoder(bytes.NewReader(data))
decoder.KnownFields(true)
if err := decoder.Decode(cfg); err != nil && !errors.Is(err, io.EOF) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return nil, fmt.Errorf("parse config %q: %w", path, err)
}
var trailing yaml.Node
if err := decoder.Decode(&trailing); err != nil {
if !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("parse config %q: %w", path, err)
}
} else {
return nil, fmt.Errorf("parse config %q: multiple YAML documents are not supported", path)
}

// Keep existing clip_url-only configurations usable after the safer
// loopback default was introduced. An explicit clip_listen, including
Expand Down
73 changes: 73 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,79 @@ func TestLoad_PartialYAML_MergesWithDefaults(t *testing.T) {
}
}

func TestLoad_RejectsUnknownTopLevelKey(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "cfg.yaml")
content := "remote: dev@workstation\nauto_discoverd: true\n"
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatalf("write config: %v", err)
}

_, err := Load(path)
if err == nil {
t.Fatal("Load() accepted an unknown top-level key")
}
if !strings.Contains(err.Error(), "auto_discoverd") {
t.Fatalf("Load() error = %v, want unknown key name", err)
}
if !strings.Contains(err.Error(), path) {
t.Fatalf("Load() error = %v, want config path", err)
}
}

func TestLoad_EmptyYAMLKeepsDefaults(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "empty.yaml")
if err := os.WriteFile(path, nil, 0o644); err != nil {
t.Fatalf("write config: %v", err)
}

cfg, err := Load(path)
if err != nil {
t.Fatalf("Load() error = %v, want empty YAML to keep defaults", err)
}
if cfg.LogLevel != Default().LogLevel || cfg.SSHPort != Default().SSHPort {
t.Fatalf("Load() defaults = log level %q, SSH port %d; want %q, %d", cfg.LogLevel, cfg.SSHPort, Default().LogLevel, Default().SSHPort)
}
}

func TestLoad_RejectsTrailingYAMLDocument(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "multi.yaml")
content := "remote: dev@workstation\n---\nauto_discoverd: true\n"
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatalf("write config: %v", err)
}

_, err := Load(path)
if err == nil {
t.Fatal("Load() accepted multiple YAML documents")
}
if !strings.Contains(err.Error(), "multiple YAML documents") {
t.Fatalf("Load() error = %v, want multiple-document error", err)
}
if !strings.Contains(err.Error(), path) {
t.Fatalf("Load() error = %v, want config path", err)
}
}

func TestLoad_RejectsMalformedTrailingYAMLDocument(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "multi-invalid.yaml")
content := "remote: dev@workstation\n---\nnot: [valid\n"
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatalf("write config: %v", err)
}

_, err := Load(path)
if err == nil {
t.Fatal("Load() accepted a malformed trailing YAML document")
}
if !strings.Contains(err.Error(), path) {
t.Fatalf("Load() error = %v, want config path", err)
}
}

func TestLoad_InvalidYAML(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "bad.yaml")
Expand Down
Loading