link.go 3.9 KB

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