link.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. package pub
  2. import (
  3. "errors"
  4. "fmt"
  5. "golang.org/x/exp/slices"
  6. "servitor/mime"
  7. "servitor/object"
  8. "net/url"
  9. "strings"
  10. )
  11. type Link struct {
  12. kind string
  13. mediaType *mime.MediaType
  14. mediaTypeErr error
  15. uri *url.URL
  16. uriErr error
  17. alt string
  18. altErr error
  19. height uint64
  20. heightErr error
  21. width uint64
  22. widthErr error
  23. }
  24. func NewLink(input any) (*Link, error) {
  25. l := &Link{}
  26. asMap, ok := input.(map[string]any)
  27. if !ok {
  28. return nil, fmt.Errorf("can't turn non-object %T into Link", input)
  29. }
  30. o := object.Object(asMap)
  31. var err error
  32. if l.kind, err = o.GetString("type"); err != nil {
  33. return nil, err
  34. }
  35. if !slices.Contains([]string{
  36. "Link", "Audio", "Document", "Image", "Video",
  37. }, l.kind) {
  38. return nil, fmt.Errorf("%w: %s is not a Link", ErrWrongType, l.kind)
  39. }
  40. if l.kind == "Link" {
  41. l.uri, l.uriErr = o.GetURL("href")
  42. l.height, l.heightErr = o.GetNumber("height")
  43. l.width, l.widthErr = o.GetNumber("width")
  44. } else {
  45. l.uri, l.uriErr = o.GetURL("url")
  46. l.heightErr = object.ErrKeyNotPresent
  47. l.widthErr = object.ErrKeyNotPresent
  48. }
  49. l.mediaType, l.mediaTypeErr = o.GetMediaType("mediaType")
  50. l.alt, l.altErr = o.GetString("name")
  51. return l, nil
  52. }
  53. func (l *Link) Alt() (string, error) {
  54. if l.altErr == nil {
  55. return l.alt, nil
  56. } else if errors.Is(l.altErr, object.ErrKeyNotPresent) {
  57. if l.uriErr == nil {
  58. return l.uri.String(), nil
  59. } else {
  60. return "", l.uriErr
  61. }
  62. } else {
  63. return "", l.altErr
  64. }
  65. }
  66. func (l *Link) rating() (uint64, error) {
  67. var height, width uint64
  68. if l.heightErr == nil {
  69. height = l.height
  70. } else if errors.Is(l.heightErr, object.ErrKeyNotPresent) {
  71. height = 1
  72. } else {
  73. return 0, l.heightErr
  74. }
  75. if l.widthErr == nil {
  76. width = l.width
  77. } else if errors.Is(l.widthErr, object.ErrKeyNotPresent) {
  78. width = 1
  79. } else {
  80. return 0, l.widthErr
  81. }
  82. return height * width, nil
  83. }
  84. func SelectBestLink(links []*Link, supertype string) (*Link, error) {
  85. if len(links) == 0 {
  86. return nil, errors.New("can't select best link of type " + supertype + "/* from an empty list")
  87. }
  88. bestLink := links[0]
  89. // TODO: loop through once and validate errors, then proceed assuming no errors
  90. for _, thisLink := range links[1:] {
  91. var bestLinkSupertypeMatches bool
  92. if errors.Is(bestLink.mediaTypeErr, object.ErrKeyNotPresent) {
  93. bestLinkSupertypeMatches = false
  94. } else if bestLink.mediaTypeErr != nil {
  95. return nil, bestLink.mediaTypeErr
  96. } else {
  97. bestLinkSupertypeMatches = bestLink.mediaType.Supertype == supertype
  98. }
  99. var thisLinkSuperTypeMatches bool
  100. if errors.Is(thisLink.mediaTypeErr, object.ErrKeyNotPresent) {
  101. thisLinkSuperTypeMatches = false
  102. } else if thisLink.mediaTypeErr != nil {
  103. return nil, thisLink.mediaTypeErr
  104. } else {
  105. thisLinkSuperTypeMatches = thisLink.mediaType.Supertype == supertype
  106. }
  107. if thisLinkSuperTypeMatches && !bestLinkSupertypeMatches {
  108. bestLink = thisLink
  109. continue
  110. } else if !thisLinkSuperTypeMatches && bestLinkSupertypeMatches {
  111. continue
  112. } else {
  113. thisRating, err := thisLink.rating()
  114. if err != nil {
  115. return nil, err
  116. }
  117. bestRating, err := bestLink.rating()
  118. if err != nil {
  119. return nil, err
  120. }
  121. if thisRating > bestRating {
  122. bestLink = thisLink
  123. continue
  124. }
  125. }
  126. }
  127. return bestLink, nil
  128. }
  129. func (l *Link) Select() (string, *mime.MediaType, bool) {
  130. return l.SelectWithDefaultMediaType(mime.Unknown())
  131. }
  132. func (l *Link) SelectWithDefaultMediaType(defaultMediaType *mime.MediaType) (string, *mime.MediaType, bool) {
  133. if l.uriErr != nil {
  134. return "", nil, false
  135. }
  136. /* I suppress this error here because it is shown in the alt text */
  137. if l.mediaTypeErr == nil {
  138. return l.uri.String(), l.mediaType, true
  139. }
  140. if l.kind == "Audio" || l.kind == "Video" || l.kind == "Image" {
  141. return l.uri.String(), mime.UnknownSubtype(strings.ToLower(l.kind)), true
  142. }
  143. return l.uri.String(), defaultMediaType, true
  144. }
  145. func SelectFirstLink(links []*Link) (*Link, error) {
  146. if len(links) == 0 {
  147. return &Link{}, errors.New("can't select first Link from an empty list of links")
  148. } else {
  149. return links[0], nil
  150. }
  151. }