aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2025-02-20 22:53:53 +0100
committerStefan Majewsky <majewsky@gmx.net>2025-02-20 22:54:46 +0100
commit945c5da63191b90bfcd9086e56d1f0efd72d4c68 (patch)
tree8645f26fc0d86b4c72de01b267b7186b25a78147
parent676fba2564431dfc8138bc4f08e503d9c6cadfb0 (diff)
downloadgo-gg-945c5da63191b90bfcd9086e56d1f0efd72d4c68.tar.gz
refined: reduce boilerplate, add Literal() constructor
-rw-r--r--refined/builder.go (renamed from refined/condition.go)13
-rw-r--r--refined/refined_test.go18
-rw-r--r--refined/value.go46
3 files changed, 54 insertions, 23 deletions
diff --git a/refined/condition.go b/refined/builder.go
index af3b12c..7744320 100644
--- a/refined/condition.go
+++ b/refined/builder.go
@@ -11,10 +11,21 @@ import (
"regexp"
)
-type Condition[T any] interface {
+type Builder[T any, V any] interface {
MatchesValue(T) error
+ Build(T, Verification) V
}
+type Verification interface {
+ isVerification(verificationSeal)
+}
+
+type verification struct{}
+
+type verificationSeal struct{}
+
+func (verification) isVerification(_ verificationSeal) {}
+
// Building block for writing MatchesValue() implementations.
func RegexpMatch(rx *regexp.Regexp, value string) error {
if !rx.MatchString(value) {
diff --git a/refined/refined_test.go b/refined/refined_test.go
index c6e98e8..44f8a7c 100644
--- a/refined/refined_test.go
+++ b/refined/refined_test.go
@@ -20,7 +20,7 @@ var accountNameRx = regexp.MustCompile(`^[a-z_][a-z0-9_]*$`)
// Full demonstration of a refinement type for the test.
type AccountName struct {
- refined.Value[AccountName, string]
+ refined.Value[AccountName, AccountName, string]
}
// Demonstration of a struct containing a refinement type.
@@ -28,19 +28,19 @@ type AccountData struct {
Name AccountName
}
-func NewAccountName(value string) (AccountName, error) {
- v, err := refined.NewValue[AccountName](value)
- return AccountName{v}, err
-}
-
-// MatchesValue implements the refined.Condition interface.
+// MatchesValue implements the refined.Builder interface.
func (AccountName) MatchesValue(value string) error {
return refined.RegexpMatch(accountNameRx, value)
}
+// Build implements the refined.Builder interface.
+func (AccountName) Build(value string, v refined.Verification) AccountName {
+ return AccountName{refined.Build[AccountName](value, v)}
+}
+
// Example for how to access the contained value in computations.
func (n AccountName) ContainerName() string {
- return fmt.Sprintf("container-for-%s", n.Get())
+ return fmt.Sprintf("container-for-%s", n.Raw())
}
func TestAccountName(t *testing.T) {
@@ -48,7 +48,7 @@ func TestAccountName(t *testing.T) {
var d1 AccountData
err := json.Unmarshal(buf1, &d1)
AssertEqual(t, err, error(nil))
- AssertEqual(t, d1.Name.Get(), "foo")
+ AssertEqual(t, d1.Name.Raw(), "foo")
// TODO: fails because we need specialized unmarshaling logic on type AccountData
buf2 := []byte(`{}`)
diff --git a/refined/value.go b/refined/value.go
index f9cb626..15e9bc1 100644
--- a/refined/value.go
+++ b/refined/value.go
@@ -12,36 +12,56 @@ import (
. "github.com/majewsky/gg/option"
)
-// TODO: how do we express a literal constructor, preferably in a generic way? e.g. `var demoAccountName = refined.Literal[AccountName]("demo")`
-
// NOTE: The zero value is illegal and will panic on use.
-type Value[Self Condition[T], T any] struct {
+type Value[B Builder[T, V], V any, T any] struct {
value Option[T]
}
-func NewValue[C Condition[T], T any](value T) (Value[C, T], error) {
- var c C
- err := c.MatchesValue(value)
+func Build[B Builder[T, V], V any, T any](value T, _ Verification) Value[B, V, T] {
+ return Value[B, V, T]{Some(value)}
+}
+
+func New[B Builder[T, V], V any, T any](value T) (V, error) {
+ var b B
+ err := b.MatchesValue(value)
+ if err != nil {
+ var empty V
+ return empty, err
+ }
+ return b.Build(value, verification{}), nil
+}
+
+func Literal[B Builder[T, V], V any, T any](value T) V {
+ result, err := New[B](value)
+ if err != nil {
+ panic(err.Error())
+ }
+ return result
+}
+
+func newValue[B Builder[T, V], V any, T any](value T) (Value[B, V, T], error) {
+ var b B
+ err := b.MatchesValue(value)
if err != nil {
- return Value[C, T]{None[T]()}, err
+ return Value[B, V, T]{None[T]()}, err
}
- return Value[C, T]{Some(value)}, nil
+ return Value[B, V, T]{Some(value)}, nil
}
-func (v Value[Self, T]) Get() T {
+func (v Value[B, V, T]) Raw() T {
return v.value.UnwrapOrPanic("illegal use of zero-valued instance of Refined type")
}
-func (v *Value[Self, T]) UnmarshalJSON(buf []byte) error {
+func (v *Value[B, V, T]) UnmarshalJSON(buf []byte) error {
var value T
err := json.Unmarshal(buf, &value)
if err != nil {
return err
}
- *v, err = NewValue[Self](value)
+ *v, err = newValue[B, V, T](value)
return err
}
-func (v Value[Self, T]) MarshalJSON() ([]byte, error) {
- return json.Marshal(v.Get())
+func (v Value[B, V, T]) MarshalJSON() ([]byte, error) {
+ return json.Marshal(v.Raw())
}