diff --git a/config/params.go b/config/params.go index f3eb220..e7fdcec 100644 --- a/config/params.go +++ b/config/params.go @@ -18,6 +18,7 @@ var ( ErrParamNotStringSlice = errors.New("not a string slice") ErrParamNotByteSlice = errors.New("not a byte slice") ErrParamNotIntSlice = errors.New("not an int slice") + ErrParamNotObjectSlice = errors.New("not an object slice") ) func (p Params) GetString(key string) (string, error) { @@ -143,3 +144,30 @@ func (p Params) GetByteSlice(key string) ([]byte, error) { return byteSlice, nil } + +func (p Params) GetObjectSlice(key string) ([]map[string]interface{}, error) { + value, ok := p[key] + if !ok { + return nil, ErrParamNotFound + } + + alreadyObjectSlice, ok := value.([]map[string]interface{}) + if ok { + return alreadyObjectSlice, nil + } + + interfaceSlice, ok := value.([]any) + if !ok { + return nil, ErrParamNotSlice + } + + objectSlice := make([]map[string]interface{}, len(interfaceSlice)) + for i, v := range interfaceSlice { + obj, ok := v.(map[string]interface{}) + if !ok { + return nil, ErrParamNotObjectSlice + } + objectSlice[i] = obj + } + return objectSlice, nil +} diff --git a/internal/processor/osc-message-create.go b/internal/processor/osc-message-create.go index 3e2b51c..1972b40 100644 --- a/internal/processor/osc-message-create.go +++ b/internal/processor/osc-message-create.go @@ -32,14 +32,23 @@ func init() { Description: "arguments for the OSC message", Type: "array", Items: &jsonschema.Schema{ - Type: "string", + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "value": { + Title: "Value", + Description: "Value of the argument", + Type: "string", + }, + "type": { + Title: "Type", + Description: "OSC type of the argument", + Type: "string", + }, + }, + Required: []string{"value", "type"}, + AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}}, }, }, - "types": { - Title: "Argument Types", - Description: "string of OSC types corresponding to the arguments in args", - Type: "string", - }, }, Required: []string{"address"}, AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}}, @@ -57,7 +66,7 @@ func init() { return nil, err } - argStrings, err := params.GetStringSlice("args") + argObjects, err := params.GetObjectSlice("args") if err != nil { if errors.Is(err, config.ErrParamNotFound) { return &MessageCreate{config: processorConfig, Address: addressTemplate}, nil @@ -66,27 +75,39 @@ func init() { } } - typesString, err := params.GetString("types") - if err != nil { - return nil, fmt.Errorf("osc.message.create types error: %w", err) - } - - if len(argStrings) != len(typesString) { - return nil, errors.New("osc.message.create args and types must be the same length") + types := []string{} + for _, argObject := range argObjects { + argType, ok := argObject["type"] + if !ok { + return nil, errors.New("osc.message.create arg type error: not found") + } + argTypeStr, ok := argType.(string) + if !ok { + return nil, errors.New("osc.message.create arg type error: not a string") + } + types = append(types, argTypeStr) } argTemplates := []*template.Template{} - for _, argString := range argStrings { + for _, argObject := range argObjects { + argValue, ok := argObject["value"] + if !ok { + return nil, errors.New("osc.message.create arg value error: not found") + } + argValueStr, ok := argValue.(string) + if !ok { + return nil, errors.New("osc.message.create arg value error: not a string") + } - argTemplate, err := template.New("arg").Parse(argString) + argTemplate, err := template.New("arg").Parse(argValueStr) if err != nil { return nil, err } argTemplates = append(argTemplates, argTemplate) } - return &MessageCreate{config: processorConfig, Address: addressTemplate, Args: argTemplates, Types: typesString}, nil + return &MessageCreate{config: processorConfig, Address: addressTemplate, Args: argTemplates, Types: types}, nil }, }) } @@ -95,7 +116,7 @@ type MessageCreate struct { config config.ProcessorConfig Address *template.Template Args []*template.Template - Types string + Types []string } func (omc *MessageCreate) Process(ctx context.Context, wrappedPayload common.WrappedPayload) (common.WrappedPayload, error) { @@ -165,15 +186,15 @@ func (omc *MessageCreate) Type() string { return omc.config.Type } -func argToTypedArg(rawArg string, oscType byte) (osc.Arg, error) { +func argToTypedArg(rawArg string, oscType string) (osc.Arg, error) { switch oscType { - case 's': + case "s": return osc.Arg{ Value: rawArg, Type: "s", }, nil - case 'i': + case "i": number, err := strconv.ParseInt(rawArg, 10, 32) if err != nil { return osc.Arg{}, err @@ -182,7 +203,7 @@ func argToTypedArg(rawArg string, oscType byte) (osc.Arg, error) { Value: int32(number), Type: "i", }, nil - case 'f': + case "f": number, err := strconv.ParseFloat(rawArg, 32) if err != nil { return osc.Arg{}, err @@ -191,7 +212,7 @@ func argToTypedArg(rawArg string, oscType byte) (osc.Arg, error) { Value: float32(number), Type: "f", }, nil - case 'b': + case "b": data, err := hex.DecodeString(rawArg) if err != nil { return osc.Arg{}, err @@ -200,7 +221,7 @@ func argToTypedArg(rawArg string, oscType byte) (osc.Arg, error) { Value: data, Type: "b", }, nil - case 'h': + case "h": number, err := strconv.ParseInt(rawArg, 10, 64) if err != nil { return osc.Arg{}, err @@ -209,7 +230,7 @@ func argToTypedArg(rawArg string, oscType byte) (osc.Arg, error) { Value: int64(number), Type: "h", }, nil - case 'd': + case "d": number, err := strconv.ParseFloat(rawArg, 64) if err != nil { return osc.Arg{}, err @@ -218,22 +239,22 @@ func argToTypedArg(rawArg string, oscType byte) (osc.Arg, error) { Value: float64(number), Type: "d", }, nil - case 'T': + case "T": return osc.Arg{ Value: true, Type: "T", }, nil - case 'F': + case "F": return osc.Arg{ Value: false, Type: "F", }, nil - case 'N': + case "N": return osc.Arg{ Value: nil, Type: "N", }, nil default: - return osc.Arg{}, fmt.Errorf("osc.message.create unhandled osc type: %c", oscType) + return osc.Arg{}, fmt.Errorf("osc.message.create unhandled osc type: %s", oscType) } } diff --git a/internal/processor/test/osc-message-create_test.go b/internal/processor/test/osc-message-create_test.go index bd13b5e..f90578e 100644 --- a/internal/processor/test/osc-message-create_test.go +++ b/internal/processor/test/osc-message-create_test.go @@ -64,8 +64,7 @@ func TestGoodOSCMessageCreate(t *testing.T) { name: "address with template and string arg", params: map[string]any{ "address": "/test/{{.Payload.Value}}", - "args": []any{"arg1"}, - "types": "s", + "args": []map[string]any{{"value": "arg1", "type": "s"}}, }, payload: map[string]any{"Value": "value"}, expected: &osc.Message{Address: "/test/value", Args: []osc.Arg{{Value: "arg1", Type: "s"}}}, @@ -74,8 +73,11 @@ func TestGoodOSCMessageCreate(t *testing.T) { name: "address with template and mixed args", params: map[string]any{ "address": "/test/{{.Payload.Value}}", - "args": []any{"arg1", "42", "3.14"}, - "types": "sif", + "args": []map[string]any{ + {"value": "arg1", "type": "s"}, + {"value": "42", "type": "i"}, + {"value": "3.14", "type": "f"}, + }, }, payload: map[string]any{"Value": "value"}, expected: &osc.Message{ @@ -91,8 +93,7 @@ func TestGoodOSCMessageCreate(t *testing.T) { name: "address with template and int64 arg", params: map[string]any{ "address": "/test/{{.Payload.Value}}", - "args": []any{"42"}, - "types": "h", + "args": []map[string]any{{"value": "42", "type": "h"}}, }, payload: map[string]any{"Value": "value"}, expected: &osc.Message{Address: "/test/value", Args: []osc.Arg{{Value: int64(42), Type: "h"}}}, @@ -101,8 +102,7 @@ func TestGoodOSCMessageCreate(t *testing.T) { name: "address with template and double arg", params: map[string]any{ "address": "/test/{{.Payload.Value}}", - "args": []any{"42"}, - "types": "d", + "args": []map[string]any{{"value": "42", "type": "d"}}, }, payload: map[string]any{"Value": "value"}, expected: &osc.Message{Address: "/test/value", Args: []osc.Arg{{Value: float64(42), Type: "d"}}}, @@ -111,8 +111,7 @@ func TestGoodOSCMessageCreate(t *testing.T) { name: "address with template and true arg", params: map[string]any{ "address": "/test/{{.Payload.Value}}", - "args": []any{""}, - "types": "T", + "args": []map[string]any{{"value": "", "type": "T"}}, }, payload: map[string]any{"Value": "value"}, expected: &osc.Message{Address: "/test/value", Args: []osc.Arg{{Value: true, Type: "T"}}}, @@ -121,8 +120,7 @@ func TestGoodOSCMessageCreate(t *testing.T) { name: "address with template and false arg", params: map[string]any{ "address": "/test/{{.Payload.Value}}", - "args": []any{""}, - "types": "F", + "args": []map[string]any{{"value": "", "type": "F"}}, }, payload: map[string]any{"Value": "value"}, expected: &osc.Message{Address: "/test/value", Args: []osc.Arg{{Value: false, Type: "F"}}}, @@ -131,8 +129,7 @@ func TestGoodOSCMessageCreate(t *testing.T) { name: "address with template and nil arg", params: map[string]any{ "address": "/test/{{.Payload.Value}}", - "args": []any{""}, - "types": "N", + "args": []map[string]any{{"value": "", "type": "N"}}, }, payload: map[string]any{"Value": "value"}, expected: &osc.Message{Address: "/test/value", Args: []osc.Arg{{Value: nil, Type: "N"}}}, @@ -141,8 +138,7 @@ func TestGoodOSCMessageCreate(t *testing.T) { name: "blob arg", params: map[string]any{ "address": "/test", - "args": []any{"deadbeef"}, - "types": "b", + "args": []map[string]any{{"value": "deadbeef", "type": "b"}}, }, payload: "", expected: &osc.Message{Address: "/test", Args: []osc.Arg{{Value: []byte{0xde, 0xad, 0xbe, 0xef}, Type: "b"}}}, @@ -224,66 +220,51 @@ func TestBadOSCMessageCreate(t *testing.T) { params: map[string]any{ "address": "/test", "args": "not an array", - "types": "s", }, payload: "test", errorString: "osc.message.create args error: not a slice", }, { - name: "args without types parameter", + name: "args not an object array", params: map[string]any{ "address": "/test", "args": []any{"arg1"}, }, payload: "test", - errorString: "osc.message.create types error: not found", + errorString: "osc.message.create args error: not an object slice", }, { - name: "args and types length mismatch", + name: "arg value not a string", params: map[string]any{ "address": "/test", - "args": []any{"arg1", "arg2"}, - "types": "s", + "args": []map[string]any{{"value": 123, "type": "s"}}, }, payload: "test", - errorString: "osc.message.create args and types must be the same length", - }, - { - name: "non-string arg", - params: map[string]any{ - "address": "/test", - "args": []any{"arg1", 123}, - "types": "ss", - }, - payload: "test", - errorString: "osc.message.create args error: not a string slice", + errorString: "osc.message.create arg value error: not a string", }, { name: "bad arg template", params: map[string]any{ "address": "/test", - "args": []any{"{{"}, - "types": "s", + "args": []map[string]any{{"value": "{{", "type": "s"}}, }, payload: "test", errorString: "template: arg:1: unclosed action", }, { - name: "non-string types parameter", + name: "non-string type parameter", params: map[string]any{ "address": "/test", - "args": []any{"arg1"}, - "types": 123, + "args": []map[string]any{{"value": "arg1", "type": 123}}, }, payload: "test", - errorString: "osc.message.create types error: not a string", + errorString: "osc.message.create arg type error: not a string", }, { name: "invalid type in types parameter", params: map[string]any{ "address": "/test", - "args": []any{"arg1"}, - "types": "x", + "args": []map[string]any{{"value": "arg1", "type": "x"}}, }, payload: "test", errorString: "osc.message.create unhandled osc type: x", @@ -313,11 +294,10 @@ func TestBadOSCMessageCreate(t *testing.T) { errorString: "osc.message.create address must start with '/'", }, { - name: "address template with missing field", + name: "arg template with missing field", params: map[string]any{ "address": "/test", - "args": []any{"{{.missing}}"}, - "types": "s", + "args": []map[string]any{{"value": "{{.missing}}", "type": "s"}}, }, payload: "test", errorString: "template: arg:1:2: executing \"arg\" at <.missing>: can't evaluate field missing in type common.WrappedPayload", @@ -326,8 +306,7 @@ func TestBadOSCMessageCreate(t *testing.T) { name: "wrong arg type for int arg", params: map[string]any{ "address": "/test", - "args": []any{"{{.Payload}}"}, - "types": "i", + "args": []map[string]any{{"value": "{{.Payload}}", "type": "i"}}, }, payload: "test", errorString: "strconv.ParseInt: parsing \"test\": invalid syntax", @@ -336,8 +315,7 @@ func TestBadOSCMessageCreate(t *testing.T) { name: "wrong arg type for float arg", params: map[string]any{ "address": "/test", - "args": []any{"{{.Payload}}"}, - "types": "f", + "args": []map[string]any{{"value": "{{.Payload}}", "type": "f"}}, }, payload: "test", errorString: "strconv.ParseFloat: parsing \"test\": invalid syntax", @@ -346,8 +324,7 @@ func TestBadOSCMessageCreate(t *testing.T) { name: "wrong arg type for blob arg", params: map[string]any{ "address": "/test", - "args": []any{"{{.Payload}}"}, - "types": "b", + "args": []map[string]any{{"value": "{{.Payload}}", "type": "b"}}, }, payload: "test", errorString: "encoding/hex: invalid byte: U+0074 't'", @@ -356,8 +333,7 @@ func TestBadOSCMessageCreate(t *testing.T) { name: "wrong arg type for int64 arg", params: map[string]any{ "address": "/test", - "args": []any{"{{.Payload}}"}, - "types": "h", + "args": []map[string]any{{"value": "{{.Payload}}", "type": "h"}}, }, payload: "test", errorString: "strconv.ParseInt: parsing \"test\": invalid syntax", @@ -366,8 +342,7 @@ func TestBadOSCMessageCreate(t *testing.T) { name: "wrong arg type for double arg", params: map[string]any{ "address": "/test", - "args": []any{"{{.Payload}}"}, - "types": "d", + "args": []map[string]any{{"value": "{{.Payload}}", "type": "d"}}, }, payload: "test", errorString: "strconv.ParseFloat: parsing \"test\": invalid syntax", @@ -416,8 +391,7 @@ func BenchmarkOSCMessageCreate(b *testing.B) { Type: "osc.message.create", Params: map[string]any{ "address": "/hello", - "args": []any{"{{.Payload}}"}, - "types": "i", + "args": []map[string]any{{"value": "{{.Payload}}", "type": "i"}}, }, })