Skip to content

Commit 26394d8

Browse files
authored
Accept JSON data types as action parameters (#191)
* Fixed ordering of generated PDDL domain action params from being affected by non-deterministic map key iteration. * Added action params validation when prepping an orchestration. * Action params can include arrays and objects. * The CLI can accept objects and arrays as action params.
1 parent 4a1f710 commit 26394d8

13 files changed

+1040
-41
lines changed

cli/cmd/verify.go

+41-8
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ execute the required project services`,
7070
return fmt.Errorf("unknown webhook for project %s", projectName)
7171
}
7272

73-
actionParams, err := convertToActionParams(data)
73+
actionParams, err := parseActionParamsJSON(data)
7474
if err != nil {
7575
return err
7676
}
@@ -120,7 +120,8 @@ execute the required project services`,
120120
},
121121
}
122122

123-
cmd.Flags().StringSliceVarP(&data, "data", "d", []string{}, "Data to supplement action in format param:value")
123+
//cmd.Flags().StringSliceVarP(&data, "data", "d", []string{}, "Data to supplement action in format param:value")
124+
cmd.Flags().StringArrayVarP(&data, "data", "d", []string{}, "Data to supplement action in format param:value or param:json")
124125
cmd.Flags().StringVarP(&webhookUrl, "webhook", "w", "", `Webhook url to accept results
125126
(defaults to first configured webhook)`)
126127
cmd.Flags().StringVarP(&timeout, "timeout", "t", "", `Set execution timeout duration per service/agent
@@ -133,18 +134,50 @@ execute the required project services`,
133134
return cmd
134135
}
135136

