From 945c5da63191b90bfcd9086e56d1f0efd72d4c68 Mon Sep 17 00:00:00 2001 From: Stefan Majewsky Date: Thu, 20 Feb 2025 22:53:53 +0100 Subject: refined: reduce boilerplate, add Literal() constructor --- refined/builder.go | 35 +++++++++++++++++++++++++++++++++++ refined/condition.go | 24 ------------------------ refined/refined_test.go | 18 +++++++++--------- refined/value.go | 46 +++++++++++++++++++++++++++++++++------------- 4 files changed, 77 insertions(+), 46 deletions(-) create mode 100644 refined/builder.go delete mode 100644 refined/condition.go (limited to 'refined') diff --git a/refined/builder.go b/refined/builder.go new file mode 100644 index 0000000..7744320 --- /dev/null +++ b/refined/builder.go @@ -0,0 +1,35 @@ +/******************************************************************************* +* Copyright 2025 Stefan Majewsky +* SPDX-License-Identifier: Apache-2.0 +* Refer to the file "LICENSE" for details. +*******************************************************************************/ + +package refined + +import ( + "errors" + "regexp" +) + +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) { + return errors.New("TODO: error message") + } + return nil +} diff --git a/refined/condition.go b/refined/condition.go deleted file mode 100644 index af3b12c..0000000 --- a/refined/condition.go +++ /dev/null @@ -1,24 +0,0 @@ -/******************************************************************************* -* Copyright 2025 Stefan Majewsky -* SPDX-License-Identifier: Apache-2.0 -* Refer to the file "LICENSE" for details. -*******************************************************************************/ - -package refined - -import ( - "errors" - "regexp" -) - -type Condition[T any] interface { - MatchesValue(T) error -} - -// Building block for writing MatchesValue() implementations. -func RegexpMatch(rx *regexp.Regexp, value string) error { - if !rx.MatchString(value) { - return errors.New("TODO: error message") - } - return nil -} 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()) } -- cgit v1.2.3