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  }