diff --git a/api/richmessage_test.go b/api/richmessage_test.go new file mode 100644 index 0000000..3041236 --- /dev/null +++ b/api/richmessage_test.go @@ -0,0 +1,210 @@ +package api + +import ( + "testing" + + json "github.com/goccy/go-json" + "github.com/stretchr/testify/require" +) + +// TestUpdate_RichMessage_Decodes is a regression test for the Bot API v10 +// rich_message decode failure: RichBlock (21 variants) and RichText (25 +// variants) had no knownDiscriminators entry, so genapi never emitted +// UnmarshalRichBlock / UnmarshalRichText and any update whose message +// carried rich_message failed to decode — in production this rejected +// roughly 8.6% of Telegram updates with HTTP 400. Uses goccy/go-json, the +// codec production runs; plain encoding/json failed the same way. +func TestUpdate_RichMessage_Decodes(t *testing.T) { + body := `{"update_id":1,"message":{"message_id":7,"date":1789000000,"chat":{"id":-100123,"type":"supergroup"},` + + `"rich_message":{"blocks":[{"type":"paragraph","text":{"type":"bold","text":{"type":"anchor","name":"hi"}}}]}}}` + + var u Update + require.NoError(t, json.Unmarshal([]byte(body), &u)) + + require.NotNil(t, u.Message) + require.NotNil(t, u.Message.RichMessage) + require.Len(t, u.Message.RichMessage.Blocks, 1) + + para, ok := u.Message.RichMessage.Blocks[0].(*RichBlockParagraph) + require.True(t, ok, "blocks[0] must dispatch to *RichBlockParagraph, got %T", u.Message.RichMessage.Blocks[0]) + + bold, ok := para.Text.(*RichTextBold) + require.True(t, ok, "paragraph.text must dispatch to *RichTextBold, got %T", para.Text) + + anchor, ok := bold.Text.(*RichTextAnchor) + require.True(t, ok, "bold.text must dispatch to *RichTextAnchor, got %T", bold.Text) + require.Equal(t, "hi", anchor.Name) +} + +// TestUnmarshalRichBlock_UnknownType confirms an unrecognised discriminator +// value surfaces a clear error instead of silently decoding a zero-valued +// interface. +func TestUnmarshalRichBlock_UnknownType(t *testing.T) { + _, err := UnmarshalRichBlock([]byte(`{"type":"not_a_real_block"}`)) + require.Error(t, err) + require.ErrorContains(t, err, "unknown type") +} + +// TestUpdate_RichMessage_UnknownBlockType confirms the unknown-type error +// from UnmarshalRichBlock propagates all the way up through +// RichMessage.UnmarshalJSON to the top-level Update decode. +func TestUpdate_RichMessage_UnknownBlockType(t *testing.T) { + body := `{"update_id":1,"message":{"message_id":7,"date":1789000000,"chat":{"id":-100123,"type":"supergroup"},` + + `"rich_message":{"blocks":[{"type":"not_a_real_block"}]}}}` + + var u Update + err := json.Unmarshal([]byte(body), &u) + require.Error(t, err) + require.ErrorContains(t, err, "unknown type") +} + +// TestUpdate_RichMessage_PlainStringText is a regression test for the Bot +// API v10 RichText decode gap this task fixes. Telegram declares RichText +// as decodable from "either a String for plain text, an Array of +// RichText, or any of the following [object] types" — but the IR (and +// therefore UnmarshalRichText) only ever recorded the object variants, so +// a paragraph whose text is a bare JSON string failed with "invalid +// character '\"' looking for beginning of value". A plain-text paragraph +// is the most common real rich_message payload, so this was the +// production-facing half of the bug. +func TestUpdate_RichMessage_PlainStringText(t *testing.T) { + body := `{"update_id":1,"message":{"message_id":7,"date":1789000000,"chat":{"id":-100123,"type":"supergroup"},` + + `"rich_message":{"blocks":[{"type":"paragraph","text":"hello world"}]}}}` + + var u Update + require.NoError(t, json.Unmarshal([]byte(body), &u)) + + require.NotNil(t, u.Message) + require.NotNil(t, u.Message.RichMessage) + require.Len(t, u.Message.RichMessage.Blocks, 1) + + para, ok := u.Message.RichMessage.Blocks[0].(*RichBlockParagraph) + require.True(t, ok, "blocks[0] must dispatch to *RichBlockParagraph, got %T", u.Message.RichMessage.Blocks[0]) + + plain, ok := para.Text.(*RichTextPlain) + require.True(t, ok, "paragraph.text must dispatch to *RichTextPlain for a bare JSON string, got %T", para.Text) + require.Equal(t, "hello world", plain.Text) +} + +// TestUpdate_RichMessage_ArrayText confirms Telegram's second declared +// RichText alternate — a bare JSON array of RichText — decodes into +// RichTextSequence, with each element dispatched recursively through +// UnmarshalRichText (an object variant in this case). +func TestUpdate_RichMessage_ArrayText(t *testing.T) { + body := `{"update_id":1,"message":{"message_id":7,"date":1789000000,"chat":{"id":-100123,"type":"supergroup"},` + + `"rich_message":{"blocks":[{"type":"paragraph","text":[` + + `{"type":"bold","text":"x"},{"type":"italic","text":"y"}` + + `]}]}}}` + + var u Update + require.NoError(t, json.Unmarshal([]byte(body), &u)) + + para, ok := u.Message.RichMessage.Blocks[0].(*RichBlockParagraph) + require.True(t, ok) + + seq, ok := para.Text.(*RichTextSequence) + require.True(t, ok, "paragraph.text must dispatch to *RichTextSequence for a JSON array, got %T", para.Text) + require.Len(t, seq.Items, 2) + + bold, ok := seq.Items[0].(*RichTextBold) + require.True(t, ok, "sequence[0] must be *RichTextBold, got %T", seq.Items[0]) + boldText, ok := bold.Text.(*RichTextPlain) + require.True(t, ok) + require.Equal(t, "x", boldText.Text) + + italic, ok := seq.Items[1].(*RichTextItalic) + require.True(t, ok, "sequence[1] must be *RichTextItalic, got %T", seq.Items[1]) + italicText, ok := italic.Text.(*RichTextPlain) + require.True(t, ok) + require.Equal(t, "y", italicText.Text) +} + +// TestUpdate_RichMessage_ArrayText_MixedElements confirms an array whose +// elements mix the bare-string shape and the object shape dispatches +// each element independently — the array alternate and the string +// alternate compose, they aren't mutually exclusive per element. +func TestUpdate_RichMessage_ArrayText_MixedElements(t *testing.T) { + body := `{"update_id":1,"message":{"message_id":7,"date":1789000000,"chat":{"id":-100123,"type":"supergroup"},` + + `"rich_message":{"blocks":[{"type":"paragraph","text":[` + + `"plain segment",{"type":"bold","text":"bold segment"}` + + `]}]}}}` + + var u Update + require.NoError(t, json.Unmarshal([]byte(body), &u)) + + para, ok := u.Message.RichMessage.Blocks[0].(*RichBlockParagraph) + require.True(t, ok) + + seq, ok := para.Text.(*RichTextSequence) + require.True(t, ok) + require.Len(t, seq.Items, 2) + + plain, ok := seq.Items[0].(*RichTextPlain) + require.True(t, ok, "sequence[0] must be *RichTextPlain, got %T", seq.Items[0]) + require.Equal(t, "plain segment", plain.Text) + + bold, ok := seq.Items[1].(*RichTextBold) + require.True(t, ok, "sequence[1] must be *RichTextBold, got %T", seq.Items[1]) + boldText, ok := bold.Text.(*RichTextPlain) + require.True(t, ok) + require.Equal(t, "bold segment", boldText.Text) +} + +// TestRichTextPlain_MarshalJSON_RoundTrip confirms the plain-text +// alternate marshals back to a bare JSON string (not an object), and +// that string decodes straight back through UnmarshalRichText. +func TestRichTextPlain_MarshalJSON_RoundTrip(t *testing.T) { + v := &RichTextPlain{Text: "hello"} + + b, err := json.Marshal(v) + require.NoError(t, err) + require.JSONEq(t, `"hello"`, string(b)) + + decoded, err := UnmarshalRichText(b) + require.NoError(t, err) + got, ok := decoded.(*RichTextPlain) + require.True(t, ok) + require.Equal(t, "hello", got.Text) +} + +// TestRichTextSequence_MarshalJSON_RoundTrip confirms the array +// alternate marshals back to a bare JSON array (not an object wrapping +// one), preserving each element's own wire shape, and round-trips +// through UnmarshalRichText. +func TestRichTextSequence_MarshalJSON_RoundTrip(t *testing.T) { + v := &RichTextSequence{ + Items: []RichText{ + &RichTextPlain{Text: "a"}, + &RichTextBold{Text: &RichTextPlain{Text: "b"}}, + }, + } + + b, err := json.Marshal(v) + require.NoError(t, err) + require.JSONEq(t, `["a",{"type":"bold","text":"b"}]`, string(b)) + + decoded, err := UnmarshalRichText(b) + require.NoError(t, err) + seq, ok := decoded.(*RichTextSequence) + require.True(t, ok) + require.Len(t, seq.Items, 2) + + p0, ok := seq.Items[0].(*RichTextPlain) + require.True(t, ok) + require.Equal(t, "a", p0.Text) + + b1, ok := seq.Items[1].(*RichTextBold) + require.True(t, ok) + p1, ok := b1.Text.(*RichTextPlain) + require.True(t, ok) + require.Equal(t, "b", p1.Text) +} + +// TestUnmarshalRichText_UnrecognisedShape confirms a JSON value that is +// neither a string, an array, nor an object surfaces a clear error +// instead of silently producing a zero-valued interface. +func TestUnmarshalRichText_UnrecognisedShape(t *testing.T) { + _, err := UnmarshalRichText([]byte(`42`)) + require.Error(t, err) + require.ErrorContains(t, err, "unrecognised JSON value") +} diff --git a/api/types.gen.go b/api/types.gen.go index cd34008..570ff05 100644 --- a/api/types.gen.go +++ b/api/types.gen.go @@ -7,14 +7,16 @@ package api import ( + "bytes" "fmt" "github.com/goccy/go-json" "io" ) -var _ = io.Discard // keep import even if no fields use io -var _ = json.Marshal // keep import for UnmarshalXxx helpers -var _ = fmt.Errorf // keep import for UnmarshalXxx helpers +var _ = io.Discard // keep import even if no fields use io +var _ = json.Marshal // keep import for UnmarshalXxx helpers +var _ = fmt.Errorf // keep import for UnmarshalXxx helpers +var _ = bytes.TrimLeft // keep import for shape-dispatching UnmarshalXxx helpers (unions with alternates) // This object represents an incoming update.At most one of the optional fields can be present in any given update. type Update struct { @@ -1394,6 +1396,42 @@ func (*InputMediaVenue) isInputPollMedia() {} // isInputPollMedia is the marker method that makes InputMediaVideo implement InputPollMedia. func (*InputMediaVideo) isInputPollMedia() {} +// UnmarshalInputPollMedia decodes a InputPollMedia from JSON by inspecting the +// "type" field and dispatching to the correct concrete type. +func UnmarshalInputPollMedia(data []byte) (InputPollMedia, error) { + var probe struct { + V string `json:"type"` + } + if err := json.Unmarshal(data, &probe); err != nil { + return nil, err + } + var v InputPollMedia + switch probe.V { + case "animation": + v = &InputMediaAnimation{} + case "audio": + v = &InputMediaAudio{} + case "document": + v = &InputMediaDocument{} + case "live_photo": + v = &InputMediaLivePhoto{} + case "location": + v = &InputMediaLocation{} + case "photo": + v = &InputMediaPhoto{} + case "venue": + v = &InputMediaVenue{} + case "video": + v = &InputMediaVideo{} + default: + return nil, fmt.Errorf("InputPollMedia: unknown type %q", probe.V) + } + if err := json.Unmarshal(data, v); err != nil { + return nil, err + } + return v, nil +} + // InputPollOptionMedia is a union type. The following concrete variants implement // it: // - InputMediaAnimation @@ -1432,6 +1470,42 @@ func (*InputMediaVenue) isInputPollOptionMedia() {} // isInputPollOptionMedia is the marker method that makes InputMediaVideo implement InputPollOptionMedia. func (*InputMediaVideo) isInputPollOptionMedia() {} +// UnmarshalInputPollOptionMedia decodes a InputPollOptionMedia from JSON by inspecting the +// "type" field and dispatching to the correct concrete type. +func UnmarshalInputPollOptionMedia(data []byte) (InputPollOptionMedia, error) { + var probe struct { + V string `json:"type"` + } + if err := json.Unmarshal(data, &probe); err != nil { + return nil, err + } + var v InputPollOptionMedia + switch probe.V { + case "animation": + v = &InputMediaAnimation{} + case "link": + v = &InputMediaLink{} + case "live_photo": + v = &InputMediaLivePhoto{} + case "location": + v = &InputMediaLocation{} + case "photo": + v = &InputMediaPhoto{} + case "sticker": + v = &InputMediaSticker{} + case "venue": + v = &InputMediaVenue{} + case "video": + v = &InputMediaVideo{} + default: + return nil, fmt.Errorf("InputPollOptionMedia: unknown type %q", probe.V) + } + if err := json.Unmarshal(data, v); err != nil { + return nil, err + } + return v, nil +} + // This object contains information about one answer option in a poll. type PollOption struct { // Unique identifier of the option, persistent on option addition and deletion @@ -1464,6 +1538,30 @@ type InputPollOption struct { Media InputPollOptionMedia `json:"media,omitempty"` } +// UnmarshalJSON decodes InputPollOption by dispatching union-typed fields +// (Media) through their concrete UnmarshalXxx helpers. +func (m *InputPollOption) UnmarshalJSON(data []byte) error { + type Alias InputPollOption + aux := &struct { + Media json.RawMessage `json:"media,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Media) > 0 && string(aux.Media) != "null" { + v, err := UnmarshalInputPollOptionMedia(aux.Media) + if err != nil { + return fmt.Errorf("decoding media: %w", err) + } + m.Media = v + + } + + return nil +} + // This object represents an answer of a user in a non-anonymous poll. type PollAnswer struct { // Unique poll identifier @@ -4191,6 +4289,40 @@ func (*BotCommandScopeChatAdministrators) isBotCommandScope() {} // isBotCommandScope is the marker method that makes BotCommandScopeChatMember implement BotCommandScope. func (*BotCommandScopeChatMember) isBotCommandScope() {} +// UnmarshalBotCommandScope decodes a BotCommandScope from JSON by inspecting the +// "type" field and dispatching to the correct concrete type. +func UnmarshalBotCommandScope(data []byte) (BotCommandScope, error) { + var probe struct { + V string `json:"type"` + } + if err := json.Unmarshal(data, &probe); err != nil { + return nil, err + } + var v BotCommandScope + switch probe.V { + case "all_chat_administrators": + v = &BotCommandScopeAllChatAdministrators{} + case "all_group_chats": + v = &BotCommandScopeAllGroupChats{} + case "all_private_chats": + v = &BotCommandScopeAllPrivateChats{} + case "chat": + v = &BotCommandScopeChat{} + case "chat_administrators": + v = &BotCommandScopeChatAdministrators{} + case "chat_member": + v = &BotCommandScopeChatMember{} + case "default": + v = &BotCommandScopeDefault{} + default: + return nil, fmt.Errorf("BotCommandScope: unknown type %q", probe.V) + } + if err := json.Unmarshal(data, v); err != nil { + return nil, err + } + return v, nil +} + // Represents the default scope of bot commands. Default commands are used if no commands with a narrower scope are specified for the user. type BotCommandScopeDefault struct { // Scope type, must be default @@ -4820,6 +4952,38 @@ func (*InputMediaPhoto) isInputMedia() {} // isInputMedia is the marker method that makes InputMediaVideo implement InputMedia. func (*InputMediaVideo) isInputMedia() {} +// UnmarshalInputMedia decodes a InputMedia from JSON by inspecting the +// "type" field and dispatching to the correct concrete type. +func UnmarshalInputMedia(data []byte) (InputMedia, error) { + var probe struct { + V string `json:"type"` + } + if err := json.Unmarshal(data, &probe); err != nil { + return nil, err + } + var v InputMedia + switch probe.V { + case "animation": + v = &InputMediaAnimation{} + case "audio": + v = &InputMediaAudio{} + case "document": + v = &InputMediaDocument{} + case "live_photo": + v = &InputMediaLivePhoto{} + case "photo": + v = &InputMediaPhoto{} + case "video": + v = &InputMediaVideo{} + default: + return nil, fmt.Errorf("InputMedia: unknown type %q", probe.V) + } + if err := json.Unmarshal(data, v); err != nil { + return nil, err + } + return v, nil +} + // Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent. type InputMediaAnimation struct { // Type of the media, must be animation @@ -5202,6 +5366,32 @@ func (*InputPaidMediaPhoto) isInputPaidMedia() {} // isInputPaidMedia is the marker method that makes InputPaidMediaVideo implement InputPaidMedia. func (*InputPaidMediaVideo) isInputPaidMedia() {} +// UnmarshalInputPaidMedia decodes a InputPaidMedia from JSON by inspecting the +// "type" field and dispatching to the correct concrete type. +func UnmarshalInputPaidMedia(data []byte) (InputPaidMedia, error) { + var probe struct { + V string `json:"type"` + } + if err := json.Unmarshal(data, &probe); err != nil { + return nil, err + } + var v InputPaidMedia + switch probe.V { + case "live_photo": + v = &InputPaidMediaLivePhoto{} + case "photo": + v = &InputPaidMediaPhoto{} + case "video": + v = &InputPaidMediaVideo{} + default: + return nil, fmt.Errorf("InputPaidMedia: unknown type %q", probe.V) + } + if err := json.Unmarshal(data, v); err != nil { + return nil, err + } + return v, nil +} + // The paid media to send is a live photo. type InputPaidMediaLivePhoto struct { // Type of the media, must be live_photo @@ -5304,6 +5494,30 @@ func (*InputProfilePhotoStatic) isInputProfilePhoto() {} // isInputProfilePhoto is the marker method that makes InputProfilePhotoAnimated implement InputProfilePhoto. func (*InputProfilePhotoAnimated) isInputProfilePhoto() {} +// UnmarshalInputProfilePhoto decodes a InputProfilePhoto from JSON by inspecting the +// "type" field and dispatching to the correct concrete type. +func UnmarshalInputProfilePhoto(data []byte) (InputProfilePhoto, error) { + var probe struct { + V string `json:"type"` + } + if err := json.Unmarshal(data, &probe); err != nil { + return nil, err + } + var v InputProfilePhoto + switch probe.V { + case "animated": + v = &InputProfilePhotoAnimated{} + case "static": + v = &InputProfilePhotoStatic{} + default: + return nil, fmt.Errorf("InputProfilePhoto: unknown type %q", probe.V) + } + if err := json.Unmarshal(data, v); err != nil { + return nil, err + } + return v, nil +} + // A static profile photo in the .JPG format. type InputProfilePhotoStatic struct { // Type of the profile photo, must be static @@ -5368,6 +5582,30 @@ func (*InputStoryContentPhoto) isInputStoryContent() {} // isInputStoryContent is the marker method that makes InputStoryContentVideo implement InputStoryContent. func (*InputStoryContentVideo) isInputStoryContent() {} +// UnmarshalInputStoryContent decodes a InputStoryContent from JSON by inspecting the +// "type" field and dispatching to the correct concrete type. +func UnmarshalInputStoryContent(data []byte) (InputStoryContent, error) { + var probe struct { + V string `json:"type"` + } + if err := json.Unmarshal(data, &probe); err != nil { + return nil, err + } + var v InputStoryContent + switch probe.V { + case "photo": + v = &InputStoryContentPhoto{} + case "video": + v = &InputStoryContentVideo{} + default: + return nil, fmt.Errorf("InputStoryContent: unknown type %q", probe.V) + } + if err := json.Unmarshal(data, v); err != nil { + return nil, err + } + return v, nil +} + // Describes a photo to post as a story. type InputStoryContentPhoto struct { // Type of the content, must be photo @@ -5504,6 +5742,38 @@ type RichMessage struct { IsRtl *bool `json:"is_rtl,omitempty"` } +// UnmarshalJSON decodes RichMessage by dispatching union-typed fields +// (Blocks) through their concrete UnmarshalXxx helpers. +func (m *RichMessage) UnmarshalJSON(data []byte) error { + type Alias RichMessage + aux := &struct { + Blocks json.RawMessage `json:"blocks,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Blocks) > 0 && string(aux.Blocks) != "null" { + var raws []json.RawMessage + if err := json.Unmarshal(aux.Blocks, &raws); err != nil { + return fmt.Errorf("decoding blocks: %w", err) + } + decoded := make([]RichBlock, 0, len(raws)) + for i, r := range raws { + v, err := UnmarshalRichBlock(r) + if err != nil { + return fmt.Errorf("decoding blocks[%d]: %w", i, err) + } + decoded = append(decoded, v) + } + m.Blocks = decoded + + } + + return nil +} + // Describes a rich message to be sent. Exactly one of the fields html, markdown, or blocks must be used. type InputRichMessage struct { // Optional. Content of the rich message to send described as a list of blocks @@ -5520,6 +5790,38 @@ type InputRichMessage struct { SkipEntityDetection *bool `json:"skip_entity_detection,omitempty"` } +// UnmarshalJSON decodes InputRichMessage by dispatching union-typed fields +// (Blocks) through their concrete UnmarshalXxx helpers. +func (m *InputRichMessage) UnmarshalJSON(data []byte) error { + type Alias InputRichMessage + aux := &struct { + Blocks json.RawMessage `json:"blocks,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Blocks) > 0 && string(aux.Blocks) != "null" { + var raws []json.RawMessage + if err := json.Unmarshal(aux.Blocks, &raws); err != nil { + return fmt.Errorf("decoding blocks: %w", err) + } + decoded := make([]InputRichBlock, 0, len(raws)) + for i, r := range raws { + v, err := UnmarshalInputRichBlock(r) + if err != nil { + return fmt.Errorf("decoding blocks[%d]: %w", i, err) + } + decoded = append(decoded, v) + } + m.Blocks = decoded + + } + + return nil +} + // Describes a media element embedded in an outgoing rich message. type InputRichMessageMedia struct { // Unique identifier of the media used in a tg://photo?id=, tg://video?id=, tg://document?id=, or tg://audio?id= link. 1-64 characters, only A-Z, a-z, 0-9, _ and - are allowed. @@ -5554,6 +5856,30 @@ type RichMessageButton struct { Disabled *DisabledButton `json:"disabled,omitempty"` } +// UnmarshalJSON decodes RichMessageButton by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichMessageButton) UnmarshalJSON(data []byte) error { + type Alias RichMessageButton + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // RichText is a union type. The following concrete variants implement // it: // - RichTextBold @@ -5582,6 +5908,8 @@ type RichMessageButton struct { // - RichTextAnchorLink // - RichTextReference // - RichTextReferenceLink +// - RichTextPlain (bare JSON string) +// - RichTextSequence (JSON array of RichText) // // This object represents a rich formatted text. Currently, it can be either a String for plain text, an Array of RichText, or any of the following types: type RichText interface{ isRichText() } @@ -5664,6 +5992,139 @@ func (*RichTextReference) isRichText() {} // isRichText is the marker method that makes RichTextReferenceLink implement RichText. func (*RichTextReferenceLink) isRichText() {} +// RichTextPlain implements RichText for the bare JSON string wire +// shape Telegram documents for this union, alongside its object variants. +type RichTextPlain struct { + Text string +} + +// isRichText is the marker method that makes RichTextPlain implement RichText. +func (*RichTextPlain) isRichText() {} + +// MarshalJSON encodes RichTextPlain back to its bare JSON string wire shape. +func (v *RichTextPlain) MarshalJSON() ([]byte, error) { + return json.Marshal(v.Text) +} + +// RichTextSequence implements RichText for the JSON array of RichText wire +// shape Telegram documents for this union, alongside its object variants. +type RichTextSequence struct { + Items []RichText +} + +// isRichText is the marker method that makes RichTextSequence implement RichText. +func (*RichTextSequence) isRichText() {} + +// MarshalJSON encodes RichTextSequence back to its bare JSON array wire shape. +func (v *RichTextSequence) MarshalJSON() ([]byte, error) { + return json.Marshal(v.Items) +} + +// UnmarshalRichText decodes a RichText from JSON. Telegram documents +// this type as either a bare JSON string, a JSON array of RichText, or +// an object dispatched by its "type" field; +// leading whitespace is trimmed and the first remaining byte selects +// the shape. +func UnmarshalRichText(data []byte) (RichText, error) { + trimmed := bytes.TrimLeft(data, " \t\r\n") + if len(trimmed) == 0 { + return nil, fmt.Errorf("RichText: empty JSON value") + } + switch trimmed[0] { + case '"': + var s string + if err := json.Unmarshal(data, &s); err != nil { + return nil, fmt.Errorf("RichText: decoding string: %w", err) + } + return &RichTextPlain{Text: s}, nil + case '[': + var raws []json.RawMessage + if err := json.Unmarshal(data, &raws); err != nil { + return nil, fmt.Errorf("RichText: decoding array: %w", err) + } + items := make([]RichText, 0, len(raws)) + for i, r := range raws { + v, err := UnmarshalRichText(r) + if err != nil { + return nil, fmt.Errorf("RichText: decoding array[%d]: %w", i, err) + } + items = append(items, v) + } + return &RichTextSequence{Items: items}, nil + case '{': + // fall through to the discriminator switch below. + default: + return nil, fmt.Errorf("RichText: unrecognised JSON value starting with %q", trimmed[0]) + } + var probe struct { + V string `json:"type"` + } + if err := json.Unmarshal(data, &probe); err != nil { + return nil, err + } + var v RichText + switch probe.V { + case "anchor": + v = &RichTextAnchor{} + case "anchor_link": + v = &RichTextAnchorLink{} + case "bank_card_number": + v = &RichTextBankCardNumber{} + case "bold": + v = &RichTextBold{} + case "bot_command": + v = &RichTextBotCommand{} + case "button": + v = &RichTextButton{} + case "cashtag": + v = &RichTextCashtag{} + case "code": + v = &RichTextCode{} + case "custom_emoji": + v = &RichTextCustomEmoji{} + case "date_time": + v = &RichTextDateTime{} + case "email_address": + v = &RichTextEmailAddress{} + case "hashtag": + v = &RichTextHashtag{} + case "italic": + v = &RichTextItalic{} + case "marked": + v = &RichTextMarked{} + case "mathematical_expression": + v = &RichTextMathematicalExpression{} + case "mention": + v = &RichTextMention{} + case "phone_number": + v = &RichTextPhoneNumber{} + case "reference": + v = &RichTextReference{} + case "reference_link": + v = &RichTextReferenceLink{} + case "spoiler": + v = &RichTextSpoiler{} + case "strikethrough": + v = &RichTextStrikethrough{} + case "subscript": + v = &RichTextSubscript{} + case "superscript": + v = &RichTextSuperscript{} + case "text_mention": + v = &RichTextTextMention{} + case "underline": + v = &RichTextUnderline{} + case "url": + v = &RichTextUrl{} + default: + return nil, fmt.Errorf("RichText: unknown type %q", probe.V) + } + if err := json.Unmarshal(data, v); err != nil { + return nil, err + } + return v, nil +} + // A bold text. type RichTextBold struct { // Type of the rich text, always “bold” @@ -5688,6 +6149,30 @@ func (v *RichTextBold) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextBold by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextBold) UnmarshalJSON(data []byte) error { + type Alias RichTextBold + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // An italicized text. type RichTextItalic struct { // Type of the rich text, always “italic” @@ -5712,6 +6197,30 @@ func (v *RichTextItalic) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextItalic by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextItalic) UnmarshalJSON(data []byte) error { + type Alias RichTextItalic + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // An underlined text. type RichTextUnderline struct { // Type of the rich text, always “underline” @@ -5736,6 +6245,30 @@ func (v *RichTextUnderline) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextUnderline by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextUnderline) UnmarshalJSON(data []byte) error { + type Alias RichTextUnderline + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A strikethrough text. type RichTextStrikethrough struct { // Type of the rich text, always “strikethrough” @@ -5760,6 +6293,30 @@ func (v *RichTextStrikethrough) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextStrikethrough by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextStrikethrough) UnmarshalJSON(data []byte) error { + type Alias RichTextStrikethrough + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A text covered by a spoiler. type RichTextSpoiler struct { // Type of the rich text, always “spoiler” @@ -5784,6 +6341,30 @@ func (v *RichTextSpoiler) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextSpoiler by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextSpoiler) UnmarshalJSON(data []byte) error { + type Alias RichTextSpoiler + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // Formatted date and time. type RichTextDateTime struct { // Type of the rich text, always “date_time” @@ -5812,6 +6393,30 @@ func (v *RichTextDateTime) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextDateTime by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextDateTime) UnmarshalJSON(data []byte) error { + type Alias RichTextDateTime + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A mention of a Telegram user by their identifier. type RichTextTextMention struct { // Type of the rich text, always “text_mention” @@ -5838,6 +6443,30 @@ func (v *RichTextTextMention) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextTextMention by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextTextMention) UnmarshalJSON(data []byte) error { + type Alias RichTextTextMention + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A subscript text. type RichTextSubscript struct { // Type of the rich text, always “subscript” @@ -5862,6 +6491,30 @@ func (v *RichTextSubscript) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextSubscript by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextSubscript) UnmarshalJSON(data []byte) error { + type Alias RichTextSubscript + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A superscript text. type RichTextSuperscript struct { // Type of the rich text, always “superscript” @@ -5886,6 +6539,30 @@ func (v *RichTextSuperscript) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextSuperscript by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextSuperscript) UnmarshalJSON(data []byte) error { + type Alias RichTextSuperscript + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A marked text. type RichTextMarked struct { // Type of the rich text, always “marked” @@ -5910,6 +6587,30 @@ func (v *RichTextMarked) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextMarked by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextMarked) UnmarshalJSON(data []byte) error { + type Alias RichTextMarked + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A monowidth text. type RichTextCode struct { // Type of the rich text, always “code” @@ -5934,6 +6635,30 @@ func (v *RichTextCode) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextCode by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextCode) UnmarshalJSON(data []byte) error { + type Alias RichTextCode + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A custom emoji. type RichTextCustomEmoji struct { // Type of the rich text, always “custom_emoji” @@ -6010,6 +6735,30 @@ func (v *RichTextUrl) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextUrl by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextUrl) UnmarshalJSON(data []byte) error { + type Alias RichTextUrl + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A text with an email address. type RichTextEmailAddress struct { // Type of the rich text, always “email_address” @@ -6036,6 +6785,30 @@ func (v *RichTextEmailAddress) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextEmailAddress by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextEmailAddress) UnmarshalJSON(data []byte) error { + type Alias RichTextEmailAddress + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A text with a phone number. type RichTextPhoneNumber struct { // Type of the rich text, always “phone_number” @@ -6062,6 +6835,30 @@ func (v *RichTextPhoneNumber) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextPhoneNumber by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextPhoneNumber) UnmarshalJSON(data []byte) error { + type Alias RichTextPhoneNumber + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A text with a bank card number. type RichTextBankCardNumber struct { // Type of the rich text, always “bank_card_number” @@ -6088,6 +6885,30 @@ func (v *RichTextBankCardNumber) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextBankCardNumber by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextBankCardNumber) UnmarshalJSON(data []byte) error { + type Alias RichTextBankCardNumber + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A mention by a username. type RichTextMention struct { // Type of the rich text, always “mention” @@ -6114,6 +6935,30 @@ func (v *RichTextMention) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextMention by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextMention) UnmarshalJSON(data []byte) error { + type Alias RichTextMention + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A hashtag. type RichTextHashtag struct { // Type of the rich text, always “hashtag” @@ -6140,6 +6985,30 @@ func (v *RichTextHashtag) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextHashtag by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextHashtag) UnmarshalJSON(data []byte) error { + type Alias RichTextHashtag + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A cashtag. type RichTextCashtag struct { // Type of the rich text, always “cashtag” @@ -6166,6 +7035,30 @@ func (v *RichTextCashtag) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextCashtag by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextCashtag) UnmarshalJSON(data []byte) error { + type Alias RichTextCashtag + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A bot command. type RichTextBotCommand struct { // Type of the rich text, always “bot_command” @@ -6192,6 +7085,30 @@ func (v *RichTextBotCommand) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextBotCommand by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextBotCommand) UnmarshalJSON(data []byte) error { + type Alias RichTextBotCommand + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A button. type RichTextButton struct { // Type of the rich text, always “button” @@ -6266,6 +7183,30 @@ func (v *RichTextAnchorLink) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextAnchorLink by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextAnchorLink) UnmarshalJSON(data []byte) error { + type Alias RichTextAnchorLink + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A reference. type RichTextReference struct { // Type of the rich text, always “reference” @@ -6292,6 +7233,30 @@ func (v *RichTextReference) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextReference by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextReference) UnmarshalJSON(data []byte) error { + type Alias RichTextReference + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A link to a reference. type RichTextReferenceLink struct { // Type of the rich text, always “reference_link” @@ -6318,6 +7283,30 @@ func (v *RichTextReferenceLink) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichTextReferenceLink by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichTextReferenceLink) UnmarshalJSON(data []byte) error { + type Alias RichTextReferenceLink + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // Caption of a rich formatted block. type RichBlockCaption struct { // Block caption @@ -6326,6 +7315,40 @@ type RichBlockCaption struct { Credit RichText `json:"credit,omitempty"` } +// UnmarshalJSON decodes RichBlockCaption by dispatching union-typed fields +// (Text, Credit) through their concrete UnmarshalXxx helpers. +func (m *RichBlockCaption) UnmarshalJSON(data []byte) error { + type Alias RichBlockCaption + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + Credit json.RawMessage `json:"credit,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + if len(aux.Credit) > 0 && string(aux.Credit) != "null" { + v, err := UnmarshalRichText(aux.Credit) + if err != nil { + return fmt.Errorf("decoding credit: %w", err) + } + m.Credit = v + + } + + return nil +} + // Cell in a table. type RichBlockTableCell struct { // Optional. Text in the cell. If omitted, then the cell is invisible. @@ -6342,6 +7365,30 @@ type RichBlockTableCell struct { Valign RichBlockTableCellValign `json:"valign"` } +// UnmarshalJSON decodes RichBlockTableCell by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichBlockTableCell) UnmarshalJSON(data []byte) error { + type Alias RichBlockTableCell + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // An item of a list. type RichBlockListItem struct { // Label of the item @@ -6358,6 +7405,38 @@ type RichBlockListItem struct { Type RichBlockListItemType `json:"type,omitempty"` } +// UnmarshalJSON decodes RichBlockListItem by dispatching union-typed fields +// (Blocks) through their concrete UnmarshalXxx helpers. +func (m *RichBlockListItem) UnmarshalJSON(data []byte) error { + type Alias RichBlockListItem + aux := &struct { + Blocks json.RawMessage `json:"blocks,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Blocks) > 0 && string(aux.Blocks) != "null" { + var raws []json.RawMessage + if err := json.Unmarshal(aux.Blocks, &raws); err != nil { + return fmt.Errorf("decoding blocks: %w", err) + } + decoded := make([]RichBlock, 0, len(raws)) + for i, r := range raws { + v, err := UnmarshalRichBlock(r) + if err != nil { + return fmt.Errorf("decoding blocks[%d]: %w", i, err) + } + decoded = append(decoded, v) + } + m.Blocks = decoded + + } + + return nil +} + // RichBlock is a union type. The following concrete variants implement // it: // - RichBlockParagraph @@ -6460,6 +7539,74 @@ func (*RichBlockVoiceNote) isRichBlock() {} // isRichBlock is the marker method that makes RichBlockThinking implement RichBlock. func (*RichBlockThinking) isRichBlock() {} +// UnmarshalRichBlock decodes a RichBlock from JSON by inspecting the +// "type" field and dispatching to the correct concrete type. +func UnmarshalRichBlock(data []byte) (RichBlock, error) { + var probe struct { + V string `json:"type"` + } + if err := json.Unmarshal(data, &probe); err != nil { + return nil, err + } + var v RichBlock + switch probe.V { + case "anchor": + v = &RichBlockAnchor{} + case "animation": + v = &RichBlockAnimation{} + case "audio": + v = &RichBlockAudio{} + case "blockquote": + v = &RichBlockBlockQuotation{} + case "buttons": + v = &RichBlockButtons{} + case "collage": + v = &RichBlockCollage{} + case "details": + v = &RichBlockDetails{} + case "divider": + v = &RichBlockDivider{} + case "document": + v = &RichBlockDocument{} + case "expandable_blockquote": + v = &RichBlockExpandableBlockQuotation{} + case "footer": + v = &RichBlockFooter{} + case "heading": + v = &RichBlockSectionHeading{} + case "list": + v = &RichBlockList{} + case "map": + v = &RichBlockMap{} + case "mathematical_expression": + v = &RichBlockMathematicalExpression{} + case "paragraph": + v = &RichBlockParagraph{} + case "photo": + v = &RichBlockPhoto{} + case "pre": + v = &RichBlockPreformatted{} + case "pullquote": + v = &RichBlockPullQuotation{} + case "slideshow": + v = &RichBlockSlideshow{} + case "table": + v = &RichBlockTable{} + case "thinking": + v = &RichBlockThinking{} + case "video": + v = &RichBlockVideo{} + case "voice_note": + v = &RichBlockVoiceNote{} + default: + return nil, fmt.Errorf("RichBlock: unknown type %q", probe.V) + } + if err := json.Unmarshal(data, v); err != nil { + return nil, err + } + return v, nil +} + // A text paragraph, corresponding to the HTML tag

