Repository: slack-go/slack
Pull request #1155 (2023-01-04 10:18:28)

Add support for Rich Text Lists
This PR adds support for rich text lists.
For example:
```json
{
    "token": "...",
    "team_id": "...",
    "api_app_id": "...",
    "event": {
        "client_msg_id": "...",
        "type": "message",
        "text": "test message with list\n1. first point\n2. second point\n• first bullet\n• second bullet\n",
        "user": "...",
        "ts": "1672827783.092039",
        "blocks": [
            {
                "type": "rich_text",
                "block_id": "...",
                "elements": [
                    {
                        "type": "rich_text_section",
                        "elements": [
                            {
                                "type": "text",
                                "text": "test message with list\n"
                            }
                        ]
                    },
                    {
                        "type": "rich_text_list",
                        "elements": [
                            {
                                "type": "rich_text_section",
                                "elements": [
                                    {
                                        "type": "text",
                                        "text": "first point"
                                    }
                                ]
                            },
                            {
                                "type": "rich_text_section",
                                "elements": [
                                    {
                                        "type": "text",
                                        "text": "second point"
                                    }
                                ]
                            }
                        ],
                        "style": "ordered",
                        "indent": 0,
                        "border": 0
                    },
                    {
                        "type": "rich_text_list",
                        "elements": [
                            {
                                "type": "rich_text_section",
                                "elements": [
                                    {
                                        "type": "text",
                                        "text": "first bullet"
                                    }
                                ]
                            },
                            {
                                "type": "rich_text_section",
                                "elements": [
                                    {
                                        "type": "text",
                                        "text": "second bullet"
                                    }
                                ]
                            }
                        ],
                        "style": "bullet",
                        "indent": 0,
                        "border": 0
                    },
                    {
                        "type": "rich_text_section",
                        "elements": []
                    }
                ]
            }
        ],
        "team": "...",
        "channel": "...",
        "event_ts": "1672827783.092039",
        "channel_type": "im"
    },
    "type": "event_callback",
    "event_id": "...",
    "event_time": 1672827783,
    "authorizations": [
        {
            "enterprise_id": null,
            "team_id": "...",
            "user_id": "...",
            "is_bot": true,
            "is_enterprise_install": false
        }
    ],
    "is_ext_shared_channel": false,
    "event_context": "..."
}
```

Reference: https://api.slack.com/changelog/2019-09-what-they-see-is-what-you-get-and-more-and-less

PierekEast: This is great. I produced almost identical code and then realised that there is an existing PR already. Good job! 🚀 

