github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/helper/fields/data.go (about)

     1  package fields
     2  
     3  import (
     4  	"fmt"
     5  
     6  	multierror "github.com/hashicorp/go-multierror"
     7  	"github.com/mitchellh/mapstructure"
     8  )
     9  
    10  // FieldData contains the raw data and the schema that the data should adhere to
    11  type FieldData struct {
    12  	Raw    map[string]interface{}
    13  	Schema map[string]*FieldSchema
    14  }
    15  
    16  // Validate cycles through the raw data and validates conversions in the schema.
    17  // It also checks for the existence and value of required fields.
    18  func (d *FieldData) Validate() error {
    19  	var result *multierror.Error
    20  
    21  	// Scan for missing required fields
    22  	for field, schema := range d.Schema {
    23  		if schema.Required {
    24  			_, ok := d.Raw[field]
    25  			if !ok {
    26  				result = multierror.Append(result, fmt.Errorf(
    27  					"field %q is required", field))
    28  			}
    29  		}
    30  	}
    31  
    32  	// Validate field type and value
    33  	for field, value := range d.Raw {
    34  		schema, ok := d.Schema[field]
    35  		if !ok {
    36  			result = multierror.Append(result, fmt.Errorf(
    37  				"%q is an invalid field", field))
    38  			continue
    39  		}
    40  
    41  		switch schema.Type {
    42  		case TypeBool, TypeInt, TypeMap, TypeArray, TypeString:
    43  			val, _, err := d.getPrimitive(field, schema)
    44  			if err != nil {
    45  				result = multierror.Append(result, fmt.Errorf(
    46  					"field %q with input %q doesn't seem to be of type %s",
    47  					field, value, schema.Type))
    48  			}
    49  			// Check that we don't have an empty value for required fields
    50  			if schema.Required && val == schema.Type.Zero() {
    51  				result = multierror.Append(result, fmt.Errorf(
    52  					"field %q is required, but no value was found", field))
    53  			}
    54  		default:
    55  			result = multierror.Append(result, fmt.Errorf(
    56  				"unknown field type %s for field %s", schema.Type, field))
    57  		}
    58  	}
    59  
    60  	return result.ErrorOrNil()
    61  }
    62  
    63  // Get gets the value for the given field. If the key is an invalid field,
    64  // FieldData will panic. If you want a safer version of this method, use
    65  // GetOk. If the field k is not set, the default value (if set) will be
    66  // returned, otherwise the zero value will be returned.
    67  func (d *FieldData) Get(k string) interface{} {
    68  	schema, ok := d.Schema[k]
    69  	if !ok {
    70  		panic(fmt.Sprintf("field %s not in the schema", k))
    71  	}
    72  
    73  	value, ok := d.GetOk(k)
    74  	if !ok {
    75  		value = schema.DefaultOrZero()
    76  	}
    77  
    78  	return value
    79  }
    80  
    81  // GetOk gets the value for the given field. The second return value
    82  // will be false if the key is invalid or the key is not set at all.
    83  func (d *FieldData) GetOk(k string) (interface{}, bool) {
    84  	schema, ok := d.Schema[k]
    85  	if !ok {
    86  		return nil, false
    87  	}
    88  
    89  	result, ok, err := d.GetOkErr(k)
    90  	if err != nil {
    91  		panic(fmt.Sprintf("error reading %s: %s", k, err))
    92  	}
    93  
    94  	if ok && result == nil {
    95  		result = schema.DefaultOrZero()
    96  	}
    97  
    98  	return result, ok
    99  }
   100  
   101  // GetOkErr is the most conservative of all the Get methods. It returns
   102  // whether key is set or not, but also an error value. The error value is
   103  // non-nil if the field doesn't exist or there was an error parsing the
   104  // field value.
   105  func (d *FieldData) GetOkErr(k string) (interface{}, bool, error) {
   106  	schema, ok := d.Schema[k]
   107  	if !ok {
   108  		return nil, false, fmt.Errorf("unknown field: %s", k)
   109  	}
   110  
   111  	switch schema.Type {
   112  	case TypeBool, TypeInt, TypeMap, TypeArray, TypeString:
   113  		return d.getPrimitive(k, schema)
   114  	default:
   115  		return nil, false,
   116  			fmt.Errorf("unknown field type %s for field %s", schema.Type, k)
   117  	}
   118  }
   119  
   120  // getPrimitive tries to convert the raw value of a field to its data type as
   121  // defined in the schema. It does strict type checking, so the value will need
   122  // to be able to convert to the appropriate type directly.
   123  func (d *FieldData) getPrimitive(
   124  	k string, schema *FieldSchema) (interface{}, bool, error) {
   125  	raw, ok := d.Raw[k]
   126  	if !ok {
   127  		return nil, false, nil
   128  	}
   129  
   130  	switch schema.Type {
   131  	case TypeBool:
   132  		var result bool
   133  		if err := mapstructure.Decode(raw, &result); err != nil {
   134  			return nil, true, err
   135  		}
   136  		return result, true, nil
   137  
   138  	case TypeInt:
   139  		var result int
   140  		if err := mapstructure.Decode(raw, &result); err != nil {
   141  			return nil, true, err
   142  		}
   143  		return result, true, nil
   144  
   145  	case TypeString:
   146  		var result string
   147  		if err := mapstructure.Decode(raw, &result); err != nil {
   148  			return nil, true, err
   149  		}
   150  		return result, true, nil
   151  
   152  	case TypeMap:
   153  		var result map[string]interface{}
   154  		if err := mapstructure.Decode(raw, &result); err != nil {
   155  			return nil, true, err
   156  		}
   157  		return result, true, nil
   158  
   159  	case TypeArray:
   160  		var result []interface{}
   161  		if err := mapstructure.Decode(raw, &result); err != nil {
   162  			return nil, true, err
   163  		}
   164  		return result, true, nil
   165  
   166  	default:
   167  		panic(fmt.Sprintf("Unknown type: %s", schema.Type))
   168  	}
   169  }