diff --git a/internal/provider/ansible/ansible.go b/internal/provider/ansible/ansible.go index ef1e969..464591f 100644 --- a/internal/provider/ansible/ansible.go +++ b/internal/provider/ansible/ansible.go @@ -9,6 +9,7 @@ import ( "encoding/json" "net/http" "net/url" + "strconv" "strings" "git.unkin.net/unkin/artifactapi/internal/auth" @@ -42,12 +43,27 @@ func (p *Provider) UpstreamURL(remote models.Remote, path string) string { return strings.TrimRight(remote.BaseURL, "/") + "/" + strings.TrimLeft(path, "/") } +const maxPageLimit = 100 + // ForwardedQuery keeps limit/offset pagination on metadata paths only. -func (p *Provider) ForwardedQuery(path string) []string { +func (p *Provider) ForwardedQuery(path string) map[string]provider.QueryValidator { if isArtifact(path) { return nil } - return []string{"limit", "offset"} + return map[string]provider.QueryValidator{"limit": pageLimit, "offset": pageOffset} +} + +func pageOffset(v string) (string, bool) { + n, err := strconv.ParseUint(v, 10, 32) + return strconv.FormatUint(n, 10), err == nil +} + +func pageLimit(v string) (string, bool) { + n, err := strconv.ParseUint(v, 10, 32) + if err != nil || n == 0 { + return "", false + } + return strconv.FormatUint(min(n, maxPageLimit), 10), true } // RewriteResponse points href, download_url and links.* at the remote. Links diff --git a/internal/provider/ansible/ansible_test.go b/internal/provider/ansible/ansible_test.go index 8c96db7..978eacc 100644 --- a/internal/provider/ansible/ansible_test.go +++ b/internal/provider/ansible/ansible_test.go @@ -2,7 +2,6 @@ package ansible import ( "encoding/json" - "reflect" "testing" "git.unkin.net/unkin/artifactapi/internal/provider" @@ -40,12 +39,39 @@ func TestContentTypeIgnoresQuery(t *testing.T) { func TestForwardedQuery(t *testing.T) { p := &Provider{} - if got := p.ForwardedQuery("api/v3/collections/a/b/versions/"); !reflect.DeepEqual(got, []string{"limit", "offset"}) { - t.Errorf("metadata ForwardedQuery = %v", got) - } if got := p.ForwardedQuery(tarball); got != nil { t.Errorf("tarball ForwardedQuery = %v, want nil", got) } + q := p.ForwardedQuery("api/v3/collections/a/b/versions/") + if len(q) != 2 || q["limit"] == nil || q["offset"] == nil { + t.Fatalf("metadata ForwardedQuery keys = %v, want limit and offset", q) + } + tests := []struct { + param, in, want string + ok bool + }{ + {"limit", "10", "10", true}, + {"limit", "007", "7", true}, + {"limit", "100", "100", true}, + {"limit", "5000", "100", true}, + {"limit", "0", "", false}, + {"limit", "", "", false}, + {"limit", "-1", "", false}, + {"limit", "+5", "", false}, + {"limit", "1e3", "", false}, + {"limit", "10 ", "", false}, + {"offset", "0", "0", true}, + {"offset", "000", "0", true}, + {"offset", "0100", "100", true}, + {"offset", "abc", "", false}, + {"offset", "99999999999999999999", "", false}, + } + for _, tt := range tests { + got, ok := q[tt.param](tt.in) + if ok != tt.ok || (ok && got != tt.want) { + t.Errorf("%s(%q) = %q, %v, want %q, %v", tt.param, tt.in, got, ok, tt.want, tt.ok) + } + } } func rewrite(t *testing.T, remote models.Remote, body string) map[string]any {