diff options
Diffstat (limited to 'refined')
| -rw-r--r-- | refined/refined.go | 59 | ||||
| -rw-r--r-- | refined/refined_test.go | 58 |
2 files changed, 117 insertions, 0 deletions
diff --git a/refined/refined.go b/refined/refined.go new file mode 100644 index 0000000..b40fd34 --- /dev/null +++ b/refined/refined.go @@ -0,0 +1,59 @@ +/******************************************************************************* +* Copyright 2025 Stefan Majewsky <majewsky@gmx.net> +* SPDX-License-Identifier: Apache-2.0 +* Refer to the file "LICENSE" for details. +*******************************************************************************/ + +package refined + +import ( + "encoding/json" + "errors" + "regexp" + + . "github.com/majewsky/gg/option" +) + +// NOTE: The zero value is illegal and will panic on use. +type Refined[Self Condition[T], T any] struct { + value Option[T] +} + +type Condition[T any] interface { + MatchesValue(T) error +} + +func Refine[C Condition[T], T any](value T) (Refined[C, T], error) { + var c C + err := c.MatchesValue(value) + if err != nil { + return Refined[C, T]{None[T]()}, err + } + return Refined[C, T]{Some(value)}, nil +} + +func (r Refined[Self, T]) GetValue() T { + return r.value.UnwrapOrPanicf("illegal use of zero-valued instance of Refined type") +} + +func (r *Refined[Self, T]) UnmarshalJSON(buf []byte) error { + var value T + err := json.Unmarshal(buf, &value) + if err != nil { + return err + } + *r, err = Refine[Self](value) + return err +} + +func (r Refined[Self, T]) MarshalJSON() ([]byte, error) { + return json.Marshal(r.GetValue()) +} + +// 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 new file mode 100644 index 0000000..8faad1f --- /dev/null +++ b/refined/refined_test.go @@ -0,0 +1,58 @@ +/******************************************************************************* +* Copyright 2025 Stefan Majewsky <majewsky@gmx.net> +* SPDX-License-Identifier: GPL-3.0-only +* refined.Refer to the file "LICENSE" for details. +*******************************************************************************/ + +package refined_test + +import ( + "encoding/json" + "fmt" + "regexp" + "testing" + + . "github.com/majewsky/gg/internal/test" + "github.com/majewsky/gg/refined" +) + +var accountNameRx = regexp.MustCompile(`^[a-z_][a-z0-9_]*$`) + +// Full demonstration of a refinement type for the test. +type AccountName struct { + refined.Refined[AccountName, string] +} + +// Demonstration of a struct containing a refinement type. +type AccountData struct { + Name AccountName +} + +func NewAccountName(value string) (AccountName, error) { + r, err := refined.Refine[AccountName](value) + return AccountName{r}, err +} + +// MatchesValue implements the refined.Condition interface. +func (AccountName) MatchesValue(value string) error { + return refined.RegexpMatch(accountNameRx, value) +} + +// Example for how to access the contained value in computations. +func (n AccountName) ContainerName() string { + return fmt.Sprintf("container-for-%s", n.GetValue()) +} + +func TestAccountName(t *testing.T) { + buf1 := []byte(`{"Name":"foo"}`) + var d1 AccountData + err := json.Unmarshal(buf1, &d1) + AssertEqual(t, err, error(nil)) + AssertEqual(t, d1.Name.GetValue(), "foo") + + // TODO: fails because we need specialized unmarshaling logic on type AccountData + buf2 := []byte(`{}`) + var d2 AccountData + err = json.Unmarshal(buf2, &d2) + AssertEqual(t, err.Error(), "foo") +} |
