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 }