From a2efed0ee28cb3658223eee29c9722841b6aca7e Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sun, 1 Mar 2026 21:47:37 -0600 Subject: [PATCH] add processor to pull method out of struct --- internal/processor/struct-method-get.go | 65 +++++++ .../processor/test/struct-method-get_test.go | 176 ++++++++++++++++++ schema/processors.schema.json | 20 ++ 3 files changed, 261 insertions(+) create mode 100644 internal/processor/struct-method-get.go create mode 100644 internal/processor/test/struct-method-get_test.go diff --git a/internal/processor/struct-method-get.go b/internal/processor/struct-method-get.go new file mode 100644 index 0000000..f6df5a8 --- /dev/null +++ b/internal/processor/struct-method-get.go @@ -0,0 +1,65 @@ +package processor + +import ( + "context" + "errors" + "fmt" + "reflect" + + "github.com/jwetzell/showbridge-go/internal/config" +) + +type StructMethodGet struct { + config config.ProcessorConfig + Name string +} + +func (sm *StructMethodGet) Process(ctx context.Context, payload any) (any, error) { + s := reflect.ValueOf(payload) + + if s.Kind() != reflect.Struct { + return nil, errors.New("struct.method.get processor only accepts a struct payload") + } + + method := s.MethodByName(sm.Name) + if !method.IsValid() { + return nil, fmt.Errorf("struct.method.get method '%s' does not exist", sm.Name) + } + + value := method.Call(nil) + + if len(value) == 0 { + return nil, nil + } + + if len(value) == 1 { + return value[0].Interface(), nil + } + + results := make([]any, len(value)) + + for i, v := range value { + results[i] = v.Interface() + } + + return results, nil +} + +func (sm *StructMethodGet) Type() string { + return sm.config.Type +} + +func init() { + RegisterProcessor(ProcessorRegistration{ + Type: "struct.method.get", + New: func(config config.ProcessorConfig) (Processor, error) { + params := config.Params + nameString, err := params.GetString("name") + if err != nil { + return nil, fmt.Errorf("struct.method.get name error: %w", err) + } + + return &StructMethodGet{config: config, Name: nameString}, nil + }, + }) +} diff --git a/internal/processor/test/struct-method-get_test.go b/internal/processor/test/struct-method-get_test.go new file mode 100644 index 0000000..d5fc9a9 --- /dev/null +++ b/internal/processor/test/struct-method-get_test.go @@ -0,0 +1,176 @@ +package processor_test + +import ( + "testing" + + "github.com/jwetzell/showbridge-go/internal/config" + "github.com/jwetzell/showbridge-go/internal/processor" +) + +func TestStructMethodGetFromRegistry(t *testing.T) { + registration, ok := processor.ProcessorRegistry["struct.method.get"] + if !ok { + t.Fatalf("struct.method.get processor not registered") + } + + processorInstance, err := registration.New(config.ProcessorConfig{ + Type: "struct.method.get", + Params: map[string]any{ + "name": "GetData", + }, + }) + if err != nil { + t.Fatalf("failed to create struct.method.get processor: %s", err) + } + + if processorInstance.Type() != "struct.method.get" { + t.Fatalf("struct.method.get processor has wrong type: %s", processorInstance.Type()) + } + + payload := TestStruct{Data: "hello"} + expected := "hello" + + got, err := processorInstance.Process(t.Context(), payload) + if err != nil { + t.Fatalf("struct.method.get processing failed: %s", err) + } + + if got != expected { + t.Fatalf("struct.method.get got %+v, expected %+v", got, expected) + } +} + +func TestGoodStructMethodGet(t *testing.T) { + + tests := []struct { + name string + params map[string]any + payload any + expected any + }{ + { + name: "string field", + params: map[string]any{"name": "GetString"}, + payload: TestStruct{String: "hello"}, + expected: "hello", + }, + { + name: "int field", + params: map[string]any{"name": "GetInt"}, + payload: TestStruct{Int: 42}, + expected: 42, + }, + { + name: "float field", + params: map[string]any{"name": "GetFloat"}, + payload: TestStruct{Float: 3.14}, + expected: 3.14, + }, + { + name: "bool field", + params: map[string]any{"name": "GetBool"}, + payload: TestStruct{Bool: true}, + expected: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + registration, ok := processor.ProcessorRegistry["struct.method.get"] + if !ok { + t.Fatalf("struct.method.get processor not registered") + } + + processorInstance, err := registration.New(config.ProcessorConfig{ + Type: "struct.method.get", + Params: test.params, + }) + + if err != nil { + t.Fatalf("struct.method.get failed to create processor: %s", err) + } + + got, err := processorInstance.Process(t.Context(), test.payload) + + if err != nil { + t.Fatalf("struct.method.get failed: %s", err) + } + + if got != test.expected { + t.Fatalf("struct.method.get got %s, expected %s", got, test.expected) + } + }) + } +} + +func TestBadStructMethodGet(t *testing.T) { + tests := []struct { + name string + params map[string]any + payload any + errorString string + }{ + { + name: "no name param", + payload: TestStruct{Data: "hello"}, + params: map[string]any{}, + errorString: "struct.method.get name error: not found", + }, + { + name: "non string name", + payload: TestStruct{Data: "hello"}, + params: map[string]any{ + "name": 1, + }, + errorString: "struct.method.get name error: not a string", + }, + { + name: "missing method", + payload: TestStruct{String: "hello"}, + params: map[string]any{ + "name": "NonExistentMethod", + }, + errorString: "struct.method.get method 'NonExistentMethod' does not exist", + }, + { + name: "not a struct payload", + payload: "not a struct", + params: map[string]any{ + "name": "NonExistentMethod", + }, + errorString: "struct.method.get processor only accepts a struct payload", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + + registration, ok := processor.ProcessorRegistry["struct.method.get"] + if !ok { + t.Fatalf("struct.method.get processor not registered") + } + + processorInstance, err := registration.New(config.ProcessorConfig{ + Type: "struct.method.get", + Params: test.params, + }) + + if err != nil { + if test.errorString != err.Error() { + t.Fatalf("struct.method.get got error '%s', expected '%s'", err.Error(), test.errorString) + } + return + } + + got, err := processorInstance.Process(t.Context(), test.payload) + + if err == nil { + t.Fatalf("struct.method.get expected to fail but got payload: %s", got) + } + + if err.Error() != test.errorString { + t.Fatalf("struct.method.get got error '%s', expected '%s'", err.Error(), test.errorString) + } + }) + } +} diff --git a/schema/processors.schema.json b/schema/processors.schema.json index 7b83892..43194f2 100644 --- a/schema/processors.schema.json +++ b/schema/processors.schema.json @@ -761,6 +761,26 @@ "required": ["type", "params"], "additionalProperties": false }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "struct.method.get" + }, + "params": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": ["name"] + } + }, + "required": ["type", "params"], + "additionalProperties": false + }, { "type": "object", "properties": {