aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2025-07-02 17:18:25 +0200
committerStefan Majewsky <majewsky@gmx.net>2025-07-02 17:30:30 +0200
commit876b3b930dc8c90391082ee5b629f847fbb5d337 (patch)
treed4ce8504fdeced6ff9ea802ae777269d05fc3943
parente95f01274201b6fde1c1ea7818c7a7e0a77f0f43 (diff)
downloadgo-gg-876b3b930dc8c90391082ee5b629f847fbb5d337.tar.gz
refined: UnmarshalJSON needs a PreScalar type after allrefinement-types-3
-rw-r--r--refined/struct.go24
-rw-r--r--refined/struct_test.go38
-rw-r--r--refined/value.go70
-rw-r--r--refined/value_test.go8
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")
}