From 36f9619fc457b8ab5053f8aa882587fa1e5dc2c6 Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 08:01:02 +0000 Subject: [PATCH] feat(forge): add safe stringers for core value types Co-Authored-By: Virgil --- ax_stringer_test.go | 81 +++++++++++++++++++++++++++++++++++++++++++++ client.go | 25 ++++++++++++++ pagination.go | 53 +++++++++++++++++++++++++++++ params.go | 40 ++++++++++++++++++++++ 4 files changed, 199 insertions(+) create mode 100644 ax_stringer_test.go diff --git a/ax_stringer_test.go b/ax_stringer_test.go new file mode 100644 index 0000000..167bc02 --- /dev/null +++ b/ax_stringer_test.go @@ -0,0 +1,81 @@ +package forge + +import ( + "fmt" + "testing" +) + +func TestParams_String_Good(t *testing.T) { + params := Params{"repo": "go-forge", "owner": "core"} + want := `forge.Params{owner="core", repo="go-forge"}` + if got := params.String(); got != want { + t.Fatalf("got String()=%q, want %q", got, want) + } + if got := fmt.Sprint(params); got != want { + t.Fatalf("got fmt.Sprint=%q, want %q", got, want) + } + if got := fmt.Sprintf("%#v", params); got != want { + t.Fatalf("got GoString=%q, want %q", got, want) + } +} + +func TestParams_String_NilSafe(t *testing.T) { + var params Params + want := "forge.Params{}" + if got := params.String(); got != want { + t.Fatalf("got String()=%q, want %q", got, want) + } + if got := fmt.Sprint(params); got != want { + t.Fatalf("got fmt.Sprint=%q, want %q", got, want) + } + if got := fmt.Sprintf("%#v", params); got != want { + t.Fatalf("got GoString=%q, want %q", got, want) + } +} + +func TestListOptions_String_Good(t *testing.T) { + opts := ListOptions{Page: 2, Limit: 25} + want := "forge.ListOptions{page=2, limit=25}" + if got := opts.String(); got != want { + t.Fatalf("got String()=%q, want %q", got, want) + } + if got := fmt.Sprint(opts); got != want { + t.Fatalf("got fmt.Sprint=%q, want %q", got, want) + } + if got := fmt.Sprintf("%#v", opts); got != want { + t.Fatalf("got GoString=%q, want %q", got, want) + } +} + +func TestRateLimit_String_Good(t *testing.T) { + rl := RateLimit{Limit: 80, Remaining: 79, Reset: 1700000003} + want := "forge.RateLimit{limit=80, remaining=79, reset=1700000003}" + if got := rl.String(); got != want { + t.Fatalf("got String()=%q, want %q", got, want) + } + if got := fmt.Sprint(rl); got != want { + t.Fatalf("got fmt.Sprint=%q, want %q", got, want) + } + if got := fmt.Sprintf("%#v", rl); got != want { + t.Fatalf("got GoString=%q, want %q", got, want) + } +} + +func TestPagedResult_String_Good(t *testing.T) { + page := PagedResult[int]{ + Items: []int{1, 2, 3}, + TotalCount: 10, + Page: 2, + HasMore: true, + } + want := "forge.PagedResult{items=3, totalCount=10, page=2, hasMore=true}" + if got := page.String(); got != want { + t.Fatalf("got String()=%q, want %q", got, want) + } + if got := fmt.Sprint(page); got != want { + t.Fatalf("got fmt.Sprint=%q, want %q", got, want) + } + if got := fmt.Sprintf("%#v", page); got != want { + t.Fatalf("got GoString=%q, want %q", got, want) + } +} diff --git a/client.go b/client.go index 04643c9..fff8a8c 100644 --- a/client.go +++ b/client.go @@ -109,6 +109,31 @@ type RateLimit struct { Reset int64 } +// String returns a safe summary of the rate limit state. +// +// Usage: +// +// rl := client.RateLimit() +// _ = rl.String() +func (r RateLimit) String() string { + return core.Concat( + "forge.RateLimit{limit=", + strconv.Itoa(r.Limit), + ", remaining=", + strconv.Itoa(r.Remaining), + ", reset=", + strconv.FormatInt(r.Reset, 10), + "}", + ) +} + +// GoString returns a safe Go-syntax summary of the rate limit state. +// +// Usage: +// +// _ = fmt.Sprintf("%#v", client.RateLimit()) +func (r RateLimit) GoString() string { return r.String() } + // Client is a low-level HTTP client for the Forgejo API. // // Usage: diff --git a/pagination.go b/pagination.go index d375633..203b15a 100644 --- a/pagination.go +++ b/pagination.go @@ -23,6 +23,28 @@ type ListOptions struct { Limit int // items per page (default 50) } +// String returns a safe summary of the pagination options. +// +// Usage: +// +// _ = forge.DefaultList.String() +func (o ListOptions) String() string { + return core.Concat( + "forge.ListOptions{page=", + strconv.Itoa(o.Page), + ", limit=", + strconv.Itoa(o.Limit), + "}", + ) +} + +// GoString returns a safe Go-syntax summary of the pagination options. +// +// Usage: +// +// _ = fmt.Sprintf("%#v", forge.DefaultList) +func (o ListOptions) GoString() string { return o.String() } + // DefaultList provides sensible default pagination. // // Usage: @@ -44,6 +66,37 @@ type PagedResult[T any] struct { HasMore bool } +// String returns a safe summary of a page of results. +// +// Usage: +// +// page, _ := forge.ListPage[types.Repository](...) +// _ = page.String() +func (r PagedResult[T]) String() string { + items := 0 + if r.Items != nil { + items = len(r.Items) + } + return core.Concat( + "forge.PagedResult{items=", + strconv.Itoa(items), + ", totalCount=", + strconv.Itoa(r.TotalCount), + ", page=", + strconv.Itoa(r.Page), + ", hasMore=", + strconv.FormatBool(r.HasMore), + "}", + ) +} + +// GoString returns a safe Go-syntax summary of a page of results. +// +// Usage: +// +// _ = fmt.Sprintf("%#v", page) +func (r PagedResult[T]) GoString() string { return r.String() } + // ListPage fetches a single page of results. // Extra query params can be passed via the query map. // diff --git a/params.go b/params.go index 9646e97..50c291f 100644 --- a/params.go +++ b/params.go @@ -2,6 +2,9 @@ package forge import ( "net/url" + "sort" + "strconv" + "strings" core "dappco.re/go/core" ) @@ -15,6 +18,43 @@ import ( // _ = params type Params map[string]string +// String returns a safe summary of the path parameters. +// +// Usage: +// +// _ = forge.Params{"owner": "core"}.String() +func (p Params) String() string { + if p == nil { + return "forge.Params{}" + } + + keys := make([]string, 0, len(p)) + for k := range p { + keys = append(keys, k) + } + sort.Strings(keys) + + var b strings.Builder + b.WriteString("forge.Params{") + for i, k := range keys { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(k) + b.WriteString("=") + b.WriteString(strconv.Quote(p[k])) + } + b.WriteString("}") + return b.String() +} + +// GoString returns a safe Go-syntax summary of the path parameters. +// +// Usage: +// +// _ = fmt.Sprintf("%#v", forge.Params{"owner": "core"}) +func (p Params) GoString() string { return p.String() } + // ResolvePath substitutes {placeholders} in path with values from params. // // Usage: