link.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. package pub
  2. import (
  3. "net/url"
  4. "errors"
  5. "mimicry/object"
  6. "mimicry/mime"
  7. "fmt"
  8. "golang.org/x/exp/slices"
  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. // TODO: narrow input to o (an object.Object)
  26. o, ok := input.(object.Object)
  27. if !ok {
  28. return nil, fmt.Errorf("can't turn non-object %T into Link", input)
  29. }
  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) Kind() string {
  53. return l.kind
  54. }
  55. func (l *Link) Alt() (string, error) {
  56. if l.altErr == nil {
  57. return l.alt, nil
  58. } else if errors.Is(l.altErr, object.ErrKeyNotPresent) {
  59. if l.uriErr == nil {
  60. return l.uri.String(), nil
  61. } else {
  62. return "", l.uriErr
  63. }
  64. } else {
  65. return "", l.altErr
  66. }
  67. }
  68. func (l *Link) rating() (uint64, error) {
  69. var height, width uint64
  70. if l.heightErr == nil {
  71. height = l.height
  72. } else if errors.Is(l.heightErr, object.ErrKeyNotPresent) {
  73. height = 1
  74. } else {
  75. return 0, l.heightErr
  76. }
  77. if l.widthErr == nil {
  78. width = l.width
  79. } else if errors.Is(l.widthErr, object.ErrKeyNotPresent) {
  80. width = 1
  81. } else {
  82. return 0, l.widthErr
  83. }
  84. return height * width, nil
  85. }
  86. func (l *Link) MediaType() (*mime.MediaType, error) {
  87. return l.mediaType, l.mediaTypeErr
  88. }
  89. func SelectBestLink(links []*Link, supertype string) (*Link, error) {
  90. if len(links) == 0 {
  91. return &Link{}, errors.New("can't select best link of type " + supertype + "/* from an empty list")
  92. }
  93. bestLink := links[0]
  94. // TODO: loop through once and validate errors, then proceed assuming no errors
  95. for _, thisLink := range links[1:] {
  96. var bestLinkSupertypeMatches bool
  97. if errors.Is(bestLink.mediaTypeErr, object.ErrKeyNotPresent) {
  98. bestLinkSupertypeMatches = false
  99. } else if bestLink.mediaTypeErr != nil {
  100. return nil, bestLink.mediaTypeErr
  101. } else {
  102. bestLinkSupertypeMatches = bestLink.mediaType.Supertype == supertype
  103. }
  104. var thisLinkSuperTypeMatches bool
  105. if errors.Is(thisLink.mediaTypeErr, object.ErrKeyNotPresent) {
  106. thisLinkSuperTypeMatches = false
  107. } else if thisLink.mediaTypeErr != nil {
  108. return nil, thisLink.mediaTypeErr
  109. } else {
  110. thisLinkSuperTypeMatches = thisLink.mediaType.Supertype == supertype
  111. }
  112. if thisLinkSuperTypeMatches && !bestLinkSupertypeMatches {
  113. bestLink = thisLink
  114. continue
  115. } else if !thisLinkSuperTypeMatches && bestLinkSupertypeMatches {
  116. continue
  117. } else {
  118. thisRating, err := thisLink.rating()
  119. if err != nil { return nil, err }
  120. bestRating, err := bestLink.rating()
  121. if err != nil { return nil, err }
  122. if thisRating > bestRating {
  123. bestLink = thisLink
  124. continue
  125. }
  126. }
  127. }
  128. return bestLink, nil
  129. }
  130. func SelectFirstLink(links []*Link) (*Link, error) {
  131. if len(links) == 0 {
  132. return &Link{}, errors.New("can't select first Link from an empty list of links")
  133. } else {
  134. return links[0], nil
  135. }
  136. }