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  }