. type RichBlockParagraph struct { // Type of the block, always “paragraph” @@ -6484,6 +7631,30 @@ func (v *RichBlockParagraph) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichBlockParagraph by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichBlockParagraph) UnmarshalJSON(data []byte) error { + type Alias RichBlockParagraph + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A section heading, corresponding to the HTML tags

,

,

,

,

, or
. type RichBlockSectionHeading struct { // Type of the block, always “heading” @@ -6510,6 +7681,30 @@ func (v *RichBlockSectionHeading) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON decodes RichBlockSectionHeading by dispatching union-typed fields +// (Text) through their concrete UnmarshalXxx helpers. +func (m *RichBlockSectionHeading) UnmarshalJSON(data []byte) error { + type Alias RichBlockSectionHeading + aux := &struct { + Text json.RawMessage `json:"text,omitempty"` + *Alias + }{Alias: (*Alias)(m)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.Text) > 0 && string(aux.Text) != "null" { + v, err := UnmarshalRichText(aux.Text) + if err != nil { + return fmt.Errorf("decoding text: %w", err) + } + m.Text = v + + } + + return nil +} + // A preformatted text block, corresponding to the nested HTML tags
 and .
 type RichBlockPreformatted struct {
 	// Type of the block, always “pre”
@@ -6536,6 +7731,30 @@ func (v *RichBlockPreformatted) MarshalJSON() ([]byte, error) {
 	})
 }
 
+// UnmarshalJSON decodes RichBlockPreformatted by dispatching union-typed fields
+// (Text) through their concrete UnmarshalXxx helpers.
+func (m *RichBlockPreformatted) UnmarshalJSON(data []byte) error {
+	type Alias RichBlockPreformatted
+	aux := &struct {
+		Text json.RawMessage `json:"text,omitempty"`
+		*Alias
+	}{Alias: (*Alias)(m)}
+	if err := json.Unmarshal(data, aux); err != nil {
+		return err
+	}
+
+	if len(aux.Text) > 0 && string(aux.Text) != "null" {
+		v, err := UnmarshalRichText(aux.Text)
+		if err != nil {
+			return fmt.Errorf("decoding text: %w", err)
+		}
+		m.Text = v
+
+	}
+
+	return nil
+}
+
 // A footer, corresponding to the HTML tag