diff --git a/internal/domain/utils.go b/internal/domain/utils.go index eb29095..32a0f1a 100644 --- a/internal/domain/utils.go +++ b/internal/domain/utils.go @@ -1,7 +1,10 @@ package domain import ( + "bytes" + "encoding/base64" "net/url" + "strings" ) func parseURL(rawURL string) (*url.URL, error) { @@ -14,3 +17,56 @@ func parseURL(rawURL string) (*url.URL, error) { return u, nil } + +const ( + cursorSeparator = "," + cursorKeyValueSeparator = "=" +) + +func encodeCursor(m map[string]string) string { + if len(m) == 0 { + return "" + } + + n := len(cursorSeparator) * (len(m) - 1) + for k, v := range m { + n += len(k) + len(v) + len(cursorKeyValueSeparator) + } + + var b bytes.Buffer + b.Grow(n) + + for k, v := range m { + if b.Len() > 0 { + _, _ = b.WriteString(cursorSeparator) + } + + _, _ = b.WriteString(k) + _, _ = b.WriteString(cursorKeyValueSeparator) + _, _ = b.WriteString(v) + } + + return base64.StdEncoding.EncodeToString(b.Bytes()) +} + +func decodeCursor(s string) (map[string]string, error) { + decodedBytes, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return nil, ErrInvalidCursor + } + decoded := string(decodedBytes) + + kvs := strings.Split(decoded, cursorSeparator) + + m := make(map[string]string, len(kvs)) + + for _, kv := range kvs { + k, v, found := strings.Cut(kv, cursorKeyValueSeparator) + if !found { + return nil, ErrInvalidCursor + } + m[k] = v + } + + return m, nil +} diff --git a/internal/domain/version.go b/internal/domain/version.go index b950958..f7d244e 100644 --- a/internal/domain/version.go +++ b/internal/domain/version.go @@ -1,10 +1,8 @@ package domain import ( - "encoding/base64" "fmt" "net/url" - "strings" ) const ( @@ -137,18 +135,12 @@ func decodeVersionCursor(encoded string) (VersionCursor, error) { return VersionCursor{}, err } - decodedBytes, err := base64.StdEncoding.DecodeString(encoded) + m, err := decodeCursor(encoded) if err != nil { - return VersionCursor{}, ErrInvalidCursor - } - decoded := string(decodedBytes) - - code, ok := strings.CutPrefix(decoded, "code=") - if !ok { - return VersionCursor{}, ErrInvalidCursor + return VersionCursor{}, err } - vc, err := NewVersionCursor(code) + vc, err := NewVersionCursor(m["code"]) if err != nil { return VersionCursor{}, ErrInvalidCursor } @@ -169,7 +161,9 @@ func (vc VersionCursor) Encode() string { return "" } - return base64.StdEncoding.EncodeToString([]byte("code=" + vc.code)) + return encodeCursor(map[string]string{ + "code": vc.code, + }) } type ListVersionsParams struct {