Patch:
diff --git a/block_rich_text.go b/block_rich_text.go
index 01e5cdb..aea1597 100644
--- a/block_rich_text.go
+++ b/block_rich_text.go
@@ -40,6 +40,9 @@ func (e *RichTextBlock) UnmarshalJSON(b []byte) error {
 		switch s.Type {
 		case RTESection:
 			elem = &RichTextSection{}
+		case RTEList:
+			elem = &RichTextList{}
+
 		default:
 			elems = append(elems, &RichTextUnknown{
 				Type: s.Type,
@@ -92,12 +95,88 @@ func (u RichTextUnknown) RichTextElementType() RichTextElementType {
 	return u.Type
 }
 
+type RichTextListElementType string
+
+const (
+	RTEListOrdered RichTextListElementType = "ordered"
+	RTEListBullet  RichTextListElementType = "bullet"
+)
+
+type RichTextList struct {
+	Type     RichTextElementType     `json:"type"`
+	Elements []RichTextElement       `json:"elements"`
+	Style    RichTextListElementType `json:"style"`
+	Indent   int                     `json:"indent"`
+}
+
+// NewRichTextList returns a new rich text list element.
+func NewRichTextList(style RichTextListElementType, indent int, elements ...RichTextElement) *RichTextList {
+	return &RichTextList{
+		Type:     RTEList,
+		Elements: elements,
+		Style:    style,
+		Indent:   indent,
+	}
+}
+
+// ElementType returns the type of the Element
+func (s RichTextList) RichTextElementType() RichTextElementType {
+	return s.Type
+}
+
+func (e *RichTextList) UnmarshalJSON(b []byte) error {
+	var raw struct {
+		RawElements []json.RawMessage       `json:"elements"`
+		Style       RichTextListElementType `json:"style"`
+		Indent      int                     `json:"indent"`
+	}
+	if string(b) == "{}" {
+		return nil
+	}
+	if err := json.Unmarshal(b, &raw); err != nil {
+		return err
+	}
+	elems := make([]RichTextElement, 0, len(raw.RawElements))
+	for _, r := range raw.RawElements {
+		var s struct {
+			Type RichTextElementType `json:"type"`
+		}
+		if err := json.Unmarshal(r, &s); err != nil {
+			return err
+		}
+		var elem RichTextElement
+		switch s.Type {
+		case RTESection:
+			elem = &RichTextSection{}
+		case RTEList:
+			elem = &RichTextList{}
+		default:
+			elems = append(elems, &RichTextUnknown{
+				Type: s.Type,
+				Raw:  string(r),
+			})
+			continue
+		}
+		if err := json.Unmarshal(r, elem); err != nil {
+			return err
+		}
+		elems = append(elems, elem)
+	}
+	*e = RichTextList{
+		Type:     RTEList,
+		Elements: elems,
+		Style:    raw.Style,
+		Indent:   raw.Indent,
+	}
+	return nil
+}
+
 type RichTextSection struct {
 	Type     RichTextElementType      `json:"type"`
 	Elements []RichTextSectionElement `json:"elements"`
 }
 
-// ElementType returns the type of the Element
+// RichTextElementType returns the type of the Element
 func (s RichTextSection) RichTextElementType() RichTextElementType {
 	return s.Type
 }


Test patch:
diff --git a/block_rich_text_test.go b/block_rich_text_test.go
index 270dde9..2788c97 100644
--- a/block_rich_text_test.go
+++ b/block_rich_text_test.go
@@ -36,11 +36,12 @@ func TestRichTextBlock_UnmarshalJSON(t *testing.T) {
 		err      error
 	}{
 		{
-			[]byte(`{"elements":[{"type":"rich_text_unknown"},{"type":"rich_text_section"}]}`),
+			[]byte(`{"elements":[{"type":"rich_text_unknown"},{"type":"rich_text_section"},{"type":"rich_text_list"}]}`),
 			RichTextBlock{
 				Elements: []RichTextElement{
 					&RichTextUnknown{Type: RTEUnknown, Raw: `{"type":"rich_text_unknown"}`},
 					&RichTextSection{Type: RTESection, Elements: []RichTextSectionElement{}},
+					&RichTextList{Type: RTEList, Elements: []RichTextElement{}},
 				},
 			},
 			nil,
@@ -117,3 +118,84 @@ func TestRichTextSection_UnmarshalJSON(t *testing.T) {
 		}
 	}
 }
+
+func TestRichTextList_UnmarshalJSON(t *testing.T) {
+	cases := []struct {
+		raw      []byte
+		expected RichTextList
+		err      error
+	}{
+		{
+			[]byte(`{"style":"ordered","elements":[{"type":"rich_text_unknown","value":10},{"type":"rich_text_section","elements":[{"type":"text","text":"hi"}]}]}`),
+			RichTextList{
+				Type:  RTEList,
+				Style: RTEListOrdered,
+				Elements: []RichTextElement{
+					&RichTextUnknown{Type: RTEUnknown, Raw: `{"type":"rich_text_unknown","value":10}`},
+					&RichTextSection{
+						Type: RTESection,
+						Elements: []RichTextSectionElement{
+							&RichTextSectionTextElement{Type: RTSEText, Text: "hi"},
+						},
+					},
+				},
+			},
+			nil,
+		},
+		{
+			[]byte(`{"style":"ordered","elements":[{"type":"rich_text_list","style":"bullet","elements":[{"type":"rich_text_section","elements":[{"type":"text","text":"hi"}]}]}]}`),
+			RichTextList{
+				Type:  RTEList,
+				Style: RTEListOrdered,
+				Elements: []RichTextElement{
+					&RichTextList{
+						Type:  RTEList,
+						Style: RTEListBullet,
+						Elements: []RichTextElement{
+							&RichTextSection{
+								Type: RTESection,
+								Elements: []RichTextSectionElement{
+									&RichTextSectionTextElement{Type: RTSEText, Text: "hi"},
+								},
+							},
+						},
+					},
+				},
+			},
+			nil,
+		},
+		{
+			[]byte(`{"type": "rich_text_list","elements":[]}`),
+			RichTextList{
+				Type:     RTEList,
+				Elements: []RichTextElement{},
+			},
+			nil,
+		},
+		{
+			[]byte(`{"type": "rich_text_list","elements":[],"indent":2}`),
+			RichTextList{
+				Type:     RTEList,
+				Indent:   2,
+				Elements: []RichTextElement{},
+			},
+			nil,
+		},
+	}
+	for _, tc := range cases {
+		var actual RichTextList
+		err := json.Unmarshal(tc.raw, &actual)
+		if err != nil {
+			if tc.err == nil {
+				t.Errorf("unexpected error: %s", err)
+			}
+			t.Errorf("expected error is %s, but got %s", tc.err, err)
+		}
+		if tc.err != nil {
+			t.Errorf("expected to raise an error %s", tc.err)
+		}
+		if diff := deep.Equal(actual, tc.expected); diff != nil {
+			t.Errorf("actual value does not match expected one\n%s", diff)
+		}
+	}
+}