package source import ( "fmt" "math" "reflect" "sort" ) // CloneMetadata returns an independently owned copy of JSON-shaped metadata. // It preserves concrete map, slice, and array types while rejecting values that // cannot be safely represented as JSON-shaped metadata. func CloneMetadata(metadata map[string]any) (map[string]any, error) { if len(metadata) == 0 { return nil, nil } cloned, err := cloneMetadataValue(reflect.ValueOf(metadata), "metadata", make(map[metadataVisit]struct{})) if err != nil { return nil, err } return cloned.Interface().(map[string]any), nil } type metadataVisit struct { typ reflect.Type ptr uintptr } func cloneMetadataValue(value reflect.Value, location string, active map[metadataVisit]struct{}) (reflect.Value, error) { if !value.IsValid() { return value, nil } switch value.Kind() { case reflect.Interface: if value.IsNil() { return reflect.Zero(value.Type()), nil } cloned, err := cloneMetadataValue(value.Elem(), location, active) if err != nil { return reflect.Value{}, err } result := reflect.New(value.Type()).Elem() result.Set(cloned) return result, nil case reflect.Map: if value.IsNil() { return reflect.Zero(value.Type()), nil } if value.Type().Key().Kind() != reflect.String { return reflect.Value{}, fmt.Errorf("%s has unsupported map key type %s", location, value.Type().Key()) } leave, err := enterMetadataValue(value, active, location) if err != nil { return reflect.Value{}, err } defer leave() keys := value.MapKeys() sort.Slice(keys, func(i, j int) bool { return keys[i].String() < keys[j].String() }) result := reflect.MakeMapWithSize(value.Type(), value.Len()) for _, key := range keys { cloned, err := cloneMetadataValue(value.MapIndex(key), location+"."+key.String(), active) if err != nil { return reflect.Value{}, err } result.SetMapIndex(key, cloned) } return result, nil case reflect.Slice: if value.IsNil() { return reflect.Zero(value.Type()), nil } leave, err := enterMetadataValue(value, active, location) if err != nil { return reflect.Value{}, err } defer leave() result := reflect.MakeSlice(value.Type(), value.Len(), value.Len()) for i := 0; i < value.Len(); i++ { cloned, err := cloneMetadataValue(value.Index(i), fmt.Sprintf("%s[%d]", location, i), active) if err != nil { return reflect.Value{}, err } result.Index(i).Set(cloned) } return result, nil case reflect.Array: result := reflect.New(value.Type()).Elem() for i := 0; i < value.Len(); i++ { cloned, err := cloneMetadataValue(value.Index(i), fmt.Sprintf("%s[%d]", location, i), active) if err != nil { return reflect.Value{}, err } result.Index(i).Set(cloned) } return result, nil case reflect.Float32, reflect.Float64: if math.IsNaN(value.Float()) || math.IsInf(value.Float(), 0) { return reflect.Value{}, fmt.Errorf("%s has a non-finite number", location) } return value, nil case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.String: return value, nil default: return reflect.Value{}, fmt.Errorf("%s has unsupported type %s", location, value.Type()) } } func enterMetadataValue(value reflect.Value, active map[metadataVisit]struct{}, location string) (func(), error) { visit := metadataVisit{typ: value.Type(), ptr: value.Pointer()} if _, exists := active[visit]; exists { return nil, fmt.Errorf("%s contains a cycle", location) } active[visit] = struct{}{} return func() { delete(active, visit) }, nil }