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 }