feat: support allowedValues, escape, minValue and maxValue for parameters (#1770)

## Description

To minimize SQL injection risks when using template parameters, it is
highly recommended that user utilizes the following added fields for
parameters.

### Allow user to indicate allowed values via list or regex
Add new `allowedValues` field to all parameter type. It can be used as
follows (can be used in the `parameter` field or `templateParameter`
field):

```
templateParameters:
    - name: tableName
       type: string
       description: table name.
       allowedValues:
            - flights_table
            - tickets_table
            - "^h.*" # support any words starting with the letter h
```

### Support escaping delimiters for identifiers in string parameters
Supporting `backticks`, `double-quotes`, `single-quotes`,
`square-brackets` as escaping delimiters. Example to apply escaping
delimiters:
```
# other fields
statement: SELECT {{array .columnName}} FROM {{ .tableName }}
templateParameters:
      - name: tableName
        type: string
        description: table name.
        escape: double-quotes
      - name: columnName
        type: array
        description: column names.
        items:
          name: column
          type: string
          description: Name of the column to select
          escape: double-quotes
```
This example will resolve to following: - 
* Data provided: `{"tableName": "table_name", "columnName": ["foo",
"bar"]}`
* Statement with escape: `SELECT "foo", "bar" FROM "table_name"`
* Statement without escape: `SELECT foo, bar FROM table_name`

Escaping delimiters can be used for identifiers (in template parameters)
or string literals. If `allowedValues` were used, Toolbox will check for
allowed values before applying delimiters.

### Support value range in numeric parameters
Supporting `minValue` and `maxValue` for parameters of type `integer`
and `float`. Example:
```
parameters:
      - name: price
        type: integer
        description: price of item
        minValue: 1
        maxValue: 50
```

If `allowedValues` were used, Toolbox will check for allowed values
before checking for min and max values.

### References


| parameter name | type | required | description |
|------------------|-----|---------|-------------|
| allowedValues | []string | true | We will check input value against
this. User can either provide a list of allowed values or regex string.
|
| escape | string | false | Only available for type `string`. Indicate
the escaping delimiters used for the parameter. This field is intended
to be used with templateParameters. Must be one of "single-quotes",
"double-quotes", "backticks", "square-brackets". |
| minValue | int or float | false | Only available for type `integer`
and `float`. Indicate the minimum value allowed. |
| maxValue | int or float | false | Only available for type `integer`
and `float`. Indicate the maximum value allowed. |

## PR Checklist

> Thank you for opening a Pull Request! Before submitting your PR, there
are a
> few things you can do to make sure it goes smoothly:

- [x] Make sure you reviewed

[CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md)
- [x] Make sure to open an issue as a

[bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose)
  before writing your code! That way we can discuss the change, evaluate
  designs, and agree on the general idea
- [x] Ensure the tests and linter pass
- [x] Code coverage does not decrease (if any source code was changed)
- [x] Appropriate docs were updated (if necessary)
- [x] Make sure to add `!` if this involve a breaking change

🛠️ Fixes #779
This commit is contained in:
Yuan Teoh
2025-10-22 19:36:53 -07:00
committed by GitHub
parent a06d0d8735
commit eaf77406fd
3 changed files with 526 additions and 33 deletions

View File

@@ -77,13 +77,17 @@ the parameter.
description: Airline unique 2 letter identifier
```
| **field** | **type** | **required** | **description** |
|-------------|:---------------:|:------------:|-----------------------------------------------------------------------------|
| name | string | true | Name of the parameter. |
| type | string | true | Must be one of "string", "integer", "float", "boolean" "array" |
| description | string | true | Natural language description of the parameter to describe it to the agent. |
| default | parameter type | false | Default value of the parameter. If provided, `required` will be `false`. |
| required | bool | false | Indicate if the parameter is required. Default to `true`. |
| **field** | **type** | **required** | **description** |
|---------------|:--------------:|:------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| name | string | true | Name of the parameter. |
| type | string | true | Must be one of "string", "integer", "float", "boolean" "array" |
| description | string | true | Natural language description of the parameter to describe it to the agent. |
| default | parameter type | false | Default value of the parameter. If provided, `required` will be `false`. |
| required | bool | false | Indicate if the parameter is required. Default to `true`. |
| allowedValues | []string | false | Input value will be checked against this field. Regex is also supported. |
| escape | string | false | Only available for type `string`. Indicate the escaping delimiters used for the parameter. This field is intended to be used with templateParameters. Must be one of "single-quotes", "double-quotes", "backticks", "square-brackets". |
| minValue | int or float | false | Only available for type `integer` and `float`. Indicate the minimum value allowed. |
| maxValue | int or float | false | Only available for type `integer` and `float`. Indicate the maximum value allowed. |
### Array Parameters
@@ -104,14 +108,15 @@ in the list using the items field:
SELECT * FROM airlines WHERE preferred_airlines = ANY($1);
```
| **field** | **type** | **required** | **description** |
|-------------|:----------------:|:------------:|-----------------------------------------------------------------------------|
| name | string | true | Name of the parameter. |
| type | string | true | Must be "array" |
| description | string | true | Natural language description of the parameter to describe it to the agent. |
| default | parameter type | false | Default value of the parameter. If provided, `required` will be `false`. |
| required | bool | false | Indicate if the parameter is required. Default to `true`. |
| items | parameter object | true | Specify a Parameter object for the type of the values in the array. |
| **field** | **type** | **required** | **description** |
|---------------|:----------------:|:------------:|----------------------------------------------------------------------------|
| name | string | true | Name of the parameter. |
| type | string | true | Must be "array" |
| description | string | true | Natural language description of the parameter to describe it to the agent. |
| default | parameter type | false | Default value of the parameter. If provided, `required` will be `false`. |
| required | bool | false | Indicate if the parameter is required. Default to `true`. |
| allowedValues | []string | false | Input value will be checked against this field. Regex is also supported. |
| items | parameter object | true | Specify a Parameter object for the type of the values in the array. |
{{< notice note >}}
Items in array should not have a `default` or `required` value. If provided, it
@@ -181,10 +186,10 @@ user's ID token.
field: sub
```
| **field** | **type** | **required** | **description** |
|-----------|:--------:|:------------:|-----------------------------------------------------------------------------------------|
| name | string | true | Name of the [authServices](../authServices/) used to verify the OIDC auth token. |
| field | string | true | Claim field decoded from the OIDC token used to auto-populate this parameter. |
| **field** | **type** | **required** | **description** |
|-----------|:--------:|:------------:|----------------------------------------------------------------------------------|
| name | string | true | Name of the [authServices](../authServices/) used to verify the OIDC auth token. |
| field | string | true | Claim field decoded from the OIDC token used to auto-populate this parameter. |
### Template Parameters
@@ -206,6 +211,14 @@ table names, they are prone to SQL injections. Basic parameters are preferred
for performance and safety reasons.
{{< /notice >}}
{{< notice tip >}}
To minimize SQL injection risk when using template parameters, always provide
the `allowedValues` field within the parameter to restrict inputs.
Alternatively, for `string` type parameters, you can use the `escape` field to
add delimiters to the identifier. For `integer` or `float` type parameters, you
can use `minValue` and `maxValue` to define the allowable range.
{{< /notice >}}
```yaml
tools:
select_columns_from_table:
@@ -231,14 +244,18 @@ tools:
name: column
type: string
description: Name of a column to select
escape: double-quotes # with this, the statement will resolve to `SELECT "id", "name" FROM flights`
```
| **field** | **type** | **required** | **description** |
|-------------|:----------------:|:-------------:|-------------------------------------------------------------------------------------|
| name | string | true | Name of the template parameter. |
| type | string | true | Must be one of "string", "integer", "float", "boolean" "array" |
| description | string | true | Natural language description of the template parameter to describe it to the agent. |
| items | parameter object |true (if array)| Specify a Parameter object for the type of the values in the array (string only). |
| **field** | **type** | **required** | **description** |
|---------------|:----------------:|:---------------:|-------------------------------------------------------------------------------------|
| name | string | true | Name of the template parameter. |
| type | string | true | Must be one of "string", "integer", "float", "boolean", "array" |
| description | string | true | Natural language description of the template parameter to describe it to the agent. |
| default | parameter type | false | Default value of the parameter. If provided, `required` will be `false`. |
| required | bool | false | Indicate if the parameter is required. Default to `true`. |
| allowedValues | []string | false | Input value will be checked against this field. Regex is also supported. |
| items | parameter object | true (if array) | Specify a Parameter object for the type of the values in the array (string only). |
## Authorized Invocations

View File

@@ -19,6 +19,8 @@ import (
"context"
"encoding/json"
"fmt"
"reflect"
"regexp"
"slices"
"strings"
"text/template"
@@ -35,6 +37,14 @@ const (
typeMap = "map"
)
// delimiters for string parameter escaping
const (
escapeBackticks = "backticks"
escapeDoubleQuotes = "double-quotes"
escapeSingleQuotes = "single-quotes"
escapeSquareBrackets = "square-brackets"
)
// ParamValues is an ordered list of ParamValue
type ParamValues []ParamValue
@@ -278,7 +288,7 @@ func parseParamFromDelayedUnmarshaler(ctx context.Context, u *util.DelayedUnmars
t, ok := p["type"]
if !ok {
return nil, fmt.Errorf("parameter is missing 'type' field: %w", err)
return nil, fmt.Errorf("parameter is missing 'type' field")
}
dec, err := util.NewStrictDecoder(p)
@@ -413,12 +423,13 @@ type ParameterMcpManifest struct {
// CommonParameter are default fields that are emebdding in most Parameter implementations. Embedding this stuct will give the object Name() and Type() functions.
type CommonParameter struct {
Name string `yaml:"name" validate:"required"`
Type string `yaml:"type" validate:"required"`
Desc string `yaml:"description" validate:"required"`
Required *bool `yaml:"required"`
AuthServices []ParamAuthService `yaml:"authServices"`
AuthSources []ParamAuthService `yaml:"authSources"` // Deprecated: Kept for compatibility.
Name string `yaml:"name" validate:"required"`
Type string `yaml:"type" validate:"required"`
Desc string `yaml:"description" validate:"required"`
Required *bool `yaml:"required"`
AllowedValues []any `yaml:"allowedValues"`
AuthServices []ParamAuthService `yaml:"authServices"`
AuthSources []ParamAuthService `yaml:"authSources"` // Deprecated: Kept for compatibility.
}
// GetName returns the name specified for the Parameter.
@@ -440,6 +451,39 @@ func (p *CommonParameter) GetRequired() bool {
return *p.Required
}
// GetAllowedValues returns the allowed values for the Parameter.
func (p *CommonParameter) GetAllowedValues() []any {
return p.AllowedValues
}
// IsAllowedValues checks if the value is allowed.
func (p *CommonParameter) IsAllowedValues(v any) bool {
if len(p.AllowedValues) == 0 {
return true
}
for _, av := range p.AllowedValues {
if MatchStringOrRegex(v, av) {
return true
}
}
return false
}
// MatchStringOrRegex checks if the input matches the target
func MatchStringOrRegex(input, target any) bool {
targetS, ok := target.(string)
if !ok {
return input == target
}
re, err := regexp.Compile(targetS)
if err != nil {
// if target is not regex, run direct comparison
return input == target
}
inputS := fmt.Sprintf("%s", input)
return re.MatchString(inputS)
}
// McpManifest returns the MCP manifest for the Parameter.
func (p *CommonParameter) McpManifest() (ParameterMcpManifest, []string) {
authServiceNames := getAuthServiceNames(p.AuthServices)
@@ -499,6 +543,19 @@ func NewStringParameterWithDefault(name string, defaultV, desc string) *StringPa
}
}
// NewStringParameterWithEscape is a convenience function for initializing a StringParameter.
func NewStringParameterWithEscape(name, desc, escape string) *StringParameter {
return &StringParameter{
CommonParameter: CommonParameter{
Name: name,
Type: typeString,
Desc: desc,
AuthServices: nil,
},
Escape: &escape,
}
}
// NewStringParameterWithRequired is a convenience function for initializing a StringParameter.
func NewStringParameterWithRequired(name string, desc string, required bool) *StringParameter {
return &StringParameter{
@@ -524,12 +581,26 @@ func NewStringParameterWithAuth(name string, desc string, authServices []ParamAu
}
}
// NewStringParameterWithAllowedValues is a convenience function for initializing a StringParameter with a list of allowedValues
func NewStringParameterWithAllowedValues(name string, desc string, allowedValues []any) *StringParameter {
return &StringParameter{
CommonParameter: CommonParameter{
Name: name,
Type: typeString,
Desc: desc,
AllowedValues: allowedValues,
AuthServices: nil,
},
}
}
var _ Parameter = &StringParameter{}
// StringParameter is a parameter representing the "string" type.
type StringParameter struct {
CommonParameter `yaml:",inline"`
Default *string `yaml:"default"`
Escape *string `yaml:"escape"`
}
// Parse casts the value "v" as a "string".
@@ -538,9 +609,30 @@ func (p *StringParameter) Parse(v any) (any, error) {
if !ok {
return nil, &ParseTypeError{p.Name, p.Type, v}
}
if !p.IsAllowedValues(newV) {
return nil, fmt.Errorf("%s is not an allowed value", newV)
}
if p.Escape != nil {
return applyEscape(*p.Escape, newV)
}
return newV, nil
}
func applyEscape(escape, v string) (any, error) {
switch escape {
case escapeBackticks:
return fmt.Sprintf("`%s`", v), nil
case escapeDoubleQuotes:
return fmt.Sprintf(`"%s"`, v), nil
case escapeSingleQuotes:
return fmt.Sprintf(`'%s'`, v), nil
case escapeSquareBrackets:
return fmt.Sprintf("[%s]", v), nil
default:
return nil, fmt.Errorf("%s is not an allowed escaping delimiter", escape)
}
}
func (p *StringParameter) GetAuthServices() []ParamAuthService {
return p.AuthServices
}
@@ -578,6 +670,20 @@ func NewIntParameter(name string, desc string) *IntParameter {
}
}
// NewIntParameterWithRange is a convenience function for initializing a IntParameter.
func NewIntParameterWithRange(name string, desc string, minValue *int, maxValue *int) *IntParameter {
return &IntParameter{
CommonParameter: CommonParameter{
Name: name,
Type: typeInt,
Desc: desc,
AuthServices: nil,
},
MinValue: minValue,
MaxValue: maxValue,
}
}
// NewIntParameterWithDefault is a convenience function for initializing a IntParameter with default value.
func NewIntParameterWithDefault(name string, defaultV int, desc string) *IntParameter {
return &IntParameter{
@@ -616,12 +722,27 @@ func NewIntParameterWithAuth(name string, desc string, authServices []ParamAuthS
}
}
// NewIntParameterWithAllowedValues is a convenience function for initializing a IntParameter with a list of allowedValues
func NewIntParameterWithAllowedValues(name string, desc string, allowedValues []any) *IntParameter {
return &IntParameter{
CommonParameter: CommonParameter{
Name: name,
Type: typeString,
Desc: desc,
AllowedValues: allowedValues,
AuthServices: nil,
},
}
}
var _ Parameter = &IntParameter{}
// IntParameter is a parameter representing the "int" type.
type IntParameter struct {
CommonParameter `yaml:",inline"`
Default *int `yaml:"default"`
MinValue *int `yaml:"minValue"`
MaxValue *int `yaml:"maxValue"`
}
func (p *IntParameter) Parse(v any) (any, error) {
@@ -642,6 +763,15 @@ func (p *IntParameter) Parse(v any) (any, error) {
}
out = int(newI)
}
if !p.IsAllowedValues(out) {
return nil, fmt.Errorf("%d is not an allowed value", out)
}
if p.MinValue != nil && out < *p.MinValue {
return nil, fmt.Errorf("%d is under the minimum value", out)
}
if p.MaxValue != nil && out > *p.MaxValue {
return nil, fmt.Errorf("%d is above the maximum value", out)
}
return out, nil
}
@@ -682,6 +812,20 @@ func NewFloatParameter(name string, desc string) *FloatParameter {
}
}
// NewFloatParameterWithRange is a convenience function for initializing a FloatParameter.
func NewFloatParameterWithRange(name string, desc string, minValue *float64, maxValue *float64) *FloatParameter {
return &FloatParameter{
CommonParameter: CommonParameter{
Name: name,
Type: typeFloat,
Desc: desc,
AuthServices: nil,
},
MinValue: minValue,
MaxValue: maxValue,
}
}
// NewFloatParameterWithDefault is a convenience function for initializing a FloatParameter with default value.
func NewFloatParameterWithDefault(name string, defaultV float64, desc string) *FloatParameter {
return &FloatParameter{
@@ -720,12 +864,27 @@ func NewFloatParameterWithAuth(name string, desc string, authServices []ParamAut
}
}
// NewFloatParameterWithAllowedValues is a convenience function for initializing a FloatParameter with a list of allowed values.
func NewFloatParameterWithAllowedValues(name string, desc string, allowedValues []any) *FloatParameter {
return &FloatParameter{
CommonParameter: CommonParameter{
Name: name,
Type: typeFloat,
Desc: desc,
AllowedValues: allowedValues,
AuthServices: nil,
},
}
}
var _ Parameter = &FloatParameter{}
// FloatParameter is a parameter representing the "float" type.
type FloatParameter struct {
CommonParameter `yaml:",inline"`
Default *float64 `yaml:"default"`
MinValue *float64 `yaml:"minValue"`
MaxValue *float64 `yaml:"maxValue"`
}
func (p *FloatParameter) Parse(v any) (any, error) {
@@ -744,6 +903,15 @@ func (p *FloatParameter) Parse(v any) (any, error) {
}
out = float64(newI)
}
if !p.IsAllowedValues(out) {
return nil, fmt.Errorf("%g is not an allowed value", out)
}
if p.MinValue != nil && out < *p.MinValue {
return nil, fmt.Errorf("%g is under the minimum value", out)
}
if p.MaxValue != nil && out > *p.MaxValue {
return nil, fmt.Errorf("%g is above the maximum value", out)
}
return out, nil
}
@@ -832,6 +1000,19 @@ func NewBooleanParameterWithAuth(name string, desc string, authServices []ParamA
}
}
// NewBooleanParameterWithAllowedValues is a convenience function for initializing a BooleanParameter with a list of allowed values.
func NewBooleanParameterWithAllowedValues(name string, desc string, allowedValues []any) *BooleanParameter {
return &BooleanParameter{
CommonParameter: CommonParameter{
Name: name,
Type: typeBool,
Desc: desc,
AllowedValues: allowedValues,
AuthServices: nil,
},
}
}
var _ Parameter = &BooleanParameter{}
// BooleanParameter is a parameter representing the "boolean" type.
@@ -845,6 +1026,9 @@ func (p *BooleanParameter) Parse(v any) (any, error) {
if !ok {
return nil, &ParseTypeError{p.Name, p.Type, v}
}
if !p.IsAllowedValues(newV) {
return nil, fmt.Errorf("%t is not an allowed value", newV)
}
return newV, nil
}
@@ -927,6 +1111,20 @@ func NewArrayParameterWithAuth(name string, desc string, items Parameter, authSe
}
}
// NewArrayParameterWithAllowedValues is a convenience function for initializing a ArrayParameter with a list of allowed values.
func NewArrayParameterWithAllowedValues(name string, desc string, allowedValues []any, items Parameter) *ArrayParameter {
return &ArrayParameter{
CommonParameter: CommonParameter{
Name: name,
Type: typeArray,
Desc: desc,
AllowedValues: allowedValues,
AuthServices: nil,
},
Items: items,
}
}
var _ Parameter = &ArrayParameter{}
// ArrayParameter is a parameter representing the "array" type.
@@ -959,11 +1157,27 @@ func (p *ArrayParameter) UnmarshalYAML(ctx context.Context, unmarshal func(inter
return nil
}
func (p *ArrayParameter) IsAllowedValues(v []any) bool {
a := p.GetAllowedValues()
if len(a) == 0 {
return true
}
for _, av := range a {
if reflect.DeepEqual(v, av) {
return true
}
}
return false
}
func (p *ArrayParameter) Parse(v any) (any, error) {
arrVal, ok := v.([]any)
if !ok {
return nil, &ParseTypeError{p.Name, p.Type, arrVal}
}
if !p.IsAllowedValues(arrVal) {
return nil, fmt.Errorf("%s is not an allowed value", arrVal)
}
rtn := make([]any, 0, len(arrVal))
for idx, val := range arrVal {
val, err := p.Items.Parse(val)
@@ -1083,6 +1297,19 @@ func NewMapParameterWithAuth(name string, desc string, valueType string, authSer
}
}
// NewMapParameterWithAllowedValues is a convenience function for initializing a MapParameter with a list of allowed values.
func NewMapParameterWithAllowedValues(name string, desc string, allowedValues []any, valueType string) *MapParameter {
return &MapParameter{
CommonParameter: CommonParameter{
Name: name,
Type: "map",
Desc: desc,
AllowedValues: allowedValues,
},
ValueType: valueType,
}
}
// UnmarshalYAML handles parsing the MapParameter from YAML input.
func (p *MapParameter) UnmarshalYAML(ctx context.Context, unmarshal func(interface{}) error) error {
var rawItem struct {
@@ -1124,12 +1351,28 @@ func getPrototypeParameter(typeName string) (Parameter, error) {
}
}
func (p *MapParameter) IsAllowedValues(v map[string]any) bool {
a := p.GetAllowedValues()
if len(a) == 0 {
return true
}
for _, av := range a {
if reflect.DeepEqual(v, av) {
return true
}
}
return false
}
// Parse validates and parses an incoming value for the map parameter.
func (p *MapParameter) Parse(v any) (any, error) {
m, ok := v.(map[string]any)
if !ok {
return nil, &ParseTypeError{p.Name, p.Type, m}
}
if !p.IsAllowedValues(m) {
return nil, fmt.Errorf("%s is not an allowed value", m)
}
// for generic maps, convert json.Numbers to their corresponding types
if p.ValueType == "" {
convertedData, err := util.ConvertNumbers(m)

View File

@@ -693,6 +693,8 @@ func TestAuthParametersMarshal(t *testing.T) {
}
func TestParametersParse(t *testing.T) {
intValue := 2
floatValue := 1.5
tcs := []struct {
name string
params tools.Parameters
@@ -719,6 +721,75 @@ func TestParametersParse(t *testing.T) {
"my_string": 4,
},
},
{
name: "string allowed",
params: tools.Parameters{
tools.NewStringParameterWithAllowedValues("my_string", "this param is a string", []any{"foo"}),
},
in: map[string]any{
"my_string": "foo",
},
want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: "foo"}},
},
{
name: "string allowed regex",
params: tools.Parameters{
tools.NewStringParameterWithAllowedValues("my_string", "this param is a string", []any{"^f.*"}),
},
in: map[string]any{
"my_string": "foo",
},
want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: "foo"}},
},
{
name: "string not allowed",
params: tools.Parameters{
tools.NewStringParameterWithAllowedValues("my_string", "this param is a string", []any{"foo"}),
},
in: map[string]any{
"my_string": "bar",
},
},
{
name: "string with escape backticks",
params: tools.Parameters{
tools.NewStringParameterWithEscape("my_string", "this param is a string", "backticks"),
},
in: map[string]any{
"my_string": "foo",
},
want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: "`foo`"}},
},
{
name: "string with escape double quotes",
params: tools.Parameters{
tools.NewStringParameterWithEscape("my_string", "this param is a string", "double-quotes"),
},
in: map[string]any{
"my_string": "foo",
},
want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: `"foo"`}},
},
{
name: "string with escape single quotes",
params: tools.Parameters{
tools.NewStringParameterWithEscape("my_string", "this param is a string", "single-quotes"),
},
in: map[string]any{
"my_string": "foo",
},
want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: `'foo'`}},
},
{
name: "string with escape square brackets",
params: tools.Parameters{
tools.NewStringParameterWithEscape("my_string", "this param is a string", "square-brackets"),
},
in: map[string]any{
"my_string": "foo",
},
want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: "[foo]"}},
},
{
name: "int",
params: tools.Parameters{
@@ -748,6 +819,63 @@ func TestParametersParse(t *testing.T) {
},
want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: math.MaxInt64}},
},
{
name: "int allowed",
params: tools.Parameters{
tools.NewIntParameterWithAllowedValues("my_int", "this param is an int", []any{1}),
},
in: map[string]any{
"my_int": 1,
},
want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: 1}},
},
{
name: "int not allowed",
params: tools.Parameters{
tools.NewIntParameterWithAllowedValues("my_int", "this param is an int", []any{1}),
},
in: map[string]any{
"my_int": 2,
},
},
{
name: "int minValue",
params: tools.Parameters{
tools.NewIntParameterWithRange("my_int", "this param is an int", &intValue, nil),
},
in: map[string]any{
"my_int": 3,
},
want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: 3}},
},
{
name: "int minValue disallow",
params: tools.Parameters{
tools.NewIntParameterWithRange("my_int", "this param is an int", &intValue, nil),
},
in: map[string]any{
"my_int": 1,
},
},
{
name: "int maxValue",
params: tools.Parameters{
tools.NewIntParameterWithRange("my_int", "this param is an int", nil, &intValue),
},
in: map[string]any{
"my_int": 1,
},
want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: 1}},
},
{
name: "int maxValue disallow",
params: tools.Parameters{
tools.NewIntParameterWithRange("my_int", "this param is an int", nil, &intValue),
},
in: map[string]any{
"my_int": 3,
},
},
{
name: "float",
params: tools.Parameters{
@@ -767,6 +895,63 @@ func TestParametersParse(t *testing.T) {
"my_float": true,
},
},
{
name: "float allowed",
params: tools.Parameters{
tools.NewFloatParameterWithAllowedValues("my_float", "this param is a float", []any{1.1}),
},
in: map[string]any{
"my_float": 1.1,
},
want: tools.ParamValues{tools.ParamValue{Name: "my_float", Value: 1.1}},
},
{
name: "float not allowed",
params: tools.Parameters{
tools.NewFloatParameterWithAllowedValues("my_float", "this param is a float", []any{1.1}),
},
in: map[string]any{
"my_float": 1.2,
},
},
{
name: "float minValue",
params: tools.Parameters{
tools.NewFloatParameterWithRange("my_float", "this param is a float", &floatValue, nil),
},
in: map[string]any{
"my_float": 1.8,
},
want: tools.ParamValues{tools.ParamValue{Name: "my_float", Value: 1.8}},
},
{
name: "float minValue disallow",
params: tools.Parameters{
tools.NewFloatParameterWithRange("my_float", "this param is a float", &floatValue, nil),
},
in: map[string]any{
"my_float": 1.2,
},
},
{
name: "float maxValue",
params: tools.Parameters{
tools.NewFloatParameterWithRange("my_float", "this param is a float", nil, &floatValue),
},
in: map[string]any{
"my_float": 1.2,
},
want: tools.ParamValues{tools.ParamValue{Name: "my_float", Value: 1.2}},
},
{
name: "float maxValue disallow",
params: tools.Parameters{
tools.NewFloatParameterWithRange("my_float", "this param is a float", nil, &floatValue),
},
in: map[string]any{
"my_float": 1.8,
},
},
{
name: "bool",
params: tools.Parameters{
@@ -786,6 +971,25 @@ func TestParametersParse(t *testing.T) {
"my_bool": 1.5,
},
},
{
name: "bool allowed",
params: tools.Parameters{
tools.NewBooleanParameterWithAllowedValues("my_bool", "this param is a bool", []any{false}),
},
in: map[string]any{
"my_bool": false,
},
want: tools.ParamValues{tools.ParamValue{Name: "my_bool", Value: false}},
},
{
name: "bool not allowed",
params: tools.Parameters{
tools.NewBooleanParameterWithAllowedValues("my_bool", "this param is a bool", []any{false}),
},
in: map[string]any{
"my_bool": true,
},
},
{
name: "string default",
params: tools.Parameters{
@@ -858,6 +1062,16 @@ func TestParametersParse(t *testing.T) {
in: map[string]any{},
want: tools.ParamValues{tools.ParamValue{Name: "my_bool", Value: nil}},
},
{
name: "array with string escape",
params: tools.Parameters{
tools.NewArrayParameter("my_array", "an array", tools.NewStringParameterWithEscape("my_string", "string item", "backticks")),
},
in: map[string]any{
"my_array": []string{"val1", "val2"},
},
want: tools.ParamValues{tools.ParamValue{Name: "my_array", Value: []any{string("`val1`"), string("`val2`")}}},
},
{
name: "map",
params: tools.Parameters{
@@ -903,6 +1117,25 @@ func TestParametersParse(t *testing.T) {
in: map[string]any{},
want: tools.ParamValues{tools.ParamValue{Name: "my_map_not_required", Value: nil}},
},
{
name: "map allowed",
params: tools.Parameters{
tools.NewMapParameterWithAllowedValues("my_map", "a map", []any{map[string]any{"key1": "val1"}}, "string"),
},
in: map[string]any{
"my_map": map[string]any{"key1": "val1"},
},
want: tools.ParamValues{tools.ParamValue{Name: "my_map", Value: map[string]any{"key1": "val1"}}},
},
{
name: "map not allowed",
params: tools.Parameters{
tools.NewMapParameterWithAllowedValues("my_map", "a map", []any{map[string]any{"key1": "val1"}}, "string"),
},
in: map[string]any{
"my_map": map[string]any{"key1": "val2"},
},
},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
@@ -1497,7 +1730,7 @@ func TestFailParametersUnmarshal(t *testing.T) {
"description": "this is a param for string",
},
},
err: "parameter is missing 'type' field: %!w(<nil>)",
err: "parameter is missing 'type' field",
},
{
name: "common parameter missing description",