go-emarshal

[library] easy programmable marshalling of embedded types
git clone https://hhvn.uk/go-emarshal
git clone git://hhvn.uk/go-emarshal
Log | Files | Refs | LICENSE

emarshal.go (4161B)


      1 // Package emarshal makes (un)marshalling embedded types easy.
      2 //
      3 // The embedded types should each specify a marshalling and unmarshalling
      4 // method. The are then accessed by the Marshal and Unmarshal functions in this
      5 // package.
      6 //
      7 // The intended use case (as seen in the example) is for these functions to be
      8 // run by the MarshalJSON/UnmarshalJSON (or equivalent for other encodings)
      9 // methods on the parent type.
     10 package emarshal // import "hhvn.uk/go-emarshal"
     11 
     12 import (
     13 	"fmt"
     14 	"errors"
     15 	"reflect"
     16 
     17 	"hhvn.uk/go-superstruct"
     18 )
     19 
     20 var (
     21 	ErrNotPointer = errors.New("value is not pointer")
     22 	ErrBadMarshal = errors.New("bad marshaller")
     23 	ErrBadUnmarshal = errors.New("bad unmarshaller")
     24 )
     25 
     26 // MarshalFunc is a function that can be passed to Marshal().
     27 type MarshalFunc func(any) ([]byte, error)
     28 
     29 // EMarshalMethod documents the signature of an EMarshal... method
     30 type EMarshalMethod func() (any, error)
     31 
     32 // Marshal returns an encoded form of in.
     33 //
     34 // The encoded form is made by running all methods with the prefix "EMarshal"
     35 // of in, and then combining these values into a single struct and encoding it
     36 // with fn.
     37 //
     38 // The signature of a such a method is documented by EMarshalMethod.
     39 func Marshal(in any, fn MarshalFunc) ([]byte, error) {
     40 	t  := reflect.TypeOf(in)
     41 	tn := t.Name()
     42 	
     43 	mm := getMethods(t, "EMarshal")
     44 
     45 	rs := make([]any, len(mm))
     46 
     47 	for i, m := range mm {
     48 		mn := m.Name
     49 		man, _, mrn, mrt := methodInfo(m)
     50 
     51 		switch {
     52 		case man != 0:
     53 			return nil, methodErr(tn, mn, ErrBadMarshal,
     54 				fmt.Sprintf("expected 0 arguments, not %d", man))
     55 		case mrn != 2:
     56 			return nil, methodErr(tn, mn, ErrBadMarshal,
     57 				fmt.Sprintf("expected 2 return values, not %d", mrn))
     58 		case mrt[0].Kind() != reflect.Interface:
     59 			return nil, methodErr(tn, mn, ErrBadMarshal,
     60 				"2nd return value is not an interface")
     61 		case !isErr(mrt[1]):
     62 			return nil, methodErr(tn, mn, ErrBadMarshal,
     63 				"2nd return value is not an error")
     64 		}
     65 
     66 		r := m.Func.Call([]reflect.Value{reflect.ValueOf(in)})
     67 		
     68 		err, ok := r[1].Interface().(error)
     69 		if ok { // ok is only true when err is not nil
     70 			return nil, err
     71 		}
     72 
     73 		rs[i] = r[0].Interface()
     74 	}
     75 
     76 	s, err := superstruct.CreateAndCopy(rs...)
     77 	if err != nil {
     78 		return nil, err
     79 	}
     80 
     81 	return fn(s)
     82 }
     83 
     84 // UnmarshalFunc is a function that can be passed to Unmarshal().
     85 type UnmarshalFunc func([]byte, any) error
     86 
     87 // Unpack is the function that is passed to an EUnmarshalMethod.
     88 type Unpack func(any) error
     89 
     90 // EUnmarshalMethod documents the signature of an EUnmarshal... method
     91 type EUnmarshalMethod func(Unpack) error
     92 
     93 // Unmarshal decodes a byte slice into the value pointed to by in.
     94 //
     95 // It does this by running all the methods with the prefix "EUnmarshal" of in.
     96 // The methods are passed an anonymous function that will unpack the data into
     97 // a chosen variable using fn.
     98 //
     99 // The signature of a such a method is documented by EUnmarshalMethod.
    100 func Unmarshal(in any, b []byte, fn UnmarshalFunc) error {
    101 	t  := reflect.TypeOf(in)
    102 
    103 	if t.Kind() != reflect.Interface && t.Kind() != reflect.Pointer {
    104 		return fmt.Errorf("unmarshal: 1st %w", ErrNotPointer)
    105 	}
    106 
    107 	tn := t.Elem().Name()
    108 
    109 	mm := getMethods(t, "EUnmarshal")
    110 
    111 	unpacker := func(a any) error {
    112 		return fn(b, a)
    113 	}
    114 
    115 	for _, m := range mm {
    116 		mn := m.Name
    117 		man, mat, mrn, mrt := methodInfo(m)
    118 
    119 		switch {
    120 		case man != 1:
    121 			return methodErr(tn, mn, ErrBadUnmarshal,
    122 				fmt.Sprintf("expected 1 argument, not %d", man))
    123 		case mat[0].Kind() != reflect.Func:
    124 			return methodErr(tn, mn, ErrBadUnmarshal,
    125 				"argument is not a function")
    126 		case mrn != 1:
    127 			return methodErr(tn, mn, ErrBadUnmarshal,
    128 				fmt.Sprintf("expected 1 return not %d", mrn))
    129 		case !isErr(mrt[0]):
    130 			return methodErr(tn, mn, ErrBadUnmarshal,
    131 				"return value is not an error")
    132 		}
    133 
    134 		fan, fat, frn, frt := funcInfo(mat[0])
    135 
    136 		if fan != 1 || frn != 1 || fat[0].Kind() != reflect.Interface || !isErr(frt[0]) {
    137 			return methodErr(tn, mn, ErrBadMarshal,
    138 				"first argument is not an Unpack function")
    139 		}
    140 
    141 		r := m.Func.Call([]reflect.Value{
    142 			reflect.ValueOf(in),
    143 			reflect.ValueOf(unpacker),
    144 		})
    145 
    146 		err, ok := r[0].Interface().(error)
    147 		if ok {
    148 			return err
    149 		}
    150 	}
    151 
    152 	return nil
    153 }