diff options
| author | Stefan Majewsky <majewsky@gmx.net> | 2025-07-02 17:18:25 +0200 |
|---|---|---|
| committer | Stefan Majewsky <majewsky@gmx.net> | 2025-07-02 17:30:30 +0200 |
| commit | 876b3b930dc8c90391082ee5b629f847fbb5d337 (patch) | |
| tree | d4ce8504fdeced6ff9ea802ae777269d05fc3943 /refined | |
| parent | e95f01274201b6fde1c1ea7818c7a7e0a77f0f43 (diff) | |
| download | go-gg-refinement-types-3.tar.gz | |
refined: UnmarshalJSON needs a PreScalar type after allrefinement-types-3
Diffstat (limited to 'refined')
| -rw-r--r-- | refined/struct.go | 24 | ||||
| -rw-r--r-- | refined/struct_test.go | 38 | ||||
| -rw-r--r-- | refined/value.go | 70 | ||||
| -rw-r--r-- | refined/value_test.go | 8 |
4 files changed, 85 insertions, 55 deletions
diff --git a/refined/struct.go b/refined/struct.go index 35a0c85..ee34f0f 100644 --- a/refined/struct.go +++ b/refined/struct.go @@ -3,10 +3,26 @@ package refined -type Struct[P any] struct { - Has P +import "encoding/json" + +type Struct[T any] struct { + Has T +} + +func NewStruct[T any](attributes T) Struct[T] { + return Struct[T]{Has: attributes} } -func NewStruct[P any](payload P) Struct[P] { - return Struct[P]{Has: payload} +func (s Struct[T]) MarshalJSON() ([]byte, error) { + return json.Marshal(s.Has) +} + +func (s *Struct[T]) UnmarshalJSON(buf []byte) error { + err := json.Unmarshal(buf, &s.Has) + if err != nil { + return err + } + + // TODO reflect on the fields of s.Has; if any are refined.Value that are not occupied, attempt to fill the zero value through Refine() + return nil } diff --git a/refined/struct_test.go b/refined/struct_test.go index b07b8a5..ac4b667 100644 --- a/refined/struct_test.go +++ b/refined/struct_test.go @@ -4,8 +4,8 @@ package refined_test import ( + "encoding/json" "fmt" - "math" "regexp" "testing" @@ -17,9 +17,9 @@ type AccountID struct { refined.Scalar[AccountID, uint64] } -func (AccountID) Refine(c refined.Challenge[AccountID, uint64]) (AccountID, error) { - s, err := refined.RangeCheck(c, 1, math.MaxUint64) - return AccountID{s}, err +func (AccountID) RefinedMatch(value uint64) bool { return value > 0 } +func (AccountID) RefinedBuild(p refined.PreScalar[AccountID, uint64]) AccountID { + return AccountID{p.Into()} } type AccountName struct { @@ -28,27 +28,35 @@ type AccountName struct { var accountNameRx = regexp.MustCompile(`^[a-z][a-z0-9_]*$`) -func (AccountName) Refine(c refined.Challenge[AccountName, string]) (AccountName, error) { - s, err := refined.RegexpCheck(c, accountNameRx) - return AccountName{s}, err +func (AccountName) RefinedMatch(value string) bool { return accountNameRx.MatchString(value) } +func (AccountName) RefinedBuild(p refined.PreScalar[AccountName, string]) AccountName { + return AccountName{p.Into()} } -type AccountInfo = refined.Struct[AccountInfoPayload] - -type AccountInfoPayload struct { - ID AccountID `json:"id,omitzero"` - Name AccountName `json:"name,omitzero"` +type AccountInfo = refined.Struct[AccountInfoAttributes] +type AccountInfoAttributes struct { + ID AccountID `json:"id"` + Name AccountName `json:"name"` } -func (p AccountInfoPayload) ReadableName() string { +func (p AccountInfoAttributes) ReadableName() string { return fmt.Sprintf("%s (ID %d)", p.Name.Raw(), p.ID.Raw()) } func TestAccountInfo(t *testing.T) { - var info AccountInfo //nolint:staticcheck - info = refined.NewStruct(AccountInfoPayload{ + var info AccountInfo + err := json.Unmarshal([]byte(`{"id":42,"name":"foo"}`), &info) + AssertEqual(t, err, nil) + AssertEqual(t, info.Has.ID.Raw(), 42) + AssertEqual(t, info.Has.Name.Raw(), "foo") + + info = refined.NewStruct(AccountInfoAttributes{ ID: refined.Literal[AccountID](53), }) info.Has.Name = refined.Literal[AccountName]("hello") AssertEqual(t, info.Has.ReadableName(), "hello (ID 53)") + + buf, err := json.Marshal(info) + AssertEqual(t, err, nil) + AssertEqual(t, string(buf), `{"id":53,"name":"hello"}`) } diff --git a/refined/value.go b/refined/value.go index cfaa412..a15e4f1 100644 --- a/refined/value.go +++ b/refined/value.go @@ -4,9 +4,8 @@ package refined import ( - "cmp" + "encoding/json" "errors" - "regexp" . "github.com/majewsky/gg/option" ) @@ -21,52 +20,61 @@ func (s Scalar[S, V]) Raw() V { func New[S IsAScalar[S, V], V any](value V) (S, error) { var empty S - return empty.Refine(Challenge[S, V]{Value: value, valid: true}) + if empty.RefinedMatch(value) { + return empty.RefinedBuild(PreScalar[S, V]{value: Some(value)}), nil + } else { + return empty, errors.New("TODO 2") + } } func Literal[S IsAScalar[S, V], V any](value V) S { - var empty S - s, err := empty.Refine(Challenge[S, V]{Value: value, valid: true}) + s, err := New[S, V](value) if err != nil { - panic("TODO 2") + panic(err.Error()) } return s } -type IsAScalar[S any, V any] interface { - Refine(Challenge[S, V]) (S, error) +func (s Scalar[S, V]) MarshalJSON() ([]byte, error) { + return json.Marshal(s.Raw()) } -type Challenge[S any, V any] struct { - Value V - valid bool -} +func (s *Scalar[S, V]) UnmarshalJSON(buf []byte) error { + var value V + err := json.Unmarshal(buf, &value) + if err != nil { + return err + } -func (c Challenge[S, V]) Accept() Scalar[S, V] { - if !c.valid { - panic("broken Challenge object") + // We cannot directly call `New[S, V](value)` here because we cannot prove statically + // that `S` satisfies `IsAScalar[S, V]`. + var empty S + if r, ok := any(empty).(IsAScalar[S, V]); ok { + if r.RefinedMatch(value) { + *s = Scalar[S, V]{value: Some(value)} + return nil + } else { + return errors.New("TODO 3") + } + } else { + return errors.New("TODO 4") } - return Scalar[S, V]{value: Some(c.Value)} } -func RangeCheck[S any, V cmp.Ordered](c Challenge[S, V], minimum, maximum V) (Scalar[S, V], error) { - if minimum <= maximum && c.Value >= minimum && c.Value <= maximum { - return c.Accept(), nil - } - return Scalar[S, V]{}, errors.New("TODO 3") +type IsAScalar[S any, V any] interface { + // We need both steps separately. New() wants to have an S at the end, which goes through both steps. + // But Unmarshal...() wants to obtain a Scalar[S, V], so it only wants to use the first step. + RefinedMatch(V) bool + RefinedBuild(PreScalar[S, V]) S } -func RegexpCheck[S any, V ~string](c Challenge[S, V], rx *regexp.Regexp) (Scalar[S, V], error) { - if rx.MatchString(string(c.Value)) { - return c.Accept(), nil - } - return Scalar[S, V]{}, errors.New("TODO 4") +type PreScalar[S any, V any] struct { + value Option[V] } -func NotZeroCheck[S any, V comparable](c Challenge[S, V]) (Scalar[S, V], error) { - var zero V - if c.Value != zero { - return c.Accept(), nil +func (p PreScalar[S, V]) Into() Scalar[S, V] { + if p.value.IsNone() { + panic("broken PreScalar object") } - return Scalar[S, V]{}, errors.New("TODO 5") + return Scalar[S, V](p) } diff --git a/refined/value_test.go b/refined/value_test.go index aaf3706..9d3db11 100644 --- a/refined/value_test.go +++ b/refined/value_test.go @@ -14,10 +14,8 @@ type DiceRoll struct { refined.Scalar[DiceRoll, int] } -func (DiceRoll) Refine(c refined.Challenge[DiceRoll, int]) (DiceRoll, error) { - s, err := refined.RangeCheck(c, 1, 6) - return DiceRoll{s}, err -} +func (DiceRoll) RefinedMatch(value int) bool { return value >= 1 && value <= 6 } +func (DiceRoll) RefinedBuild(p refined.PreScalar[DiceRoll, int]) DiceRoll { return DiceRoll{p.Into()} } func TestDiceRoll(t *testing.T) { d := refined.Literal[DiceRoll](5) @@ -26,5 +24,5 @@ func TestDiceRoll(t *testing.T) { var err error d, err = refined.New[DiceRoll](7) AssertEqual(t, d, DiceRoll{}) - AssertEqual(t, err.Error(), "TODO 3") + AssertEqual(t, err.Error(), "TODO 2") } |
