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  }