go.uber.org/yarpc@v1.72.1/yarpcconfig/decode.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 yarpcconfig
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  
    27  	"github.com/uber-go/mapdecode"
    28  	"go.uber.org/yarpc"
    29  	"go.uber.org/yarpc/internal/config"
    30  	"go.uber.org/zap/zapcore"
    31  )
    32  
    33  type yarpcConfig struct {
    34  	Inbounds   inbounds                       `config:"inbounds"`
    35  	Outbounds  clientConfigs                  `config:"outbounds"`
    36  	Transports map[string]config.AttributeMap `config:"transports"`
    37  	Logging    logging                        `config:"logging"`
    38  	Metrics    metrics                        `config:"metrics"`
    39  }
    40  
    41  // metrics allows configuring the way metrics are emitted from YAML
    42  type metrics struct {
    43  	TagsBlocklist []string `config:"tagsBlocklist"`
    44  }
    45  
    46  // Fills values from this object into the provided YARPC config.
    47  func (m *metrics) fill(cfg *yarpc.Config) {
    48  	cfg.Metrics.TagsBlocklist = m.TagsBlocklist
    49  }
    50  
    51  // logging allows configuring the log levels from YAML.
    52  type logging struct {
    53  	Levels struct {
    54  		// Defaults regardless of direction.
    55  		Success          *zapLevel `config:"success"`
    56  		Failure          *zapLevel `config:"failure"`
    57  		ApplicationError *zapLevel `config:"applicationError"`
    58  		ServerError      *zapLevel `config:"serverError"`
    59  		ClientError      *zapLevel `config:"clientError"`
    60  
    61  		// Directional overrides.
    62  		Inbound  levels `config:"inbound"`
    63  		Outbound levels `config:"outbound"`
    64  	} `config:"levels"`
    65  }
    66  
    67  type levels struct {
    68  	Success          *zapLevel `config:"success"`
    69  	Failure          *zapLevel `config:"failure"`
    70  	ApplicationError *zapLevel `config:"applicationError"`
    71  	ServerError      *zapLevel `config:"serverError"`
    72  	ClientError      *zapLevel `config:"clientError"`
    73  }
    74  
    75  // Fills values from this object into the provided YARPC config.
    76  func (l *logging) fill(cfg *yarpc.Config) {
    77  	cfg.Logging.Levels.Success = (*zapcore.Level)(l.Levels.Success)
    78  	cfg.Logging.Levels.Failure = (*zapcore.Level)(l.Levels.Failure)
    79  	cfg.Logging.Levels.ApplicationError = (*zapcore.Level)(l.Levels.ApplicationError)
    80  	cfg.Logging.Levels.ServerError = (*zapcore.Level)(l.Levels.ServerError)
    81  	cfg.Logging.Levels.ClientError = (*zapcore.Level)(l.Levels.ClientError)
    82  
    83  	l.Levels.Inbound.fill(&cfg.Logging.Levels.Inbound)
    84  	l.Levels.Outbound.fill(&cfg.Logging.Levels.Outbound)
    85  }
    86  
    87  func (l *levels) fill(cfg *yarpc.DirectionalLogLevelConfig) {
    88  	cfg.Success = (*zapcore.Level)(l.Success)
    89  	cfg.Failure = (*zapcore.Level)(l.Failure)
    90  	cfg.ApplicationError = (*zapcore.Level)(l.ApplicationError)
    91  	cfg.ServerError = (*zapcore.Level)(l.ServerError)
    92  	cfg.ClientError = (*zapcore.Level)(l.ClientError)
    93  }
    94  
    95  type zapLevel zapcore.Level
    96  
    97  // mapdecode doesn't suport encoding.TextMarhsaler by default so we have to do
    98  // this manually.
    99  func (l *zapLevel) Decode(into mapdecode.Into) error {
   100  	var s string
   101  	if err := into(&s); err != nil {
   102  		return fmt.Errorf("could not decode Zap log level: %v", err)
   103  	}
   104  
   105  	err := (*zapcore.Level)(l).UnmarshalText([]byte(s))
   106  	if err != nil {
   107  		return fmt.Errorf("could not decode Zap log level: %v", err)
   108  	}
   109  	return err
   110  }
   111  
   112  type inbounds []inbound
   113  
   114  func (is *inbounds) Decode(into mapdecode.Into) error {
   115  	var items map[string]inbound
   116  	if err := into(&items); err != nil {
   117  		return fmt.Errorf("failed to decode inbound items: %v", err)
   118  	}
   119  
   120  	for k, v := range items {
   121  		if v.Type == "" {
   122  			v.Type = k
   123  		}
   124  		*is = append(*is, v)
   125  	}
   126  	return nil
   127  }
   128  
   129  type inbound struct {
   130  	Type       string
   131  	Disabled   bool
   132  	Attributes config.AttributeMap
   133  }
   134  
   135  func (i *inbound) Decode(into mapdecode.Into) error {
   136  	if err := into(&i.Attributes); err != nil {
   137  		return fmt.Errorf("failed to decode inbound: %v", err)
   138  	}
   139  
   140  	var err error
   141  	i.Type, err = i.Attributes.PopString("type")
   142  	if err != nil {
   143  		return fmt.Errorf(`failed to read attribute "type" of inbound: %v`, err)
   144  	}
   145  	i.Disabled, err = i.Attributes.PopBool("disabled")
   146  	if err != nil {
   147  		return fmt.Errorf(`failed to read attribute "disabled" of inbound: %v`, err)
   148  	}
   149  
   150  	return nil
   151  }
   152  
   153  type clientConfigs map[string]outbounds
   154  
   155  func (cc *clientConfigs) Decode(into mapdecode.Into) error {
   156  	var items map[string]outbounds
   157  	if err := into(&items); err != nil {
   158  		return fmt.Errorf("failed to decode outbound items: %v", err)
   159  	}
   160  
   161  	for k, v := range items {
   162  		if v.Service == "" {
   163  			v.Service = k
   164  		}
   165  		items[k] = v
   166  	}
   167  	*cc = items
   168  	return nil
   169  }
   170  
   171  type outbounds struct {
   172  	Service string
   173  
   174  	// Either (Unary and/or Oneway) will be set or Implicit will be set. For
   175  	// the latter case, we need to only use those configurations that that
   176  	// transport supports.
   177  	Unary    *outbound
   178  	Oneway   *outbound
   179  	Stream   *outbound
   180  	Implicit *outbound
   181  }
   182  
   183  func (o *outbounds) Decode(into mapdecode.Into) error {
   184  	var attrs config.AttributeMap
   185  	if err := into(&attrs); err != nil {
   186  		return fmt.Errorf("failed to decode outbound configuration: %v", err)
   187  	}
   188  
   189  	var err error
   190  	o.Service, err = attrs.PopString("service")
   191  	if err != nil {
   192  		return fmt.Errorf("failed to read service name for outbound: %v", err)
   193  	}
   194  
   195  	hasUnary, err := attrs.Pop("unary", &o.Unary)
   196  	if err != nil {
   197  		return fmt.Errorf("failed to unary outbound configuration: %v", err)
   198  	}
   199  
   200  	hasOneway, err := attrs.Pop("oneway", &o.Oneway)
   201  	if err != nil {
   202  		return fmt.Errorf("failed to oneway outbound configuration: %v", err)
   203  	}
   204  
   205  	hasStream, err := attrs.Pop("stream", &o.Stream)
   206  	if err != nil {
   207  		return fmt.Errorf("failed to stream outbound configuration: %v", err)
   208  	}
   209  
   210  	if hasUnary || hasOneway || hasStream {
   211  		// No more attributes should be remaining
   212  		var empty struct{}
   213  		if err := attrs.Decode(&empty); err != nil {
   214  			return fmt.Errorf(
   215  				"too many attributes in explicit outbound configuration: %v", err)
   216  		}
   217  		return nil
   218  	}
   219  
   220  	if err := attrs.Decode(&o.Implicit); err != nil {
   221  		return fmt.Errorf("failed to decode implicit outbound configuration: %v", err)
   222  	}
   223  	return nil
   224  }
   225  
   226  type outbound struct {
   227  	Type       string
   228  	Attributes config.AttributeMap
   229  }
   230  
   231  func (o *outbound) Decode(into mapdecode.Into) error {
   232  	var cfg map[string]config.AttributeMap
   233  	if err := into(&cfg); err != nil {
   234  		return fmt.Errorf("failed to decode outbound: %v", err)
   235  	}
   236  
   237  	switch len(cfg) {
   238  	case 0:
   239  		return errors.New("failed to decode outbound: an outbound type is required")
   240  	case 1:
   241  		// Move along
   242  	default:
   243  		return errors.New("failed to decode outbound: " +
   244  			"at most one outbound type may be specified")
   245  	}
   246  
   247  	for k, attrs := range cfg {
   248  		o.Type = k
   249  		o.Attributes = attrs
   250  	}
   251  
   252  	return nil
   253  }