...

Source file src/sigs.k8s.io/cli-utils/pkg/object/field.go

Documentation: sigs.k8s.io/cli-utils/pkg/object

     1  // Copyright 2021 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package object
     5  
     6  import (
     7  	"fmt"
     8  	"regexp"
     9  	"strings"
    10  
    11  	"k8s.io/apimachinery/pkg/util/validation/field"
    12  )
    13  
    14  // NestedField gets a value from a KRM map, if it exists, otherwise nil.
    15  // Fields can be string (map key) or int (array index).
    16  func NestedField(obj map[string]interface{}, fields ...interface{}) (interface{}, bool, error) {
    17  	var val interface{} = obj
    18  
    19  	for i, field := range fields {
    20  		if val == nil {
    21  			return nil, false, nil
    22  		}
    23  		switch typedField := field.(type) {
    24  		case string:
    25  			if m, ok := val.(map[string]interface{}); ok {
    26  				val, ok = m[typedField]
    27  				if !ok {
    28  					// not in map
    29  					return nil, false, nil
    30  				}
    31  			} else {
    32  				return nil, false, InvalidType(fields[:i+1], val, "map[string]interface{}")
    33  			}
    34  		case int:
    35  			if s, ok := val.([]interface{}); ok {
    36  				if typedField >= len(s) {
    37  					// index out of range
    38  					return nil, false, nil
    39  				}
    40  				val = s[typedField]
    41  			} else {
    42  				return nil, false, InvalidType(fields[:i+1], val, "[]interface{}")
    43  			}
    44  		default:
    45  			return nil, false, InvalidType(fields[:i+1], val, "string or int")
    46  		}
    47  	}
    48  	return val, true, nil
    49  }
    50  
    51  // InvalidType returns a *Error indicating "invalid value type".  This is used
    52  // to report malformed values (e.g. found int, expected string).
    53  func InvalidType(fieldPath []interface{}, value interface{}, validTypes string) *field.Error {
    54  	return Invalid(fieldPath, value,
    55  		fmt.Sprintf("found type %T, expected %s", value, validTypes))
    56  }
    57  
    58  // Invalid returns a *Error indicating "invalid value".  This is used
    59  // to report malformed values (e.g. failed regex match, too long, out of bounds).
    60  func Invalid(fieldPath []interface{}, value interface{}, detail string) *field.Error {
    61  	return &field.Error{
    62  		Type:     field.ErrorTypeInvalid,
    63  		Field:    FieldPath(fieldPath),
    64  		BadValue: value,
    65  		Detail:   detail,
    66  	}
    67  }
    68  
    69  // NotFound returns a *Error indicating "value not found".  This is
    70  // used to report failure to find a requested value (e.g. looking up an ID).
    71  func NotFound(fieldPath []interface{}, value interface{}) *field.Error {
    72  	return &field.Error{
    73  		Type:     field.ErrorTypeNotFound,
    74  		Field:    FieldPath(fieldPath),
    75  		BadValue: value,
    76  		Detail:   "",
    77  	}
    78  }
    79  
    80  // FieldPath formats a list of KRM field keys as a JSONPath expression.
    81  // The only valid field keys in KRM are strings (map keys) and ints (list keys).
    82  // Simple strings (see isSimpleString) will be delimited with a period.
    83  // Complex strings will be wrapped with square brackets and double quotes.
    84  // Integers will be wrapped with square brackets.
    85  // All other types will be formatted best-effort within square brackets.
    86  func FieldPath(fieldPath []interface{}) string {
    87  	var sb strings.Builder
    88  	for _, field := range fieldPath {
    89  		switch typedField := field.(type) {
    90  		case string:
    91  			if isSimpleString(typedField) {
    92  				_, _ = fmt.Fprintf(&sb, ".%s", typedField)
    93  			} else {
    94  				_, _ = fmt.Fprintf(&sb, "[%q]", typedField)
    95  			}
    96  		case int:
    97  			_, _ = fmt.Fprintf(&sb, "[%d]", typedField)
    98  		default:
    99  			// invalid type. try anyway...
   100  			_, _ = fmt.Fprintf(&sb, "[%#v]", typedField)
   101  		}
   102  	}
   103  	return sb.String()
   104  }
   105  
   106  var simpleStringRegex = regexp.MustCompile(`^[a-zA-Z]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$`)
   107  
   108  // isSimpleString returns true if the input follows the following rules:
   109  // - contains only alphanumeric characters, '_' or '-'
   110  // - starts with an alphabetic character
   111  // - ends with an alphanumeric character
   112  func isSimpleString(s string) bool {
   113  	return simpleStringRegex.FindString(s) != ""
   114  }
   115  

View as plain text