a73x

db525127

feat: add middleware package with YAML config loading

a73x   2026-03-31 05:46


diff --git a/middleware/middleware.go b/middleware/middleware.go
new file mode 100644
index 0000000..8be111e
--- /dev/null
+++ b/middleware/middleware.go
@@ -0,0 +1,59 @@
package middleware

import (
	"fmt"
	"os"

	"gopkg.in/yaml.v3"
)

type Rule struct {
	Match  string
	Action string
	Dest   string
}

type Middleware struct {
	rules []Rule
}

type yamlRule struct {
	Match  string `yaml:"match"`
	Action string `yaml:"action"`
	Dest   string `yaml:"dest"`
}

type yamlConfig struct {
	Middleware []yamlRule `yaml:"middleware"`
}

// New loads middleware config from a YAML file.
// Returns an empty Middleware if the file does not exist.
func New(path string) (*Middleware, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return &Middleware{}, nil
		}
		return nil, fmt.Errorf("reading middleware config: %w", err)
	}

	var cfg yamlConfig
	if err := yaml.Unmarshal(data, &cfg); err != nil {
		return nil, fmt.Errorf("parsing middleware YAML: %w", err)
	}

	rules := make([]Rule, 0, len(cfg.Middleware))
	for _, yr := range cfg.Middleware {
		if yr.Action != "save_response" {
			return nil, fmt.Errorf("unknown middleware action %q for match %q", yr.Action, yr.Match)
		}
		rules = append(rules, Rule{Match: yr.Match, Action: yr.Action, Dest: yr.Dest})
	}

	return &Middleware{rules: rules}, nil
}

func (m *Middleware) RuleCount() int {
	return len(m.rules)
}
diff --git a/middleware/middleware_test.go b/middleware/middleware_test.go
new file mode 100644
index 0000000..367a6b8
--- /dev/null
+++ b/middleware/middleware_test.go
@@ -0,0 +1,52 @@
package middleware_test

import (
	"os"
	"path/filepath"
	"testing"

	"github.com/xanderle/nono/middleware"
)

func TestNewLoadsConfig(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "middleware.yaml")
	os.WriteFile(path, []byte(`middleware:
  - match: "api.anthropic.com/api/oauth/usage"
    action: save_response
    dest: "/tmp/usage.json"
`), 0644)

	mw, err := middleware.New(path)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if mw.RuleCount() != 1 {
		t.Errorf("expected 1 rule, got %d", mw.RuleCount())
	}
}

func TestNewReturnsEmptyForMissingFile(t *testing.T) {
	mw, err := middleware.New("/nonexistent/middleware.yaml")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if mw.RuleCount() != 0 {
		t.Errorf("expected 0 rules, got %d", mw.RuleCount())
	}
}

func TestNewRejectsUnknownAction(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "middleware.yaml")
	os.WriteFile(path, []byte(`middleware:
  - match: "example.com/foo"
    action: delete_everything
    dest: "/tmp/out"
`), 0644)

	_, err := middleware.New(path)
	if err == nil {
		t.Fatal("expected error for unknown action")
	}
}