github.com/simpleiot/simpleiot@v0.18.3/data/merge.go (about) 1 package data 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 // FindNodeInStruct recursively scans the `outputStruct` for a struct 9 // with a field having a `node:"id"` tag and whose value matches `nodeID`. 10 // If `parentID` is provided, the struct must also have a field with a 11 // `node:"parent"` tag whose value matches `parentID`. If such a struct is 12 // found, the struct is returned as a reflect.Value; otherwise, an invalid 13 // reflect.Value is returned whose IsValid method returns false. 14 func FindNodeInStruct(outputStruct interface{}, nodeID string, parentID string) reflect.Value { 15 // If `nodeID` is not provided, we abort immediately 16 if nodeID == "" { 17 return reflect.Value{} 18 } 19 20 outV, outT, outK := reflectValue(outputStruct) 21 if outK != reflect.Struct { 22 return reflect.Value{} 23 } 24 25 // Scan struct fields 26 outID := "" 27 outParentID := "" 28 childValues := make(map[string]reflect.Value) 29 30 for i := 0; i < outT.NumField(); i++ { 31 sf := outT.Field(i) 32 if nt := sf.Tag.Get("node"); nt != "" { 33 if nt == "id" { 34 outID = outV.Field(i).String() 35 } else if nt == "parent" { 36 outParentID = outV.Field(i).String() 37 } 38 } else if ct := sf.Tag.Get("child"); ct != "" && 39 sf.Type.Kind() == reflect.Slice { 40 childValues[ct] = outV.Field(i) 41 } 42 } 43 44 if parentID == "" { 45 if outID == nodeID { 46 return outV // found it 47 } 48 } else if outID == nodeID && outParentID == parentID { 49 return outV // found it 50 } 51 52 // `outV` does not match; scan all children recursively 53 for _, c := range childValues { 54 // Note: `c` was already checked to ensure it's a slice 55 for i, length := 0, c.Len(); i < length; i++ { 56 childVal := FindNodeInStruct(c.Index(i), nodeID, parentID) 57 if childVal.IsValid() { 58 return childVal // found it 59 } 60 } 61 } 62 63 // Not found; return invalid value 64 return reflect.Value{} 65 } 66 67 // MergePoints takes points and updates fields in a type 68 // that have matching point tags. See [Decode] for an example type. 69 // When deleting points from arrays, the point key (index) is ignored 70 // and the last entry from the array is removed. Normally, it is recommended 71 // to send all points for an array when doing complex modifications to 72 // an array. 73 func MergePoints(id string, points []Point, outputStruct interface{}) error { 74 outV := FindNodeInStruct(outputStruct, id, "") 75 if !outV.IsValid() { 76 return fmt.Errorf( 77 "no matching struct with a `node:\"id\"` field matching %v", id, 78 ) 79 } 80 81 ne := NodeEdge{ 82 ID: id, 83 Points: points, 84 } 85 return Decode(NodeEdgeChildren{NodeEdge: ne}, outV) 86 } 87 88 // MergeEdgePoints takes edge points and updates a type that 89 // matching edgepoint tags. See [Decode] for an example type. 90 func MergeEdgePoints(id, parent string, points []Point, outputStruct interface{}) error { 91 outV := FindNodeInStruct(outputStruct, id, parent) 92 if !outV.IsValid() { 93 return fmt.Errorf( 94 "no matching struct with a `node:\"id\"` field matching %v", id, 95 ) 96 } 97 98 ne := NodeEdge{ 99 ID: id, 100 Parent: parent, 101 EdgePoints: points, 102 } 103 return Decode(NodeEdgeChildren{NodeEdge: ne}, outV) 104 }