github.com/facebookincubator/go-belt@v0.0.0-20230703220935-39cd348f1a38/pkg/valuesparser/values_parser.go (about) 1 // Copyright 2022 Meta Platforms, Inc. and affiliates. 2 // 3 // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 // 5 // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 // 7 // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 // 9 // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 // 11 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 13 package valuesparser 14 15 import ( 16 "fmt" 17 "io" 18 "reflect" 19 "strings" 20 21 "github.com/facebookincubator/go-belt/pkg/field" 22 ) 23 24 // AnySlice a handler for arbitrary values, which extracts structured fields/values 25 // by method ForEachField() and provides everything else as a string by method WriteUnparsed. 26 // 27 // It also implements fields.AbstractFields, so it could be directly used as a collection 28 // of structured fields right after just a type-casting ([]any -> AnySlice). It is 29 // supposed to be a zero allocation implementation in future. 30 // 31 // For example it is used to parse all the arguments of logger.Logger.Log. 32 type AnySlice []any 33 34 // ForEachField implements field.AbstractFields. 35 func (p *AnySlice) ForEachField(callback func(f *field.Field) bool) bool { 36 for idx := 0; idx < len(*p); { 37 value := (*p)[idx] 38 39 if value == nil { 40 idx++ 41 continue 42 } 43 44 switch v := value.(type) { 45 case field.ForEachFieldser: 46 if !v.ForEachField(callback) { 47 return false 48 } 49 idx++ 50 continue 51 case error: 52 callback(&field.Field{ 53 Key: "error", 54 Value: v, 55 Properties: nil, 56 }) 57 } 58 59 v := reflect.Indirect(reflect.ValueOf(value)) 60 switch v.Kind() { 61 case reflect.Map: 62 r := ParseMapValue(v, callback) 63 (*p)[idx] = nil 64 if !r { 65 return false 66 } 67 case reflect.Struct: 68 r := ParseStructValue(nil, v, callback) 69 (*p)[idx] = nil 70 if !r { 71 return false 72 } 73 default: 74 idx++ 75 continue 76 } 77 } 78 return true 79 } 80 81 // ParseMapValue calls the callback for each pair in the map until first false is returned 82 // 83 // It returns false if callback returned false. 84 func ParseMapValue(m reflect.Value, callback func(f *field.Field) bool) bool { 85 var f field.Field 86 for _, keyV := range m.MapKeys() { 87 valueV := m.MapIndex(keyV) 88 switch key := keyV.Interface().(type) { 89 case field.Key: 90 f.Key = key 91 default: 92 f.Key = fmt.Sprint(key) 93 } 94 f.Value = valueV.Interface() 95 if !callback(&f) { 96 return false 97 } 98 } 99 return true 100 } 101 102 // ParseStructValue parses a structure to a collection of fields. 103 // 104 // `fieldPath` is the prefix of the field-name. 105 // `_struct` is the structure to be parsed (provided as a reflect.Value). 106 // `callback` is the function called for each found field, until first false is returned. 107 // 108 // It returns false if callback returned false. 109 func ParseStructValue(fieldPath []string, _struct reflect.Value, callback func(f *field.Field) bool) bool { 110 s := reflect.Indirect(_struct) 111 112 var f field.Field 113 // TODO: optimize this 114 t := s.Type() 115 116 fieldPath = append(fieldPath, "") 117 118 fieldCount := s.NumField() 119 for fieldNum := 0; fieldNum < fieldCount; fieldNum++ { 120 structFieldType := t.Field(fieldNum) 121 if structFieldType.PkgPath != "" { 122 // unexported 123 continue 124 } 125 logTag := structFieldType.Tag.Get("log") 126 if logTag == "-" { 127 continue 128 } 129 structField := s.Field(fieldNum) 130 if structField.IsZero() { 131 continue 132 } 133 value := reflect.Indirect(structField) 134 135 pathComponent := structFieldType.Name 136 if logTag != "" { 137 pathComponent = logTag 138 } 139 fieldPath[len(fieldPath)-1] = pathComponent 140 141 if value.Kind() == reflect.Struct { 142 if !ParseStructValue(fieldPath, value, callback) { 143 return false 144 } 145 continue 146 } 147 148 f.Key = strings.Join(fieldPath, ".") 149 f.Value = value.Interface() 150 if !callback(&f) { 151 return false 152 } 153 } 154 155 return true 156 } 157 158 // Len implements field.AbstractFields. 159 func (p *AnySlice) Len() int { 160 return len(*p) 161 } 162 163 // WriteUnparsed writes unstructured data (everything that was never considered as a structured field by 164 // ForEachField method) to the given io.Writer. 165 func (p *AnySlice) WriteUnparsed(w io.Writer) { 166 for _, value := range *p { 167 if value == nil { 168 continue 169 } 170 171 fmt.Fprint(w, value) 172 } 173 }