github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmn/iter_fields.go (about)

     1  // Package cmn provides common constants, types, and utilities for AIS clients
     2  // and AIStore.
     3  /*
     4   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     5   */
     6  package cmn
     7  
     8  import (
     9  	"fmt"
    10  	"reflect"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/NVIDIA/aistore/api/apc"
    16  	"github.com/NVIDIA/aistore/cmn/cos"
    17  	"github.com/NVIDIA/aistore/cmn/debug"
    18  )
    19  
    20  const IterFieldNameSepa = "."
    21  
    22  const (
    23  	tagOmitempty = "omitempty" // the field must be omitted when empty (only for read-only walk)
    24  	tagOmit      = "omit"      // the field must be omitted
    25  	tagReadonly  = "readonly"  // the field can be only read
    26  	tagInline    = "inline"    // the fields of a struct are embedded into parent field keys
    27  )
    28  
    29  type (
    30  	// Represents a single named field
    31  	IterField interface {
    32  		Value() any                          // returns the value
    33  		String() string                      // string representation of the value
    34  		SetValue(v any, force ...bool) error // `force` ignores `tagReadonly` (to be used with caution!)
    35  	}
    36  
    37  	field struct {
    38  		name    string
    39  		v       reflect.Value
    40  		listTag string
    41  		opts    IterOpts
    42  		dirty   bool // indicates `SetValue` done
    43  	}
    44  
    45  	IterOpts struct {
    46  		// Skip fields based on allowed tag
    47  		Allowed string
    48  		// Visits all the fields, not only the leaves.
    49  		VisitAll bool
    50  		// Read-only walk is true by default (compare with `UpdateFieldValue`)
    51  		// Note that `tagOmitempty` is limited to read-only - has no effect when `OnlyRead == false`.
    52  		OnlyRead bool
    53  	}
    54  
    55  	updateFunc func(uniqueTag string, field IterField) (error, bool)
    56  )
    57  
    58  // interface guard
    59  var _ IterField = (*field)(nil)
    60  
    61  // IterFields walks the struct and calls `updf` callback at every leaf field that it
    62  // encounters. The (nested) names are created by joining the json tag with dot.
    63  // Iteration supports reading another, custom tag `list` with values:
    64  //   - `tagOmitempty` - omit empty fields (only for read run)
    65  //   - `tagOmit` - omit field
    66  //   - `tagReadonly` - field cannot be updated (returns error on `SetValue`)
    67  //
    68  // Examples of usages for tags can be found in `BucketProps` or `Config` structs.
    69  //
    70  // Passing additional options with `IterOpts` can for example call callback
    71  // also at the non-leaf structures.
    72  func IterFields(v any, updf updateFunc, opts ...IterOpts) error {
    73  	o := IterOpts{OnlyRead: true} // by default it's read run
    74  	if len(opts) > 0 {
    75  		o = opts[0]
    76  	}
    77  	_, _, err := iterFields("", v, updf, o)
    78  	return err
    79  }
    80  
    81  // UpdateFieldValue updates the field in the struct with given value.
    82  // Returns error if the field was not found or could not be updated.
    83  func UpdateFieldValue(s any, name string, value any) error {
    84  	found := false
    85  	err := IterFields(s, func(uniqueTag string, field IterField) (error, bool) {
    86  		if uniqueTag == name {
    87  			found = true
    88  			return field.SetValue(value), true
    89  		}
    90  		return nil, false
    91  	}, IterOpts{OnlyRead: false})
    92  	if err != nil {
    93  		return err
    94  	}
    95  	if !found {
    96  		return fmt.Errorf("unknown property %q", name)
    97  	}
    98  	return nil
    99  }
   100  
   101  func iterFields(prefix string, v any, updf updateFunc, opts IterOpts) (dirty, stop bool, err error) {
   102  	srcVal := reflect.ValueOf(v)
   103  	if srcVal.Kind() == reflect.Ptr {
   104  		srcVal = srcVal.Elem()
   105  	}
   106  	for i := range srcVal.NumField() {
   107  		var (
   108  			srcTyField  = srcVal.Type().Field(i)
   109  			srcValField = srcVal.Field(i)
   110  			isInline    bool
   111  		)
   112  
   113  		// Check if we need to skip given field.
   114  		listTag := srcTyField.Tag.Get("list")
   115  		if listTag == tagOmit {
   116  			continue
   117  		}
   118  
   119  		jsonTag, jsonTagPresent := srcTyField.Tag.Lookup("json")
   120  		tags := strings.Split(jsonTag, ",")
   121  		fieldName := tags[0]
   122  		if fieldName == "-" {
   123  			continue
   124  		}
   125  		if len(tags) > 1 {
   126  			isInline = tags[1] == tagInline
   127  		}
   128  
   129  		// Determines if the pointer to struct was allocated.
   130  		// In case it was  but no field in the struct was
   131  		// updated we must later set it to `nil`.
   132  		var allocatedStruct bool
   133  
   134  		// If the field is a pointer to a struct we must dereference it.
   135  		if srcValField.Kind() == reflect.Ptr && srcValField.Type().Elem().Kind() == reflect.Struct {
   136  			if srcValField.IsNil() {
   137  				allocatedStruct = true
   138  				srcValField.Set(reflect.New(srcValField.Type().Elem()))
   139  			}
   140  			srcValField = srcValField.Elem()
   141  		}
   142  
   143  		// Read-only walk skips empty (zero) fields.
   144  		if opts.OnlyRead && listTag == tagOmitempty && srcValField.IsZero() {
   145  			continue
   146  		}
   147  
   148  		if opts.Allowed != "" {
   149  			allowTag := srcTyField.Tag.Get("allow")
   150  			if allowTag != "" && allowTag != opts.Allowed {
   151  				continue
   152  			}
   153  		}
   154  
   155  		// If it's `any` we must get concrete type.
   156  		if srcValField.Kind() == reflect.Interface && !srcValField.IsZero() {
   157  			srcValField = srcValField.Elem()
   158  		}
   159  
   160  		var dirtyField bool
   161  		if srcValField.Kind() == reflect.Slice {
   162  			if !jsonTagPresent {
   163  				continue
   164  			}
   165  			name := prefix + fieldName
   166  			field := &field{name: name, v: srcValField, listTag: listTag, opts: opts}
   167  			err, stop = updf(name, field)
   168  			dirtyField = field.dirty
   169  		} else if srcValField.Kind() != reflect.Struct {
   170  			// We require that not-omitted fields have JSON tag.
   171  			debug.Assert(jsonTagPresent, prefix+"["+fieldName+"]")
   172  
   173  			// Set value for the field
   174  			name := prefix + fieldName
   175  			field := &field{name: name, v: srcValField, listTag: listTag, opts: opts}
   176  			err, stop = updf(name, field)
   177  			dirtyField = field.dirty
   178  		} else {
   179  			// Recurse into struct
   180  
   181  			// Always take address if possible (assuming that we will set value)
   182  			if srcValField.CanAddr() {
   183  				srcValField = srcValField.Addr()
   184  			}
   185  
   186  			p := prefix
   187  			if fieldName != "" {
   188  				// If struct has JSON tag, we want to include it.
   189  				p += fieldName
   190  			}
   191  
   192  			if opts.VisitAll {
   193  				field := &field{name: p, v: srcValField, listTag: listTag, opts: opts}
   194  				err, stop = updf(p, field)
   195  				dirtyField = field.dirty
   196  			}
   197  
   198  			if !strings.HasSuffix(p, IterFieldNameSepa) && !isInline {
   199  				p += IterFieldNameSepa
   200  			}
   201  
   202  			if err == nil && !stop {
   203  				dirtyField, stop, err = iterFields(p, srcValField.Interface(), updf, opts)
   204  				if allocatedStruct && !dirtyField {
   205  					// If we initialized new struct but no field inside
   206  					// it was set we must set the value of the field to
   207  					// `nil` (as it was before) otherwise we manipulated
   208  					// the field for no reason.
   209  					srcValField = srcVal.Field(i)
   210  					srcValField.Set(reflect.Zero(srcValField.Type()))
   211  				}
   212  			}
   213  		}
   214  
   215  		dirty = dirty || dirtyField
   216  		if stop {
   217  			return
   218  		}
   219  
   220  		if err != nil {
   221  			return dirty, true, err
   222  		}
   223  	}
   224  	return
   225  }
   226  
   227  // update dst with the values from src
   228  func copyProps(src, dst any, asType string) error {
   229  	var (
   230  		srcVal = reflect.ValueOf(src)
   231  		dstVal = reflect.ValueOf(dst).Elem()
   232  	)
   233  	debug.Assertf(cos.StringInSlice(asType, []string{apc.Daemon, apc.Cluster}), "unexpected config level: %s", asType)
   234  	if srcVal.Kind() == reflect.Ptr {
   235  		srcVal = srcVal.Elem()
   236  	}
   237  	for i := range srcVal.NumField() {
   238  		copyTag, ok := srcVal.Type().Field(i).Tag.Lookup("copy")
   239  		if ok && copyTag == "skip" {
   240  			continue
   241  		}
   242  
   243  		var (
   244  			srcValField = srcVal.Field(i)
   245  			fieldName   = srcVal.Type().Field(i).Name
   246  			dstValField = dstVal.FieldByName(fieldName)
   247  		)
   248  		if srcValField.IsNil() {
   249  			continue
   250  		}
   251  		t, ok := dstVal.Type().FieldByName(fieldName)
   252  		debug.Assert(ok, fieldName)
   253  
   254  		// "allow" tag is used exclusively to enforce local vs global scope
   255  		// of the config updates
   256  		allowedScope := t.Tag.Get("allow")
   257  		if allowedScope != "" && allowedScope != asType {
   258  			name := strings.ToLower(fieldName)
   259  			if allowedScope == apc.Cluster && asType == apc.Daemon {
   260  				return fmt.Errorf("%s configuration can only be globally updated", name)
   261  			}
   262  			return fmt.Errorf("cannot update %s configuration: expecting %q scope, got %q", name, allowedScope, asType)
   263  		}
   264  
   265  		if dstValField.Kind() != reflect.Struct && dstValField.Kind() != reflect.Invalid {
   266  			// Set value for the field
   267  			if srcValField.Kind() != reflect.Ptr {
   268  				dstValField.Set(srcValField)
   269  			} else {
   270  				dstValField.Set(srcValField.Elem())
   271  			}
   272  		} else {
   273  			// Recurse into struct
   274  			if err := copyProps(srcValField.Elem().Interface(), dstValField.Addr().Interface(), asType); err != nil {
   275  				return err
   276  			}
   277  		}
   278  	}
   279  	return nil
   280  }
   281  
   282  func mergeProps(src, dst any) {
   283  	var (
   284  		srcVal = reflect.ValueOf(src).Elem()
   285  		dstVal = reflect.ValueOf(dst).Elem()
   286  	)
   287  
   288  	for i := range srcVal.NumField() {
   289  		var (
   290  			srcValField = srcVal.Field(i)
   291  			dstValField = dstVal.FieldByName(srcVal.Type().Field(i).Name)
   292  		)
   293  
   294  		if srcValField.IsNil() {
   295  			continue
   296  		}
   297  
   298  		if dstValField.IsNil() ||
   299  			(srcValField.Elem().Kind() != reflect.Struct && srcValField.Elem().Kind() != reflect.Invalid) {
   300  			dstValField.Set(srcValField)
   301  			continue
   302  		}
   303  
   304  		// Recurse into struct
   305  		mergeProps(srcValField.Interface(), dstValField.Interface())
   306  	}
   307  }
   308  
   309  ///////////
   310  // field //
   311  ///////////
   312  
   313  func (f *field) Value() any { return f.v.Interface() }
   314  
   315  func (f *field) String() (s string) {
   316  	if f.v.Kind() == reflect.String {
   317  		// NOTE: this will panic if the value's type is derived from string (e.g. WritePolicy)
   318  		s = f.Value().(string)
   319  	} else {
   320  		s = fmt.Sprintf("%v", f.Value())
   321  	}
   322  	return
   323  }
   324  
   325  func (f *field) SetValue(src any, force ...bool) error {
   326  	debug.Assert(!f.opts.OnlyRead)
   327  	dst := f.v
   328  	if f.listTag == tagReadonly && (len(force) == 0 || !force[0]) {
   329  		return fmt.Errorf("property %q is readonly", f.name)
   330  	}
   331  	if !dst.CanSet() {
   332  		return fmt.Errorf("failed to set value: %v", dst)
   333  	}
   334  
   335  	srcVal := reflect.ValueOf(src)
   336  reflectDst:
   337  	if srcVal.Kind() == reflect.String {
   338  		dstType := dst.Type().Name()
   339  		// added types: cos.Duration and cos.SizeIEC
   340  		if dstType == "Duration" || dstType == "SizeIEC" {
   341  			var (
   342  				err error
   343  				d   time.Duration
   344  				n   int64
   345  				s   = srcVal.String()
   346  			)
   347  			if dstType == "Duration" {
   348  				d, err = time.ParseDuration(s)
   349  				n = int64(d)
   350  			} else {
   351  				n, err = cos.ParseSize(s, cos.UnitsIEC)
   352  			}
   353  			if err == nil {
   354  				dst.SetInt(n)
   355  				f.dirty = true
   356  			}
   357  			return err
   358  		}
   359  	}
   360  	switch srcVal.Kind() {
   361  	case reflect.String:
   362  		switch dst.Kind() {
   363  		case reflect.String:
   364  			dst.SetString(srcVal.String())
   365  		case reflect.Bool:
   366  			n, err := cos.ParseBool(srcVal.String())
   367  			if err != nil {
   368  				return err
   369  			}
   370  			dst.SetBool(n)
   371  		case reflect.Int64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
   372  			n, err := strconv.ParseInt(srcVal.String(), 10, 64)
   373  			if err != nil {
   374  				return err
   375  			}
   376  			dst.SetInt(n)
   377  		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   378  			n, err := strconv.ParseUint(srcVal.String(), 10, 64)
   379  			if err != nil {
   380  				return err
   381  			}
   382  			dst.SetUint(n)
   383  		case reflect.Float32, reflect.Float64:
   384  			n, err := strconv.ParseFloat(srcVal.String(), dst.Type().Bits())
   385  			if err != nil {
   386  				return err
   387  			}
   388  			dst.SetFloat(n)
   389  		case reflect.Ptr:
   390  			dst.Set(reflect.New(dst.Type().Elem())) // set pointer to default value
   391  			dst = dst.Elem()                        // dereference pointer
   392  			goto reflectDst
   393  		case reflect.Slice:
   394  			// A slice value looks like: "[value1 value2]"
   395  			s := strings.TrimPrefix(srcVal.String(), "[")
   396  			s = strings.TrimSuffix(s, "]")
   397  			if s != "" {
   398  				vals := strings.Split(s, " ")
   399  				tp := reflect.TypeOf(vals[0])
   400  				lst := reflect.MakeSlice(reflect.SliceOf(tp), 0, 10)
   401  				for _, v := range vals {
   402  					if v == "" {
   403  						continue
   404  					}
   405  					lst = reflect.Append(lst, reflect.ValueOf(v))
   406  				}
   407  				dst.Set(lst)
   408  			}
   409  		case reflect.Map:
   410  			// do nothing (e.g. ObjAttrs.CustomMD)
   411  		default:
   412  			debug.Assertf(false, "field.name: %s, field.type: %s", f.listTag, dst.Kind())
   413  		}
   414  	default:
   415  		if !srcVal.IsValid() {
   416  			if src != nil {
   417  				debug.FailTypeCast(srcVal)
   418  				return nil
   419  			}
   420  			srcVal = reflect.Zero(dst.Type())
   421  		}
   422  		if dst.Kind() == reflect.Ptr {
   423  			if dst.IsNil() {
   424  				dst.Set(reflect.New(dst.Type().Elem()))
   425  			}
   426  			dst = dst.Elem()
   427  		}
   428  		dst.Set(srcVal)
   429  	}
   430  
   431  	f.dirty = true
   432  	return nil
   433  }