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  }