feat(forge): add safe stringers for core value types
Some checks failed
Security Scan / security (push) Successful in 15s
Test / test (push) Has been cancelled

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 08:01:02 +00:00
parent 0dd5916f4e
commit 36f9619fc4
4 changed files with 199 additions and 0 deletions

81
ax_stringer_test.go Normal file
View file

@ -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{<nil>}"
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)
}
}

View file

@ -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:

View file

@ -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.
//

View file

@ -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{<nil>}"
}
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: