github.com/ipld/go-ipld-prime@v0.21.0/fluent/reflect.go (about) 1 package fluent 2 3 import ( 4 "fmt" 5 "reflect" 6 "sort" 7 8 "github.com/ipld/go-ipld-prime/datamodel" 9 ) 10 11 // Reflect creates a new Node by looking at a golang value with reflection 12 // and converting it into IPLD Data Model. 13 // This is a quick-and-dirty way to get data into the IPLD Data Model; 14 // it's useful for rapid prototyping and demos, 15 // but note that this feature is not intended to be suitable for "production" use 16 // due to low performance and lack of configurability. 17 // 18 // The concrete type of the returned Node is determined by 19 // the NodePrototype argument provided by the caller. 20 // 21 // No type information from the golang value will be observable in the result. 22 // 23 // The reflection will walk over any golang value, but is not configurable. 24 // Golang maps become IPLD maps; golang slices and arrays become IPLD lists; 25 // and golang structs become IPLD maps too. 26 // When converting golang structs to IPLD maps, the field names will become the map keys. 27 // Pointers and interfaces will be traversed transparently and are not visible in the output. 28 // 29 // An error will be returned if the process of assembling the Node returns any errors 30 // (for example, if the NodePrototype is for a schema-constrained Node, 31 // any validation errors from the schema will cause errors to be returned). 32 // 33 // A panic will be raised if there is any difficulty examining the golang value via reflection 34 // (for example, if the value is a struct with unexported fields, 35 // or if a non-data type like a channel or function is encountered). 36 // 37 // Some configuration (in particular, what to do about map ordering) is available via the Reflector struct. 38 // That structure has a method of the same name and signiture as this one on it. 39 // (This function is a shortcut for calling that method on a Reflector struct with default configuration.) 40 // 41 // Performance remarks: performance of this function will generally be poor. 42 // In general, creating data in golang types and then *flipping* it to IPLD form 43 // involves handling the data at least twice, and so will always be slower 44 // than just creating the same data in IPLD form programmatically directly. 45 // In particular, reflection is generally not fast, and this feature has 46 // not been optimized for either speed nor allocation avoidance. 47 // Other features in the fluent package will typically out-perform this, 48 // and using NodeAssemblers directly (without any fluent tools) will be much faster. 49 // Only use this function if performance is not of consequence. 50 func Reflect(np datamodel.NodePrototype, i interface{}) (datamodel.Node, error) { 51 return defaultReflector.Reflect(np, i) 52 } 53 54 // MustReflect is a shortcut for Reflect but panics on any error. 55 // It is useful if you need a single return value for function composition purposes. 56 func MustReflect(np datamodel.NodePrototype, i interface{}) datamodel.Node { 57 n, err := Reflect(np, i) 58 if err != nil { 59 panic(err) 60 } 61 return n 62 } 63 64 // ReflectIntoAssembler is similar to Reflect, but takes a NodeAssembler parameter 65 // instead of a Node Prototype. 66 // This may be useful if you need more direct control over allocations, 67 // or want to fill in only part of a larger node assembly process using the reflect tool. 68 // Data is accumulated by the NodeAssembler parameter, so no Node is returned. 69 func ReflectIntoAssembler(na datamodel.NodeAssembler, i interface{}) error { 70 return defaultReflector.ReflectIntoAssembler(na, i) 71 } 72 73 var defaultReflector = Reflector{ 74 MapOrder: func(x, y string) bool { 75 return x < y 76 }, 77 } 78 79 // Reflector allows configuration of the Reflect family of functions 80 // (`Reflect`, `ReflectIntoAssembler`, etc). 81 type Reflector struct { 82 // MapOrder is used to decide a deterministic order for inserting entries to maps. 83 // (This is used when converting golang maps, since their iteration order is randomized; 84 // it is not used when converting other types such as structs, since those have a stable order.) 85 // MapOrder should return x < y in the same way as sort.Interface.Less. 86 // 87 // If using a default Reflector (e.g. via the package-scope functions), 88 // this function is a simple natural golang string sort: it performs `x < y` on the strings. 89 MapOrder func(x, y string) bool 90 } 91 92 // Reflect is as per the package-scope function of the same name and signature, 93 // but using the configuration in the Reflector struct. 94 // See the package-scope function for documentation. 95 func (rcfg Reflector) Reflect(np datamodel.NodePrototype, i interface{}) (datamodel.Node, error) { 96 nb := np.NewBuilder() 97 if err := rcfg.ReflectIntoAssembler(nb, i); err != nil { 98 return nil, err 99 } 100 return nb.Build(), nil 101 } 102 103 // ReflectIntoAssembler is as per the package-scope function of the same name and signature, 104 // but using the configuration in the Reflector struct. 105 // See the package-scope function for documentation. 106 func (rcfg Reflector) ReflectIntoAssembler(na datamodel.NodeAssembler, i interface{}) error { 107 // Cover the most common values with a type-switch, as it's faster than reflection. 108 switch x := i.(type) { 109 case map[string]string: 110 keys := make([]string, 0, len(x)) 111 for k := range x { 112 keys = append(keys, k) 113 } 114 sort.Sort(sortableStrings{keys, rcfg.MapOrder}) 115 ma, err := na.BeginMap(int64(len(x))) 116 if err != nil { 117 return err 118 } 119 for _, k := range keys { 120 va, err := ma.AssembleEntry(k) 121 if err != nil { 122 return err 123 } 124 if err := va.AssignString(x[k]); err != nil { 125 return err 126 } 127 } 128 return ma.Finish() 129 case map[string]interface{}: 130 keys := make([]string, 0, len(x)) 131 for k := range x { 132 keys = append(keys, k) 133 } 134 sort.Sort(sortableStrings{keys, rcfg.MapOrder}) 135 ma, err := na.BeginMap(int64(len(x))) 136 if err != nil { 137 return err 138 } 139 for _, k := range keys { 140 va, err := ma.AssembleEntry(k) 141 if err != nil { 142 return err 143 } 144 if err := rcfg.ReflectIntoAssembler(va, x[k]); err != nil { 145 return err 146 } 147 } 148 return ma.Finish() 149 case []string: 150 la, err := na.BeginList(int64(len(x))) 151 if err != nil { 152 return err 153 } 154 for _, v := range x { 155 if err := la.AssembleValue().AssignString(v); err != nil { 156 return err 157 } 158 } 159 return la.Finish() 160 case []interface{}: 161 la, err := na.BeginList(int64(len(x))) 162 if err != nil { 163 return err 164 } 165 for _, v := range x { 166 if err := rcfg.ReflectIntoAssembler(la.AssembleValue(), v); err != nil { 167 return err 168 } 169 } 170 return la.Finish() 171 case string: 172 return na.AssignString(x) 173 case []byte: 174 return na.AssignBytes(x) 175 case int64: 176 return na.AssignInt(x) 177 case nil: 178 return na.AssignNull() 179 } 180 // That didn't fly? Reflection time. 181 rv := reflect.ValueOf(i) 182 switch rv.Kind() { 183 case reflect.Bool: 184 return na.AssignBool(rv.Bool()) 185 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 186 return na.AssignInt(rv.Int()) 187 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 188 return na.AssignInt(int64(rv.Uint())) // TODO: check overflow 189 case reflect.Float32, reflect.Float64: 190 return na.AssignFloat(rv.Float()) 191 case reflect.String: 192 return na.AssignString(rv.String()) 193 case reflect.Slice, reflect.Array: 194 if rv.Type().Elem().Kind() == reflect.Uint8 { // byte slices are a special case 195 return na.AssignBytes(rv.Bytes()) 196 } 197 l := rv.Len() 198 la, err := na.BeginList(int64(l)) 199 if err != nil { 200 return err 201 } 202 for i := 0; i < l; i++ { 203 if err := rcfg.ReflectIntoAssembler(la.AssembleValue(), rv.Index(i).Interface()); err != nil { 204 return err 205 } 206 } 207 return la.Finish() 208 case reflect.Map: 209 // the keys slice for sorting keeps things in reflect.Value form, because unboxing is cheap, 210 // but re-boxing is not cheap, and the MapIndex method requires reflect.Value again later. 211 keys := make([]reflect.Value, 0, rv.Len()) 212 itr := rv.MapRange() 213 for itr.Next() { 214 k := itr.Key() 215 if k.Kind() != reflect.String { 216 return fmt.Errorf("cannot convert a map with non-string keys (%T)", i) 217 } 218 keys = append(keys, k) 219 } 220 sort.Sort(sortableReflectStrings{keys, rcfg.MapOrder}) 221 ma, err := na.BeginMap(int64(rv.Len())) 222 if err != nil { 223 return err 224 } 225 for _, k := range keys { 226 va, err := ma.AssembleEntry(k.String()) 227 if err != nil { 228 return err 229 } 230 if err := rcfg.ReflectIntoAssembler(va, rv.MapIndex(k).Interface()); err != nil { 231 return err 232 } 233 } 234 return ma.Finish() 235 case reflect.Struct: 236 l := rv.NumField() 237 ma, err := na.BeginMap(int64(l)) 238 if err != nil { 239 return err 240 } 241 for i := 0; i < l; i++ { 242 fn := rv.Type().Field(i).Name 243 fv := rv.Field(i) 244 va, err := ma.AssembleEntry(fn) 245 if err != nil { 246 return err 247 } 248 if err := rcfg.ReflectIntoAssembler(va, fv.Interface()); err != nil { 249 return err 250 } 251 } 252 return ma.Finish() 253 case reflect.Ptr: 254 if rv.IsNil() { 255 return na.AssignNull() 256 } 257 return rcfg.ReflectIntoAssembler(na, rv.Elem()) 258 case reflect.Interface: 259 return rcfg.ReflectIntoAssembler(na, rv.Elem()) 260 } 261 // Some kints of values -- like Uintptr, Complex64/128, Channels, etc -- are not supported by this function. 262 return fmt.Errorf("fluent.Reflect: unsure how to handle type %T (kind: %v)", i, rv.Kind()) 263 } 264 265 type sortableStrings struct { 266 a []string 267 less func(x, y string) bool 268 } 269 270 func (a sortableStrings) Len() int { return len(a.a) } 271 func (a sortableStrings) Swap(i, j int) { a.a[i], a.a[j] = a.a[j], a.a[i] } 272 func (a sortableStrings) Less(i, j int) bool { return a.less(a.a[i], a.a[j]) } 273 274 type sortableReflectStrings struct { 275 a []reflect.Value 276 less func(x, y string) bool 277 } 278 279 func (a sortableReflectStrings) Len() int { return len(a.a) } 280 func (a sortableReflectStrings) Swap(i, j int) { a.a[i], a.a[j] = a.a[j], a.a[i] } 281 func (a sortableReflectStrings) Less(i, j int) bool { return a.less(a.a[i].String(), a.a[j].String()) }