go.uber.org/yarpc@v1.72.1/internal/config/mapdecode.go (about)

     1  // Copyright (c) 2022 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package config
    22  
    23  import (
    24  	"fmt"
    25  	"reflect"
    26  	"strings"
    27  
    28  	"github.com/uber-go/mapdecode"
    29  	"go.uber.org/yarpc/internal/interpolate"
    30  )
    31  
    32  const (
    33  	_tagName           = "config"
    34  	_interpolateOption = "interpolate"
    35  )
    36  
    37  // DecodeInto will decode the src's data into the dst interface.
    38  func DecodeInto(dst interface{}, src interface{}, opts ...mapdecode.Option) error {
    39  	opts = append(opts, mapdecode.TagName(_tagName))
    40  	return mapdecode.Decode(dst, src, opts...)
    41  }
    42  
    43  // InterpolateWith is a MapDecode option that will read a structField's tag
    44  // information, and if the `interpolate` option is set, it will use the
    45  // interpolate resolver to alter data as it's being decoded into the struct.
    46  func InterpolateWith(resolver interpolate.VariableResolver) mapdecode.Option {
    47  	return mapdecode.FieldHook(func(dest reflect.StructField, srcData reflect.Value) (reflect.Value, error) {
    48  		shouldInterpolate := false
    49  
    50  		options := strings.Split(dest.Tag.Get(_tagName), ",")[1:]
    51  		for _, option := range options {
    52  			if option == _interpolateOption {
    53  				shouldInterpolate = true
    54  				break
    55  			}
    56  		}
    57  
    58  		if !shouldInterpolate {
    59  			return srcData, nil
    60  		}
    61  
    62  		// Use Interface().(string) so that we handle the case where data is an
    63  		// interface{} holding a string.
    64  		v, ok := srcData.Interface().(string)
    65  		if !ok {
    66  			// Cannot interpolate non-string type. This shouldn't be an error
    67  			// because an integer field may be marked as interpolatable and may
    68  			// have received an integer as expected.
    69  			return srcData, nil
    70  		}
    71  
    72  		s, err := interpolate.Parse(v)
    73  		if err != nil {
    74  			return srcData, fmt.Errorf("failed to parse %q for interpolation: %v", v, err)
    75  		}
    76  
    77  		newV, err := s.Render(resolver)
    78  		if err != nil {
    79  			return srcData, fmt.Errorf("failed to render %q with environment variables: %v", v, err)
    80  		}
    81  
    82  		return reflect.ValueOf(newV), nil
    83  	})
    84  }