diff --git a/flows/actions/call_classifier.go b/flows/actions/call_classifier.go index ff1f76c5f..e7dce8bc6 100644 --- a/flows/actions/call_classifier.go +++ b/flows/actions/call_classifier.go @@ -38,7 +38,7 @@ type CallClassifierAction struct { Classifier *assets.ClassifierReference `json:"classifier" validate:"required"` Input string `json:"input" validate:"required" engine:"evaluated"` - ResultName string `json:"result_name" validate:"required,max=128"` + ResultName string `json:"result_name" validate:"required,result_name"` } // NewCallClassifier creates a new call classifier action diff --git a/flows/actions/call_resthook.go b/flows/actions/call_resthook.go index 4d54d4cd5..c02f9c1d3 100644 --- a/flows/actions/call_resthook.go +++ b/flows/actions/call_resthook.go @@ -67,7 +67,7 @@ type CallResthookAction struct { onlineAction Resthook string `json:"resthook" validate:"required"` - ResultName string `json:"result_name,omitempty" validate:"max=128"` + ResultName string `json:"result_name,omitempty" validate:"omitempty,result_name"` } // NewCallResthook creates a new call resthook action diff --git a/flows/actions/call_webhook.go b/flows/actions/call_webhook.go index 650b858e4..6f0b6b4b1 100644 --- a/flows/actions/call_webhook.go +++ b/flows/actions/call_webhook.go @@ -56,7 +56,7 @@ type CallWebhookAction struct { URL string `json:"url" engine:"evaluated" validate:"required"` Headers map[string]string `json:"headers,omitempty" engine:"evaluated"` Body string `json:"body,omitempty" engine:"evaluated"` - ResultName string `json:"result_name,omitempty" validate:"max=128"` + ResultName string `json:"result_name,omitempty" validate:"omitempty,result_name"` } // NewCallWebhook creates a new call webhook action diff --git a/flows/actions/open_ticket.go b/flows/actions/open_ticket.go index bb709e0a6..e0fec9206 100644 --- a/flows/actions/open_ticket.go +++ b/flows/actions/open_ticket.go @@ -38,7 +38,7 @@ type OpenTicketAction struct { Topic *assets.TopicReference `json:"topic" validate:"omitempty"` Body string `json:"body" engine:"evaluated"` // TODO will become "note" in future migration Assignee *assets.UserReference `json:"assignee" validate:"omitempty"` - ResultName string `json:"result_name" validate:"required,max=128"` + ResultName string `json:"result_name" validate:"required,result_name"` } // NewOpenTicket creates a new open ticket action diff --git a/flows/actions/set_run_result.go b/flows/actions/set_run_result.go index 0f647e06c..aabefacbb 100644 --- a/flows/actions/set_run_result.go +++ b/flows/actions/set_run_result.go @@ -32,9 +32,9 @@ type SetRunResultAction struct { baseAction universalAction - Name string `json:"name" validate:"required,max=64"` + Name string `json:"name" validate:"required,result_name"` Value string `json:"value" engine:"evaluated"` - Category string `json:"category,omitempty" engine:"localized" validate:"max=36"` + Category string `json:"category,omitempty" engine:"localized" validate:"omitempty,result_category"` } // NewSetRunResult creates a new set run result action diff --git a/flows/actions/testdata/call_resthook.json b/flows/actions/testdata/call_resthook.json index 0f6ebc653..56f898055 100644 --- a/flows/actions/testdata/call_resthook.json +++ b/flows/actions/testdata/call_resthook.json @@ -5,9 +5,9 @@ "type": "call_resthook", "uuid": "ad154980-7bf7-4ab8-8728-545fd6378912", "resthook": "doesnt-exist", - "result_name": "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" + "result_name": "12345678901234567890123456789012345678901234567890123456789012345" }, - "read_error": "field 'result_name' must be less than or equal to 128" + "read_error": "field 'result_name' is not a valid result name" }, { "description": "NOOP if resthook doesn't exist", diff --git a/flows/actions/testdata/call_webhook.json b/flows/actions/testdata/call_webhook.json index e494397d7..56867d472 100644 --- a/flows/actions/testdata/call_webhook.json +++ b/flows/actions/testdata/call_webhook.json @@ -34,9 +34,9 @@ "headers": { "Accept:": "something" }, - "result_name": "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" + "result_name": "12345678901234567890123456789012345678901234567890123456789012345" }, - "read_error": "field 'result_name' must be less than or equal to 128" + "read_error": "field 'result_name' is not a valid result name" }, { "description": "Error events created if URL, header or body contain expression errors", diff --git a/flows/actions/testdata/open_ticket.json b/flows/actions/testdata/open_ticket.json index 801e1e9a4..a3fec0ead 100644 --- a/flows/actions/testdata/open_ticket.json +++ b/flows/actions/testdata/open_ticket.json @@ -10,9 +10,9 @@ }, "body": "Where are my cookies?", "assignee": null, - "result_name": "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" + "result_name": "12345678901234567890123456789012345678901234567890123456789012345" }, - "read_error": "field 'result_name' must be less than or equal to 128" + "read_error": "field 'result_name' is not a valid result name" }, { "description": "Error event for invalid topic reference", diff --git a/flows/actions/transfer_airtime.go b/flows/actions/transfer_airtime.go index df79796b4..3c8afba88 100644 --- a/flows/actions/transfer_airtime.go +++ b/flows/actions/transfer_airtime.go @@ -35,7 +35,7 @@ type TransferAirtimeAction struct { onlineAction Amounts map[string]decimal.Decimal `json:"amounts" validate:"required"` - ResultName string `json:"result_name" validate:"required,max=128"` + ResultName string `json:"result_name" validate:"required,result_name"` } // NewTransferAirtime creates a new airtime transfer action diff --git a/flows/results.go b/flows/results.go index 2ccf63b51..6b40285f7 100644 --- a/flows/results.go +++ b/flows/results.go @@ -3,19 +3,43 @@ package flows import ( "encoding/json" "fmt" + "regexp" "sort" "strings" "time" + "github.com/go-playground/validator/v10" "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/excellent/types" "github.com/nyaruka/goflow/utils" ) +func init() { + resultNameRegex := regexp.MustCompile(`^[a-zA-Z0-9\-_\s]{1,64}$`) + resultCategoryRegex := regexp.MustCompile(`^.{1,36}$`) + + utils.RegisterValidatorTag("result_name", + func(fl validator.FieldLevel) bool { + return resultNameRegex.MatchString(fl.Field().String()) + }, + func(validator.FieldError) string { + return "is not a valid result name" + }, + ) + utils.RegisterValidatorTag("result_category", + func(fl validator.FieldLevel) bool { + return resultCategoryRegex.MatchString(fl.Field().String()) + }, + func(validator.FieldError) string { + return "is not a valid result category" + }, + ) +} + // Result describes a value captured during a run's execution. It might have been implicitly created by a router, or explicitly // created by a [set_run_result](#action:set_run_result) action. type Result struct { - Name string `json:"name" validate:"required"` + Name string `json:"name" validate:"required,result_name"` Value string `json:"value"` Category string `json:"category,omitempty"` CategoryLocalized string `json:"category_localized,omitempty"` diff --git a/flows/routers/base.go b/flows/routers/base.go index 04878598d..c57d42a72 100644 --- a/flows/routers/base.go +++ b/flows/routers/base.go @@ -194,8 +194,8 @@ func (r *baseRouter) routeToCategory(run flows.Run, step flows.Step, categoryUUI type baseRouterEnvelope struct { Type string `json:"type" validate:"required"` Wait json.RawMessage `json:"wait,omitempty"` - ResultName string `json:"result_name,omitempty"` - Categories []json.RawMessage `json:"categories,omitempty" validate:"required,min=1"` + ResultName string `json:"result_name,omitempty" validate:"omitempty,result_name"` + Categories []json.RawMessage `json:"categories,omitempty" validate:"required,min=1,dive,result_category"` } // ReadRouter reads a router from the given JSON