go.uber.org/yarpc@v1.72.1/yarpcconfig/spec.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  	"reflect"
    27  	"strings"
    28  
    29  	"github.com/uber-go/mapdecode"
    30  	"go.uber.org/multierr"
    31  	"go.uber.org/yarpc/api/peer"
    32  	"go.uber.org/yarpc/api/transport"
    33  	"go.uber.org/yarpc/internal/config"
    34  )
    35  
    36  // TransportSpec specifies the configuration parameters for a transport. These
    37  // specifications are registered against a Configurator to teach it how to
    38  // parse the configuration for that transport and build instances of it.
    39  //
    40  // Every TransportSpec MUST have a BuildTransport function. The spec may
    41  // provide BuildInbound, BuildUnaryOutbound, and BuildOnewayOutbound functions
    42  // if the Transport supports that functionality. For example, if a transport
    43  // only supports incoming and outgoing Oneway requests, its spec will provide a
    44  // BuildTransport, BuildInbound, and BuildOnewayOutbound function.
    45  //
    46  // The signature of BuildTransport must have the shape:
    47  //
    48  //  func(C, *config.Kit) (transport.Transport, error)
    49  //
    50  // Where C is a struct defining the configuration parameters for the transport,
    51  // the kit carries information and tools from the configurator to this and
    52  // other builders.
    53  //
    54  // The remaining Build* functions must have a similar signature, but also
    55  // receive the transport instance.
    56  //
    57  // 	func(C, transport.Transport, *config.Kit) (X, error)
    58  //
    59  // Where X is one of, transport.Inbound, transport.UnaryOutbound, or
    60  // transport.OnewayOutbound.
    61  //
    62  // For example,
    63  //
    64  // 	func(*OutboundConfig, transport.Transport) (transport.UnaryOutbound, error)
    65  //
    66  // Is a function to build a unary outbound from its outbound configuration and
    67  // the corresponding transport.
    68  type TransportSpec struct {
    69  	// Name of the transport
    70  	Name string
    71  
    72  	// A function in the shape,
    73  	//
    74  	// 	func(C, *config.Kit) (transport.Transport, error)
    75  	//
    76  	// Where C is a struct or pointer to a struct defining the configuration
    77  	// parameters accepted by this transport.
    78  	//
    79  	// This function will be called with the parsed configuration to build
    80  	// Transport defined by this spec.
    81  	BuildTransport interface{}
    82  
    83  	// TODO(abg): Make error returns optional
    84  
    85  	// A function in the shape,
    86  	//
    87  	// 	func(C, transport.Transport, *config.Kit) (transport.Inbound, error)
    88  	//
    89  	// Where C is a struct or pointer to a struct defining the configuration
    90  	// parameters for the inbound.
    91  	//
    92  	// This may be nil if this transport does not support inbounds.
    93  	//
    94  	// This function will be called with the parsed configuration and the
    95  	// transport built by BuildTransport to build the inbound for this
    96  	// transport.
    97  	BuildInbound interface{}
    98  
    99  	// The following two are functions in the shapes,
   100  	//
   101  	// 	func(C, transport.Transport, *config.Kit) (transport.UnaryOutbound, error)
   102  	// 	func(C, transport.Transport, *config.Kit) (transport.OnewayOutbound, error)
   103  	//
   104  	// Where C is a struct or pointer to a struct defining the configuration
   105  	// parameters for outbounds of that RPC type.
   106  	//
   107  	// Either value may be nil to indicate that the transport does not support
   108  	// unary or oneway outbounds.
   109  	//
   110  	// These functions will be called with the parsed configurations and the
   111  	// transport built by BuildTransport to build the unary and oneway
   112  	// outbounds for this transport.
   113  	BuildUnaryOutbound  interface{}
   114  	BuildOnewayOutbound interface{}
   115  	BuildStreamOutbound interface{}
   116  
   117  	// Named presets.
   118  	//
   119  	// These may be used by specifying a `with` key in the outbound
   120  	// configuration.
   121  	PeerChooserPresets []PeerChooserPreset
   122  
   123  	// TODO(abg): Allow functions to return and accept specific
   124  	// implementations. Instead of returning a transport.Transport and
   125  	// accepting a transport.Transport, we could make it so that
   126  	//
   127  	// 	BuildTransport: func(...) (*http.Transport, error)
   128  	// 	BuildInbound: func(..., t *http.Transport) (*http.Inbound, error)
   129  	//
   130  	// This will get rid of the `t.(*http.Transport)` users will have to do
   131  	// the first thing inside their BuildInbound.
   132  }
   133  
   134  // PeerChooserPreset defines a named preset for a peer chooser. Peer chooser
   135  // presets may be used by specifying a `with` key in the outbound
   136  // configuration.
   137  //
   138  // 	http:
   139  // 	  with: mypreset
   140  type PeerChooserPreset struct {
   141  	Name string
   142  
   143  	// A function in the shape,
   144  	//
   145  	//  func(peer.Transport, *config.Kit) (peer.Chooser, error)
   146  	//
   147  	// Where the first argument is the transport object for which this preset
   148  	// is being built.
   149  	BuildPeerChooser interface{}
   150  
   151  	// NOTE(abg): BuildChooser /could/ be a well-defined func type rather
   152  	// than an interface{}. We've kept it as an interface{} so that we have
   153  	// the freedom to add more information to the functions in the future.
   154  }
   155  
   156  // PeerChooserSpec specifies the configuration parameters for an outbound peer
   157  // chooser. Peer choosers dictate how peers are selected for an outbound. These
   158  // specifications are registered against a Configurator to teach it how to parse
   159  // the configuration for that peer chooser and build instances of it.
   160  //
   161  // For example, we could implement and register a peer chooser spec that selects
   162  // peers based on advanced configuration or sharding information.
   163  //
   164  // 	myoutbound:
   165  // 	  tchannel:
   166  // 	    mysharder:
   167  //        shard1: 1.1.1.1:1234
   168  //        ...
   169  type PeerChooserSpec struct {
   170  	Name string
   171  
   172  	// A function in the shape,
   173  	//
   174  	//  func(C, p peer.Transport, *config.Kit) (peer.Chooser, error)
   175  	//
   176  	// Where C is a struct or pointer to a struct defining the configuration
   177  	// parameters needed to build this peer chooser.
   178  	//
   179  	// BuildPeerChooser is required.
   180  	BuildPeerChooser interface{}
   181  }
   182  
   183  // PeerListSpec specifies the configuration parameters for an outbound peer
   184  // list. Peer lists dictate the peer selection strategy and receive updates of
   185  // new and removed peers from peer updaters. These specifications are
   186  // registered against a Configurator to teach it how to parse the
   187  // configuration for that peer list and build instances of it.
   188  //
   189  // For example, we could implement and register a peer list spec that selects
   190  // peers at random and a peer list updater which pushes updates to it by
   191  // polling a specific DNS A record.
   192  //
   193  // 	myoutbound:
   194  // 	  random:
   195  // 	    dns:
   196  // 	      name: myservice.example.com
   197  type PeerListSpec struct {
   198  	Name string
   199  
   200  	// A function in the shape,
   201  	//
   202  	//  func(C, peer.Transport, *config.Kit) (peer.ChooserList, error)
   203  	//
   204  	// Where C is a struct or pointer to a struct defining the configuration
   205  	// parameters needed to build this peer list. Parameters on the struct
   206  	// should not conflict with peer list updater names as they share the
   207  	// namespace with these fields.
   208  	//
   209  	// BuildPeerList is required.
   210  	BuildPeerList interface{}
   211  }
   212  
   213  // PeerListUpdaterSpec specifies the configuration parameters for an outbound
   214  // peer list updater. Peer list updaters inform peer lists about peers as they
   215  // are added or removed. These specifications are registered against a
   216  // Configurator to teach it how to parse the configuration for that peer list
   217  // updater and build instances of it.
   218  //
   219  // For example, we could implement a peer list updater which monitors a
   220  // specific file on the system for a list of peers and pushes updates to any
   221  // peer list.
   222  //
   223  // 	myoutbound:
   224  // 	  round-robin:
   225  // 	    peers-file:
   226  // 	      format: json
   227  // 	      path: /etc/hosts.json
   228  type PeerListUpdaterSpec struct {
   229  	// Name of the peer selection strategy.
   230  	Name string
   231  
   232  	// A function in the shape,
   233  	//
   234  	//  func(C, *config.Kit) (peer.Binder, error)
   235  	//
   236  	// Where C is a struct or pointer to a struct defining the configuration
   237  	// parameters accepted by this peer chooser.
   238  	//
   239  	// The returned peer binder will receive the peer list specified alongside
   240  	// the peer updater; it should return a peer updater that feeds updates to
   241  	// that peer list once started.
   242  	//
   243  	// BuildPeerListUpdater is required.
   244  	BuildPeerListUpdater interface{}
   245  }
   246  
   247  var (
   248  	_typeOfError           = reflect.TypeOf((*error)(nil)).Elem()
   249  	_typeOfTransport       = reflect.TypeOf((*transport.Transport)(nil)).Elem()
   250  	_typeOfInbound         = reflect.TypeOf((*transport.Inbound)(nil)).Elem()
   251  	_typeOfUnaryOutbound   = reflect.TypeOf((*transport.UnaryOutbound)(nil)).Elem()
   252  	_typeOfOnewayOutbound  = reflect.TypeOf((*transport.OnewayOutbound)(nil)).Elem()
   253  	_typeOfStreamOutbound  = reflect.TypeOf((*transport.StreamOutbound)(nil)).Elem()
   254  	_typeOfPeerTransport   = reflect.TypeOf((*peer.Transport)(nil)).Elem()
   255  	_typeOfPeerChooserList = reflect.TypeOf((*peer.ChooserList)(nil)).Elem()
   256  	_typeOfPeerChooser     = reflect.TypeOf((*peer.Chooser)(nil)).Elem()
   257  	_typeOfBinder          = reflect.TypeOf((*peer.Binder)(nil)).Elem()
   258  )
   259  
   260  // Compiled internal representation of a user-specified TransportSpec.
   261  type compiledTransportSpec struct {
   262  	Name string // name of the transport
   263  
   264  	// configSpec of the top-level transport object
   265  	Transport *configSpec
   266  
   267  	// The following are non-nil only if the transport supports that specific
   268  	// functionality.
   269  
   270  	Inbound        *configSpec
   271  	UnaryOutbound  *configSpec
   272  	OnewayOutbound *configSpec
   273  	StreamOutbound *configSpec
   274  
   275  	PeerChooserPresets map[string]*compiledPeerChooserPreset
   276  }
   277  
   278  func (s *compiledTransportSpec) SupportsUnaryOutbound() bool {
   279  	return s.UnaryOutbound != nil
   280  }
   281  
   282  func (s *compiledTransportSpec) SupportsOnewayOutbound() bool {
   283  	return s.OnewayOutbound != nil
   284  }
   285  
   286  func (s *compiledTransportSpec) SupportsStreamOutbound() bool {
   287  	return s.StreamOutbound != nil
   288  }
   289  
   290  func compileTransportSpec(spec *TransportSpec) (*compiledTransportSpec, error) {
   291  	out := compiledTransportSpec{Name: spec.Name}
   292  
   293  	if spec.Name == "" {
   294  		return nil, errors.New("field Name is required")
   295  	}
   296  
   297  	switch strings.ToLower(spec.Name) {
   298  	case "unary", "oneway", "stream":
   299  		return nil, fmt.Errorf("transport name cannot be %q: %q is a reserved name", spec.Name, spec.Name)
   300  	}
   301  
   302  	if spec.BuildTransport == nil {
   303  		return nil, errors.New("field BuildTransport is required")
   304  	}
   305  
   306  	var err error
   307  
   308  	// Helper to chain together the compile calls
   309  	appendError := func(cs *configSpec, e error) *configSpec {
   310  		err = multierr.Append(err, e)
   311  		return cs
   312  	}
   313  
   314  	out.Transport = appendError(compileTransportConfig(spec.BuildTransport))
   315  	if spec.BuildInbound != nil {
   316  		out.Inbound = appendError(compileInboundConfig(spec.BuildInbound))
   317  	}
   318  	if spec.BuildUnaryOutbound != nil {
   319  		out.UnaryOutbound = appendError(compileUnaryOutboundConfig(spec.BuildUnaryOutbound))
   320  	}
   321  	if spec.BuildOnewayOutbound != nil {
   322  		out.OnewayOutbound = appendError(compileOnewayOutboundConfig(spec.BuildOnewayOutbound))
   323  	}
   324  	if spec.BuildStreamOutbound != nil {
   325  		out.StreamOutbound = appendError(compileStreamOutboundConfig(spec.BuildStreamOutbound))
   326  	}
   327  
   328  	if len(spec.PeerChooserPresets) == 0 {
   329  		return &out, err
   330  	}
   331  
   332  	presets := make(map[string]*compiledPeerChooserPreset, len(spec.PeerChooserPresets))
   333  	out.PeerChooserPresets = presets
   334  	for _, p := range spec.PeerChooserPresets {
   335  		if _, ok := presets[p.Name]; ok {
   336  			err = multierr.Append(err, fmt.Errorf(
   337  				"found multiple peer lists with the name %q under transport %q",
   338  				p.Name, spec.Name))
   339  			continue
   340  		}
   341  
   342  		cp, e := compilePeerChooserPreset(p)
   343  		if e != nil {
   344  			err = multierr.Append(err, fmt.Errorf(
   345  				"failed to compile preset for transport %q: %v", spec.Name, e))
   346  			continue
   347  		}
   348  
   349  		presets[p.Name] = cp
   350  	}
   351  
   352  	return &out, err
   353  }
   354  
   355  func compileTransportConfig(build interface{}) (*configSpec, error) {
   356  	v := reflect.ValueOf(build)
   357  	t := v.Type()
   358  
   359  	var err error
   360  	switch {
   361  	case t.Kind() != reflect.Func:
   362  		err = errors.New("must be a function")
   363  	case t.NumIn() != 2:
   364  		err = fmt.Errorf("must accept exactly two arguments, found %v", t.NumIn())
   365  	case !isDecodable(t.In(0)):
   366  		err = fmt.Errorf("must accept a struct or struct pointer as its first argument, found %v", t.In(0))
   367  	case t.In(1) != _typeOfKit:
   368  		err = fmt.Errorf("must accept a %v as its second argument, found %v", _typeOfKit, t.In(1))
   369  	case t.NumOut() != 2:
   370  		err = fmt.Errorf("must return exactly two results, found %v", t.NumOut())
   371  	case t.Out(0) != _typeOfTransport:
   372  		err = fmt.Errorf("must return a transport.Transport as its first result, found %v", t.Out(0))
   373  	case t.Out(1) != _typeOfError:
   374  		err = fmt.Errorf("must return an error as its second result, found %v", t.Out(1))
   375  	}
   376  
   377  	if err != nil {
   378  		return nil, fmt.Errorf("invalid BuildTransport %v: %v", t, err)
   379  	}
   380  
   381  	return &configSpec{inputType: t.In(0), factory: v}, nil
   382  }
   383  
   384  func compileInboundConfig(build interface{}) (*configSpec, error) {
   385  	v := reflect.ValueOf(build)
   386  	t := v.Type()
   387  
   388  	if err := validateConfigFunc(t, _typeOfInbound); err != nil {
   389  		return nil, fmt.Errorf("invalid BuildInbound: %v", err)
   390  	}
   391  
   392  	inputType := t.In(0)
   393  
   394  	fields := fieldNames(inputType)
   395  	if _, hasType := fields["Type"]; hasType {
   396  		return nil, errors.New("inbound configurations must not have a Type field: Type is a reserved field name")
   397  	}
   398  
   399  	if _, hasDisabled := fields["Disabled"]; hasDisabled {
   400  		return nil, errors.New("inbound configurations must not have a Disabled field: Disabled is a reserved field name")
   401  	}
   402  
   403  	return &configSpec{inputType: inputType, factory: v}, nil
   404  }
   405  
   406  func compileUnaryOutboundConfig(build interface{}) (*configSpec, error) {
   407  	v := reflect.ValueOf(build)
   408  	t := v.Type()
   409  
   410  	if err := validateConfigFunc(t, _typeOfUnaryOutbound); err != nil {
   411  		return nil, fmt.Errorf("invalid BuildUnaryOutbound: %v", err)
   412  	}
   413  
   414  	return &configSpec{inputType: t.In(0), factory: v}, nil
   415  }
   416  
   417  func compileOnewayOutboundConfig(build interface{}) (*configSpec, error) {
   418  	v := reflect.ValueOf(build)
   419  	t := v.Type()
   420  
   421  	if err := validateConfigFunc(t, _typeOfOnewayOutbound); err != nil {
   422  		return nil, fmt.Errorf("invalid BuildOnewayOutbound: %v", err)
   423  	}
   424  
   425  	return &configSpec{inputType: t.In(0), factory: v}, nil
   426  }
   427  
   428  func compileStreamOutboundConfig(build interface{}) (*configSpec, error) {
   429  	v := reflect.ValueOf(build)
   430  	t := v.Type()
   431  
   432  	if err := validateConfigFunc(t, _typeOfStreamOutbound); err != nil {
   433  		return nil, fmt.Errorf("invalid BuildStreamOutbound: %v", err)
   434  	}
   435  
   436  	return &configSpec{inputType: t.In(0), factory: v}, nil
   437  }
   438  
   439  // Common validation for all build functions except Tranport.
   440  func validateConfigFunc(t reflect.Type, outputType reflect.Type) error {
   441  	switch {
   442  	case t.Kind() != reflect.Func:
   443  		return errors.New("must be a function")
   444  	case t.NumIn() != 3:
   445  		return fmt.Errorf("must accept exactly three arguments, found %v", t.NumIn())
   446  	case !isDecodable(t.In(0)):
   447  		return fmt.Errorf("must accept a struct or struct pointer as its first argument, found %v", t.In(0))
   448  	case t.In(1) != _typeOfTransport:
   449  		// TODO: We can make this smarter by making transport.Transport
   450  		// optional and either the first or the second argument instead of
   451  		// requiring it as the second argument.
   452  		return fmt.Errorf("must accept a transport.Transport as its second argument, found %v", t.In(1))
   453  	case t.In(2) != _typeOfKit:
   454  		return fmt.Errorf("must accept a %v as its third argument, found %v", _typeOfKit, t.In(2))
   455  	case t.NumOut() != 2:
   456  		return fmt.Errorf("must return exactly two results, found %v", t.NumOut())
   457  	case t.Out(0) != outputType:
   458  		return fmt.Errorf("must return a %v as its first result, found %v", outputType, t.Out(0))
   459  	case t.Out(1) != _typeOfError:
   460  		return fmt.Errorf("must return an error as its second result, found %v", t.Out(1))
   461  	}
   462  
   463  	return nil
   464  }
   465  
   466  type compiledPeerChooserPreset struct {
   467  	name    string
   468  	factory reflect.Value
   469  }
   470  
   471  // Build builds the peer.Chooser from the compiled peer chooser preset.
   472  func (c *compiledPeerChooserPreset) Build(t peer.Transport, k *Kit) (peer.Chooser, error) {
   473  	results := c.factory.Call([]reflect.Value{reflect.ValueOf(t), reflect.ValueOf(k)})
   474  	chooser, _ := results[0].Interface().(peer.Chooser)
   475  	err, _ := results[1].Interface().(error)
   476  	return chooser, err
   477  }
   478  
   479  func compilePeerChooserPreset(preset PeerChooserPreset) (*compiledPeerChooserPreset, error) {
   480  	if preset.Name == "" {
   481  		return nil, errors.New("field Name is required")
   482  	}
   483  
   484  	if preset.BuildPeerChooser == nil {
   485  		return nil, errors.New("field BuildPeerChooser is required")
   486  	}
   487  
   488  	v := reflect.ValueOf(preset.BuildPeerChooser)
   489  	t := v.Type()
   490  
   491  	var err error
   492  	switch {
   493  	case t.Kind() != reflect.Func:
   494  		err = errors.New("must be a function")
   495  	case t.NumIn() != 2:
   496  		err = fmt.Errorf("must accept exactly two arguments, found %v", t.NumIn())
   497  	case t.In(0) != _typeOfPeerTransport:
   498  		err = fmt.Errorf("must accept a peer.Transport as its first argument, found %v", t.In(0))
   499  	case t.In(1) != _typeOfKit:
   500  		err = fmt.Errorf("must accept a %v as its second argument, found %v", _typeOfKit, t.In(1))
   501  	case t.NumOut() != 2:
   502  		err = fmt.Errorf("must return exactly two results, found %v", t.NumOut())
   503  	case t.Out(0) != _typeOfPeerChooser:
   504  		err = fmt.Errorf("must return a peer.Chooser as its first result, found %v", t.Out(0))
   505  	case t.Out(1) != _typeOfError:
   506  		err = fmt.Errorf("must return an error as its second result, found %v", t.Out(1))
   507  	}
   508  
   509  	if err != nil {
   510  		return nil, fmt.Errorf("invalid BuildPeerChooser %v: %v", t, err)
   511  	}
   512  
   513  	return &compiledPeerChooserPreset{name: preset.Name, factory: v}, nil
   514  }
   515  
   516  // Compiled internal representation of a user-specified PeerChooserSpec.
   517  type compiledPeerChooserSpec struct {
   518  	Name        string
   519  	PeerChooser *configSpec
   520  }
   521  
   522  func compilePeerChooserSpec(spec *PeerChooserSpec) (*compiledPeerChooserSpec, error) {
   523  	out := compiledPeerChooserSpec{Name: spec.Name}
   524  
   525  	if spec.Name == "" {
   526  		return nil, errors.New("field Name is required")
   527  	}
   528  
   529  	if spec.BuildPeerChooser == nil {
   530  		return nil, errors.New("field BuildPeerChooser is required")
   531  	}
   532  
   533  	buildPeerChooser, err := compilePeerChooserConfig(spec.BuildPeerChooser)
   534  	if err != nil {
   535  		return nil, err
   536  	}
   537  	out.PeerChooser = buildPeerChooser
   538  
   539  	return &out, nil
   540  }
   541  
   542  func compilePeerChooserConfig(build interface{}) (*configSpec, error) {
   543  	v := reflect.ValueOf(build)
   544  	t := v.Type()
   545  
   546  	var err error
   547  	switch {
   548  	case t.Kind() != reflect.Func:
   549  		err = errors.New("must be a function")
   550  	case t.NumIn() != 3:
   551  		err = fmt.Errorf("must accept exactly three arguments, found %v", t.NumIn())
   552  	case !isDecodable(t.In(0)):
   553  		err = fmt.Errorf("must accept a struct or struct pointer as its first argument, found %v", t.In(0))
   554  	case t.In(1) != _typeOfPeerTransport:
   555  		err = fmt.Errorf("must accept a %v as its second argument, found %v", _typeOfPeerTransport, t.In(1))
   556  	case t.In(2) != _typeOfKit:
   557  		err = fmt.Errorf("must accept a %v as its third argument, found %v", _typeOfKit, t.In(2))
   558  	case t.NumOut() != 2:
   559  		err = fmt.Errorf("must return exactly two results, found %v", t.NumOut())
   560  	case t.Out(0) != _typeOfPeerChooser:
   561  		err = fmt.Errorf("must return a peer.Chooser as its first result, found %v", t.Out(0))
   562  	case t.Out(1) != _typeOfError:
   563  		err = fmt.Errorf("must return an error as its second result, found %v", t.Out(1))
   564  	}
   565  
   566  	if err != nil {
   567  		return nil, fmt.Errorf("invalid BuildPeerChooser %v: %v", t, err)
   568  	}
   569  
   570  	return &configSpec{inputType: t.In(0), factory: v}, nil
   571  }
   572  
   573  // Compiled internal representation of a user-specified PeerListSpec.
   574  type compiledPeerListSpec struct {
   575  	Name     string
   576  	PeerList *configSpec
   577  }
   578  
   579  func compilePeerListSpec(spec *PeerListSpec) (*compiledPeerListSpec, error) {
   580  	out := compiledPeerListSpec{Name: spec.Name}
   581  
   582  	if spec.Name == "" {
   583  		return nil, errors.New("field Name is required")
   584  	}
   585  
   586  	if spec.BuildPeerList == nil {
   587  		return nil, errors.New("field BuildPeerList is required")
   588  	}
   589  
   590  	buildPeerList, err := compilePeerListConfig(spec.BuildPeerList)
   591  	if err != nil {
   592  		return nil, err
   593  	}
   594  	out.PeerList = buildPeerList
   595  
   596  	return &out, nil
   597  }
   598  
   599  func compilePeerListConfig(build interface{}) (*configSpec, error) {
   600  	v := reflect.ValueOf(build)
   601  	t := v.Type()
   602  
   603  	var err error
   604  	switch {
   605  	case t.Kind() != reflect.Func:
   606  		err = errors.New("must be a function")
   607  	case t.NumIn() != 3:
   608  		err = fmt.Errorf("must accept exactly three arguments, found %v", t.NumIn())
   609  	case !isDecodable(t.In(0)):
   610  		err = fmt.Errorf("must accept a struct or struct pointer as its first argument, found %v", t.In(0))
   611  	case t.In(1) != _typeOfPeerTransport:
   612  		err = fmt.Errorf("must accept a %v as its second argument, found %v", _typeOfPeerTransport, t.In(1))
   613  	case t.In(2) != _typeOfKit:
   614  		err = fmt.Errorf("must accept a %v as its third argument, found %v", _typeOfKit, t.In(2))
   615  	case t.NumOut() != 2:
   616  		err = fmt.Errorf("must return exactly two results, found %v", t.NumOut())
   617  	case t.Out(0) != _typeOfPeerChooserList:
   618  		err = fmt.Errorf("must return a peer.ChooserList as its first result, found %v", t.Out(0))
   619  	case t.Out(1) != _typeOfError:
   620  		err = fmt.Errorf("must return an error as its second result, found %v", t.Out(1))
   621  	}
   622  
   623  	if err != nil {
   624  		return nil, fmt.Errorf("invalid BuildPeerList %v: %v", t, err)
   625  	}
   626  
   627  	return &configSpec{inputType: t.In(0), factory: v}, nil
   628  }
   629  
   630  type compiledPeerListUpdaterSpec struct {
   631  	Name            string
   632  	PeerListUpdater *configSpec
   633  }
   634  
   635  func compilePeerListUpdaterSpec(spec *PeerListUpdaterSpec) (*compiledPeerListUpdaterSpec, error) {
   636  	out := compiledPeerListUpdaterSpec{Name: spec.Name}
   637  
   638  	if spec.Name == "" {
   639  		return nil, errors.New("field Name is required")
   640  	}
   641  
   642  	if spec.BuildPeerListUpdater == nil {
   643  		return nil, errors.New("field BuildPeerListUpdater is required")
   644  	}
   645  
   646  	buildPeerListUpdater, err := compilePeerListUpdaterConfig(spec.Name, spec.BuildPeerListUpdater)
   647  	if err != nil {
   648  		return nil, err
   649  	}
   650  	out.PeerListUpdater = buildPeerListUpdater
   651  
   652  	return &out, nil
   653  }
   654  
   655  func compilePeerListUpdaterConfig(name string, build interface{}) (*configSpec, error) {
   656  	v := reflect.ValueOf(build)
   657  	t := v.Type()
   658  
   659  	var err error
   660  	switch {
   661  	case t.Kind() != reflect.Func:
   662  		err = errors.New("must be a function")
   663  	case t.NumIn() != 2:
   664  		err = fmt.Errorf("must accept exactly two arguments, found %v", t.NumIn())
   665  	case !isDecodable(t.In(0)):
   666  		err = fmt.Errorf("must accept a struct or struct pointer as its first argument, found %v", t.In(0))
   667  	case t.In(1) != _typeOfKit:
   668  		err = fmt.Errorf("must accept a %v as its second argument, found %v", _typeOfKit, t.In(1))
   669  	case t.NumOut() != 2:
   670  		err = fmt.Errorf("must return exactly two results, found %v", t.NumOut())
   671  	case t.Out(0) != _typeOfBinder:
   672  		err = fmt.Errorf("must return a peer.Binder as its first result, found %v", t.Out(0))
   673  	case t.Out(1) != _typeOfError:
   674  		err = fmt.Errorf("must return an error as its second result, found %v", t.Out(1))
   675  	}
   676  
   677  	if err != nil {
   678  		return nil, fmt.Errorf("invalid BuildPeerListUpdater %v: %v", t, err)
   679  	}
   680  
   681  	return &configSpec{inputType: t.In(0), factory: v}, nil
   682  }
   683  
   684  // Validated representation of a configuration function specified by the user.
   685  type configSpec struct {
   686  	// Type of object expected by the factory function
   687  	inputType reflect.Type
   688  
   689  	// Factory function to call
   690  	factory reflect.Value
   691  
   692  	// Example:
   693  	//
   694  	// 	factory = func(http.InboundConfig, ..) (transport.Inbound, error) { .. }
   695  	// 	inputType = http.InboundConfig
   696  }
   697  
   698  // Decode the configuration for this type from the data map.
   699  func (cs *configSpec) Decode(attrs config.AttributeMap, opts ...mapdecode.Option) (*buildable, error) {
   700  	inputConfig := reflect.New(cs.inputType)
   701  	if err := attrs.Decode(inputConfig.Interface(), opts...); err != nil {
   702  		return nil, fmt.Errorf("failed to decode %v: %v", cs.inputType, err)
   703  	}
   704  	return &buildable{factory: cs.factory, inputData: inputConfig.Elem()}, nil
   705  }
   706  
   707  // A fully configured object that can be built into an
   708  // Inbound/Outbound/Transport.
   709  type buildable struct {
   710  	// Decoded configuration data. This is a value of the same type as the
   711  	// factory function's input argument.
   712  	inputData reflect.Value
   713  
   714  	// A function that accepts Config as its first argument and returns a
   715  	// result and an error.
   716  	//
   717  	// Build(...) will call this function and interpret the result.
   718  	factory reflect.Value
   719  
   720  	// Example:
   721  	//
   722  	// 	factory = func(*http.InboundConfig, _) .. { .. }
   723  	// 	inputData = &http.InboundConfig{Address: ..}
   724  }
   725  
   726  // Build the object configured by this value. The arguments are passed to the
   727  // build function with the underlying configuration as the first parameter.
   728  //
   729  // Arguments may be reflect.Value objects or any other type.
   730  func (cv *buildable) Build(args ...interface{}) (interface{}, error) {
   731  	// This function roughly translates to,
   732  	//
   733  	// 	return factory(inputData, args...)
   734  
   735  	callArgs := make([]reflect.Value, len(args)+1)
   736  	callArgs[0] = cv.inputData
   737  
   738  	for i, v := range args {
   739  		if value, ok := v.(reflect.Value); ok {
   740  			callArgs[i+1] = value
   741  		} else {
   742  			callArgs[i+1] = reflect.ValueOf(v)
   743  		}
   744  	}
   745  
   746  	result := cv.factory.Call(callArgs)
   747  	err, _ := result[1].Interface().(error)
   748  	return result[0].Interface(), err
   749  }
   750  
   751  // Returns a list of struct fields for the given type. The type may be a
   752  // struct or a pointer to a struct (arbitrarily deep).
   753  func fieldNames(t reflect.Type) map[string]struct{} {
   754  	for ; t.Kind() == reflect.Ptr; t = t.Elem() {
   755  	}
   756  
   757  	if t.Kind() != reflect.Struct {
   758  		return nil
   759  	}
   760  
   761  	fields := make(map[string]struct{}, t.NumField())
   762  	for i := 0; i < t.NumField(); i++ {
   763  		field := t.Field(i)
   764  		if field.PkgPath != "" {
   765  			continue // unexported field
   766  		}
   767  		fields[field.Name] = struct{}{}
   768  	}
   769  	return fields
   770  }
   771  
   772  func isDecodable(t reflect.Type) bool {
   773  	for ; t.Kind() == reflect.Ptr; t = t.Elem() {
   774  	}
   775  
   776  	// TODO(abg): Do we want to support top-level map types for configuration
   777  
   778  	return t.Kind() == reflect.Struct
   779  }