a73x

5096042b

feat: add Match and SaveResponse to middleware

a73x   2026-03-31 05:47


diff --git a/middleware/middleware.go b/middleware/middleware.go
index 8be111e..1e8b10e 100644
--- a/middleware/middleware.go
+++ b/middleware/middleware.go
@@ -57,3 +57,21 @@ func New(path string) (*Middleware, error) {
func (m *Middleware) RuleCount() int {
	return len(m.rules)
}

// Match checks if host+path matches any middleware rule.
// Returns the first matching rule, or nil.
func (m *Middleware) Match(host, path string) *Rule {
	url := host + path
	for i := range m.rules {
		if m.rules[i].Match == url {
			return &m.rules[i]
		}
	}
	return nil
}

// SaveResponse writes the response body to the rule's destination file,
// overwriting any existing content.
func (r *Rule) SaveResponse(body []byte) error {
	return os.WriteFile(r.Dest, body, 0644)
}
diff --git a/middleware/middleware_test.go b/middleware/middleware_test.go
index 367a6b8..5c2b170 100644
--- a/middleware/middleware_test.go
+++ b/middleware/middleware_test.go
@@ -50,3 +50,80 @@ func TestNewRejectsUnknownAction(t *testing.T) {
		t.Fatal("expected error for unknown action")
	}
}

func TestMatchReturnsRuleForMatchingURL(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, _ := middleware.New(path)
	rule := mw.Match("api.anthropic.com", "/api/oauth/usage")
	if rule == nil {
		t.Fatal("expected a match")
	}
	if rule.Dest != "/tmp/usage.json" {
		t.Errorf("expected dest /tmp/usage.json, got %s", rule.Dest)
	}
}

func TestMatchReturnsNilForNoMatch(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, _ := middleware.New(path)
	rule := mw.Match("example.com", "/other")
	if rule != nil {
		t.Fatal("expected no match")
	}
}

func TestSaveResponseWritesBodyToFile(t *testing.T) {
	dest := filepath.Join(t.TempDir(), "out.json")
	rule := &middleware.Rule{
		Match:  "example.com/data",
		Action: "save_response",
		Dest:   dest,
	}

	body := []byte(`{"tokens": 42}`)
	err := rule.SaveResponse(body)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	got, err := os.ReadFile(dest)
	if err != nil {
		t.Fatalf("failed to read dest file: %v", err)
	}
	if string(got) != string(body) {
		t.Errorf("expected %q, got %q", body, got)
	}
}

func TestSaveResponseOverwritesExistingFile(t *testing.T) {
	dest := filepath.Join(t.TempDir(), "out.json")
	os.WriteFile(dest, []byte("old data"), 0644)

	rule := &middleware.Rule{
		Match:  "example.com/data",
		Action: "save_response",
		Dest:   dest,
	}

	body := []byte(`{"new": true}`)
	rule.SaveResponse(body)

	got, _ := os.ReadFile(dest)
	if string(got) != string(body) {
		t.Errorf("expected %q, got %q", body, got)
	}
}