package slack import ( "encoding/json" "fmt" "github.com/pkg/errors" ) type sumtype struct { TypeVal string `json:"type"` } // MarshalJSON implements the Marshaller interface for Blocks so that any JSON // marshalling is delegated and proper type determination can be made before marshal func (b Blocks) MarshalJSON() ([]byte, error) { bytes, err := json.Marshal(b.BlockSet) if err != nil { return nil, err } return bytes, nil } // UnmarshalJSON implements the Unmarshaller interface for Blocks, so that any JSON // unmarshalling is delegated and proper type determination can be made before unmarshal func (b *Blocks) UnmarshalJSON(data []byte) error { var raw []json.RawMessage if string(data) == "{}" { return nil } err := json.Unmarshal(data, &raw) if err != nil { return err } var blocks Blocks for _, r := range raw { s := sumtype{} err := json.Unmarshal(r, &s) if err != nil { return err } var blockType string if s.TypeVal != "" { blockType = s.TypeVal } var block Block switch blockType { case "actions": block = &ActionBlock{} case "context": block = &ContextBlock{} case "divider": block = &DividerBlock{} case "file": block = &FileBlock{} case "image": block = &ImageBlock{} case "input": block = &InputBlock{} case "section": block = &SectionBlock{} default: block = &UnknownBlock{} } err = json.Unmarshal(r, block) if err != nil { return err } blocks.BlockSet = append(blocks.BlockSet, block) } *b = blocks return nil } // UnmarshalJSON implements the Unmarshaller interface for InputBlock, so that any JSON // unmarshalling is delegated and proper type determination can be made before unmarshal func (b *InputBlock) UnmarshalJSON(data []byte) error { type alias InputBlock a := struct { Element json.RawMessage `json:"element"` *alias }{ alias: (*alias)(b), } if err := json.Unmarshal(data, &a); err != nil { return err } s := sumtype{} if err := json.Unmarshal(a.Element, &s); err != nil { return nil } var e BlockElement switch s.TypeVal { case "datepicker": e = &DatePickerBlockElement{} case "plain_text_input": e = &PlainTextInputBlockElement{} case "static_select", "external_select", "users_select", "conversations_select", "channels_select": e = &SelectBlockElement{} case "multi_static_select", "multi_external_select", "multi_users_select", "multi_conversations_select", "multi_channels_select": e = &MultiSelectBlockElement{} case "checkboxes": e = &CheckboxGroupsBlockElement{} case "overflow": e = &OverflowBlockElement{} case "radio_buttons": e = &RadioButtonsBlockElement{} default: return errors.New("unsupported block element type") } if err := json.Unmarshal(a.Element, e); err != nil { return err } b.Element = e return nil } // MarshalJSON implements the Marshaller interface for BlockElements so that any JSON // marshalling is delegated and proper type determination can be made before marshal func (b *BlockElements) MarshalJSON() ([]byte, error) { bytes, err := json.Marshal(b.ElementSet) if err != nil { return nil, err } return bytes, nil } // UnmarshalJSON implements the Unmarshaller interface for BlockElements, so that any JSON // unmarshalling is delegated and proper type determination can be made before unmarshal func (b *BlockElements) UnmarshalJSON(data []byte) error { var raw []json.RawMessage if string(data) == "{}" { return nil } err := json.Unmarshal(data, &raw) if err != nil { return err } var blockElements BlockElements for _, r := range raw { s := sumtype{} err := json.Unmarshal(r, &s) if err != nil { return err } var blockElementType string if s.TypeVal != "" { blockElementType = s.TypeVal } var blockElement BlockElement switch blockElementType { case "image": blockElement = &ImageBlockElement{} case "button": blockElement = &ButtonBlockElement{} case "overflow": blockElement = &OverflowBlockElement{} case "datepicker": blockElement = &DatePickerBlockElement{} case "plain_text_input": blockElement = &PlainTextInputBlockElement{} case "checkboxes": blockElement = &CheckboxGroupsBlockElement{} case "radio_buttons": blockElement = &RadioButtonsBlockElement{} case "static_select", "external_select", "users_select", "conversations_select", "channels_select": blockElement = &SelectBlockElement{} default: return fmt.Errorf("unsupported block element type %v", blockElementType) } err = json.Unmarshal(r, blockElement) if err != nil { return err } blockElements.ElementSet = append(blockElements.ElementSet, blockElement) } *b = blockElements return nil } // MarshalJSON implements the Marshaller interface for Accessory so that any JSON // marshalling is delegated and proper type determination can be made before marshal func (a *Accessory) MarshalJSON() ([]byte, error) { bytes, err := json.Marshal(toBlockElement(a)) if err != nil { return nil, err } return bytes, nil } // UnmarshalJSON implements the Unmarshaller interface for Accessory, so that any JSON // unmarshalling is delegated and proper type determination can be made before unmarshal func (a *Accessory) UnmarshalJSON(data []byte) error { var r json.RawMessage if string(data) == "{\"accessory\":null}" { return nil } err := json.Unmarshal(data, &r) if err != nil { return err } s := sumtype{} err = json.Unmarshal(r, &s) if err != nil { return err } var blockElementType string if s.TypeVal != "" { blockElementType = s.TypeVal } switch blockElementType { case "image": element, err := unmarshalBlockElement(r, &ImageBlockElement{}) if err != nil { return err } a.ImageElement = element.(*ImageBlockElement) case "button": element, err := unmarshalBlockElement(r, &ButtonBlockElement{}) if err != nil { return err } a.ButtonElement = element.(*ButtonBlockElement) case "overflow": element, err := unmarshalBlockElement(r, &OverflowBlockElement{}) if err != nil { return err } a.OverflowElement = element.(*OverflowBlockElement) case "datepicker": element, err := unmarshalBlockElement(r, &DatePickerBlockElement{}) if err != nil { return err } a.DatePickerElement = element.(*DatePickerBlockElement) case "plain_text_input": element, err := unmarshalBlockElement(r, &PlainTextInputBlockElement{}) if err != nil { return err } a.PlainTextInputElement = element.(*PlainTextInputBlockElement) case "radio_buttons": element, err := unmarshalBlockElement(r, &RadioButtonsBlockElement{}) if err != nil { return err } a.RadioButtonsElement = element.(*RadioButtonsBlockElement) case "static_select", "external_select", "users_select", "conversations_select", "channels_select": element, err := unmarshalBlockElement(r, &SelectBlockElement{}) if err != nil { return err } a.SelectElement = element.(*SelectBlockElement) case "multi_static_select", "multi_external_select", "multi_users_select", "multi_conversations_select", "multi_channels_select": element, err := unmarshalBlockElement(r, &MultiSelectBlockElement{}) if err != nil { return err } a.MultiSelectElement = element.(*MultiSelectBlockElement) case "checkboxes": element, err := unmarshalBlockElement(r, &CheckboxGroupsBlockElement{}) if err != nil { return err } a.CheckboxGroupsBlockElement = element.(*CheckboxGroupsBlockElement) default: element, err := unmarshalBlockElement(r, &UnknownBlockElement{}) if err != nil { return err } a.UnknownElement = element.(*UnknownBlockElement) } return nil } func unmarshalBlockElement(r json.RawMessage, element BlockElement) (BlockElement, error) { err := json.Unmarshal(r, element) if err != nil { return nil, err } return element, nil } func toBlockElement(element *Accessory) BlockElement { if element.ImageElement != nil { return element.ImageElement } if element.ButtonElement != nil { return element.ButtonElement } if element.OverflowElement != nil { return element.OverflowElement } if element.DatePickerElement != nil { return element.DatePickerElement } if element.PlainTextInputElement != nil { return element.PlainTextInputElement } if element.RadioButtonsElement != nil { return element.RadioButtonsElement } if element.CheckboxGroupsBlockElement != nil { return element.CheckboxGroupsBlockElement } if element.SelectElement != nil { return element.SelectElement } if element.MultiSelectElement != nil { return element.MultiSelectElement } return nil } // MarshalJSON implements the Marshaller interface for ContextElements so that any JSON // marshalling is delegated and proper type determination can be made before marshal func (e *ContextElements) MarshalJSON() ([]byte, error) { bytes, err := json.Marshal(e.Elements) if err != nil { return nil, err } return bytes, nil } // UnmarshalJSON implements the Unmarshaller interface for ContextElements, so that any JSON // unmarshalling is delegated and proper type determination can be made before unmarshal func (e *ContextElements) UnmarshalJSON(data []byte) error { var raw []json.RawMessage if string(data) == "{\"elements\":null}" { return nil } err := json.Unmarshal(data, &raw) if err != nil { return err } for _, r := range raw { s := sumtype{} err := json.Unmarshal(r, &s) if err != nil { return err } var contextElementType string if s.TypeVal != "" { contextElementType = s.TypeVal } switch contextElementType { case PlainTextType, MarkdownType: elem, err := unmarshalBlockObject(r, &TextBlockObject{}) if err != nil { return err } e.Elements = append(e.Elements, elem.(*TextBlockObject)) case "image": elem, err := unmarshalBlockElement(r, &ImageBlockElement{}) if err != nil { return err } e.Elements = append(e.Elements, elem.(*ImageBlockElement)) default: return errors.New("unsupported context element type") } } return nil }