136-
func convertToActionParams(params []string) ([]map[string]string, error) {
137-
var actionParams []map[string]string
137+
func parseActionParamsJSON(params []string) ([]map[string]interface{}, error) {
138+
var actionParams []map[string]interface{}
139+
138140
for _, param := range params {
139141
parts := strings.SplitN(param, ":", 2)
140142
if len(parts) != 2 {
141143
return nil, fmt.Errorf("invalid parameter format: %s (should be name:value)", param)
142144
}
143-
actionParams = append(actionParams, map[string]string{
144-
"field": parts[0],
145-
"value": parts[1],
146-
})
145+
146+
field := parts[0]
147+
valueStr := parts[1]
148+
149+
// Check for array-like format with commas but no brackets (easier CLI input)
150+
if strings.Contains(valueStr, ",") && !strings.HasPrefix(valueStr, "[") {
151+
// Split by comma and create a string array
152+
values := strings.Split(valueStr, ",")
153+
// Trim spaces
154+
for i, v := range values {
155+
values[i] = strings.TrimSpace(v)
156+
}
157+
actionParams = append(actionParams, map[string]interface{}{
158+
"field": field,
159+
"value": values, // This will be serialized as a JSON array
160+
})
161+
} else if (strings.HasPrefix(valueStr, "[") && strings.HasSuffix(valueStr, "]")) ||
162+
(strings.HasPrefix(valueStr, "{") && strings.HasSuffix(valueStr, "}")) {
163+
// Standard JSON parsing
164+
var jsonValue interface{}
165+
if err := json.Unmarshal([]byte(valueStr), &jsonValue); err != nil {
166+
return nil, fmt.Errorf("invalid JSON for parameter %s: %w", field, err)
167+
}
168+
actionParams = append(actionParams, map[string]interface{}{
169+
"field": field,
170+
"value": jsonValue,
171+
})
172+
} else {
173+
// Regular string value
174+
actionParams = append(actionParams, map[string]interface{}{
175+
"field": field,
176+
"value": valueStr,
177+
})
178+
}
147179
}
180+
148181
return actionParams, nil
149182
}
150183

cli/internal/api/api.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ type OrchestrationRequest struct {
5151
Action struct {
5252
Content string
5353
} `json:"action"`
54-
Data []map[string]string `json:"data"`
55-
Webhook string `json:"webhook"`
56-
Timeout string `json:"timeout,omitempty"`
57-
HealthCheckGracePeriod string `json:"healthCheckGracePeriod,omitempty"`
54+
Data []map[string]interface{} `json:"data"`
55+
Webhook string `json:"webhook"`
56+
Timeout string `json:"timeout,omitempty"`
57+
HealthCheckGracePeriod string `json:"healthCheckGracePeriod,omitempty"`
5858
}
5959

6060
type Status string

planengine/actionvalid.go

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
7+
package main
8+
9+
import (
10+
"fmt"
11+
"reflect"
12+
)
13+
14+
// ValidateActionParams validates that all parameters in ActionParams contain only
15+
// acceptable JSON types and returns an error if invalid types are found
16+
func ValidateActionParams(params ActionParams) error {
17+
for _, param := range params {
18+
if err := ValidateActionParamValue(param.Field, param.Value); err != nil {
19+
return err
20+
}
21+
}
22+
return nil
23+
}
24+
25+
// ValidateActionParamValue validates that a value contains only acceptable JSON types:
26+
// - Primitives (string, number, boolean, null)
27+
// - Arrays of any valid JSON values (including nested arrays)
28+
// - Objects (maps) with any valid JSON values
29+
func ValidateActionParamValue(field string, value interface{}) error {
30+
if value == nil {
31+
return nil
32+
}
33+
34+
switch v := value.(type) {
35+
case string, float64, int, int64, float32, bool, uint, uint64, uint32:
36+
// Basic primitives are valid
37+
return nil
38+
39+
case []interface{}:
40+
// Check each element in the array
41+
for i, element := range v {
42+
if err := ValidateActionParamValue(fmt.Sprintf("%s[%d]", field, i), element); err != nil {
43+
return fmt.Errorf("invalid array element at %s: %w", field, err)
44+
}
45+
}
46+
return nil
47+
48+
case map[string]interface{}:
49+
// Objects (maps) are now valid
50+
// Check each value in the object
51+
for key, element := range v {
52+
if err := ValidateActionParamValue(fmt.Sprintf("%s.%s", field, key), element); err != nil {
53+
return fmt.Errorf("invalid object property at %s.%s: %w", field, key, err)
54+
}
55+
}
56+
return nil
57+
58+
default:
59+
// Check for array types and object types using reflection
60+
val := reflect.ValueOf(value)
61+
kind := val.Kind()
62+
63+
// Handle arrays/slices
64+
if kind == reflect.Slice || kind == reflect.Array {
65+
// For empty arrays, we can't determine element type, so assume it's valid
66+
if val.Len() == 0 {
67+
return nil
68+
}
69+
70+
// Recursively check each element in the array
71+
for i := 0; i < val.Len(); i++ {
72+
elemVal := val.Index(i).Interface()
73+
if err := ValidateActionParamValue(fmt.Sprintf("%s[%d]", field, i), elemVal); err != nil {
74+
return err
75+
}
76+
}
77+
return nil
78+
}
79+
80+
// Handle maps/objects
81+
if kind == reflect.Map {
82+
// For empty maps, assume it's valid
83+
if val.Len() == 0 {
84+
return nil
85+
}
86+
87+
// Iterate through map keys and validate each value
88+
for _, key := range val.MapKeys() {
89+
elemVal := val.MapIndex(key).Interface()
90+
keyStr := fmt.Sprintf("%v", key.Interface())
91+
if err := ValidateActionParamValue(fmt.Sprintf("%s.%s", field, keyStr), elemVal); err != nil {
92+
return err
93+
}
94+
}
95+
return nil
96+
}
97+
98+
// If we can't recognize the type as valid, reject it
99+
return fmt.Errorf("field '%s' has unsupported type %T", field, value)
100+
}
101+
}

0 commit comments

Comments
 (0)