versia-go/pkg/versia/utils/content_map.go

151 lines
3.4 KiB
Go

package versiautils
import (
"encoding/json"
"errors"
"fmt"
"slices"
)
var (
validTextContentTypes = []string{"text/html", "text/plain"}
validImageContentTypes = []string{"image/png", "image/jpeg", "image/gif", "image/svg+xml"}
)
// ContentMap is a map of content types to their respective content.
type ContentMap[T any] map[string]T
type UnexpectedContentTypeError struct {
MIMEType string
}
func (e UnexpectedContentTypeError) Error() string {
return fmt.Sprintf("unexpected content type: %s", e.MIMEType)
}
func (m ContentMap[T]) unmarshalJSON(raw []byte, valid []string) error {
var cm map[string]json.RawMessage
if err := json.Unmarshal(raw, &cm); err != nil {
return err
}
m = make(ContentMap[T])
errs := make([]error, 0)
for k, v := range cm {
if valid != nil {
if !slices.Contains(valid, k) {
errs = append(errs, UnexpectedContentTypeError{k})
continue
}
}
var c T
if err := json.Unmarshal(v, &c); err != nil {
errs = append(errs, err)
continue
}
m[k] = c
}
if len(errs) > 0 {
return MultipleError{errs}
}
return nil
}
func (m ContentMap[T]) getPreferred(preferred []string) *T {
for _, v := range preferred {
if c, ok := m[v]; ok {
return &c
}
}
return nil
}
// TextContent is embedded string. For more information, see the [Spec].
//
// [Spec]: https://versia.pub/structures/content-format
type TextContent struct {
Content string `json:"content"`
// Remote is always false
Remote bool `json:"remote"`
}
type TextContentTypeMap ContentMap[TextContent]
func (t TextContentTypeMap) UnmarshalJSON(data []byte) error {
return (ContentMap[TextContent])(t).unmarshalJSON(data, validTextContentTypes)
}
func (t TextContentTypeMap) String() string {
if c := (ContentMap[TextContent])(t).getPreferred(validTextContentTypes); c != nil {
return c.Content
}
return ""
}
// File is a file or other piece of content. For more information, see the [Spec].
//
// [Spec]: https://versia.pub/structures/content-format
type File struct {
// Remote is always true
Remote bool `json:"remote"`
// URL to the attachment
Content *URL `json:"content"`
Description *string `json:"description,omitempty"`
Hash *DataHash `json:"hash,omitempty"`
Size *int `json:"size,omitempty"`
// BlurHash is available when the content type is an image
BlurHash *string `json:"blurhash,omitempty"`
// BlurHash is available when the content type is an image
Height *int `json:"height,omitempty"`
// BlurHash is available when the content type is an image
Width *int `json:"width,omitempty"`
FPS *int `json:"fps,omitempty"`
}
type DataHash struct {
SHA256 string `json:"sha256"`
}
type ImageContentMap ContentMap[File]
func (i ImageContentMap) UnmarshalJSON(data []byte) error {
return (ContentMap[File])(i).unmarshalJSON(data, validImageContentTypes)
}
func (i ImageContentMap) String() string {
if c := (ContentMap[File])(i).getPreferred(validImageContentTypes); c != nil {
return c.Content.String()
}
return ""
}
type NoteAttachmentContentMap ContentMap[File]
var ErrContentMapEntryNotRemote = errors.New("content map entry not remote")
func (i NoteAttachmentContentMap) UnmarshalJSON(data []byte) error {
if err := (ContentMap[File])(i).unmarshalJSON(data, nil); err != nil {
return err
}
errs := make([]error, 0)
for _, a := range i {
if !a.Remote {
errs = append(errs, ErrContentMapEntryNotRemote)
}
}
if len(errs) > 0 {
return MultipleError{errs}
}
return nil
}