khenaidoo | 5fc5cea | 2021-08-11 17:39:16 -0400 | [diff] [blame] | 1 | /* |
| 2 | * |
| 3 | * Copyright 2019 gRPC authors. |
| 4 | * |
| 5 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | * you may not use this file except in compliance with the License. |
| 7 | * You may obtain a copy of the License at |
| 8 | * |
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | * |
| 11 | * Unless required by applicable law or agreed to in writing, software |
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | * See the License for the specific language governing permissions and |
| 15 | * limitations under the License. |
| 16 | * |
| 17 | */ |
| 18 | |
| 19 | // Package attributes defines a generic key/value store used in various gRPC |
| 20 | // components. |
| 21 | // |
Joey Armstrong | ba3d9d1 | 2024-01-15 14:22:11 -0500 | [diff] [blame] | 22 | // # Experimental |
khenaidoo | 5fc5cea | 2021-08-11 17:39:16 -0400 | [diff] [blame] | 23 | // |
| 24 | // Notice: This package is EXPERIMENTAL and may be changed or removed in a |
| 25 | // later release. |
| 26 | package attributes |
| 27 | |
Akash Kankanala | 761955c | 2024-02-21 19:32:20 +0530 | [diff] [blame^] | 28 | import ( |
| 29 | "fmt" |
| 30 | "strings" |
| 31 | ) |
| 32 | |
khenaidoo | 5fc5cea | 2021-08-11 17:39:16 -0400 | [diff] [blame] | 33 | // Attributes is an immutable struct for storing and retrieving generic |
| 34 | // key/value pairs. Keys must be hashable, and users should define their own |
khenaidoo | 5cb0d40 | 2021-12-08 14:09:16 -0500 | [diff] [blame] | 35 | // types for keys. Values should not be modified after they are added to an |
| 36 | // Attributes or if they were received from one. If values implement 'Equal(o |
| 37 | // interface{}) bool', it will be called by (*Attributes).Equal to determine |
| 38 | // whether two values with the same key should be considered equal. |
khenaidoo | 5fc5cea | 2021-08-11 17:39:16 -0400 | [diff] [blame] | 39 | type Attributes struct { |
| 40 | m map[interface{}]interface{} |
| 41 | } |
| 42 | |
khenaidoo | 5cb0d40 | 2021-12-08 14:09:16 -0500 | [diff] [blame] | 43 | // New returns a new Attributes containing the key/value pair. |
| 44 | func New(key, value interface{}) *Attributes { |
| 45 | return &Attributes{m: map[interface{}]interface{}{key: value}} |
khenaidoo | 5fc5cea | 2021-08-11 17:39:16 -0400 | [diff] [blame] | 46 | } |
| 47 | |
khenaidoo | 5cb0d40 | 2021-12-08 14:09:16 -0500 | [diff] [blame] | 48 | // WithValue returns a new Attributes containing the previous keys and values |
| 49 | // and the new key/value pair. If the same key appears multiple times, the |
| 50 | // last value overwrites all previous values for that key. To remove an |
| 51 | // existing key, use a nil value. value should not be modified later. |
| 52 | func (a *Attributes) WithValue(key, value interface{}) *Attributes { |
khenaidoo | 5fc5cea | 2021-08-11 17:39:16 -0400 | [diff] [blame] | 53 | if a == nil { |
khenaidoo | 5cb0d40 | 2021-12-08 14:09:16 -0500 | [diff] [blame] | 54 | return New(key, value) |
khenaidoo | 5fc5cea | 2021-08-11 17:39:16 -0400 | [diff] [blame] | 55 | } |
khenaidoo | 5cb0d40 | 2021-12-08 14:09:16 -0500 | [diff] [blame] | 56 | n := &Attributes{m: make(map[interface{}]interface{}, len(a.m)+1)} |
khenaidoo | 5fc5cea | 2021-08-11 17:39:16 -0400 | [diff] [blame] | 57 | for k, v := range a.m { |
| 58 | n.m[k] = v |
| 59 | } |
khenaidoo | 5cb0d40 | 2021-12-08 14:09:16 -0500 | [diff] [blame] | 60 | n.m[key] = value |
khenaidoo | 5fc5cea | 2021-08-11 17:39:16 -0400 | [diff] [blame] | 61 | return n |
| 62 | } |
| 63 | |
| 64 | // Value returns the value associated with these attributes for key, or nil if |
khenaidoo | 5cb0d40 | 2021-12-08 14:09:16 -0500 | [diff] [blame] | 65 | // no value is associated with key. The returned value should not be modified. |
khenaidoo | 5fc5cea | 2021-08-11 17:39:16 -0400 | [diff] [blame] | 66 | func (a *Attributes) Value(key interface{}) interface{} { |
| 67 | if a == nil { |
| 68 | return nil |
| 69 | } |
| 70 | return a.m[key] |
| 71 | } |
khenaidoo | 5cb0d40 | 2021-12-08 14:09:16 -0500 | [diff] [blame] | 72 | |
| 73 | // Equal returns whether a and o are equivalent. If 'Equal(o interface{}) |
| 74 | // bool' is implemented for a value in the attributes, it is called to |
| 75 | // determine if the value matches the one stored in the other attributes. If |
| 76 | // Equal is not implemented, standard equality is used to determine if the two |
khenaidoo | 257f319 | 2021-12-15 16:46:37 -0500 | [diff] [blame] | 77 | // values are equal. Note that some types (e.g. maps) aren't comparable by |
| 78 | // default, so they must be wrapped in a struct, or in an alias type, with Equal |
| 79 | // defined. |
khenaidoo | 5cb0d40 | 2021-12-08 14:09:16 -0500 | [diff] [blame] | 80 | func (a *Attributes) Equal(o *Attributes) bool { |
| 81 | if a == nil && o == nil { |
| 82 | return true |
| 83 | } |
| 84 | if a == nil || o == nil { |
| 85 | return false |
| 86 | } |
| 87 | if len(a.m) != len(o.m) { |
| 88 | return false |
| 89 | } |
| 90 | for k, v := range a.m { |
| 91 | ov, ok := o.m[k] |
| 92 | if !ok { |
| 93 | // o missing element of a |
| 94 | return false |
| 95 | } |
| 96 | if eq, ok := v.(interface{ Equal(o interface{}) bool }); ok { |
| 97 | if !eq.Equal(ov) { |
| 98 | return false |
| 99 | } |
| 100 | } else if v != ov { |
| 101 | // Fallback to a standard equality check if Value is unimplemented. |
| 102 | return false |
| 103 | } |
| 104 | } |
| 105 | return true |
| 106 | } |
Akash Kankanala | 761955c | 2024-02-21 19:32:20 +0530 | [diff] [blame^] | 107 | |
| 108 | // String prints the attribute map. If any key or values throughout the map |
| 109 | // implement fmt.Stringer, it calls that method and appends. |
| 110 | func (a *Attributes) String() string { |
| 111 | var sb strings.Builder |
| 112 | sb.WriteString("{") |
| 113 | first := true |
| 114 | for k, v := range a.m { |
| 115 | var key, val string |
| 116 | if str, ok := k.(interface{ String() string }); ok { |
| 117 | key = str.String() |
| 118 | } |
| 119 | if str, ok := v.(interface{ String() string }); ok { |
| 120 | val = str.String() |
| 121 | } |
| 122 | if !first { |
| 123 | sb.WriteString(", ") |
| 124 | } |
| 125 | sb.WriteString(fmt.Sprintf("%q: %q, ", key, val)) |
| 126 | first = false |
| 127 | } |
| 128 | sb.WriteString("}") |
| 129 | return sb.String() |
| 130 | } |