github.com/simpleiot/simpleiot@v0.18.3/data/decode.go (about) 1 package data 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "reflect" 8 "strconv" 9 10 "golang.org/x/exp/slices" 11 ) 12 13 // NodeEdgeChildren is used to pass a tree node structure into the 14 // decoder 15 type NodeEdgeChildren struct { 16 NodeEdge `yaml:",inline"` 17 Children []NodeEdgeChildren `yaml:",omitempty"` 18 } 19 20 func (ne NodeEdgeChildren) String() string { 21 var childHelper func(NodeEdgeChildren, string) string 22 23 childHelper = func(ne NodeEdgeChildren, indent string) string { 24 ret := indent + ne.NodeEdge.String() 25 for _, c := range ne.Children { 26 ret += childHelper(c, indent+" ") 27 } 28 return ret 29 } 30 31 return childHelper(ne, "") 32 } 33 34 // reflectValueT is the `reflect.Type` for a `reflect.Value` 35 var reflectValueT = reflect.TypeOf(reflect.Value{}) 36 37 // GroupedPoints are Points grouped by their `Point.Type`. While scanning 38 // through the list of points, we also keep track of whether or not the 39 // points are keyed with positive integer values (for decoding into arrays) 40 type GroupedPoints struct { 41 // KeyNotIndex is set to a Point's `Key` field if it *cannot* be parsed as a 42 // positive integer 43 // Note: If `Key` is empty string (""), it is treated as "0" 44 KeyNotIndex string 45 // KeyMaxInt is the largest `Point.Key` value in Points 46 KeyMaxInt int 47 // Points is the list of Points for this group 48 Points []Point 49 50 // TODO: We can also simultaneously implement sorting floating point keys 51 // KeysNumeric is set if and only if each Point's `Key` field is numeric 52 // and can be parsed as a float64 53 // KeysNumeric bool 54 // KeyMaxFloat is the largest `Point.Key` value in Points 55 // KeyMaxFloat float64 56 } 57 58 // SetValue populates v with the Points in the group 59 func (g GroupedPoints) SetValue(v reflect.Value) error { 60 t := v.Type() 61 k := t.Kind() 62 // Special case to handle pointers to structs 63 if k == reflect.Pointer && t.Elem().Kind() == reflect.Struct { 64 // Populate validFields with all fields in struct 65 validFields := make(map[string]bool) 66 i, numField := 0, t.Elem().NumField() 67 for ; i < numField; i++ { 68 sf := t.Elem().Field(i) 69 key := sf.Tag.Get("point") 70 if key == "" { 71 key = sf.Tag.Get("edgepoint") 72 } 73 if key == "" { 74 key = ToCamelCase(sf.Name) 75 } 76 validFields[key] = true 77 } 78 // Remove validFields as tombstone points are found 79 for _, p := range g.Points { 80 if p.Tombstone%2 == 1 { 81 delete(validFields, p.Key) 82 } else { 83 validFields[p.Key] = true 84 } 85 } 86 // If all points have tombstones, we just set the pointer to nil 87 if len(validFields) == 0 { 88 v.Set(reflect.Zero(t)) 89 return nil 90 } 91 // If a valid point exists in the group, then initialize the pointer 92 // if needed 93 if v.IsNil() { 94 v.Set(reflect.New(t.Elem())) 95 } 96 v = v.Elem() 97 t = v.Type() 98 k = t.Kind() 99 } 100 switch k { 101 case reflect.Array, reflect.Slice: 102 // Ensure all keys are array indexes 103 if g.KeyNotIndex != "" { 104 return fmt.Errorf( 105 "Point.Key %v is not a valid index", 106 g.KeyNotIndex, 107 ) 108 } 109 // Check array bounds 110 if g.KeyMaxInt > maxStructureSize { 111 return fmt.Errorf( 112 "Point.Key %v exceeds %v", 113 g.KeyMaxInt, maxStructureSize, 114 ) 115 } 116 if k == reflect.Array && g.KeyMaxInt > t.Len()-1 { 117 return fmt.Errorf( 118 "Point.Key %v exceeds array size %v", 119 g.KeyMaxInt, t.Len(), 120 ) 121 } 122 // Expand slice if needed 123 if k == reflect.Slice && g.KeyMaxInt > v.Len()-1 { 124 if !v.CanSet() { 125 return fmt.Errorf("cannot set value %v", v) 126 } 127 if g.KeyMaxInt+1 <= v.Cap() { 128 // Optimization: use excess capacity of `v` 129 v.Set(v.Slice(0, g.KeyMaxInt+1)) 130 } else { 131 newV := reflect.MakeSlice(t, g.KeyMaxInt+1, g.KeyMaxInt+1) 132 reflect.Copy(newV, v) 133 v.Set(newV) 134 } 135 } 136 // Set array / slice values 137 deletedIndexes := []int{} 138 for _, p := range g.Points { 139 // Note: array / slice values are set directly on the indexed Value 140 index, _ := strconv.Atoi(p.Key) 141 if p.Tombstone%2 == 1 { 142 deletedIndexes = append(deletedIndexes, index) 143 // Ignore this deleted value if it won't fit in the slice anyway 144 // Note: KeyMaxInt is not set for points with Tombstone set, so 145 // index could still be out of range. 146 if index >= v.Len() { 147 continue 148 } 149 } 150 // Finally, set the value in the slice 151 err := setVal(p, v.Index(index)) 152 if err != nil { 153 return err 154 } 155 } 156 // We can now trim the underlying slice to remove trailing values that 157 // were deleted in this decode. Note: this does not guarantee that 158 // slices are always trimmed completely (for example, values can be 159 // deleted across multiple calls of Decode) 160 if k == reflect.Slice { 161 slices.Sort(deletedIndexes) 162 lastIndex := v.Len() - 1 163 for i := len(deletedIndexes) - 1; i >= 0; i-- { 164 if deletedIndexes[i] < lastIndex { 165 break 166 } else if deletedIndexes[i] == lastIndex { 167 lastIndex-- 168 } 169 // else only decrement i 170 } 171 v.Set(v.Slice(0, lastIndex+1)) 172 } 173 case reflect.Map: 174 // Ensure map is keyed by string 175 if keyK := t.Key().Kind(); keyK != reflect.String { 176 return fmt.Errorf("cannot set map keyed by %v", keyK) 177 } 178 if len(g.Points) > maxStructureSize { 179 return fmt.Errorf( 180 "number of points %v exceeds maximum of %v for a map", 181 len(g.Points), maxStructureSize, 182 ) 183 } 184 // Ensure points are keyed 185 // Note: No longer relevant, as all points as keyed now 186 // if !g.Keyed { 187 // return fmt.Errorf("points missing Key") 188 // } 189 190 // Ensure map is initialized 191 if v.IsNil() { 192 if !v.CanSet() { 193 return fmt.Errorf("cannot set value %v", v) 194 } 195 v.Set(reflect.MakeMapWithSize(t, len(g.Points))) 196 } 197 // Set map values 198 for _, p := range g.Points { 199 // Enforce valid Key value 200 key := p.Key 201 if key == "" { 202 key = "0" 203 } 204 if p.Tombstone%2 == 1 { 205 // We want to delete the map entry if Tombstone is set 206 v.SetMapIndex(reflect.ValueOf(key), reflect.Value{}) 207 } else { 208 // Create and set a new map value 209 // Note: map values must be set on newly created Values 210 // because (unlike arrays / slices) any value returned by 211 // `MapIndex` is not settable 212 newV := reflect.New(t.Elem()).Elem() 213 err := setVal(p, newV) 214 if err != nil { 215 return err 216 } 217 v.SetMapIndex(reflect.ValueOf(key), newV) 218 } 219 } 220 case reflect.Struct: 221 // Create map of Points 222 values := make(map[string]Point, len(g.Points)) 223 for _, p := range g.Points { 224 values[p.Key] = p 225 } 226 // Write points to struct 227 for numField, i := v.NumField(), 0; i < numField; i++ { 228 sf := t.Field(i) 229 key := sf.Tag.Get("point") 230 if key == "" { 231 key = sf.Tag.Get("edgepoint") 232 } 233 if key == "" { 234 key = ToCamelCase(sf.Name) 235 } 236 // Ensure points are keyed 237 if key == "" { 238 return fmt.Errorf("point missing Key") 239 } 240 if val, ok := values[key]; ok { 241 err := setVal(val, v.Field(i)) 242 if err != nil { 243 return err 244 } 245 } 246 } 247 default: 248 if len(g.Points) > 1 { 249 log.Printf( 250 "Decode warning, decoded multiple points to %v:\n%v", 251 // Cast to `Points` type with a `String()` method which prints 252 // a trailing newline 253 t, Points(g.Points), 254 ) 255 } 256 for _, p := range g.Points { 257 err := setVal(p, v) 258 if err != nil { 259 return err 260 } 261 } 262 } 263 return nil 264 } 265 266 // Decode converts a Node to custom struct. 267 // output can be a struct type that contains 268 // node, point, and edgepoint tags as shown below. 269 // It is recommended that id and parent node tags 270 // always be included. 271 // 272 // type exType struct { 273 // ID string `node:"id"` 274 // Parent string `node:"parent"` 275 // Description string `point:"description"` 276 // Count int `point:"count"` 277 // Role string `edgepoint:"role"` 278 // Tombstone bool `edgepoint:"tombstone"` 279 // Conditions []Condition `child:"condition"` 280 // } 281 // 282 // outputStruct can also be a *reflect.Value 283 // 284 // Some consideration is needed when using Decode and MergePoints to 285 // decode points into Go slices. Slices are never allocated / copied 286 // unless they are being expanded. Instead, deleted points are written 287 // to the slice as the zero value. However, for a given Decode call, 288 // if points are deleted from the end of the slice, Decode will re-slice 289 // it to remove those values from the slice. Thus, there is an important 290 // consideration for clients: if they wish to rely on slices being 291 // truncated when points are deleted, points must be batched in order 292 // such that Decode sees the trailing deleted points first. Put another 293 // way, Decode does not care about points deleted from prior calls to 294 // Decode, so "holes" of zero values may still appear at the end of a 295 // slice under certain circumstances. Consider points with integer 296 // values [0, 1, 2, 3, 4]. If tombstone is set on point with Key 3 297 // followed by a point tombstone set on point with Key 4, the resulting 298 // slice will be [0, 1, 2] if these points are batched together, but 299 // if they are sent separately (thus resulting in multiple Decode calls), 300 // the resulting slice will be [0, 1, 2, 0]. 301 func Decode(input NodeEdgeChildren, outputStruct any) error { 302 outV, outT, outK := reflectValue(outputStruct) 303 if outK != reflect.Struct { 304 return fmt.Errorf("error decoding to %v; must be a struct", outK) 305 } 306 var retErr error 307 308 // Group points and children by type 309 pointGroups := make(map[string]GroupedPoints) 310 edgePointGroups := make(map[string]GroupedPoints) 311 childGroups := make(map[string][]NodeEdgeChildren) 312 313 // we first collect all points into groups by type 314 // this is required in case we are decoding into a map or array 315 // Note: Even points with tombstones set are processed here; later we set 316 // the destination to the zero value if a tombstone is present. 317 for _, p := range input.NodeEdge.Points { 318 g, ok := pointGroups[p.Type] // uses zero value if not found 319 if !ok { 320 g.KeyMaxInt = -1 321 } 322 if p.Key != "" { 323 index, err := strconv.Atoi(p.Key) 324 if err != nil || index < 0 { 325 g.KeyNotIndex = p.Key 326 } else if index > g.KeyMaxInt && p.Tombstone%2 == 0 { 327 // Note: Do not set `KeyMaxInt` if Tombstone is set. We don't 328 // need to expand the slice in this case. 329 g.KeyMaxInt = index 330 } 331 } 332 // else p.Key is treated like "0"; no need to update `g` at all 333 g.Points = append(g.Points, p) 334 pointGroups[p.Type] = g 335 } 336 for _, p := range input.NodeEdge.EdgePoints { 337 g, ok := edgePointGroups[p.Type] 338 if !ok { 339 g.KeyMaxInt = -1 340 } 341 if p.Key != "" { 342 index, err := strconv.Atoi(p.Key) 343 if err != nil || index < 0 { 344 g.KeyNotIndex = p.Key 345 } else if index > g.KeyMaxInt && p.Tombstone%2 == 0 { 346 g.KeyMaxInt = index 347 } 348 } 349 g.Points = append(g.Points, p) 350 edgePointGroups[p.Type] = g 351 } 352 for _, c := range input.Children { 353 childGroups[c.NodeEdge.Type] = append(childGroups[c.NodeEdge.Type], c) 354 } 355 356 // now process the fields in the output struct 357 for i := 0; i < outT.NumField(); i++ { 358 sf := outT.Field(i) 359 // look at tags to determine if we have a point, edgepoint, node attribute, or child node 360 if pt := sf.Tag.Get("point"); pt != "" { 361 // see if we have any points for this field point type 362 g, ok := pointGroups[pt] 363 if ok { 364 // Write points into struct field 365 err := g.SetValue(outV.Field(i)) 366 if err != nil { 367 retErr = errors.Join(retErr, fmt.Errorf( 368 "decode error for type %v: %w", pt, err, 369 )) 370 } 371 } 372 } else if et := sf.Tag.Get("edgepoint"); et != "" { 373 g, ok := edgePointGroups[et] 374 if ok { 375 // Write points into struct field 376 err := g.SetValue(outV.Field(i)) 377 if err != nil { 378 retErr = errors.Join(retErr, fmt.Errorf( 379 "decode error for type %v: %w", et, err, 380 )) 381 } 382 } 383 } else if nt := sf.Tag.Get("node"); nt != "" { 384 // Set ID or Parent field where appropriate 385 if nt == "id" && input.NodeEdge.ID != "" { 386 v := outV.Field(i) 387 if !v.CanSet() { 388 retErr = errors.Join(retErr, fmt.Errorf( 389 "decode error for id: cannot set", 390 )) 391 continue 392 } 393 if v.Kind() != reflect.String { 394 retErr = errors.Join(retErr, fmt.Errorf( 395 "decode error for id: not a string", 396 )) 397 continue 398 } 399 v.SetString(input.NodeEdge.ID) 400 } else if nt == "parent" && input.NodeEdge.Parent != "" { 401 v := outV.Field(i) 402 if !v.CanSet() { 403 retErr = errors.Join(retErr, fmt.Errorf( 404 "decode error for parent: cannot set", 405 )) 406 continue 407 } 408 if v.Kind() != reflect.String { 409 retErr = errors.Join(retErr, fmt.Errorf( 410 "decode error for parent: not a string", 411 )) 412 continue 413 } 414 v.SetString(input.NodeEdge.Parent) 415 } 416 } else if ct := sf.Tag.Get("child"); ct != "" { 417 g, ok := childGroups[ct] 418 if ok { 419 // Ensure field is a settable slice 420 v := outV.Field(i) 421 t := v.Type() 422 if t.Kind() != reflect.Slice { 423 retErr = errors.Join(retErr, fmt.Errorf( 424 "decode error for child %v: not a slice", ct, 425 )) 426 continue 427 } 428 if !v.CanSet() { 429 retErr = errors.Join(retErr, fmt.Errorf( 430 "decode error for child %v: cannot set", ct, 431 )) 432 continue 433 } 434 435 // Initialize slice 436 v.Set(reflect.MakeSlice(t, len(g), len(g))) 437 for i, child := range g { 438 err := Decode(child, v.Index(i)) 439 if err != nil { 440 retErr = errors.Join(retErr, fmt.Errorf( 441 "decode error for child %v: %w", ct, err, 442 )) 443 } 444 } 445 } 446 } 447 } 448 449 return retErr 450 } 451 452 // setVal writes a scalar Point value / text to a reflect.Value 453 // Supports boolean, integer, floating point, and string destinations 454 // Writes the zero value to `v` if the Point has an odd Tombstone value 455 func setVal(p Point, v reflect.Value) error { 456 if !v.CanSet() { 457 return fmt.Errorf("cannot set value") 458 } 459 if p.Tombstone%2 == 1 { 460 // Set to zero value 461 v.Set(reflect.Zero(v.Type())) 462 return nil 463 } 464 if v.Kind() == reflect.Pointer { 465 if v.IsNil() { 466 // Initialize pointer 467 v.Set(reflect.New(v.Type().Elem())) 468 } 469 v = v.Elem() 470 } 471 switch k := v.Kind(); k { 472 case reflect.Bool: 473 v.SetBool(FloatToBool(p.Value)) 474 case reflect.Int, 475 reflect.Int8, 476 reflect.Int16, 477 reflect.Int32, 478 reflect.Int64: 479 480 if v.OverflowInt(int64(p.Value)) { 481 return fmt.Errorf("int overflow: %v", p.Value) 482 } 483 v.SetInt(int64(p.Value)) 484 case reflect.Uint, 485 reflect.Uint8, 486 reflect.Uint16, 487 reflect.Uint32, 488 reflect.Uint64: 489 490 if p.Value < 0 || v.OverflowUint(uint64(p.Value)) { 491 return fmt.Errorf("uint overflow: %v", p.Value) 492 } 493 v.SetUint(uint64(p.Value)) 494 case reflect.Float32, reflect.Float64: 495 v.SetFloat(p.Value) 496 case reflect.String: 497 v.SetString(p.Text) 498 default: 499 return fmt.Errorf("unsupported type: %v", k) 500 } 501 return nil 502 } 503 504 // reflectValue returns a reflect.Value from an interface 505 // This function dereferences `output` if it's a pointer or a reflect.Value 506 func reflectValue(output any) ( 507 outV reflect.Value, outT reflect.Type, outK reflect.Kind, 508 ) { 509 outV = reflect.Indirect(reflect.ValueOf(output)) 510 outT = outV.Type() 511 512 if outT == reflectValueT { 513 // `output` was a reflect.Value or *reflect.Value 514 outV = outV.Interface().(reflect.Value) 515 outT = outV.Type() 516 } 517 518 outK = outT.Kind() 519 return 520 }