mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-01-11 00:18:17 -05:00
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:
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user