github.com/Jeffail/benthos/v3@v3.65.0/public/service/config.go (about) 1 package service 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "sort" 7 "strings" 8 "time" 9 10 "github.com/Jeffail/benthos/v3/internal/bloblang/query" 11 "github.com/Jeffail/benthos/v3/internal/bundle" 12 "github.com/Jeffail/benthos/v3/internal/docs" 13 "github.com/Jeffail/benthos/v3/lib/log" 14 "github.com/Jeffail/benthos/v3/lib/manager" 15 "github.com/Jeffail/benthos/v3/lib/metrics" 16 "github.com/Jeffail/gabs/v2" 17 "gopkg.in/yaml.v3" 18 ) 19 20 // ConfigField describes a field within a component configuration, to be added 21 // to a ConfigSpec. 22 type ConfigField struct { 23 field docs.FieldSpec 24 } 25 26 // NewStringField describes a new string type config field. 27 func NewStringField(name string) *ConfigField { 28 return &ConfigField{ 29 field: docs.FieldString(name, ""), 30 } 31 } 32 33 // NewDurationField describes a new duration string type config field, allowing 34 // users to define a time interval with strings of the form 60s, 3m, etc. 35 func NewDurationField(name string) *ConfigField { 36 // TODO: Add linting rule for duration 37 return &ConfigField{ 38 field: docs.FieldString(name, ""), 39 } 40 } 41 42 // NewStringEnumField describes a new string type config field that can have one 43 // of a discrete list of values. 44 func NewStringEnumField(name string, options ...string) *ConfigField { 45 return &ConfigField{ 46 field: docs.FieldString(name, "").HasOptions(options...).LintOptions(), 47 } 48 } 49 50 // NewStringAnnotatedEnumField describes a new string type config field that can 51 // have one of a discrete list of values, where each value must be accompanied 52 // by a description that annotates its behaviour in the documentation. 53 func NewStringAnnotatedEnumField(name string, options map[string]string) *ConfigField { 54 optionKeys := make([]string, 0, len(options)) 55 for key := range options { 56 optionKeys = append(optionKeys, key) 57 } 58 sort.Strings(optionKeys) 59 60 flatOptions := make([]string, 0, len(options)*2) 61 for _, o := range optionKeys { 62 flatOptions = append(flatOptions, o, options[o]) 63 } 64 65 return &ConfigField{ 66 field: docs.FieldString(name, "").HasAnnotatedOptions(flatOptions...).LintOptions(), 67 } 68 } 69 70 // NewStringListField describes a new config field consisting of a list of 71 // strings. 72 func NewStringListField(name string) *ConfigField { 73 return &ConfigField{ 74 field: docs.FieldString(name, "").Array(), 75 } 76 } 77 78 // NewStringMapField describes a new config field consisting of an object of 79 // arbitrary keys with string values. 80 func NewStringMapField(name string) *ConfigField { 81 return &ConfigField{ 82 field: docs.FieldString(name, "").Map(), 83 } 84 } 85 86 // NewIntField describes a new int type config field. 87 func NewIntField(name string) *ConfigField { 88 return &ConfigField{ 89 field: docs.FieldInt(name, ""), 90 } 91 } 92 93 // NewIntListField describes a new config field consisting of a list of 94 // integers. 95 func NewIntListField(name string) *ConfigField { 96 return &ConfigField{ 97 field: docs.FieldInt(name, "").Array(), 98 } 99 } 100 101 // NewIntMapField describes a new config field consisting of an object of 102 // arbitrary keys with integer values. 103 func NewIntMapField(name string) *ConfigField { 104 return &ConfigField{ 105 field: docs.FieldInt(name, "").Map(), 106 } 107 } 108 109 // NewFloatField describes a new float type config field. 110 func NewFloatField(name string) *ConfigField { 111 return &ConfigField{ 112 field: docs.FieldFloat(name, ""), 113 } 114 } 115 116 // NewBoolField describes a new bool type config field. 117 func NewBoolField(name string) *ConfigField { 118 return &ConfigField{ 119 field: docs.FieldBool(name, ""), 120 } 121 } 122 123 // NewObjectField describes a new object type config field, consisting of one 124 // or more child fields. 125 func NewObjectField(name string, fields ...*ConfigField) *ConfigField { 126 children := make([]docs.FieldSpec, len(fields)) 127 for i, f := range fields { 128 children[i] = f.field 129 } 130 return &ConfigField{ 131 field: docs.FieldCommon(name, "").WithChildren(children...), 132 } 133 } 134 135 // NewObjectListField describes a new list type config field consisting of 136 // objects with one or more child fields. 137 func NewObjectListField(name string, fields ...*ConfigField) *ConfigField { 138 objField := NewObjectField(name, fields...) 139 return &ConfigField{ 140 field: objField.field.Array(), 141 } 142 } 143 144 // NewInternalField returns a ConfigField derived from an internal package field 145 // spec. This function is for internal use only. 146 func NewInternalField(ifield docs.FieldSpec) *ConfigField { 147 return &ConfigField{ 148 field: ifield, 149 } 150 } 151 152 // Description adds a description to the field which will be shown when printing 153 // documentation for the component config spec. 154 func (c *ConfigField) Description(d string) *ConfigField { 155 c.field.Description = d 156 return c 157 } 158 159 // Advanced marks a config field as being advanced, and therefore it will not 160 // appear in simplified documentation examples. 161 func (c *ConfigField) Advanced() *ConfigField { 162 c.field = c.field.Advanced() 163 return c 164 } 165 166 // Default specifies a default value that this field will assume if it is 167 // omitted from a provided config. Fields that do not have a default value are 168 // considered mandatory, and so parsing a config will fail in their absence. 169 func (c *ConfigField) Default(v interface{}) *ConfigField { 170 c.field = c.field.HasDefault(v) 171 return c 172 } 173 174 // Optional specifies that a field is optional even when a default value has not 175 // been specified. When a field is marked as optional you can test its presence 176 // within a parsed config with the method Contains. 177 func (c *ConfigField) Optional() *ConfigField { 178 c.field = c.field.Optional() 179 return c 180 } 181 182 // Example adds an example value to the field which will be shown when printing 183 // documentation for the component config spec. 184 func (c *ConfigField) Example(e interface{}) *ConfigField { 185 c.field.Examples = append(c.field.Examples, e) 186 return c 187 } 188 189 // Version specifies the specific version at which this field was added to the 190 // component. 191 func (c *ConfigField) Version(v string) *ConfigField { 192 c.field = c.field.AtVersion(v) 193 return c 194 } 195 196 //------------------------------------------------------------------------------ 197 198 // ConfigSpec describes the configuration specification for a plugin 199 // component. This will be used for validating and linting configuration files 200 // and providing a parsed configuration struct to the plugin constructor. 201 type ConfigSpec struct { 202 component docs.ComponentSpec 203 configCtor ConfigStructConstructor 204 } 205 206 func (c *ConfigSpec) configFromNode(mgr bundle.NewManagement, node *yaml.Node) (*ParsedConfig, error) { 207 if c.configCtor != nil { 208 conf := c.configCtor() 209 if err := node.Decode(conf); err != nil { 210 return nil, err 211 } 212 return &ParsedConfig{mgr: mgr, asStruct: conf}, nil 213 } 214 215 fields, err := c.component.Config.YAMLToValue(node, docs.ToValueConfig{}) 216 if err != nil { 217 return nil, err 218 } 219 220 return &ParsedConfig{mgr: mgr, generic: fields}, nil 221 } 222 223 // ParseYAML attempts to parse a YAML document as the defined configuration spec 224 // and returns a parsed config view. The provided environment determines which 225 // child components and Bloblang functions can be created by the fields of the 226 // spec, you can leave the environment nil to use the global environment. 227 // 228 // This method is intended for testing purposes and is not required for normal 229 // use of plugin components, as parsing is managed by other components. 230 func (c *ConfigSpec) ParseYAML(yamlStr string, env *Environment) (*ParsedConfig, error) { 231 if env == nil { 232 env = globalEnvironment 233 } 234 235 var nconf yaml.Node 236 if err := yaml.Unmarshal([]byte(yamlStr), &nconf); err != nil { 237 return nil, err 238 } 239 if nconf.Kind == yaml.DocumentNode && len(nconf.Content) > 0 { 240 nconf = *nconf.Content[0] 241 } 242 243 mgr, err := manager.NewV2( 244 manager.NewResourceConfig(), nil, log.Noop(), metrics.Noop(), 245 manager.OptSetEnvironment(env.internal), 246 manager.OptSetBloblangEnvironment(env.getBloblangParserEnv()), 247 ) 248 if err != nil { 249 return nil, fmt.Errorf("failed to instantiate resources: %w", err) 250 } 251 252 return c.configFromNode(mgr, &nconf) 253 } 254 255 // NewConfigSpec creates a new empty component configuration spec. If the 256 // plugin does not require configuration fields the result of this call is 257 // enough. 258 func NewConfigSpec() *ConfigSpec { 259 return &ConfigSpec{ 260 component: docs.ComponentSpec{ 261 Status: docs.StatusExperimental, 262 Plugin: true, 263 Config: docs.FieldComponent(), 264 }, 265 } 266 } 267 268 // ConfigStructConstructor is a function signature that must return a pointer to 269 // a struct to be used for parsing configuration fields of a component plugin, 270 // ideally instanciated with default field values. 271 // 272 // The function will be called each time a parsed configuration file contains 273 // the plugin type, and the returned struct will be unmarshalled as YAML using 274 // gopkg.in/yaml.v3. 275 // 276 // The returned value must be a pointer type in order to be properly 277 // unmarshalled during config parsing. 278 // 279 // Deprecated: This config mechanism exists only as an interim solution for 280 // plugin authors migrating from the previous APIs. 281 type ConfigStructConstructor func() interface{} 282 283 // NewStructConfigSpec creates a new component configuration spec around a 284 // constructor func. The provided constructor func will be used during parsing 285 // in order to validate and return fields for the plugin from a configuration 286 // file. 287 // 288 // Deprecated: This config mechanism exists only as an interim solution for 289 // plugin authors migrating from the previous APIs. 290 func NewStructConfigSpec(ctor ConfigStructConstructor) (*ConfigSpec, error) { 291 var node yaml.Node 292 if err := node.Encode(ctor()); err != nil { 293 return nil, fmt.Errorf("unable to marshal config struct as yaml: %v", err) 294 } 295 296 confSpec := NewConfigSpec() 297 confSpec.component.Config = confSpec.component.Config.WithChildren(docs.FieldsFromYAML(&node)...) 298 confSpec.configCtor = ctor 299 300 return confSpec, nil 301 } 302 303 // Stable sets a documentation label on the component indicating that its 304 // configuration spec is stable. Plugins are considered experimental by default. 305 func (c *ConfigSpec) Stable() *ConfigSpec { 306 c.component.Status = docs.StatusStable 307 return c 308 } 309 310 // Beta sets a documentation label on the component indicating that its 311 // configuration spec is ready for beta testing, meaning backwards incompatible 312 // changes will not be made unless a fundamental problem is found. Plugins are 313 // considered experimental by default. 314 func (c *ConfigSpec) Beta() *ConfigSpec { 315 c.component.Status = docs.StatusBeta 316 return c 317 } 318 319 // Deprecated sets a documentation label on the component indicating that it is 320 // now deprecated. Plugins are considered experimental by default. 321 func (c *ConfigSpec) Deprecated() *ConfigSpec { 322 c.component.Status = docs.StatusDeprecated 323 return c 324 } 325 326 // Categories adds one or more string tags to the component, these are used for 327 // arbitrarily grouping components in documentation. 328 func (c *ConfigSpec) Categories(categories ...string) *ConfigSpec { 329 c.component.Categories = categories 330 return c 331 } 332 333 // Version specifies that this component was introduced in a given version. 334 func (c *ConfigSpec) Version(v string) *ConfigSpec { 335 c.component.Version = v 336 return c 337 } 338 339 // Summary adds a short summary to the plugin configuration spec that describes 340 // the general purpose of the component. 341 func (c *ConfigSpec) Summary(summary string) *ConfigSpec { 342 c.component.Summary = summary 343 return c 344 } 345 346 // Description adds a description to the plugin configuration spec that 347 // describes in more detail the behaviour of the component and how it should be 348 // used. 349 func (c *ConfigSpec) Description(description string) *ConfigSpec { 350 c.component.Description = description 351 return c 352 } 353 354 // Field sets the specification of a field within the config spec, used for 355 // linting and generating documentation for the component. 356 // 357 // If the provided field has an empty name then it registered as the value at 358 // the root of the config spec. 359 // 360 // When creating a spec with a struct constructor the fields from that struct 361 // will already be inferred. However, setting a field explicitly is sometimes 362 // useful for enriching the field documentation with more information. 363 func (c *ConfigSpec) Field(f *ConfigField) *ConfigSpec { 364 if f.field.Name == "" { 365 // Set field to root of config spec 366 c.component.Config = f.field 367 return c 368 } 369 370 c.component.Config.Type = docs.FieldTypeObject 371 for i, s := range c.component.Config.Children { 372 if s.Name == f.field.Name { 373 c.component.Config.Children[i] = f.field 374 return c 375 } 376 } 377 378 c.component.Config.Children = append(c.component.Config.Children, f.field) 379 return c 380 } 381 382 // Example adds an example to the plugin configuration spec that demonstrates 383 // how the component can be used. An example has a title, summary, and a YAML 384 // configuration showing a real use case. 385 func (c *ConfigSpec) Example(title, summary, config string) *ConfigSpec { 386 c.component.Examples = append(c.component.Examples, docs.AnnotatedExample{ 387 Title: title, 388 Summary: summary, 389 Config: config, 390 }) 391 return c 392 } 393 394 // EncodeJSON attempts to parse a JSON object as a byte slice and uses it to 395 // populate the configuration spec. The schema of this method is undocumented 396 // and is not intended for general use. 397 // 398 // Experimental: This method is not intended for general use and could have its 399 // signature and/or behaviour changed outside of major version bumps. 400 func (c *ConfigSpec) EncodeJSON(v []byte) error { 401 return json.Unmarshal(v, &c.component) 402 } 403 404 //------------------------------------------------------------------------------ 405 406 // ConfigView is a struct returned by a Benthos service environment when walking 407 // the list of registered components and provides access to information about 408 // the component. 409 type ConfigView struct { 410 component docs.ComponentSpec 411 } 412 413 // Summary returns a documentation summary of the component, often formatted as 414 // markdown. 415 func (c *ConfigView) Summary() string { 416 return c.component.Summary 417 } 418 419 // Description returns a documentation description of the component, often 420 // formatted as markdown. 421 func (c *ConfigView) Description() string { 422 return c.component.Description 423 } 424 425 // IsDeprecated returns true if the component is marked as deprecated. 426 func (c *ConfigView) IsDeprecated() bool { 427 return c.component.Status == docs.StatusDeprecated 428 } 429 430 // FormatJSON returns a byte slice of the component configuration formatted as a 431 // JSON object. The schema of this method is undocumented and is not intended 432 // for general use. 433 // 434 // Experimental: This method is not intended for general use and could have its 435 // signature and/or behaviour changed outside of major version bumps. 436 func (c *ConfigView) FormatJSON() ([]byte, error) { 437 return json.Marshal(c.component) 438 } 439 440 //------------------------------------------------------------------------------ 441 442 // ParsedConfig represents a plugin configuration that has been validated and 443 // parsed from a ConfigSpec, and allows plugin constructors to access 444 // configuration fields. 445 // 446 // The correct way to access configuration fields depends on how the 447 // configuration spec was built. For example, if the spec was established with 448 // a struct constructor then the method AsStruct should be used in order to 449 // access the parsed struct. 450 type ParsedConfig struct { 451 hiddenPath []string 452 mgr bundle.NewManagement 453 asStruct interface{} 454 generic interface{} 455 } 456 457 // AsStruct returns the root of the parsed config. If the configuration spec was 458 // built around a config constructor then the value returned will match the type 459 // returned by the constructor, otherwise it will be a generic 460 // map[string]interface{} type. 461 // 462 // Deprecated: This config mechanism exists only as an interim solution for 463 // plugin authors migrating from the previous APIs. 464 func (p *ParsedConfig) AsStruct() interface{} { 465 return p.asStruct 466 } 467 468 // Namespace returns a version of the parsed config at a given field namespace. 469 // This is useful for extracting multiple fields under the same grouping. 470 func (p *ParsedConfig) Namespace(path ...string) *ParsedConfig { 471 tmpConfig := *p 472 tmpConfig.hiddenPath = append([]string{}, p.hiddenPath...) 473 tmpConfig.hiddenPath = append(tmpConfig.hiddenPath, path...) 474 return &tmpConfig 475 } 476 477 // Field accesses a field from the parsed config by its name and returns the 478 // value if the field is found and a boolean indicating whether it was found. 479 // Nested fields can be accessed by specifing the series of field names. 480 // 481 // This method is not valid when the configuration spec was built around a 482 // config constructor. 483 func (p *ParsedConfig) field(path ...string) (interface{}, bool) { 484 gObj := gabs.Wrap(p.generic).S(p.hiddenPath...) 485 if exists := gObj.Exists(path...); !exists { 486 return nil, false 487 } 488 return gObj.S(path...).Data(), true 489 } 490 491 func (p *ParsedConfig) fullDotPath(path ...string) string { 492 var fullPath []string 493 fullPath = append(fullPath, p.hiddenPath...) 494 fullPath = append(fullPath, path...) 495 return strings.Join(fullPath, ".") 496 } 497 498 // Contains checks whether the parsed config contains a given field identified 499 // by its name. 500 func (p *ParsedConfig) Contains(path ...string) bool { 501 gObj := gabs.Wrap(p.generic).S(p.hiddenPath...) 502 return gObj.Exists(path...) 503 } 504 505 // FieldString accesses a string field from the parsed config by its name. If 506 // the field is not found or is not a string an error is returned. 507 // 508 // This method is not valid when the configuration spec was built around a 509 // config constructor. 510 func (p *ParsedConfig) FieldString(path ...string) (string, error) { 511 v, exists := p.field(path...) 512 if !exists { 513 return "", fmt.Errorf("field '%v' was not found in the config", p.fullDotPath(path...)) 514 } 515 str, ok := v.(string) 516 if !ok { 517 return "", fmt.Errorf("expected field '%v' to be a string, got %T", p.fullDotPath(path...), v) 518 } 519 return str, nil 520 } 521 522 // FieldDuration accesses a duration string field from the parsed config by its 523 // name. If the field is not found or is not a valid duration string an error is 524 // returned. 525 // 526 // This method is not valid when the configuration spec was built around a 527 // config constructor. 528 func (p *ParsedConfig) FieldDuration(path ...string) (time.Duration, error) { 529 v, exists := p.field(path...) 530 if !exists { 531 return 0, fmt.Errorf("field '%v' was not found in the config", p.fullDotPath(path...)) 532 } 533 str, ok := v.(string) 534 if !ok { 535 return 0, fmt.Errorf("expected field '%v' to be a string, got %T", p.fullDotPath(path...), v) 536 } 537 d, err := time.ParseDuration(str) 538 if err != nil { 539 return 0, fmt.Errorf("failed to parse '%v' as a duration string: %w", p.fullDotPath(path...), err) 540 } 541 return d, nil 542 } 543 544 // FieldStringList accesses a field that is a list of strings from the parsed 545 // config by its name and returns the value. Returns an error if the field is 546 // not found, or is not a list of strings. 547 // 548 // This method is not valid when the configuration spec was built around a 549 // config constructor. 550 func (p *ParsedConfig) FieldStringList(path ...string) ([]string, error) { 551 v, exists := p.field(path...) 552 if !exists { 553 return nil, fmt.Errorf("field '%v' was not found in the config", p.fullDotPath(path...)) 554 } 555 iList, ok := v.([]interface{}) 556 if !ok { 557 if sList, ok := v.([]string); ok { 558 return sList, nil 559 } 560 return nil, fmt.Errorf("expected field '%v' to be a string list, got %T", p.fullDotPath(path...), v) 561 } 562 sList := make([]string, len(iList)) 563 for i, ev := range iList { 564 if sList[i], ok = ev.(string); !ok { 565 return nil, fmt.Errorf("expected field '%v' to be a string list, found an element of type %T", p.fullDotPath(path...), ev) 566 } 567 } 568 return sList, nil 569 } 570 571 // FieldStringMap accesses a field that is an object of arbitrary keys and 572 // string values from the parsed config by its name and returns the value. 573 // Returns an error if the field is not found, or is not an object of strings. 574 // 575 // This method is not valid when the configuration spec was built around a 576 // config constructor. 577 func (p *ParsedConfig) FieldStringMap(path ...string) (map[string]string, error) { 578 v, exists := p.field(path...) 579 if !exists { 580 return nil, fmt.Errorf("field '%v' was not found in the config", p.fullDotPath(path...)) 581 } 582 iMap, ok := v.(map[string]interface{}) 583 if !ok { 584 if sMap, ok := v.(map[string]string); ok { 585 return sMap, nil 586 } 587 return nil, fmt.Errorf("expected field '%v' to be a string map, got %T", p.fullDotPath(path...), v) 588 } 589 sMap := make(map[string]string, len(iMap)) 590 for k, ev := range iMap { 591 if sMap[k], ok = ev.(string); !ok { 592 return nil, fmt.Errorf("expected field '%v' to be a string map, found an element of type %T", p.fullDotPath(path...), ev) 593 } 594 } 595 return sMap, nil 596 } 597 598 // FieldInt accesses an int field from the parsed config by its name and returns 599 // the value. Returns an error if the field is not found or is not an int. 600 // 601 // This method is not valid when the configuration spec was built around a 602 // config constructor. 603 func (p *ParsedConfig) FieldInt(path ...string) (int, error) { 604 v, exists := p.field(path...) 605 if !exists { 606 return 0, fmt.Errorf("field '%v' was not found in the config", p.fullDotPath(path...)) 607 } 608 i, err := query.IGetInt(v) 609 if err != nil { 610 return 0, fmt.Errorf("expected field '%v' to be an int, got %T", p.fullDotPath(path...), v) 611 } 612 return int(i), nil 613 } 614 615 // FieldIntList accesses a field that is a list of integers from the parsed 616 // config by its name and returns the value. Returns an error if the field is 617 // not found, or is not a list of integers. 618 // 619 // This method is not valid when the configuration spec was built around a 620 // config constructor. 621 func (p *ParsedConfig) FieldIntList(path ...string) ([]int, error) { 622 v, exists := p.field(path...) 623 if !exists { 624 return nil, fmt.Errorf("field '%v' was not found in the config", p.fullDotPath(path...)) 625 } 626 iList, ok := v.([]interface{}) 627 if !ok { 628 if sList, ok := v.([]int); ok { 629 return sList, nil 630 } 631 return nil, fmt.Errorf("expected field '%v' to be an integer list, got %T", p.fullDotPath(path...), v) 632 } 633 sList := make([]int, len(iList)) 634 for i, ev := range iList { 635 iv, err := query.IToInt(ev) 636 if err != nil { 637 return nil, fmt.Errorf("expected field '%v' to be an integer list, found an element of type %T", p.fullDotPath(path...), ev) 638 } 639 sList[i] = int(iv) 640 } 641 return sList, nil 642 } 643 644 // FieldIntMap accesses a field that is an object of arbitrary keys and 645 // integer values from the parsed config by its name and returns the value. 646 // Returns an error if the field is not found, or is not an object of integers. 647 // 648 // This method is not valid when the configuration spec was built around a 649 // config constructor. 650 func (p *ParsedConfig) FieldIntMap(path ...string) (map[string]int, error) { 651 v, exists := p.field(path...) 652 if !exists { 653 return nil, fmt.Errorf("field '%v' was not found in the config", p.fullDotPath(path...)) 654 } 655 iMap, ok := v.(map[string]interface{}) 656 if !ok { 657 if sMap, ok := v.(map[string]int); ok { 658 return sMap, nil 659 } 660 return nil, fmt.Errorf("expected field '%v' to be an integer map, got %T", p.fullDotPath(path...), v) 661 } 662 sMap := make(map[string]int, len(iMap)) 663 for k, ev := range iMap { 664 iv, err := query.IToInt(ev) 665 if err != nil { 666 return nil, fmt.Errorf("expected field '%v' to be an integer map, found an element of type %T", p.fullDotPath(path...), ev) 667 } 668 sMap[k] = int(iv) 669 } 670 return sMap, nil 671 } 672 673 // FieldFloat accesses a float field from the parsed config by its name and 674 // returns the value. Returns an error if the field is not found or is not a 675 // float. 676 // 677 // This method is not valid when the configuration spec was built around a 678 // config constructor. 679 func (p *ParsedConfig) FieldFloat(path ...string) (float64, error) { 680 v, exists := p.field(path...) 681 if !exists { 682 return 0, fmt.Errorf("field '%v' was not found in the config", p.fullDotPath(path...)) 683 } 684 f, err := query.IGetNumber(v) 685 if err != nil { 686 return 0, fmt.Errorf("expected field '%v' to be a float, got %T", p.fullDotPath(path...), v) 687 } 688 return f, nil 689 } 690 691 // FieldBool accesses a bool field from the parsed config by its name and 692 // returns the value. Returns an error if the field is not found or is not a 693 // bool. 694 // 695 // This method is not valid when the configuration spec was built around a 696 // config constructor. 697 func (p *ParsedConfig) FieldBool(path ...string) (bool, error) { 698 v, e := p.field(path...) 699 if !e { 700 return false, fmt.Errorf("field '%v' was not found in the config", p.fullDotPath(path...)) 701 } 702 b, ok := v.(bool) 703 if !ok { 704 return false, fmt.Errorf("expected field '%v' to be a bool, got %T", p.fullDotPath(path...), v) 705 } 706 return b, nil 707 } 708 709 // FieldObjectList accesses a field that is a list of objects from the parsed 710 // config by its name and returns the value as an array of *ParsedConfig types, 711 // where each one represents an object in the list. Returns an error if the 712 // field is not found, or is not a list of objects. 713 // 714 // This method is not valid when the configuration spec was built around a 715 // config constructor. 716 func (p *ParsedConfig) FieldObjectList(path ...string) ([]*ParsedConfig, error) { 717 v, exists := p.field(path...) 718 if !exists { 719 return nil, fmt.Errorf("field '%v' was not found in the config", p.fullDotPath(path...)) 720 } 721 iList, ok := v.([]interface{}) 722 if !ok { 723 return nil, fmt.Errorf("expected field '%v' to be a list, got %T", p.fullDotPath(path...), v) 724 } 725 sList := make([]*ParsedConfig, len(iList)) 726 for i, ev := range iList { 727 sList[i] = &ParsedConfig{ 728 mgr: p.mgr, 729 generic: ev, 730 } 731 } 732 return sList, nil 733 }