diff --git a/block_rich_text.go b/block_rich_text.go index aea15974..b6a4b4ce 100644 --- a/block_rich_text.go +++ b/block_rich_text.go @@ -42,7 +42,10 @@ func (e *RichTextBlock) UnmarshalJSON(b []byte) error { elem = &RichTextSection{} case RTEList: elem = &RichTextList{} - + case RTEQuote: + elem = &RichTextQuote{} + case RTEPreformatted: + elem = &RichTextPreformatted{} default: elems = append(elems, &RichTextUnknown{ Type: s.Type, @@ -150,6 +153,10 @@ func (e *RichTextList) UnmarshalJSON(b []byte) error { elem = &RichTextSection{} case RTEList: elem = &RichTextList{} + case RTEQuote: + elem = &RichTextQuote{} + case RTEPreformatted: + elem = &RichTextPreformatted{} default: elems = append(elems, &RichTextUnknown{ Type: s.Type, @@ -460,3 +467,62 @@ type RichTextSectionUnknownElement struct { func (r RichTextSectionUnknownElement) RichTextSectionElementType() RichTextSectionElementType { return r.Type } + +// RichTextQuote represents rich_text_quote element type. +type RichTextQuote RichTextSection + +// RichTextElementType returns the type of the Element +func (s *RichTextQuote) RichTextElementType() RichTextElementType { + return s.Type +} + +func (s *RichTextQuote) UnmarshalJSON(b []byte) error { + // reusing the RichTextSection struct, as it's the same as RichTextQuote. + var rts RichTextSection + if err := json.Unmarshal(b, &rts); err != nil { + return err + } + *s = RichTextQuote(rts) + s.Type = RTEQuote + return nil +} + +// RichTextPreformatted represents rich_text_quote element type. +type RichTextPreformatted struct { + RichTextSection + Border int `json:"border"` +} + +// RichTextElementType returns the type of the Element +func (s *RichTextPreformatted) RichTextElementType() RichTextElementType { + return s.Type +} + +func (s *RichTextPreformatted) UnmarshalJSON(b []byte) error { + var rts RichTextSection + if err := json.Unmarshal(b, &rts); err != nil { + return err + } + // we define standalone fields because we need to unmarshal the border + // field. We can not directly unmarshal the data into + // RichTextPreformatted because it will cause an infinite loop. We also + // can not define a struct with embedded RichTextSection and Border fields + // because the json package will not unmarshal the data into the + // standalone fields, once it sees UnmarshalJSON method on the embedded + // struct. The drawback is that we have to process the data twice, and + // have to define a standalone struct with the same set of fields as the + // original struct, which may become a maintenance burden (i.e. update the + // fields in two places, should it ever change). + var standalone struct { + Border int `json:"border"` + } + if err := json.Unmarshal(b, &standalone); err != nil { + return err + } + *s = RichTextPreformatted{ + RichTextSection: rts, + Border: standalone.Border, + } + s.Type = RTEPreformatted + return nil +} diff --git a/block_rich_text_test.go b/block_rich_text_test.go index 2788c973..a9f04b7d 100644 --- a/block_rich_text_test.go +++ b/block_rich_text_test.go @@ -27,6 +27,60 @@ const ( } ] }` + + richTextQuotePayload = `{ + "type": "rich_text", + "block_id": "G7G", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "Holy moly\n\n" + } + ] + }, + { + "type": "rich_text_preformatted", + "elements": [ + { + "type": "text", + "text": "Preformatted\n\n" + } + ], + "border": 2 + }, + { + "type": "rich_text_quote", + "elements": [ + { + "type": "text", + "text": "Quote\n\n" + } + ] + }, + { + "type": "rich_text_quote", + "elements": [ + { + "type": "text", + "text": "Another quote" + } + ] + }, + { + "type": "rich_text_preformatted", + "elements": [ + { + "type": "text", + "text": "Another preformatted\n\n" + } + ], + "border": 42 + } + ] + }` ) func TestRichTextBlock_UnmarshalJSON(t *testing.T) { @@ -55,6 +109,38 @@ func TestRichTextBlock_UnmarshalJSON(t *testing.T) { }, nil, }, + { + []byte(dummyPayload), + RichTextBlock{ + Type: MBTRichText, + BlockID: "FaYCD", + Elements: []RichTextElement{ + &RichTextSection{ + Type: RTESection, + Elements: []RichTextSectionElement{ + &RichTextSectionChannelElement{Type: RTSEChannel, ChannelID: "C012345678"}, + &RichTextSectionTextElement{Type: RTSEText, Text: "dummy_text"}, + }, + }, + }, + }, + nil, + }, + { + []byte(richTextQuotePayload), + RichTextBlock{ + Type: MBTRichText, + BlockID: "G7G", + Elements: []RichTextElement{ + &RichTextSection{Type: RTESection, Elements: []RichTextSectionElement{&RichTextSectionTextElement{Type: RTSEText, Text: "Holy moly\n\n"}}}, + &RichTextPreformatted{RichTextSection: RichTextSection{Type: RTEPreformatted, Elements: []RichTextSectionElement{&RichTextSectionTextElement{Type: RTSEText, Text: "Preformatted\n\n"}}}, Border: 2}, + &RichTextQuote{Type: RTEQuote, Elements: []RichTextSectionElement{&RichTextSectionTextElement{Type: RTSEText, Text: "Quote\n\n"}}}, + &RichTextQuote{Type: RTEQuote, Elements: []RichTextSectionElement{&RichTextSectionTextElement{Type: RTSEText, Text: "Another quote"}}}, + &RichTextPreformatted{RichTextSection: RichTextSection{Type: RTEPreformatted, Elements: []RichTextSectionElement{&RichTextSectionTextElement{Type: RTSEText, Text: "Another preformatted\n\n"}}}, Border: 42}, + }, + }, + nil, + }, } for _, tc := range cases { var actual RichTextBlock @@ -63,10 +149,10 @@ func TestRichTextBlock_UnmarshalJSON(t *testing.T) { if tc.err == nil { t.Errorf("unexpected error: %s", err) } - t.Errorf("expected error is %s, but got %s", tc.err, err) + t.Errorf("expected error is %v, but got %v", tc.err, err) } if tc.err != nil { - t.Errorf("expected to raise an error %s", tc.err) + t.Errorf("expected to raise an error %v", tc.err) } if diff := deep.Equal(actual, tc.expected); diff != nil { t.Errorf("actual value does not match expected one\n%s", diff) @@ -199,3 +285,79 @@ func TestRichTextList_UnmarshalJSON(t *testing.T) { } } } + +func TestRichTextQuote_Marshal(t *testing.T) { + t.Run("rich_text_section", func(t *testing.T) { + const rawRSE = "{\"type\":\"rich_text_section\",\"elements\":[{\"type\":\"text\",\"text\":\"Some Text\"}]}" + + var got RichTextSection + if err := json.Unmarshal([]byte(rawRSE), &got); err != nil { + t.Fatal(err) + } + want := RichTextSection{ + Type: RTESection, + Elements: []RichTextSectionElement{ + &RichTextSectionTextElement{Type: RTSEText, Text: "Some Text"}, + }, + } + + if diff := deep.Equal(got, want); diff != nil { + t.Errorf("actual value does not match expected one\n%s", diff) + } + b, err := json.Marshal(got) + if err != nil { + t.Fatal(err) + } + if diff := deep.Equal(string(b), rawRSE); diff != nil { + t.Errorf("actual value does not match expected one\n%s", diff) + } + }) + t.Run("rich_text_quote", func(t *testing.T) { + const rawRTS = "{\"type\":\"rich_text_quote\",\"elements\":[{\"type\":\"text\",\"text\":\"Some text\"}]}" + + var got RichTextQuote + if err := json.Unmarshal([]byte(rawRTS), &got); err != nil { + t.Fatal(err) + } + want := RichTextQuote{ + Type: RTEQuote, + Elements: []RichTextSectionElement{ + &RichTextSectionTextElement{Type: RTSEText, Text: "Some text"}, + }, + } + if diff := deep.Equal(got, want); diff != nil { + t.Errorf("actual value does not match expected one\n%s", diff) + } + b, err := json.Marshal(got) + if err != nil { + t.Fatal(err) + } + if diff := deep.Equal(string(b), rawRTS); diff != nil { + t.Errorf("actual value does not match expected one\n%s", diff) + } + }) + t.Run("rich_text_preformatted", func(t *testing.T) { + const rawRTP = "{\"type\":\"rich_text_preformatted\",\"elements\":[{\"type\":\"text\",\"text\":\"Some other text\"}],\"border\":2}" + want := RichTextPreformatted{ + RichTextSection: RichTextSection{ + Type: RTEPreformatted, + Elements: []RichTextSectionElement{&RichTextSectionTextElement{Type: RTSEText, Text: "Some other text"}}, + }, + Border: 2, + } + var got RichTextPreformatted + if err := json.Unmarshal([]byte(rawRTP), &got); err != nil { + t.Fatal(err) + } + if diff := deep.Equal(got, want); diff != nil { + t.Errorf("actual value does not match expected one\n%s", diff) + } + b, err := json.Marshal(got) + if err != nil { + t.Fatal(err) + } + if diff := deep.Equal(string(b), rawRTP); diff != nil { + t.Errorf("actual value does not match expected one\n%s", diff) + } + }) +}