···11+package safety
22+33+import "reflect"
44+55+func isNil[T any](t T) bool {
66+ v := reflect.ValueOf(t)
77+ kind := v.Kind()
88+ // Must be one of these types to be nillable
99+ return (kind == reflect.Pointer ||
1010+ kind == reflect.Interface ||
1111+ kind == reflect.Slice ||
1212+ kind == reflect.Map ||
1313+ kind == reflect.Chan ||
1414+ kind == reflect.Func) &&
1515+ v.IsNil()
1616+}
1717+1818+// Optional[T] represents a value that may or may not be present. It is the
1919+// safe equivalent of sql.NullString, sql.NullInt64, etc. — keeping DB
2020+// implementation details out of the domain layer.
2121+//
2222+// Use None[T]() for an absent value and Some(v) for a present one.
2323+type Optional[T any] struct {
2424+ Value T
2525+ Valid bool
2626+}
2727+2828+// None returns an absent Optional[T].
2929+func None[T any]() Optional[T] {
3030+ return Optional[T]{}
3131+}
3232+3333+// Some returns a present Optional[T] wrapping v.
3434+func Some[T any](v T) Optional[T] {
3535+ if isNil(v) {
3636+ panic("valid optional should never be nil")
3737+ }
3838+ return Optional[T]{Value: v, Valid: true}
3939+}
4040+4141+// Get returns the underlying value and whether it is present.
4242+func (o Optional[T]) Get() (T, bool) {
4343+ return o.Value, o.Valid
4444+}
4545+4646+// Or returns the underlying value if present, otherwise the provided default.
4747+func (o Optional[T]) Or(def T) T {
4848+ if o.Valid {
4949+ return o.Value
5050+ }
5151+ return def
5252+}
5353+5454+// Ptr[T] is a non-nullable pointer. Constructing one with a nil pointer panics,
5555+// making the non-null guarantee explicit and visible in the type itself — as
5656+// opposed to *T, which is silently nullable.
5757+//
5858+// Use MakePtr(v) to create one from a value, or NewPtr(p) from a pointer you
5959+// know to be non-nil.
6060+type Ptr[T any] struct {
6161+ v *T
6262+}
6363+6464+// NewPtr wraps an existing pointer. Panics if p is nil.
6565+func NewPtr[T any](p *T) Ptr[T] {
6666+ if p == nil {
6767+ panic("safety.NewPtr: nil pointer")
6868+ }
6969+ return Ptr[T]{v: p}
7070+}
7171+7272+// MakePtr stores the address of a copy of v and is always non-nil.
7373+func MakePtr[T any](v T) Ptr[T] {
7474+ return Ptr[T]{v: &v}
7575+}
7676+7777+// Get returns the underlying non-nil pointer.
7878+func (p Ptr[T]) Get() *T {
7979+ return p.v
8080+}
+113
pkg/common/safety/safety_test.go
···11+package safety
22+33+import (
44+ "testing"
55+)
66+77+// ── Optional ─────────────────────────────────────────────────────────────────
88+99+func TestNone(t *testing.T) {
1010+ o := None[int]()
1111+ if o.Valid {
1212+ t.Error("None: expected Valid = false")
1313+ }
1414+ var zero int
1515+ if o.Value != zero {
1616+ t.Errorf("None: expected zero value, got %v", o.Value)
1717+ }
1818+}
1919+2020+func TestSome(t *testing.T) {
2121+ o := Some(42)
2222+ if !o.Valid {
2323+ t.Error("Some: expected Valid = true")
2424+ }
2525+ if o.Value != 42 {
2626+ t.Errorf("Some: expected 42, got %v", o.Value)
2727+ }
2828+}
2929+3030+func TestSomePanicsOnNil(t *testing.T) {
3131+ defer func() {
3232+ if r := recover(); r == nil {
3333+ t.Error("Some(nil) should panic")
3434+ }
3535+ }()
3636+ var p *int
3737+ _ = Some(p)
3838+}
3939+4040+func TestSomeDoesNotPanicOnNonNilPointer(t *testing.T) {
4141+ v := 42
4242+ p := &v
4343+ defer func() {
4444+ if r := recover(); r != nil {
4545+ t.Errorf("Some(non-nil) should not panic, got %v", r)
4646+ }
4747+ }()
4848+ o := Some(p)
4949+ if !o.Valid || o.Value != p {
5050+ t.Error("expected valid optional wrapping pointer")
5151+ }
5252+}
5353+5454+func TestOptionalGet(t *testing.T) {
5555+ present := Some("hello")
5656+ v, ok := present.Get()
5757+ if !ok || v != "hello" {
5858+ t.Errorf("Get present: got (%q, %v), want (\"hello\", true)", v, ok)
5959+ }
6060+6161+ absent := None[string]()
6262+ v, ok = absent.Get()
6363+ if ok || v != "" {
6464+ t.Errorf("Get absent: got (%q, %v), want (\"\", false)", v, ok)
6565+ }
6666+}
6767+6868+func TestOptionalOr(t *testing.T) {
6969+ present := Some(7)
7070+ if got := present.Or(99); got != 7 {
7171+ t.Errorf("Or present: got %d, want 7", got)
7272+ }
7373+7474+ absent := None[int]()
7575+ if got := absent.Or(99); got != 99 {
7676+ t.Errorf("Or absent: got %d, want 99", got)
7777+ }
7878+}
7979+8080+// ── Ptr ──────────────────────────────────────────────────────────────────────
8181+8282+func TestNewPtr(t *testing.T) {
8383+ v := 42
8484+ p := NewPtr(&v)
8585+ if *p.Get() != 42 {
8686+ t.Errorf("NewPtr: got %d, want 42", *p.Get())
8787+ }
8888+}
8989+9090+func TestNewPtrPanicsOnNil(t *testing.T) {
9191+ defer func() {
9292+ if r := recover(); r == nil {
9393+ t.Error("NewPtr(nil) should panic")
9494+ }
9595+ }()
9696+ var p *int
9797+ _ = NewPtr(p)
9898+}
9999+100100+func TestMakePtr(t *testing.T) {
101101+ p := MakePtr(42)
102102+ if *p.Get() != 42 {
103103+ t.Errorf("MakePtr: got %d, want 42", *p.Get())
104104+ }
105105+}
106106+107107+func TestMakePtrIndependentCopies(t *testing.T) {
108108+ p1 := MakePtr(1)
109109+ p2 := MakePtr(2)
110110+ if *p1.Get() != 1 || *p2.Get() != 2 {
111111+ t.Error("MakePtr copies should be independent")
112112+ }
113113+}