go.temporal.io/server@v1.23.0/common/searchattribute/validator.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. 4 // 5 // Copyright (c) 2020 Uber Technologies, Inc. 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 25 package searchattribute 26 27 import ( 28 "errors" 29 "fmt" 30 31 commonpb "go.temporal.io/api/common/v1" 32 "go.temporal.io/api/serviceerror" 33 34 "go.temporal.io/server/common/dynamicconfig" 35 "go.temporal.io/server/common/namespace" 36 "go.temporal.io/server/common/payload" 37 "go.temporal.io/server/common/persistence/visibility/manager" 38 ) 39 40 type ( 41 // Validator is used to validate search attributes 42 Validator struct { 43 searchAttributesProvider Provider 44 searchAttributesMapperProvider MapperProvider 45 searchAttributesNumberOfKeysLimit dynamicconfig.IntPropertyFnWithNamespaceFilter 46 searchAttributesSizeOfValueLimit dynamicconfig.IntPropertyFnWithNamespaceFilter 47 searchAttributesTotalSizeLimit dynamicconfig.IntPropertyFnWithNamespaceFilter 48 visibilityManager manager.VisibilityManager 49 50 // allowList allows list of values when it's not keyword list type. 51 allowList dynamicconfig.BoolPropertyFnWithNamespaceFilter 52 53 // suppressErrorSetSystemSearchAttribute suppresses errors when the user 54 // attempts to set values in system search attributes. 55 suppressErrorSetSystemSearchAttribute dynamicconfig.BoolPropertyFnWithNamespaceFilter 56 } 57 ) 58 59 // NewValidator create Validator 60 func NewValidator( 61 searchAttributesProvider Provider, 62 searchAttributesMapperProvider MapperProvider, 63 searchAttributesNumberOfKeysLimit dynamicconfig.IntPropertyFnWithNamespaceFilter, 64 searchAttributesSizeOfValueLimit dynamicconfig.IntPropertyFnWithNamespaceFilter, 65 searchAttributesTotalSizeLimit dynamicconfig.IntPropertyFnWithNamespaceFilter, 66 visibilityManager manager.VisibilityManager, 67 allowList dynamicconfig.BoolPropertyFnWithNamespaceFilter, 68 suppressErrorSetSystemSearchAttribute dynamicconfig.BoolPropertyFnWithNamespaceFilter, 69 ) *Validator { 70 return &Validator{ 71 searchAttributesProvider: searchAttributesProvider, 72 searchAttributesMapperProvider: searchAttributesMapperProvider, 73 searchAttributesNumberOfKeysLimit: searchAttributesNumberOfKeysLimit, 74 searchAttributesSizeOfValueLimit: searchAttributesSizeOfValueLimit, 75 searchAttributesTotalSizeLimit: searchAttributesTotalSizeLimit, 76 visibilityManager: visibilityManager, 77 allowList: allowList, 78 suppressErrorSetSystemSearchAttribute: suppressErrorSetSystemSearchAttribute, 79 } 80 } 81 82 // Validate search attributes are valid for writing. 83 // The search attributes must be unaliased before calling validation. 84 func (v *Validator) Validate(searchAttributes *commonpb.SearchAttributes, namespace string) error { 85 if searchAttributes == nil { 86 return nil 87 } 88 89 lengthOfFields := len(searchAttributes.GetIndexedFields()) 90 if lengthOfFields > v.searchAttributesNumberOfKeysLimit(namespace) { 91 return serviceerror.NewInvalidArgument( 92 fmt.Sprintf( 93 "number of search attributes %d exceeds limit %d", 94 lengthOfFields, 95 v.searchAttributesNumberOfKeysLimit(namespace), 96 ), 97 ) 98 } 99 100 saTypeMap, err := v.searchAttributesProvider.GetSearchAttributes( 101 v.visibilityManager.GetIndexName(), 102 false, 103 ) 104 if err != nil { 105 return serviceerror.NewInvalidArgument( 106 fmt.Sprintf("unable to get search attributes from cluster metadata: %v", err), 107 ) 108 } 109 110 saMap := make(map[string]any, len(searchAttributes.GetIndexedFields())) 111 for saFieldName, saPayload := range searchAttributes.GetIndexedFields() { 112 // user search attribute cannot be a system search attribute 113 if _, err = saTypeMap.getType(saFieldName, systemCategory); err == nil { 114 if v.suppressErrorSetSystemSearchAttribute(namespace) { 115 // if suppressing the error, then just ignore the search attribute 116 continue 117 } 118 return serviceerror.NewInvalidArgument( 119 fmt.Sprintf("%s attribute can't be set in SearchAttributes", saFieldName), 120 ) 121 } 122 123 saType, err := saTypeMap.getType(saFieldName, customCategory|predefinedCategory) 124 if err != nil { 125 if errors.Is(err, ErrInvalidName) { 126 return v.validationError( 127 "search attribute %s is not defined", 128 saFieldName, 129 namespace, 130 ) 131 } 132 return v.validationError( 133 fmt.Sprintf("unable to get %s search attribute type: %v", "%s", err), 134 saFieldName, 135 namespace, 136 ) 137 } 138 139 saValue, err := DecodeValue(saPayload, saType, v.allowList(namespace)) 140 if err != nil { 141 var invalidValue interface{} 142 if err = payload.Decode(saPayload, &invalidValue); err != nil { 143 invalidValue = fmt.Sprintf("value from <%s>", saPayload.String()) 144 } 145 return v.validationError( 146 fmt.Sprintf( 147 "invalid value for search attribute %s of type %s: %v", 148 "%s", 149 saType, 150 invalidValue, 151 ), 152 saFieldName, 153 namespace, 154 ) 155 } 156 saMap[saFieldName] = saValue 157 } 158 _, err = v.visibilityManager.ValidateCustomSearchAttributes(saMap) 159 return err 160 } 161 162 // ValidateSize validate search attributes are valid for writing and not exceed limits. 163 // The search attributes must be unaliased before calling validation. 164 func (v *Validator) ValidateSize(searchAttributes *commonpb.SearchAttributes, namespace string) error { 165 if searchAttributes == nil { 166 return nil 167 } 168 169 for saFieldName, saPayload := range searchAttributes.GetIndexedFields() { 170 if len(saPayload.GetData()) > v.searchAttributesSizeOfValueLimit(namespace) { 171 return v.validationError( 172 fmt.Sprintf( 173 "search attribute %s value size %d exceeds size limit %d", 174 "%s", 175 len(saPayload.GetData()), 176 v.searchAttributesSizeOfValueLimit(namespace), 177 ), 178 saFieldName, 179 namespace, 180 ) 181 } 182 } 183 184 if searchAttributes.Size() > v.searchAttributesTotalSizeLimit(namespace) { 185 return serviceerror.NewInvalidArgument( 186 fmt.Sprintf( 187 "total size of search attributes %d exceeds size limit %d", 188 searchAttributes.Size(), 189 v.searchAttributesTotalSizeLimit(namespace), 190 ), 191 ) 192 } 193 194 return nil 195 } 196 197 // Generates a validation error with search attribute alias resolution. 198 // Input `msg` must contain a single occurrence of `%s` that will be substituted 199 // by the search attribute alias. 200 func (v *Validator) validationError(msg string, saFieldName string, namespace string) error { 201 saAlias, err := v.getAlias(saFieldName, namespace) 202 if err != nil { 203 return err 204 } 205 return serviceerror.NewInvalidArgument(fmt.Sprintf(msg, saAlias)) 206 } 207 208 func (v *Validator) getAlias(saFieldName string, namespaceName string) (string, error) { 209 if IsMappable(saFieldName) { 210 mapper, err := v.searchAttributesMapperProvider.GetMapper(namespace.Name(namespaceName)) 211 if err != nil { 212 return "", err 213 } 214 if mapper != nil { 215 return mapper.GetAlias(saFieldName, namespaceName) 216 } 217 } 218 return saFieldName, nil 219 }