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 }