github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/format/common/property_encoder.go (about)

     1  package common
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"sort"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/lineaje-labs/syft/internal/log"
    11  )
    12  
    13  // FieldName return a flag to indicate this is a valid field and a name to use
    14  type FieldName func(field reflect.StructField) (string, bool)
    15  
    16  // OptionalTag given a tag name, will return the defined tag or fall back to lower camel case field name
    17  func OptionalTag(tag string) FieldName {
    18  	return func(f reflect.StructField) (string, bool) {
    19  		if n, ok := f.Tag.Lookup(tag); ok {
    20  			return n, true
    21  		}
    22  		return lowerFirst(f.Name), true
    23  	}
    24  }
    25  
    26  // TrimOmitempty trims `,omitempty` from the name
    27  func TrimOmitempty(fn FieldName) FieldName {
    28  	return func(f reflect.StructField) (string, bool) {
    29  		if v, ok := fn(f); ok {
    30  			return strings.TrimSuffix(v, ",omitempty"), true
    31  		}
    32  		return "", false
    33  	}
    34  }
    35  
    36  // RequiredTag based on the given tag, only use a field if present
    37  func RequiredTag(tag string) FieldName {
    38  	return func(f reflect.StructField) (string, bool) {
    39  		if n, ok := f.Tag.Lookup(tag); ok {
    40  			return n, true
    41  		}
    42  		return "", false
    43  	}
    44  }
    45  
    46  var (
    47  	// OptionalJSONTag uses field names defined in json tags, if available
    48  	OptionalJSONTag = TrimOmitempty(OptionalTag("json"))
    49  )
    50  
    51  // lowerFirst converts the first character of the string to lower case
    52  func lowerFirst(s string) string {
    53  	return strings.ToLower(s[0:1]) + s[1:]
    54  }
    55  
    56  // Encode recursively encodes the object's properties as an ordered set of NameValue pairs
    57  func Encode(obj interface{}, prefix string, fn FieldName) map[string]string {
    58  	if obj == nil {
    59  		return nil
    60  	}
    61  	props := map[string]string{}
    62  	encode(props, reflect.ValueOf(obj), prefix, fn)
    63  	return props
    64  }
    65  
    66  // NameValue a simple type to store stringified name/value pairs
    67  type NameValue struct {
    68  	Name  string
    69  	Value string
    70  }
    71  
    72  // Sorted returns a sorted set of NameValue pairs
    73  func Sorted(values map[string]string) (out []NameValue) {
    74  	var keys []string
    75  	for k := range values {
    76  		keys = append(keys, k)
    77  	}
    78  	sort.Strings(keys)
    79  	for _, k := range keys {
    80  		out = append(out, NameValue{
    81  			Name:  k,
    82  			Value: values[k],
    83  		})
    84  	}
    85  	return
    86  }
    87  
    88  func encode(out map[string]string, value reflect.Value, prefix string, fn FieldName) {
    89  	if !value.IsValid() || value.Type() == nil {
    90  		return
    91  	}
    92  
    93  	typ := value.Type()
    94  
    95  	switch typ.Kind() {
    96  	case reflect.Ptr:
    97  		if value.IsNil() {
    98  			return
    99  		}
   100  		value = value.Elem()
   101  		encode(out, value, prefix, fn)
   102  	case reflect.String:
   103  		v := value.String()
   104  		if v != "" {
   105  			out[prefix] = v
   106  		}
   107  	case reflect.Bool:
   108  		v := value.Bool()
   109  		out[prefix] = strconv.FormatBool(v)
   110  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   111  		v := value.Int()
   112  		out[prefix] = strconv.FormatInt(v, 10)
   113  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   114  		v := value.Uint()
   115  		out[prefix] = strconv.FormatUint(v, 10)
   116  	case reflect.Float32, reflect.Float64:
   117  		v := value.Float()
   118  		out[prefix] = fmt.Sprintf("%f", v)
   119  	case reflect.Array, reflect.Slice:
   120  		for idx := 0; idx < value.Len(); idx++ {
   121  			encode(out, value.Index(idx), fmt.Sprintf("%s:%d", prefix, idx), fn)
   122  		}
   123  	case reflect.Struct:
   124  		for i := 0; i < typ.NumField(); i++ {
   125  			pv := value.Field(i)
   126  			f := typ.Field(i)
   127  			name, ok := fieldName(f, prefix, fn)
   128  			if !ok {
   129  				continue
   130  			}
   131  			encode(out, pv, name, fn)
   132  		}
   133  	case reflect.Map:
   134  		// currently only map[string]string is really supported
   135  		for _, key := range value.MapKeys() {
   136  			encode(out, value.MapIndex(key), fmt.Sprintf("%s:%v", prefix, key.Interface()), fn)
   137  		}
   138  	default:
   139  		log.Warnf("skipping encoding of unsupported property: %s", prefix)
   140  	}
   141  }
   142  
   143  // fieldName gets the name of the field using the provided FieldName function
   144  func fieldName(f reflect.StructField, prefix string, fn FieldName) (string, bool) {
   145  	name, ok := fn(f)
   146  	if !ok {
   147  		return "", false
   148  	}
   149  	if name == "" {
   150  		return prefix, true
   151  	}
   152  	if prefix != "" {
   153  		name = fmt.Sprintf("%s:%s", prefix, name)
   154  	}
   155  	return name, true
   156  }
   157  
   158  // Decode based on the given type, applies all values to hydrate a new instance
   159  func Decode(typ reflect.Type, values map[string]string, prefix string, fn FieldName) interface{} {
   160  	isPtr := false
   161  	for typ.Kind() == reflect.Ptr {
   162  		typ = typ.Elem()
   163  		isPtr = true
   164  	}
   165  
   166  	isSlice := false
   167  	if typ.Kind() == reflect.Slice {
   168  		typ = reflect.PtrTo(typ)
   169  		isSlice = true
   170  	}
   171  
   172  	v := reflect.New(typ)
   173  
   174  	decode(values, v, prefix, fn)
   175  
   176  	switch {
   177  	case isSlice && isPtr:
   178  		return v.Elem().Interface()
   179  	case isSlice:
   180  		return PtrToStruct(v.Elem().Interface())
   181  	case isPtr:
   182  		return v.Interface()
   183  	}
   184  	return v.Elem().Interface()
   185  }
   186  
   187  // DecodeInto decodes all values to hydrate the given object instance
   188  func DecodeInto(obj interface{}, values map[string]string, prefix string, fn FieldName) {
   189  	value := reflect.ValueOf(obj)
   190  
   191  	for value.Type().Kind() == reflect.Ptr {
   192  		value = value.Elem()
   193  	}
   194  
   195  	decode(values, value, prefix, fn)
   196  }
   197  
   198  //nolint:funlen,gocognit,gocyclo
   199  func decode(vals map[string]string, value reflect.Value, prefix string, fn FieldName) bool {
   200  	if !value.IsValid() || value.Type() == nil {
   201  		return false
   202  	}
   203  
   204  	typ := value.Type()
   205  
   206  	incoming, valid := vals[prefix]
   207  	switch typ.Kind() {
   208  	case reflect.Ptr:
   209  		t := typ.Elem()
   210  		v := value
   211  		if v.IsNil() {
   212  			v = reflect.New(t)
   213  		}
   214  		if decode(vals, v.Elem(), prefix, fn) && value.CanSet() {
   215  			o := v.Interface()
   216  			log.Infof("%v", o)
   217  			value.Set(v)
   218  		} else {
   219  			return false
   220  		}
   221  	case reflect.String:
   222  		if valid {
   223  			value.SetString(incoming)
   224  		} else {
   225  			return false
   226  		}
   227  	case reflect.Bool:
   228  		if !valid {
   229  			return false
   230  		}
   231  		if b, err := strconv.ParseBool(incoming); err == nil {
   232  			value.SetBool(b)
   233  		} else {
   234  			return false
   235  		}
   236  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   237  		if !valid {
   238  			return false
   239  		}
   240  		if i, err := strconv.ParseInt(incoming, 10, 64); err == nil {
   241  			value.SetInt(i)
   242  		} else {
   243  			return false
   244  		}
   245  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   246  		if !valid {
   247  			return false
   248  		}
   249  		if i, err := strconv.ParseUint(incoming, 10, 64); err == nil {
   250  			value.SetUint(i)
   251  		} else {
   252  			return false
   253  		}
   254  	case reflect.Float32, reflect.Float64:
   255  		if !valid {
   256  			return false
   257  		}
   258  		if i, err := strconv.ParseFloat(incoming, 64); err == nil {
   259  			value.SetFloat(i)
   260  		} else {
   261  			return false
   262  		}
   263  	case reflect.Array, reflect.Slice:
   264  		values := false
   265  		t := typ.Elem()
   266  		slice := reflect.MakeSlice(typ, 0, 0)
   267  		for idx := 0; ; idx++ {
   268  			// test for index
   269  			str := fmt.Sprintf("%s:%d", prefix, idx)
   270  			// create new placeholder and decode values
   271  			newType := t
   272  			if t.Kind() == reflect.Ptr {
   273  				newType = t.Elem()
   274  			}
   275  			v := reflect.New(newType)
   276  			if decode(vals, v.Elem(), str, fn) {
   277  				// append to slice
   278  				if t.Kind() != reflect.Ptr {
   279  					v = v.Elem()
   280  				}
   281  				slice = reflect.Append(slice, v)
   282  				values = true
   283  			} else {
   284  				break
   285  			}
   286  		}
   287  		if values {
   288  			value.Set(slice)
   289  		} else {
   290  			return false
   291  		}
   292  	case reflect.Map:
   293  		values := false
   294  		keyType := typ.Key()
   295  		valueType := typ.Elem()
   296  		outMap := reflect.MakeMap(typ)
   297  		str := fmt.Sprintf("%s:", prefix)
   298  		keyVals := map[string]string{}
   299  		// iterate through all keys to find those prefixed with a reference to this map
   300  		// NOTE: this will not work for nested maps
   301  		for key := range vals {
   302  			// test for map prefix
   303  			if strings.HasPrefix(key, str) {
   304  				keyVals[key] = strings.TrimPrefix(key, str)
   305  				// create new placeholder and decode key
   306  				newKeyType := keyType
   307  				if keyType.Kind() == reflect.Ptr {
   308  					newKeyType = keyType.Elem()
   309  				}
   310  				k := reflect.New(newKeyType)
   311  				if !decode(keyVals, k.Elem(), key, fn) {
   312  					log.Debugf("unable to decode key for: %s", key)
   313  					continue
   314  				}
   315  				if keyType.Kind() != reflect.Ptr {
   316  					k = k.Elem()
   317  				}
   318  
   319  				// create new placeholder and decode value
   320  				newValueType := valueType
   321  				if valueType.Kind() == reflect.Ptr {
   322  					newValueType = valueType.Elem()
   323  				}
   324  				v := reflect.New(newValueType)
   325  				if decode(vals, v.Elem(), key, fn) {
   326  					if valueType.Kind() != reflect.Ptr {
   327  						v = v.Elem()
   328  					}
   329  
   330  					// set in map
   331  					outMap.SetMapIndex(k, v)
   332  					values = true
   333  				}
   334  			}
   335  		}
   336  		if values {
   337  			value.Set(outMap)
   338  		} else {
   339  			return false
   340  		}
   341  	case reflect.Struct:
   342  		values := false
   343  		for i := 0; i < typ.NumField(); i++ {
   344  			f := typ.Field(i)
   345  			v := value.Field(i)
   346  
   347  			name, ok := fieldName(f, prefix, fn)
   348  			if !ok {
   349  				continue
   350  			}
   351  
   352  			if decode(vals, v, name, fn) {
   353  				values = true
   354  			}
   355  		}
   356  		return values
   357  	default:
   358  		log.Warnf("unable to set field: %s", prefix)
   359  		return false
   360  	}
   361  	return true
   362  }
   363  
   364  func PtrToStruct(ptr interface{}) interface{} {
   365  	v := reflect.ValueOf(ptr)
   366  	if v.IsZero() && v.Type().Kind() != reflect.Struct {
   367  		return nil
   368  	}
   369  	switch v.Type().Kind() {
   370  	case reflect.Ptr:
   371  		return PtrToStruct(v.Elem().Interface())
   372  	case reflect.Interface:
   373  		return PtrToStruct(v.Elem().Interface())
   374  	}
   375  	return v.Interface()
   376  }