github.com/aristanetworks/goarista@v0.0.0-20240514173732-cca2755bbd44/key/stringify.go (about)

     1  // Copyright (c) 2015 Arista Networks, Inc.
     2  // Use of this source code is governed by the Apache License 2.0
     3  // that can be found in the COPYING file.
     4  
     5  package key
     6  
     7  import (
     8  	"encoding/base64"
     9  	"fmt"
    10  	"math"
    11  	"strconv"
    12  	"strings"
    13  	"unicode/utf8"
    14  )
    15  
    16  type keyStringer interface {
    17  	KeyString() string
    18  }
    19  
    20  // StringKey generates a String suitable to be used as a key in a
    21  // string index by calling k.StringKey, if available, otherwise it
    22  // calls k.String. StringKey returns the same results as
    23  // StringifyInterface(k.Key()) and should be preferred over
    24  // StringifyInterface.
    25  func StringKey(k Key) string {
    26  	if ks, ok := k.(keyStringer); ok {
    27  		return ks.KeyString()
    28  	}
    29  	return k.String()
    30  }
    31  
    32  // StringifyInterface transforms an arbitrary interface into a string
    33  // representation suitable to be used as a key, such as in a JSON
    34  // object, or as a path element.
    35  //
    36  // Deprecated: Use StringKey instead.
    37  func StringifyInterface(key interface{}) (string, error) {
    38  	return stringify(key), nil
    39  }
    40  
    41  // escape checks if the string is a valid utf-8 string.
    42  // If it is, it will return the string as is.
    43  // If it is not, it will return the base64 representation of the byte array string
    44  func escape(str string) string {
    45  	if utf8.ValidString(str) {
    46  		return str
    47  	}
    48  	return base64.StdEncoding.EncodeToString([]byte(str))
    49  }
    50  
    51  func stringify(key interface{}) string {
    52  	switch key := key.(type) {
    53  	case nil:
    54  		return "<nil>"
    55  	case bool:
    56  		return strconv.FormatBool(key)
    57  	case uint8:
    58  		return strconv.FormatUint(uint64(key), 10)
    59  	case uint16:
    60  		return strconv.FormatUint(uint64(key), 10)
    61  	case uint32:
    62  		return strconv.FormatUint(uint64(key), 10)
    63  	case uint64:
    64  		return strconv.FormatUint(key, 10)
    65  	case int8:
    66  		return strconv.FormatInt(int64(key), 10)
    67  	case int16:
    68  		return strconv.FormatInt(int64(key), 10)
    69  	case int32:
    70  		return strconv.FormatInt(int64(key), 10)
    71  	case int64:
    72  		return strconv.FormatInt(key, 10)
    73  	case float32:
    74  		return "f" + strconv.FormatInt(int64(math.Float32bits(key)), 10)
    75  	case float64:
    76  		return "f" + strconv.FormatInt(int64(math.Float64bits(key)), 10)
    77  	case string:
    78  		return escape(key)
    79  	case map[string]interface{}:
    80  		keys := SortedKeys(key)
    81  		for i, k := range keys {
    82  			v := key[k]
    83  			keys[i] = stringify(v)
    84  		}
    85  		return strings.Join(keys, "_")
    86  	case *map[string]interface{}:
    87  		return stringify(*key)
    88  	case Map:
    89  		return key.KeyString()
    90  	case *Map:
    91  		return key.KeyString()
    92  	case []interface{}:
    93  		elements := make([]string, len(key))
    94  		for i, element := range key {
    95  			elements[i] = stringify(element)
    96  		}
    97  		return strings.Join(elements, ",")
    98  	case []byte:
    99  		return base64.StdEncoding.EncodeToString(key)
   100  	case Pointer:
   101  		return "{" + key.Pointer().String() + "}"
   102  	case Path:
   103  		return "[" + key.String() + "]"
   104  	case keyStringer:
   105  		return key.KeyString()
   106  	case fmt.Stringer:
   107  		return key.String()
   108  	default:
   109  		panic(fmt.Errorf("Unable to stringify type %T: %#v", key, key))
   110  	}
   111  }
   112  
   113  // stringifyCollectionHelper is similar to StringifyInterface, but
   114  // optimizes for human readability instead of making a unique string
   115  // key suitable for JSON.
   116  func stringifyCollectionHelper(val interface{}) string {
   117  	switch val := val.(type) {
   118  	case string:
   119  		return escape(val)
   120  	case map[string]interface{}:
   121  		keys := SortedKeys(val)
   122  		for i, k := range keys {
   123  			v := val[k]
   124  			s := stringifyCollectionHelper(v)
   125  			keys[i] = k + ":" + s
   126  		}
   127  		return "map[" + strings.Join(keys, " ") + "]"
   128  	case []interface{}:
   129  		elements := make([]string, len(val))
   130  		for i, element := range val {
   131  			elements[i] = stringifyCollectionHelper(element)
   132  		}
   133  		return strings.Join(elements, ",")
   134  	case Pointer:
   135  		return "{" + val.Pointer().String() + "}"
   136  	case Path:
   137  		return "[" + val.String() + "]"
   138  	case Key:
   139  		return stringifyCollectionHelper(val.Key())
   140  	}
   141  
   142  	return fmt.Sprint(val)
   143  }