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  }