Skip to content

Commit

Permalink
Generally working, but confusing to use.
Browse files Browse the repository at this point in the history
  • Loading branch information
schmidtw committed Feb 15, 2025
1 parent d281ab4 commit d07df8a
Show file tree
Hide file tree
Showing 8 changed files with 397 additions and 326 deletions.
32 changes: 15 additions & 17 deletions format.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,19 +205,19 @@ func MustEncode(message *Message, f Format) []byte {
return output.Bytes()
}

func Decode[T MessageStructs](r io.Reader, f Format) (*T, error) {
func Decode[T UnionTypes](r io.Reader, f Format) (*T, error) {
return DecodeThenValidate[T](r, f, NoStandardValidation())
}

func DecodeBytes[T MessageStructs](buf []byte, f Format) (*T, error) {
func DecodeBytes[T UnionTypes](buf []byte, f Format) (*T, error) {
return Decode[T](bytes.NewReader(buf), f)
}

func DecodeThenValidateBytes[T MessageStructs](buf []byte, f Format, validators ...Processor) (*T, error) {
func DecodeThenValidateBytes[T UnionTypes](buf []byte, f Format, validators ...Processor) (*T, error) {
return DecodeThenValidate[T](bytes.NewReader(buf), f, validators...)
}

func DecodeThenValidate[T MessageStructs](r io.Reader, f Format, validators ...Processor) (*T, error) {
func DecodeThenValidate[T UnionTypes](r io.Reader, f Format, validators ...Processor) (*T, error) {
var msg Message
if err := f.Decoder(r).Decode(&msg); err != nil {
return nil, err
Expand All @@ -242,15 +242,15 @@ func DecodeThenValidate[T MessageStructs](r io.Reader, f Format, validators ...P
return nil, ErrNotHandled
}

func Encode[T MessageStructs](msg *T, w io.Writer, f Format) error {
func Encode[T UnionTypes](msg *T, w io.Writer, f Format) error {
return EncodeAfterValidate(msg, w, f, NoStandardValidation())
}

func EncodeBytes[T MessageStructs](msg *T, f Format) ([]byte, error) {
func EncodeBytes[T UnionTypes](msg *T, f Format) ([]byte, error) {
return EncodeAfterValidateBytes(msg, f, NoStandardValidation())
}

func EncodeAfterValidateBytes[T MessageStructs](msg *T, f Format, validators ...Processor) ([]byte, error) {
func EncodeAfterValidateBytes[T UnionTypes](msg *T, f Format, validators ...Processor) ([]byte, error) {
var buf bytes.Buffer
if err := EncodeAfterValidate(msg, &buf, f, validators...); err != nil {
return nil, err
Expand All @@ -261,7 +261,7 @@ func EncodeAfterValidateBytes[T MessageStructs](msg *T, f Format, validators ...

// EncodeBytes is a convenience function that encodes a given message into a
// byte slice.
func EncodeAfterValidate[T MessageStructs](msg *T, w io.Writer, f Format, validators ...Processor) error {
func EncodeAfterValidate[T UnionTypes](msg *T, w io.Writer, f Format, validators ...Processor) error {
if err := Validate(msg, validators...); err != nil {
return err
}
Expand All @@ -274,7 +274,7 @@ func EncodeAfterValidate[T MessageStructs](msg *T, w io.Writer, f Format, valida
case *Message:
base = m
case *Authorization, *SimpleRequestResponse, *SimpleEvent, *CRUD, *ServiceRegistration, *ServiceAlive, *Unknown:
base, err = m.(converter).To()
err = m.(converter).To(base)
}

if err != nil {
Expand All @@ -298,12 +298,11 @@ func EncodeAfterValidate[T MessageStructs](msg *T, w io.Writer, f Format, valida
// validation is considered successful. Any combination of nil errors and
// ErrNotHandled is considered a successful validation. All other errors are
// considered validation failures and the first encountered error is returned.
func Validate[T MessageStructs](msg *T, validators ...Processor) error {
_, err := validateTo(msg, validators...)
return err
func Validate[T UnionTypes](msg *T, validators ...Processor) error {
return validateTo(msg, nil, validators...)
}

func validateTo[T MessageStructs](msg *T, validators ...Processor) (*Message, error) {
func validateTo[T UnionTypes](msg *T, base *Message, validators ...Processor) error {
defaults := []Processor{
StdValidator(),
}
Expand All @@ -319,18 +318,17 @@ func validateTo[T MessageStructs](msg *T, validators ...Processor) (*Message, er

validators = append(defaults, validators...)

var base *Message
switch m := any(msg).(type) {
case *Message:
base = m
case *Authorization, *SimpleRequestResponse, *SimpleEvent, *CRUD, *ServiceRegistration, *ServiceAlive, *Unknown:
base = m.(converter).to()
m.(converter).to(base)
}

err := Processors(validators).ProcessWRP(context.Background(), *base)
if err == nil || errors.Is(err, ErrNotHandled) {
return base, nil
return nil
}

return base, err
return err
}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@ module github.com/xmidt-org/wrp-go/v4
go 1.21

require (
github.com/k0kubun/pp v3.0.1+incompatible
github.com/stretchr/testify v1.10.0
github.com/tinylib/msgp v1.2.5
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40=
github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
Expand All @@ -20,6 +28,9 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
Expand Down
170 changes: 37 additions & 133 deletions is.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,159 +3,63 @@

package wrp

import (
"context"
"errors"
"reflect"
)

func Is(msg *Message, target any, validators ...Processor) bool {
// Is reports whether the msg is the same type as the target, or is convertible
// to the target.
//
// If the msg is a *Message, it will be validated against the provided
// validators. If the msg is not a *Message, the validators will be ignored.
// If the validators are not provided, the msg will be validated against the
// default validators. To skip validation, provide the NoStandardValidation()
// as a validator.
func Is(msg, target Union, validators ...Processor) bool {
if msg == nil || target == nil {
// This is the simple way to handle nils, otherwise the nil check would
// possibly say nil != nil because of the typeness of the nils.
if msg == nil && target == nil {
return true
}
return false
return msg == target
}

// Check if the target type is a struct or a pointer to a struct
targetType := reflect.TypeOf(target)
if targetType.Kind() == reflect.Ptr {
targetType = targetType.Elem()
}
if targetType.Kind() != reflect.Struct {
// We're done, the target is not a struct that can have a valid message
// embedded in it.
return false
}
msgType := msg.MsgType()
targetType := target.MsgType()

exact := mtToStruct[msg.Type]
if exact == nil {
if !msgType.IsValid() || !targetType.IsValid() || msgType != targetType {
return false
}

var exactPtr any
if _, ok := target.(*Message); ok {
goto probably
}

exactPtr = reflect.New(reflect.TypeOf(exact)).Interface()
if reflect.TypeOf(target) == reflect.TypeOf(exactPtr) {
goto probably
}

// At this point we want to see if the target is a pointer to a struct that
// contains a Message/*Message field or a field of any of the other types in
// MessageType enum that match what this message is targeting.

// Iterate through the fields of the target struct to see if
// any of them are a Message/*Message or the exact type or *exact type.
for i := 0; i < targetType.NumField(); i++ {
field := targetType.Field(i)
if field.Type.Kind() == reflect.Ptr {
field.Type = field.Type.Elem()
}
if field.Type.Kind() == reflect.Struct {
if Is(msg, reflect.New(field.Type).Interface(), validators...) {
return true
}
}
}

return false

probably:
if err := Processors(validators).ProcessWRP(context.Background(), *msg); err != nil {
if !errors.Is(err, ErrNotHandled) {
if m, ok := msg.(*Message); ok {
if err := m.Validate(validators...); err != nil {
return false
}
}

return true
}

func findTarget(target any, typ MessageType) (any, reflect.StructField, bool) {
// Only structs are allowed to be targets.
targetType := reflect.TypeOf(target)
if targetType.Kind() == reflect.Ptr {
targetType = targetType.Elem()
}
if targetType.Kind() != reflect.Struct {
return nil, reflect.StructField{}, false
}

exact := mtToStruct[typ]
if exact == nil {
return nil, reflect.StructField{}, false
}

if reflect.TypeOf(target) == reflect.TypeOf(exact) {
return target, reflect.StructField{}, true
}

return nil, reflect.StructField{}, false
}

// Message -> target
// target -> Message
// target -> Message -> msg (if not Message)
// methods:
// As converts the msg into the target, if possible.
//
// One of the msg or target must be a *Message.
//
// To(*Message) error -- called on a target if present to allow converting to a message
// From(*Message) error -- called on a target if present to allow converting from a message
func As(msg, target any, validators ...Processor) bool {
// When the msg is a *Message, it will be validated against the provided
// validators. If the validators are not provided, the msg will be validated
// against the default validators. To skip validation, provide the
// NoStandardValidation() as a validator. If the message is not convertible to
// the target, an error will be returned.
func As(msg, target Union, validators ...Processor) error {
if msg == nil || target == nil {
return false
}

// Check if the target type is a struct or a pointer to a struct
targetType := reflect.TypeOf(target)
if targetType.Kind() == reflect.Ptr {
targetType = targetType.Elem()
}
if targetType.Kind() != reflect.Struct {
// We're done, the target is not a struct that can have a valid message
// embedded in it.
return false
}

exact := mtToStruct[msg.Type]
if exact == nil {
return false
}

var exactPtr any
if m, ok := target.(*Message); ok {
if err := Validate(msg, validators...); err != nil {
return false
if msg == target {
return nil
}
m.from(msg)
return true
return ErrInvalidMessageType
}

exactPtr = reflect.New(reflect.TypeOf(exact)).Interface()
if reflect.TypeOf(target) == reflect.TypeOf(exactPtr) {
goto probably
}

// At this point we want to see if the target is a pointer to a struct that
// contains a Message/*Message field or a field of any of the other types in
// MessageType enum that match what this message is targeting.
msgType := msg.MsgType()
targetType := target.MsgType()

// Iterate through the fields of the target struct to see if
// any of them are a Message/*Message or the exact type or *exact type.
for i := 0; i < targetType.NumField(); i++ {
field := targetType.Field(i)
if field.Type.Kind() == reflect.Ptr {
field.Type = field.Type.Elem()
}
if field.Type.Kind() == reflect.Struct {
if Is(msg, reflect.New(field.Type).Interface(), validators...) {
return true
}
}
if !msgType.IsValid() || !targetType.IsValid() || msgType != targetType {
return ErrInvalidMessageType
}

return false
if m, ok := msg.(*Message); ok {
return target.From(m, validators...)
} else if s, ok := target.(*Message); ok {
return s.To(s, validators...)
}
return ErrInvalidMessageType
}
Loading

0 comments on commit d07df8a

Please sign in to comment.