types: the fleet's shared value layer

A value belongs here when two or more services must agree on it exactly and
neither should inherit the other's machinery to do so. The package imports the
standard library only; go list -deps is 63, all stdlib.

Seeded with the metered-series window. Cloud and ai must resolve ?range/?start/
?end to the same [start, end) and the same bucket interval, and cloud reached
that 30-line rule through a package that carries 1925 dependencies.

Interval is a closed type. The bucket function is not bindable in the engines
that serve these series, so the interval reaches a query by interpolation;
ParseWindow yields Hour or Day and nothing else can be constructed through it.

ParseWindow is total: it returns a window or an error, never a zero window a
caller could read as a valid range. It also carries the resolved label, which
every call site previously re-derived.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-07-26 19:04:56 -07:00
co-authored by Hanzo Dev
commit 75b73f4b15
6 changed files with 428 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
*.test
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Hanzo AI Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+9
View File
@@ -0,0 +1,9 @@
// Package types holds the values Hanzo services share.
//
// It imports the standard library only, so a service can depend on a value
// without inheriting the machinery of whoever else uses it.
//
// A value earns a place here when two or more services must agree on it
// exactly. Fetching, storing and recording are effects, and an effect belongs
// with the service that owns it, next to the schema or transport it acts on.
package types
+3
View File
@@ -0,0 +1,3 @@
module github.com/hanzoai/types
go 1.26.4
+105
View File
@@ -0,0 +1,105 @@
package types
import (
"fmt"
"strconv"
"strings"
"time"
)
// Interval is the width of one bucket in a metered time series.
//
// The set is closed at Hour and Day. A bucket function is not a bindable
// parameter in the engines that serve these series, so the interval reaches a
// query by interpolation; keeping the set closed means a request cannot put
// anything else there.
type Interval string
const (
Hour Interval = "hour"
Day Interval = "day"
)
// Step is how much time one bucket covers.
func (i Interval) Step() time.Duration {
if i == Day {
return 24 * time.Hour
}
return time.Hour
}
// Window is a half-open time range [Start, End) and the interval its series
// buckets to.
//
// Label is the range as the caller asked for it, defaulted when the caller
// asked for nothing, so a response can say which window it answers without the
// caller re-deriving it.
type Window struct {
Label string
Start time.Time
End time.Time
Interval Interval
}
// ParseWindow resolves a range request into an absolute window.
//
// "24h", "7d" and "30d", with their aliases, are relative to now. "custom"
// reads start and end, each RFC3339 or unix seconds; end defaults to now and
// must follow start. An unrecognised label is an error rather than a silent
// default, so a typo cannot quietly change the window a caller is shown.
//
// now is a parameter so the window is a function of the arguments alone.
func ParseWindow(label, start, end string, now time.Time) (Window, error) {
now = now.UTC()
w := Window{Label: strings.TrimSpace(label)}
if w.Label == "" {
w.Label = "24h"
}
switch strings.ToLower(strings.TrimSpace(label)) {
case "", "24h", "1d", "day", "today":
w.Start, w.End, w.Interval = now.Add(-24*time.Hour), now, Hour
case "7d", "week":
w.Start, w.End, w.Interval = now.Add(-7*24*time.Hour), now, Day
case "30d", "month":
w.Start, w.End, w.Interval = now.Add(-30*24*time.Hour), now, Day
case "custom":
from, err := parseTime(start)
if err != nil {
return Window{}, fmt.Errorf("custom range requires a valid start: %w", err)
}
to := now
if strings.TrimSpace(end) != "" {
if to, err = parseTime(end); err != nil {
return Window{}, fmt.Errorf("custom range has an invalid end: %w", err)
}
}
if !to.After(from) {
return Window{}, fmt.Errorf("custom range end must be after start")
}
w.Start, w.End, w.Interval = from.UTC(), to.UTC(), Hour
if w.End.Sub(w.Start) > 48*time.Hour {
w.Interval = Day
}
default:
return Window{}, fmt.Errorf("unknown range %q", label)
}
return w, nil
}
// parseTime accepts RFC3339 or a unix-seconds string.
func parseTime(s string) (time.Time, error) {
s = strings.TrimSpace(s)
if s == "" {
return time.Time{}, fmt.Errorf("empty time")
}
if t, err := time.Parse(time.RFC3339, s); err == nil {
return t.UTC(), nil
}
if secs, err := strconv.ParseInt(s, 10, 64); err == nil && secs > 0 {
return time.Unix(secs, 0).UTC(), nil
}
return time.Time{}, fmt.Errorf("not RFC3339 or unix seconds: %q", s)
}
+289
View File
@@ -0,0 +1,289 @@
package types
import (
"fmt"
"strconv"
"strings"
"testing"
"time"
)
var now = time.Date(2026, 7, 26, 15, 4, 5, 0, time.UTC)
func TestParseWindowRelativeLabels(t *testing.T) {
for _, tc := range []struct {
label string
back time.Duration
want Interval
}{
{"", 24 * time.Hour, Hour},
{"24h", 24 * time.Hour, Hour},
{"1d", 24 * time.Hour, Hour},
{"day", 24 * time.Hour, Hour},
{"today", 24 * time.Hour, Hour},
{"7d", 7 * 24 * time.Hour, Day},
{"week", 7 * 24 * time.Hour, Day},
{"30d", 30 * 24 * time.Hour, Day},
{"month", 30 * 24 * time.Hour, Day},
{" 7D ", 7 * 24 * time.Hour, Day},
} {
t.Run(strings.TrimSpace(tc.label), func(t *testing.T) {
w, err := ParseWindow(tc.label, "", "", now)
if err != nil {
t.Fatalf("ParseWindow(%q) errored: %v", tc.label, err)
}
if !w.End.Equal(now) {
t.Errorf("End = %v, want %v", w.End, now)
}
if !w.Start.Equal(now.Add(-tc.back)) {
t.Errorf("Start = %v, want %v", w.Start, now.Add(-tc.back))
}
if w.Interval != tc.want {
t.Errorf("Interval = %q, want %q", w.Interval, tc.want)
}
})
}
}
// The label a response echoes is the caller's, defaulted when absent. Every
// caller used to re-derive this after the call; the window now carries it.
func TestParseWindowLabelIsDefaultedNotCanonicalised(t *testing.T) {
for _, tc := range []struct{ in, want string }{
{"", "24h"},
{" ", "24h"},
{"7d", "7d"},
{" 30d ", "30d"},
{"7D", "7D"},
{"custom", "custom"},
} {
in := tc.in
start := ""
if strings.TrimSpace(in) == "custom" {
start = "2026-07-01T00:00:00Z"
}
w, err := ParseWindow(in, start, "", now)
if err != nil {
t.Fatalf("ParseWindow(%q) errored: %v", in, err)
}
if w.Label != tc.want {
t.Errorf("ParseWindow(%q).Label = %q, want %q", in, w.Label, tc.want)
}
}
}
func TestParseWindowCustom(t *testing.T) {
t.Run("rfc3339 under 48h buckets hourly", func(t *testing.T) {
w, err := ParseWindow("custom", "2026-07-25T00:00:00Z", "2026-07-26T00:00:00Z", now)
if err != nil {
t.Fatalf("errored: %v", err)
}
if w.Interval != Hour {
t.Errorf("Interval = %q, want %q", w.Interval, Hour)
}
if !w.Start.Equal(time.Date(2026, 7, 25, 0, 0, 0, 0, time.UTC)) {
t.Errorf("Start = %v", w.Start)
}
})
t.Run("over 48h buckets daily", func(t *testing.T) {
w, err := ParseWindow("custom", "2026-07-01T00:00:00Z", "2026-07-26T00:00:00Z", now)
if err != nil {
t.Fatalf("errored: %v", err)
}
if w.Interval != Day {
t.Errorf("Interval = %q, want %q", w.Interval, Day)
}
})
t.Run("exactly 48h stays hourly", func(t *testing.T) {
w, err := ParseWindow("custom", "2026-07-24T00:00:00Z", "2026-07-26T00:00:00Z", now)
if err != nil {
t.Fatalf("errored: %v", err)
}
if w.Interval != Hour {
t.Errorf("Interval = %q, want %q", w.Interval, Hour)
}
})
t.Run("unix seconds accepted", func(t *testing.T) {
w, err := ParseWindow("custom", "1753401600", "1753488000", now)
if err != nil {
t.Fatalf("errored: %v", err)
}
if w.Start.Location() != time.UTC || w.End.Location() != time.UTC {
t.Errorf("bounds must be UTC, got %v/%v", w.Start.Location(), w.End.Location())
}
})
t.Run("absent end defaults to now", func(t *testing.T) {
w, err := ParseWindow("custom", "2026-07-25T00:00:00Z", "", now)
if err != nil {
t.Fatalf("errored: %v", err)
}
if !w.End.Equal(now) {
t.Errorf("End = %v, want %v", w.End, now)
}
})
}
// A rejected window must not hand back a zero Window a caller could mistake for
// a valid range.
func TestParseWindowRejects(t *testing.T) {
for _, tc := range []struct{ name, label, start, end, wantErr string }{
{"unknown label", "yesterday", "", "", `unknown range "yesterday"`},
{"unknown label keeps caller casing", "LastWeek", "", "", `unknown range "LastWeek"`},
{"custom without start", "custom", "", "", "custom range requires a valid start: empty time"},
{"custom bad start", "custom", "nonsense", "", `custom range requires a valid start: not RFC3339 or unix seconds: "nonsense"`},
{"custom bad end", "custom", "2026-07-01T00:00:00Z", "nope", `custom range has an invalid end: not RFC3339 or unix seconds: "nope"`},
{"custom end before start", "custom", "2026-07-26T00:00:00Z", "2026-07-01T00:00:00Z", "custom range end must be after start"},
{"custom end equals start", "custom", "2026-07-01T00:00:00Z", "2026-07-01T00:00:00Z", "custom range end must be after start"},
{"custom non-positive unix start", "custom", "0", "", `custom range requires a valid start: not RFC3339 or unix seconds: "0"`},
} {
t.Run(tc.name, func(t *testing.T) {
w, err := ParseWindow(tc.label, tc.start, tc.end, now)
if err == nil {
t.Fatalf("want error, got window %+v", w)
}
if err.Error() != tc.wantErr {
t.Errorf("error = %q, want %q", err.Error(), tc.wantErr)
}
if w != (Window{}) {
t.Errorf("rejected window must be zero, got %+v", w)
}
})
}
}
func TestIntervalStep(t *testing.T) {
if got := Hour.Step(); got != time.Hour {
t.Errorf("Hour.Step() = %v, want %v", got, time.Hour)
}
if got := Day.Step(); got != 24*time.Hour {
t.Errorf("Day.Step() = %v, want %v", got, 24*time.Hour)
}
}
// ParseWindow only ever yields Hour or Day, so a caller interpolating the
// interval into a query has no third case to guard against.
func TestParseWindowOnlyYieldsTheClosedSet(t *testing.T) {
labels := []string{"", "24h", "1d", "day", "today", "7d", "week", "30d", "month", "custom"}
for _, l := range labels {
start := ""
if l == "custom" {
start = "2026-07-01T00:00:00Z"
}
w, err := ParseWindow(l, start, "", now)
if err != nil {
t.Fatalf("ParseWindow(%q) errored: %v", l, err)
}
if w.Interval != Hour && w.Interval != Day {
t.Fatalf("ParseWindow(%q) yielded interval %q outside {hour, day}", l, w.Interval)
}
}
}
// resolveLegacy is the implementation this package replaced, kept verbatim as
// the oracle the parity test compares against.
func resolveLegacy(rangeLabel, startStr, endStr string, now time.Time) (start, end time.Time, interval string, err error) {
parseParam := func(s string) (time.Time, error) {
s = strings.TrimSpace(s)
if s == "" {
return time.Time{}, fmt.Errorf("empty time")
}
if t, err := time.Parse(time.RFC3339, s); err == nil {
return t.UTC(), nil
}
if secs, err := strconv.ParseInt(s, 10, 64); err == nil && secs > 0 {
return time.Unix(secs, 0).UTC(), nil
}
return time.Time{}, fmt.Errorf("not RFC3339 or unix seconds: %q", s)
}
now = now.UTC()
switch strings.ToLower(strings.TrimSpace(rangeLabel)) {
case "", "24h", "1d", "day", "today":
return now.Add(-24 * time.Hour), now, "hour", nil
case "7d", "week":
return now.Add(-7 * 24 * time.Hour), now, "day", nil
case "30d", "month":
return now.Add(-30 * 24 * time.Hour), now, "day", nil
case "custom":
start, err = parseParam(startStr)
if err != nil {
return time.Time{}, time.Time{}, "", fmt.Errorf("custom range requires a valid start: %w", err)
}
if strings.TrimSpace(endStr) == "" {
end = now
} else if end, err = parseParam(endStr); err != nil {
return time.Time{}, time.Time{}, "", fmt.Errorf("custom range has an invalid end: %w", err)
}
if !end.After(start) {
return time.Time{}, time.Time{}, "", fmt.Errorf("custom range end must be after start")
}
interval = "hour"
if end.Sub(start) > 48*time.Hour {
interval = "day"
}
return start.UTC(), end.UTC(), interval, nil
default:
return time.Time{}, time.Time{}, "", fmt.Errorf("unknown range %q", rangeLabel)
}
}
// Bounds, interval and error text must match the replaced implementation
// exactly: these values reach a live dashboard and a 400 body.
func TestParseWindowMatchesTheReplacedImplementation(t *testing.T) {
labels := []string{
"", " ", "24h", "1d", "day", "today", "7d", "week", "30d", "month",
"24H", " 7d ", "custom", "CUSTOM", "yesterday", "1h", "all",
}
bounds := []string{
"", " ", "2026-07-01T00:00:00Z", "2026-07-25T12:00:00Z", "2026-07-26T00:00:00Z",
"1753401600", "0", "-5", "nonsense", "2026-07-01",
}
cases := 0
for _, l := range labels {
for _, s := range bounds {
for _, e := range bounds {
cases++
w, gotErr := ParseWindow(l, s, e, now)
wantStart, wantEnd, wantInterval, wantErr := resolveLegacy(l, s, e, now)
switch {
case gotErr == nil && wantErr != nil:
t.Fatalf("ParseWindow(%q,%q,%q) succeeded; legacy errored %v", l, s, e, wantErr)
case gotErr != nil && wantErr == nil:
t.Fatalf("ParseWindow(%q,%q,%q) errored %v; legacy succeeded", l, s, e, gotErr)
case gotErr != nil:
if gotErr.Error() != wantErr.Error() {
t.Fatalf("ParseWindow(%q,%q,%q) error = %q, legacy = %q", l, s, e, gotErr, wantErr)
}
continue
}
if !w.Start.Equal(wantStart) {
t.Fatalf("ParseWindow(%q,%q,%q) Start = %v, legacy = %v", l, s, e, w.Start, wantStart)
}
if !w.End.Equal(wantEnd) {
t.Fatalf("ParseWindow(%q,%q,%q) End = %v, legacy = %v", l, s, e, w.End, wantEnd)
}
if string(w.Interval) != wantInterval {
t.Fatalf("ParseWindow(%q,%q,%q) Interval = %q, legacy = %q", l, s, e, w.Interval, wantInterval)
}
if got, want := w.Interval.Step(), legacyStep(wantInterval); got != want {
t.Fatalf("Step(%q) = %v, legacy = %v", w.Interval, got, want)
}
}
}
}
t.Logf("compared %d label/start/end combinations against the replaced implementation", cases)
}
// legacyStep is the bucket-width helper this package replaced.
func legacyStep(interval string) time.Duration {
if strings.EqualFold(interval, "day") {
return 24 * time.Hour
}
return time.Hour
}