diff --git a/model/slack_attachment.go b/model/slack_attachment.go index 273def8767..17abddeb06 100644 --- a/model/slack_attachment.go +++ b/model/slack_attachment.go @@ -31,9 +31,9 @@ type SlackAttachment struct { } type SlackAttachmentField struct { - Title string `json:"title"` - Value interface{} `json:"value"` - Short bool `json:"short"` + Title string `json:"title"` + Value interface{} `json:"value"` + Short SlackCompatibleBool `json:"short"` } func StringifySlackFieldValue(a []*SlackAttachment) []*SlackAttachment { diff --git a/model/slack_compatibility.go b/model/slack_compatibility.go new file mode 100644 index 0000000000..25539063d9 --- /dev/null +++ b/model/slack_compatibility.go @@ -0,0 +1,30 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "fmt" + "strings" +) + +// SlackCompatibleBool is an alias for bool that implements json.Unmarshaler +type SlackCompatibleBool bool + +// UnmarshalJSON implements json.Unmarshaler +// +// Slack allows bool values to be represented as strings ("true"/"false") or +// literals (true/false). To maintain compatibility, we define an Unmarshaler +// that supports both. +func (b *SlackCompatibleBool) UnmarshalJSON(data []byte) error { + value := strings.ToLower(string(data)) + if value == "true" || value == `"true"` { + *b = true + } else if value == "false" || value == `"false"` { + *b = false + } else { + return fmt.Errorf("unmarshal: unable to convert %s to bool", data) + } + + return nil +} diff --git a/model/slack_compatibility_test.go b/model/slack_compatibility_test.go new file mode 100644 index 0000000000..c920ea0ed2 --- /dev/null +++ b/model/slack_compatibility_test.go @@ -0,0 +1,57 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSlackCompatibleBool_UnmarshalJSON_True(t *testing.T) { + cases := []struct { + name string + payload string + }{ + {name: "literal", payload: `{"title": "Foo", "value": "Bar", "short": true}`}, + {name: "stringLower", payload: `{"title": "Foo", "value": "Bar", "short": "true"}`}, + {name: "stringMixed", payload: `{"title": "Foo", "value": "Bar", "short": "True"}`}, + {name: "stringUpper", payload: `{"title": "Foo", "value": "Bar", "short": "TRUE"}`}, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + field := &SlackAttachmentField{} + + err := json.Unmarshal([]byte(tt.payload), field) + + require.NoError(t, err) + require.True(t, bool(field.Short)) + }) + } +} + +func TestSlackCompatibleBool_UnmarshalJSON_False(t *testing.T) { + cases := []struct { + name string + payload string + }{ + {name: "literal", payload: `{"title": "Foo", "value": "Bar", "short": false}`}, + {name: "stringLower", payload: `{"title": "Foo", "value": "Bar", "short": "false"}`}, + {name: "stringMixed", payload: `{"title": "Foo", "value": "Bar", "short": "False"}`}, + {name: "stringUpper", payload: `{"title": "Foo", "value": "Bar", "short": "FALSE"}`}, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + field := &SlackAttachmentField{} + + err := json.Unmarshal([]byte(tt.payload), field) + + require.NoError(t, err) + require.False(t, bool(field.Short)) + }) + } +}