Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions temporalcli/commands.activity.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (c *TemporalActivityCompleteCommand) run(cctx *CommandContext, args []strin
}
defer cl.Close()

metadata := map[string][]byte{"encoding": []byte("json/plain")}
metadata := map[string][][]byte{"encoding": {[]byte("json/plain")}}
resultPayloads, err := CreatePayloads([][]byte{[]byte(c.Result)}, metadata, false)
if err != nil {
return err
Expand Down Expand Up @@ -67,7 +67,7 @@ func (c *TemporalActivityFailCommand) run(cctx *CommandContext, args []string) e

var detailPayloads *common.Payloads
if len(c.Detail) > 0 {
metadata := map[string][]byte{"encoding": []byte("json/plain")}
metadata := map[string][][]byte{"encoding": {[]byte("json/plain")}}
detailPayloads, err = CreatePayloads([][]byte{[]byte(c.Detail)}, metadata, false)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion temporalcli/commands.gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ type PayloadInputOptions struct {
func (v *PayloadInputOptions) buildFlags(cctx *CommandContext, f *pflag.FlagSet) {
f.StringArrayVarP(&v.Input, "input", "i", nil, "Input value. Use JSON content or set --input-meta to override. Can't be combined with --input-file. Can be passed multiple times to pass multiple arguments.")
f.StringArrayVar(&v.InputFile, "input-file", nil, "A path or paths for input file(s). Use JSON content or set --input-meta to override. Can't be combined with --input. Can be passed multiple times to pass multiple arguments.")
f.StringArrayVar(&v.InputMeta, "input-meta", nil, "Input payload metadata as a `KEY=VALUE` pair. When the KEY is \"encoding\", this overrides the default (\"json/plain\"). Can be passed multiple times.")
f.StringArrayVar(&v.InputMeta, "input-meta", nil, "Input payload metadata as a `KEY=VALUE` pair. When the KEY is \"encoding\", this overrides the default (\"json/plain\"). Can be passed multiple times. Repeated metadata keys are applied to the corresponding inputs in the provided order.")
f.BoolVar(&v.InputBase64, "input-base64", false, "Assume inputs are base64-encoded and attempt to decode them.")
}

Expand Down
14 changes: 12 additions & 2 deletions temporalcli/commands.workflow_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -623,13 +623,23 @@ func (p *PayloadInputOptions) buildRawInputPayloads() (*common.Payloads, error)
}

// Build metadata
metadata := map[string][]byte{"encoding": []byte("json/plain")}
metadata := map[string][][]byte{}
for _, meta := range p.InputMeta {
metaPieces := strings.SplitN(meta, "=", 2)
if len(metaPieces) != 2 {
return nil, fmt.Errorf("metadata %v expected to have '='", meta)
}
metadata[metaPieces[0]] = []byte(metaPieces[1])
if vals, ok := metadata[metaPieces[0]]; ok {
if len(vals) == len(inData) {
return nil, fmt.Errorf("received more --input-meta flags for key %q than number of inputs", metaPieces[0])
}
metadata[metaPieces[0]] = append(vals, []byte(metaPieces[1]))
} else {
metadata[metaPieces[0]] = [][]byte{[]byte(metaPieces[1])}
}
Comment on lines +632 to +639
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if vals, ok := metadata[metaPieces[0]]; ok {
if len(vals) == len(inData) {
return nil, fmt.Errorf("received more --input-meta flags for key %q than number of inputs", metaPieces[0])
}
metadata[metaPieces[0]] = append(vals, []byte(metaPieces[1]))
} else {
metadata[metaPieces[0]] = [][]byte{[]byte(metaPieces[1])}
}
metadata[metaPieces[0]] = append(metadata[metaPieces[0]], []byte(metaPieces[1]))
if len(metadata[metaPieces[0]]) > len(inData) {
return nil, fmt.Errorf("received more --input-meta flags for key %q than number of inputs", metaPieces[0])
}

Pedantic, but for a map of slices, it's a bit more idiomatic Go to append to the nil result of the absent key rather than have conditionals (can use intermediate vars if you are concerned with the accesses)

}
if _, ok := metadata["encoding"]; !ok {
metadata["encoding"] = [][]byte{[]byte("json/plain")}
}
return CreatePayloads(inData, metadata, p.InputBase64)
}
Expand Down
40 changes: 40 additions & 0 deletions temporalcli/commands.workflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,46 @@ func (s *SharedServerSuite) TestWorkflow_Signal_SingleWorkflowSuccess() {
s.Equal(map[string]any{"foo": "bar"}, actual)
}

func (s *SharedServerSuite) TestWorkflow_Signal_MultipleInputsWithComplexMetadata() {
// Start a random workflow
run, err := s.Client.ExecuteWorkflow(
s.Context,
client.StartWorkflowOptions{TaskQueue: s.Worker().Options.TaskQueue},
DevWorkflow,
"ignored",
)
s.NoError(err)

// Send signal
res := s.Execute(
"workflow", "signal",
"--address", s.Address(),
"-w", run.GetID(),
"--name", "my-signal",
"-i", `{"foo": "bar"}`,
"-i", `{"bar": "baz"}`,
"--input-meta", "encoding=json/proto",
"--input-meta", "messageType=foo",
"--input-meta", "messageType=bar",
)
s.NoError(res.Err)
s.NoError(s.Client.TerminateWorkflow(s.Context, run.GetID(), "", ""))
iter := s.Client.GetWorkflowHistory(s.Context, run.GetID(), "", false, enums.HISTORY_EVENT_FILTER_TYPE_ALL_EVENT)
for iter.HasNext() {
ev, err := iter.Next()
s.NoError(err)
if attr := ev.GetWorkflowExecutionSignaledEventAttributes(); attr != nil {
payloads := attr.GetInput().GetPayloads()
s.Equal("json/proto", string(payloads[0].Metadata["encoding"]))
s.Equal("json/proto", string(payloads[1].Metadata["encoding"]))
s.Equal("foo", string(payloads[0].Metadata["messageType"]))
s.Equal("bar", string(payloads[1].Metadata["messageType"]))
}
return
}
s.Fail("No signal event found in workflow history")
}

func (s *SharedServerSuite) TestWorkflow_Signal_BatchWorkflowSuccess() {
res := s.testSignalBatchWorkflow(false)
s.Contains(res.Stdout.String(), "approximately 5 workflow(s)")
Expand Down
1 change: 1 addition & 0 deletions temporalcli/commandsgen/commands.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4448,6 +4448,7 @@ option-sets:
Input payload metadata as a `KEY=VALUE` pair.
When the KEY is "encoding", this overrides the default ("json/plain").
Can be passed multiple times.
Repeated metadata keys are applied to the corresponding inputs in the provided order.
- name: input-base64
type: bool
description: |
Expand Down
23 changes: 18 additions & 5 deletions temporalcli/payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,25 @@ import (
"go.temporal.io/api/common/v1"
)

func CreatePayloads(data [][]byte, metadata map[string][]byte, isBase64 bool) (*common.Payloads, error) {
// CreatePayloads creates API Payload objects from given data and metadata slices.
// If metadata has an entry at a data index, it is used, otherwise it uses the metadata entry at index 0.
func CreatePayloads(data [][]byte, metadata map[string][][]byte, isBase64 bool) (*common.Payloads, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little confusing following this code. May need some godoc on this method explaining this map[string][][]byte data structure. As it looks today, I'd call this expecting 1:1 with data, but that's not the case. Rather it seems that the first array value is applied unless there is one for the data index. So if I have 3 pieces of data and my metadata has 2 values for "encoding", say "foo" and "bar", the first data item and the third data item get "foo" and only the middle one gets "bar".

Not saying this is wrong necessarily, just confusing and worth documenting somewhere clearly so it is clear that the logic below is by intention and what callers should expect. Maybe just toss a "If metadata has an entry at a data index, it is used, otherwise it uses the metadata entry at index 0" above the function would be enough.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like a comment was made as a result of this comment (it's a bit hard to know without an update to this thread).

ret := &common.Payloads{Payloads: make([]*common.Payload, len(data))}
for i, in := range data {
// If it's JSON, validate it
if strings.HasPrefix(string(metadata["encoding"]), "json/") && !json.Valid(in) {
return nil, fmt.Errorf("input #%v is not valid JSON", i+1)
var metadataForIndex = make(map[string][]byte, len(metadata))
for k, vals := range metadata {
if len(vals) == 0 {
continue
}
v := vals[0]
if len(vals) > i {
v = vals[i]
}
// If it's JSON, validate it
if k == "encoding" && strings.HasPrefix(string(v), "json/") && !json.Valid(in) {
return nil, fmt.Errorf("input #%v is not valid JSON", i+1)
}
metadataForIndex[k] = v
}
// Decode base64 if base64'd (std encoding only for now)
if isBase64 {
Expand All @@ -23,7 +36,7 @@ func CreatePayloads(data [][]byte, metadata map[string][]byte, isBase64 bool) (*
return nil, fmt.Errorf("input #%v is not valid base64", i+1)
}
}
ret.Payloads[i] = &common.Payload{Data: in, Metadata: metadata}
ret.Payloads[i] = &common.Payload{Data: in, Metadata: metadataForIndex}
}
return ret, nil
}
Loading