@@ -2,7 +2,9 @@ package golden
2
2
3
3
import (
4
4
"fmt"
5
+ "math"
5
6
"reflect"
7
+ "regexp"
6
8
"strconv"
7
9
"strings"
8
10
"time"
@@ -22,17 +24,32 @@ func replaceTransient(
22
24
23
25
replaced := map [string ]any {}
24
26
for key , value := range original {
27
+ // Keep the original value.
25
28
replaced [key ] = value
29
+
30
+ // Check if the field is meant to be replaced. If not, continue.
31
+ // We also check for wildcard replacements that are meant to replace all
32
+ // fields in a slice.
26
33
replacement , isTransient := transientLookup [key ]
27
- if ! isTransient {
34
+ cleanedKey := replaceIndicesInKeys (key )
35
+ replacementCleaned , isTransientCleaned := transientLookup [cleanedKey ]
36
+ if ! isTransient && ! isTransientCleaned {
37
+ // No replacement defined, continue and keep the original value.
28
38
continue
29
39
}
40
+ if isTransientCleaned {
41
+ replacement = replacementCleaned
42
+ }
30
43
44
+ // Replace the value with the replacement value.
31
45
if replacement != nil {
32
46
replaced [key ] = replacement
33
47
continue
34
48
}
35
49
50
+ // No replacement defined, we fall back to default stable values here
51
+ // (based on type).
52
+
36
53
if stringValue , isString := value .(string ); isString {
37
54
if _ , err := time .Parse (time .RFC3339 , stringValue ); err == nil {
38
55
replaced [key ] = StableTime
@@ -67,6 +84,69 @@ func replaceTransient(
67
84
return replaced
68
85
}
69
86
87
+ // roundFields rounds all the values in a map whose key is contained in the
88
+ // variadic list of roundingConfigs. The rounded value has a stable value
89
+ // according to the data type.
90
+ func roundFields (
91
+ original map [string ]any ,
92
+ roundedFields ... RoundingConfig ,
93
+ ) (map [string ]any , error ) {
94
+ roundingLookup := map [string ]int {}
95
+ for _ , field := range roundedFields {
96
+ roundingLookup [field .Key ] = field .Precision
97
+ }
98
+
99
+ replaced := map [string ]any {}
100
+ for key , value := range original {
101
+ // Keep the original value.
102
+ replaced [key ] = value
103
+
104
+ // Check if the field is meant to be rounded. If not, continue.
105
+ // We also check for wildcard replacements that are meant to replace all
106
+ // fields in a slice.
107
+ cleanedKey := replaceIndicesInKeys (key )
108
+ replacement , isRounded := roundingLookup [key ]
109
+ replacementCleaned , isRoundedCleaned := roundingLookup [cleanedKey ]
110
+ if ! isRounded && ! isRoundedCleaned {
111
+ // No rounding defined, continue and keep the original value.
112
+ continue
113
+ }
114
+ if isRoundedCleaned {
115
+ replacement = replacementCleaned
116
+ }
117
+
118
+ // We don't deal with negative precision values.
119
+ if replacement < 0 {
120
+ continue
121
+ }
122
+
123
+ // Replace the value with the rounded value.
124
+ if _ , isFloat := value .(float64 ); isFloat {
125
+ replaced [key ] = round (value .(float64 ), replacement )
126
+ continue
127
+ }
128
+
129
+ // If the value was not a float, return an error.
130
+ return nil , fmt .Errorf ("field %s is not a float" , key )
131
+ }
132
+
133
+ return replaced , nil
134
+ }
135
+
136
+ // round rounds a float64 value to a given precision.
137
+ func round (value float64 , precision int ) float64 {
138
+ shift := math .Pow (10 , float64 (precision ))
139
+ return math .Round (value * shift ) / shift
140
+ }
141
+
142
+ var keyIndexMatcher = regexp .MustCompile (`\[\d+\]` )
143
+
144
+ // replaceIndicesInKeys replaces all the indices in a key with "[]" to make it
145
+ // easier to match them with configuration defined in jq-style.
146
+ func replaceIndicesInKeys (key string ) string {
147
+ return keyIndexMatcher .ReplaceAllString (key , "[]" )
148
+ }
149
+
70
150
/*
71
151
flatten takes a nested map and flattens it into a single level map. The
72
152
flattening roughly follows the [JSONPath] standard. Please see test function to
0 commit comments