object.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. package object
  2. import (
  3. "errors"
  4. "fmt"
  5. "math"
  6. "servitor/ansi"
  7. "servitor/gemtext"
  8. "servitor/hypertext"
  9. "servitor/markdown"
  10. "servitor/mime"
  11. "servitor/plaintext"
  12. "net/url"
  13. "time"
  14. )
  15. type Object map[string]any
  16. var (
  17. ErrKeyNotPresent = errors.New("key is not present")
  18. ErrKeyWrongType = errors.New("value is incorrect type")
  19. )
  20. /* Go doesn't allow generic methods */
  21. func getPrimitive[T any](o Object, key string) (T, error) {
  22. var zero T
  23. if value, ok := o[key]; !ok || value == nil {
  24. return zero, fmt.Errorf("failed to extract \"%s\": %w", key, ErrKeyNotPresent)
  25. } else if narrowed, ok := value.(T); !ok {
  26. return zero, fmt.Errorf("failed to extract \"%s\": %w: is %T", key, ErrKeyWrongType, value)
  27. } else {
  28. return narrowed, nil
  29. }
  30. }
  31. func (o Object) GetAny(key string) (any, error) {
  32. return getPrimitive[any](o, key)
  33. }
  34. func (o Object) GetString(key string) (string, error) {
  35. value, err := getPrimitive[string](o, key)
  36. if err != nil {
  37. return "", err
  38. }
  39. value = ansi.Scrub(value)
  40. if value == "" {
  41. return "", ErrKeyNotPresent
  42. }
  43. return value, nil
  44. }
  45. func (o Object) GetNumber(key string) (uint64, error) {
  46. if number, err := getPrimitive[float64](o, key); err != nil {
  47. return 0, err
  48. } else if number != math.Trunc(number) {
  49. return 0, fmt.Errorf("failed to extract \"%s\": value is not a non-integer number", key)
  50. } else {
  51. return uint64(number), nil
  52. }
  53. }
  54. func (o Object) GetObject(key string) (Object, error) {
  55. return getPrimitive[map[string]any](o, key)
  56. }
  57. func (o Object) GetList(key string) ([]any, error) {
  58. if value, err := o.GetAny(key); err != nil {
  59. return nil, err
  60. } else if asList, isList := value.([]any); isList {
  61. return asList, nil
  62. } else {
  63. return []any{value}, nil
  64. }
  65. }
  66. func (o Object) GetTime(key string) (time.Time, error) {
  67. if value, err := o.GetString(key); err != nil {
  68. return time.Time{}, err
  69. } else {
  70. timestamp, err := time.Parse(time.RFC3339, value)
  71. if err != nil {
  72. return time.Time{}, fmt.Errorf("failed to parse time \"%s\": %w", key, err)
  73. }
  74. return timestamp, nil
  75. }
  76. }
  77. func (o Object) GetURL(key string) (*url.URL, error) {
  78. if value, err := o.GetString(key); err != nil {
  79. return nil, err
  80. } else {
  81. address, err := url.Parse(value)
  82. if err != nil {
  83. return nil, fmt.Errorf("failed to parse URL \"%s\": %w", key, err)
  84. }
  85. return address, nil
  86. }
  87. }
  88. func (o Object) GetMediaType(key string) (*mime.MediaType, error) {
  89. if value, err := o.GetString(key); err != nil {
  90. return nil, err
  91. } else {
  92. mediaType, err := mime.Parse(value)
  93. if err != nil {
  94. return nil, fmt.Errorf("failed to parse mime type \"%s\": %w", key, err)
  95. }
  96. return mediaType, nil
  97. }
  98. }
  99. type Markup interface {
  100. Render(width int) string
  101. }
  102. func (o Object) GetMarkup(contentKey string, mediaTypeKey string) (Markup, []string, error) {
  103. content, err := o.GetString(contentKey)
  104. if err != nil {
  105. return nil, nil, err
  106. }
  107. mediaType, err := o.GetMediaType(mediaTypeKey)
  108. if errors.Is(err, ErrKeyNotPresent) {
  109. mediaType = mime.Default()
  110. } else if err != nil {
  111. return nil, nil, err
  112. }
  113. switch mediaType.Essence {
  114. case "text/plain":
  115. return plaintext.NewMarkup(content)
  116. case "text/html":
  117. return hypertext.NewMarkup(content)
  118. case "text/gemini":
  119. return gemtext.NewMarkup(content)
  120. case "text/markdown":
  121. return markdown.NewMarkup(content)
  122. default:
  123. return nil, nil, errors.New("cannot render text of mime type " + mediaType.Essence)
  124. }
  125. }