diff --git a/workflows.go b/workflows.go index 26c98d1..45d92af 100644 --- a/workflows.go +++ b/workflows.go @@ -62,7 +62,6 @@ const ( type WorkflowTimerUnit string const ( - WorkflowTimerUnitSeconds WorkflowTimerUnit = "s" WorkflowTimerUnitMinutes WorkflowTimerUnit = "m" WorkflowTimerUnitHours WorkflowTimerUnit = "h" WorkflowTimerUnitDays WorkflowTimerUnit = "d" @@ -197,9 +196,9 @@ type SimplifiedContactPropertyTriggerWorkflowNode struct { // SimplifiedAddToListTriggerWorkflowNode is the AddToListTrigger variant of // [SimplifiedWorkflowNode]. type SimplifiedAddToListTriggerWorkflowNode struct { - NextNodeIDs []string `json:"nextNodeIds"` - MailingList string `json:"mailingList,omitempty"` - ReEligible *bool `json:"reEligible,omitempty"` + NextNodeIDs []string `json:"nextNodeIds"` + MailingListID *string `json:"mailingListId"` + ReEligible *bool `json:"reEligible,omitempty"` } // SimplifiedBlankTriggerWorkflowNode is the BlankTrigger variant of @@ -439,10 +438,11 @@ type ContactPropertyTriggerWorkflowNode struct { // AddToListTriggerWorkflowNode is the AddToListTrigger variant of // [WorkflowNode]. type AddToListTriggerWorkflowNode struct { - ID string `json:"id"` - WorkflowID string `json:"workflowId"` - NextNodeIDs []string `json:"nextNodeIds"` - ReEligible bool `json:"reEligible"` + ID string `json:"id"` + WorkflowID string `json:"workflowId"` + NextNodeIDs []string `json:"nextNodeIds"` + MailingListID *string `json:"mailingListId"` + ReEligible bool `json:"reEligible"` } // BlankTriggerWorkflowNode is the BlankTrigger variant of [WorkflowNode]. @@ -471,14 +471,13 @@ type TimerActionWorkflowNode struct { Unit WorkflowTimerUnit `json:"unit"` } -// SendEmailActionWorkflowNode is the SendEmailAction variant of -// [WorkflowNode]. The full variant does not include emailMessageId (only the -// simplified variant does). +// SendEmailActionWorkflowNode is the SendEmailAction variant of [WorkflowNode]. type SendEmailActionWorkflowNode struct { - ID string `json:"id"` - WorkflowID string `json:"workflowId"` - NextNodeIDs []string `json:"nextNodeIds"` - Subject string `json:"subject,omitempty"` + ID string `json:"id"` + WorkflowID string `json:"workflowId"` + NextNodeIDs []string `json:"nextNodeIds"` + EmailMessageID string `json:"emailMessageId"` + Subject string `json:"subject"` } // ExitActionWorkflowNode is the ExitAction variant of [WorkflowNode]. @@ -649,13 +648,16 @@ func marshalDiscriminated(typeName string, inner any) ([]byte, error) { if typeName == "" { return nil, fmt.Errorf("workflow node: typeName is empty") } - if inner == nil { - return nil, fmt.Errorf("workflow node: %s variant is nil", typeName) - } raw, err := json.Marshal(inner) if err != nil { return nil, err } + // A typed-nil variant pointer is a non-nil interface but marshals to + // "null"; treat that (and an untyped nil) as a missing variant instead of + // panicking on the nil map below. + if string(raw) == "null" { + return nil, fmt.Errorf("workflow node: %s variant is nil", typeName) + } var fields map[string]json.RawMessage if err := json.Unmarshal(raw, &fields); err != nil { return nil, err diff --git a/workflows_test.go b/workflows_test.go index d88b6f7..819f7d3 100644 --- a/workflows_test.go +++ b/workflows_test.go @@ -1230,3 +1230,82 @@ func TestCreatedWorkflowNode_MarshalKeepsRevisionAndChildren(t *testing.T) { t.Errorf("marshal dropped createdChildNodes: %s", raw) } } + +func TestGetWorkflowNode_AddToListTrigger(t *testing.T) { + body := `{ + "id": "node_a", + "workflowId": "wf_1", + "typeName": "AddToListTrigger", + "nextNodeIds": ["node_next"], + "mailingListId": "ml_1", + "reEligible": true + }` + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(body)) + })) + defer server.Close() + + client := NewClient("test-key", WithBaseURL(server.URL)) + node, err := client.GetWorkflowNode("wf_1", "node_a") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if node.AddToListTrigger == nil { + t.Fatal("AddToListTrigger is nil") + } + if node.AddToListTrigger.MailingListID == nil || *node.AddToListTrigger.MailingListID != "ml_1" { + t.Errorf("MailingListID = %v, want ml_1", node.AddToListTrigger.MailingListID) + } +} + +func TestGetWorkflowNode_SendEmailAction(t *testing.T) { + body := `{ + "id": "node_s", + "workflowId": "wf_1", + "typeName": "SendEmailAction", + "nextNodeIds": ["node_next"], + "emailMessageId": "em_1", + "subject": "Welcome" + }` + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(body)) + })) + defer server.Close() + + client := NewClient("test-key", WithBaseURL(server.URL)) + node, err := client.GetWorkflowNode("wf_1", "node_s") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if node.SendEmailAction == nil { + t.Fatal("SendEmailAction is nil") + } + if node.SendEmailAction.EmailMessageID != "em_1" { + t.Errorf("EmailMessageID = %q, want em_1", node.SendEmailAction.EmailMessageID) + } + if node.SendEmailAction.Subject != "Welcome" { + t.Errorf("Subject = %q, want Welcome", node.SendEmailAction.Subject) + } +} + +func TestSimplifiedAddToListNode_DecodesMailingListID(t *testing.T) { + const in = `{"typeName":"AddToListTrigger","nextNodeIds":[],"mailingListId":"ml_1","reEligible":true}` + var n SimplifiedWorkflowNode + if err := json.Unmarshal([]byte(in), &n); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if n.AddToListTrigger == nil { + t.Fatal("AddToListTrigger is nil") + } + if n.AddToListTrigger.MailingListID == nil || *n.AddToListTrigger.MailingListID != "ml_1" { + t.Errorf("MailingListID = %v, want ml_1", n.AddToListTrigger.MailingListID) + } +} + +func TestMarshalDiscriminated_TypedNilVariantErrors(t *testing.T) { + // A node with the discriminator set but the variant pointer nil must return + // an error, not panic on a nil-map write. + if _, err := json.Marshal(WorkflowNode{TypeName: WorkflowNodeTypeTimerAction}); err == nil { + t.Fatal("expected error marshaling a node with a nil variant, got none") + } +}