mirror of
https://github.com/IBM/fp-go.git
synced 2025-08-10 22:31:32 +02:00
fix: add runtime type validation
Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
@@ -304,3 +304,7 @@ func FoldMap[A, B any](m M.Monoid[B]) func(func(A) B) func([]A) B {
|
||||
func Fold[A any](m M.Monoid[A]) func([]A) A {
|
||||
return G.Fold[[]A](m)
|
||||
}
|
||||
|
||||
func Push[A any](a A) func([]A) []A {
|
||||
return G.Push[[]A](a)
|
||||
}
|
||||
|
@@ -28,6 +28,14 @@ func Of[GA ~[]A, A any](value A) GA {
|
||||
return GA{value}
|
||||
}
|
||||
|
||||
func Reduce[GA ~[]A, A, B any](fa GA, f func(B, A) B, initial B) B {
|
||||
return array.Reduce(fa, f, initial)
|
||||
}
|
||||
|
||||
func ReduceWithIndex[GA ~[]A, A, B any](fa GA, f func(int, B, A) B, initial B) B {
|
||||
return array.ReduceWithIndex(fa, f, initial)
|
||||
}
|
||||
|
||||
// From constructs an array from a set of variadic arguments
|
||||
func From[GA ~[]A, A any](data ...A) GA {
|
||||
return data
|
||||
@@ -230,3 +238,7 @@ func Fold[AS ~[]A, A any](m M.Monoid[A]) func(AS) A {
|
||||
return array.Reduce(as, m.Concat, m.Empty())
|
||||
}
|
||||
}
|
||||
|
||||
func Push[GA ~[]A, A any](a A) func(GA) GA {
|
||||
return F.Bind2nd(array.Push[GA, A], a)
|
||||
}
|
||||
|
@@ -146,8 +146,8 @@ func FromOption[E, A any](onNone func() E) func(O.Option[A]) Either[E, A] {
|
||||
return O.Fold(F.Nullary2(onNone, Left[A, E]), Right[E, A])
|
||||
}
|
||||
|
||||
func ToOption[E, A any]() func(Either[E, A]) O.Option[A] {
|
||||
return Fold(F.Ignore1of1[E](O.None[A]), O.Some[A])
|
||||
func ToOption[E, A any](ma Either[E, A]) O.Option[A] {
|
||||
return MonadFold(ma, F.Ignore1of1[E](O.None[A]), O.Some[A])
|
||||
}
|
||||
|
||||
func FromError[A any](f func(a A) error) func(A) Either[error, A] {
|
||||
|
@@ -42,10 +42,27 @@ func Reduce[GA ~[]A, A, B any](fa GA, f func(B, A) B, initial B) B {
|
||||
return current
|
||||
}
|
||||
|
||||
func ReduceWithIndex[GA ~[]A, A, B any](fa GA, f func(int, B, A) B, initial B) B {
|
||||
current := initial
|
||||
count := len(fa)
|
||||
for i := 0; i < count; i++ {
|
||||
current = f(i, current, fa[i])
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
func Append[GA ~[]A, A any](as GA, a A) GA {
|
||||
return append(as, a)
|
||||
}
|
||||
|
||||
func Push[GA ~[]A, A any](as GA, a A) GA {
|
||||
l := len(as)
|
||||
cpy := make(GA, l+1)
|
||||
copy(cpy, as)
|
||||
cpy[l] = a
|
||||
return cpy
|
||||
}
|
||||
|
||||
func Empty[GA ~[]A, A any]() GA {
|
||||
return make(GA, 0)
|
||||
}
|
||||
|
@@ -32,6 +32,6 @@ func ToTypeE[A any](src any) E.Either[error, A] {
|
||||
func ToTypeO[A any](src any) O.Option[A] {
|
||||
return F.Pipe1(
|
||||
ToTypeE[A](src),
|
||||
E.ToOption[error, A](),
|
||||
E.ToOption[error, A],
|
||||
)
|
||||
}
|
||||
|
38
lazy/example_lazy_test.go
Normal file
38
lazy/example_lazy_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package lazy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
F "github.com/IBM/fp-go/function"
|
||||
)
|
||||
|
||||
func ExampleLazy_creation() {
|
||||
// lazy function of a constant value
|
||||
val := Of(42)
|
||||
// create another function to transform this
|
||||
valS := F.Pipe1(
|
||||
val,
|
||||
Map(strconv.Itoa),
|
||||
)
|
||||
|
||||
fmt.Println(valS())
|
||||
|
||||
// Output:
|
||||
// 42
|
||||
}
|
91
types/array.go
Normal file
91
types/array.go
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
AR "github.com/IBM/fp-go/array/generic"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
T "github.com/IBM/fp-go/tuple"
|
||||
)
|
||||
|
||||
func toUnknownArray[A any](item func(reflect.Value, Context) E.Either[Errors, A], c Context, val reflect.Value) []E.Either[Errors, A] {
|
||||
l := val.Len()
|
||||
res := make([]E.Either[Errors, A], l)
|
||||
for i := l - 1; i >= 0; i-- {
|
||||
v := val.Index(i)
|
||||
res[i] = item(v, AR.Push[Context](&ContextEntry{Key: fmt.Sprintf("[%d]", i), Value: v})(c))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func flattenUnknownArray[GA ~[]A, A any](as []E.Either[Errors, A]) E.Either[Errors, GA] {
|
||||
return F.Pipe1(
|
||||
AR.Reduce(as, func(t T.Tuple2[GA, Errors], item E.Either[Errors, A]) T.Tuple2[GA, Errors] {
|
||||
return E.MonadFold(item, func(e Errors) T.Tuple2[GA, Errors] {
|
||||
return T.MakeTuple2(t.F1, append(t.F2, e...))
|
||||
}, func(a A) T.Tuple2[GA, Errors] {
|
||||
return T.MakeTuple2(append(t.F1, a), t.F2)
|
||||
})
|
||||
}, T.MakeTuple2(make(GA, len(as)), make(Errors, 0))),
|
||||
func(t T.Tuple2[GA, Errors]) E.Either[Errors, GA] {
|
||||
if AR.IsEmpty(t.F2) {
|
||||
return E.Of[Errors](t.F1)
|
||||
}
|
||||
return E.Left[GA](t.F2)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func toValidatedArray[GA ~[]A, A any](item func(reflect.Value, Context) E.Either[Errors, A], c Context, val reflect.Value) E.Either[Errors, GA] {
|
||||
return F.Pipe1(
|
||||
toUnknownArray(item, c, val),
|
||||
flattenUnknownArray[GA, A],
|
||||
)
|
||||
}
|
||||
|
||||
func validateArray[GA ~[]A, A any](item Validate[reflect.Value, A]) func(i reflect.Value, c Context) E.Either[Errors, GA] {
|
||||
var r func(i reflect.Value, c Context) E.Either[Errors, GA]
|
||||
|
||||
r = func(i reflect.Value, c Context) E.Either[Errors, GA] {
|
||||
// check for unknow array
|
||||
switch i.Kind() {
|
||||
case reflect.Slice:
|
||||
return toValidatedArray[GA](item, c, i)
|
||||
case reflect.Array:
|
||||
return toValidatedArray[GA](item, c, i)
|
||||
case reflect.Pointer:
|
||||
return r(i.Elem(), c)
|
||||
default:
|
||||
return Failure[GA](c, fmt.Sprintf("Type %T is neither an array nor a slice nor a pointer to these values", i))
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// ArrayG returns the type validator for an array
|
||||
func ArrayG[GA ~[]A, A any](item Validate[reflect.Value, A]) *Type[GA, GA, reflect.Value] {
|
||||
return FromValidate(validateArray[GA, A](item))
|
||||
}
|
||||
|
||||
// Array returns the type validator for an array
|
||||
func Array[A any](item Validate[reflect.Value, A]) *Type[[]A, []A, reflect.Value] {
|
||||
return ArrayG[[]A, A](item)
|
||||
}
|
41
types/array_test.go
Normal file
41
types/array_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
A "github.com/IBM/fp-go/array"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestArray(t *testing.T) {
|
||||
stringArray := Array(String.Validate)
|
||||
|
||||
validData := A.From(
|
||||
reflect.ValueOf(A.From("a", "b", "c")),
|
||||
reflect.ValueOf(A.Empty[string]()),
|
||||
reflect.ValueOf([]string{"a", "b"}),
|
||||
reflect.ValueOf(A.From(1, 2, 3)),
|
||||
)
|
||||
|
||||
for i := 0; i < len(validData); i++ {
|
||||
assert.True(t, E.IsRight(stringArray.Decode(validData[i])))
|
||||
}
|
||||
|
||||
}
|
16
types/kleisli.go
Normal file
16
types/kleisli.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
57
types/string.go
Normal file
57
types/string.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
E "github.com/IBM/fp-go/either"
|
||||
)
|
||||
|
||||
func validateStringFromReflect(i reflect.Value, c Context) E.Either[Errors, string] {
|
||||
switch i.Kind() {
|
||||
case reflect.String:
|
||||
return E.Of[Errors](i.String())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return E.Of[Errors](strconv.FormatInt(i.Int(), 10))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return E.Of[Errors](strconv.FormatUint(i.Uint(), 10))
|
||||
case reflect.Bool:
|
||||
return E.Of[Errors](strconv.FormatBool(i.Bool()))
|
||||
case reflect.Pointer:
|
||||
return validateStringFromReflect(i.Elem(), c)
|
||||
case reflect.Invalid:
|
||||
return Failure[string](c, "Invalid value")
|
||||
}
|
||||
|
||||
if i.CanInterface() {
|
||||
if strg, ok := i.Interface().(fmt.Stringer); ok {
|
||||
return E.Of[Errors](strg.String())
|
||||
}
|
||||
}
|
||||
|
||||
return E.Of[Errors](i.String())
|
||||
}
|
||||
|
||||
// String returns the type validator for a string
|
||||
func makeString() *Type[string, string, reflect.Value] {
|
||||
return FromValidate(validateStringFromReflect)
|
||||
}
|
||||
|
||||
// converts from any type to string
|
||||
var String = makeString()
|
47
types/string_test.go
Normal file
47
types/string_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
A "github.com/IBM/fp-go/array"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
s := "Carsten"
|
||||
validData := A.From(
|
||||
reflect.ValueOf("Carsten"),
|
||||
reflect.ValueOf(s),
|
||||
reflect.ValueOf(&s),
|
||||
reflect.ValueOf(10),
|
||||
reflect.ValueOf(true),
|
||||
reflect.ValueOf(false),
|
||||
)
|
||||
for i := 0; i < len(validData); i++ {
|
||||
assert.True(t, E.IsRight(String.Decode(validData[i])))
|
||||
}
|
||||
|
||||
invalidDataData := A.From(
|
||||
reflect.ValueOf(nil),
|
||||
)
|
||||
for i := 0; i < len(invalidDataData); i++ {
|
||||
assert.True(t, E.IsLeft(String.Decode(invalidDataData[i])))
|
||||
}
|
||||
}
|
@@ -16,8 +16,6 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
AR "github.com/IBM/fp-go/array/generic"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
@@ -31,7 +29,8 @@ type (
|
||||
}
|
||||
|
||||
ContextEntry struct {
|
||||
Key string
|
||||
Key string
|
||||
Value any
|
||||
}
|
||||
|
||||
Errors []*ValidationError
|
||||
@@ -47,10 +46,19 @@ type (
|
||||
Decode(I) E.Either[Errors, A]
|
||||
}
|
||||
|
||||
Codec[I, O, A any] interface {
|
||||
Encoder[A, O]
|
||||
Decoder[I, A]
|
||||
}
|
||||
|
||||
Guard[I, A any] func(I) option.Option[A]
|
||||
|
||||
Validate[I, A any] func(I, Context) E.Either[Errors, A]
|
||||
|
||||
Type[A, O, I any] struct {
|
||||
validate func(I, Context) E.Either[Errors, A]
|
||||
encode func(A) O
|
||||
is func(any) option.Option[A]
|
||||
is Guard[I, A]
|
||||
}
|
||||
)
|
||||
|
||||
@@ -58,15 +66,19 @@ func (t *Type[A, O, I]) Validate(i I, c Context) E.Either[Errors, A] {
|
||||
return t.validate(i, c)
|
||||
}
|
||||
|
||||
func defaultContext(value any) Context {
|
||||
return AR.Of[Context](&ContextEntry{Value: value})
|
||||
}
|
||||
|
||||
func (t *Type[A, O, I]) Decode(i I) E.Either[Errors, A] {
|
||||
return t.validate(i, AR.Of[Context](&ContextEntry{}))
|
||||
return t.validate(i, defaultContext(i))
|
||||
}
|
||||
|
||||
func (t *Type[A, O, I]) Encode(a A) O {
|
||||
return t.encode(a)
|
||||
}
|
||||
|
||||
func (t *Type[A, O, I]) Is(a any) option.Option[A] {
|
||||
func (t *Type[A, O, I]) Is(a I) option.Option[A] {
|
||||
return t.is(a)
|
||||
}
|
||||
|
||||
@@ -85,7 +97,10 @@ func (val *ValidationError) Error() string {
|
||||
func Pipe[O, I, A, B any](ab Type[B, A, A]) func(a Type[A, O, I]) Type[B, O, I] {
|
||||
return func(a Type[A, O, I]) Type[B, O, I] {
|
||||
return Type[B, O, I]{
|
||||
is: ab.Is,
|
||||
is: F.Flow2(
|
||||
a.is,
|
||||
option.Chain(ab.Is),
|
||||
),
|
||||
validate: func(i I, c Context) E.Either[Errors, B] {
|
||||
return F.Pipe1(
|
||||
a.Validate(i, c),
|
||||
@@ -112,27 +127,20 @@ func Failure[A any](c Context, message string) E.Either[Errors, A] {
|
||||
return Failures[A](AR.Of[Errors](&ValidationError{Context: c, Message: message}))
|
||||
}
|
||||
|
||||
func makeCanonicalType[A any]() Type[A, A, any] {
|
||||
is := option.ToType[A]
|
||||
|
||||
return Type[A, A, any]{
|
||||
is: is,
|
||||
validate: func(u any, c Context) E.Either[Errors, A] {
|
||||
return F.Pipe2(
|
||||
u,
|
||||
is,
|
||||
option.Fold(
|
||||
func() E.Either[Errors, A] {
|
||||
return Failure[A](c, fmt.Sprintf("source is of type %T", u))
|
||||
},
|
||||
Success[A],
|
||||
),
|
||||
)
|
||||
},
|
||||
encode: F.Identity[A],
|
||||
func guardFromValidate[A, I any](validate func(I, Context) E.Either[Errors, A]) Guard[I, A] {
|
||||
return func(i I) option.Option[A] {
|
||||
return F.Pipe1(
|
||||
validate(i, defaultContext(i)),
|
||||
E.ToOption[Errors, A],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var String = makeCanonicalType[string]()
|
||||
var Int = makeCanonicalType[int]()
|
||||
var Bool = makeCanonicalType[bool]()
|
||||
// FromValidate constructs a Type instance from just the validation function
|
||||
func FromValidate[A, I any](validate Validate[I, A]) *Type[A, A, I] {
|
||||
return &Type[A, A, I]{
|
||||
validate,
|
||||
F.Identity[A],
|
||||
guardFromValidate[A, I](validate),
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user