You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

113 lines
3.3 KiB

  1. # frozen_string_literal: true
  2. module JsonLdHelper
  3. def equals_or_includes?(haystack, needle)
  4. haystack.is_a?(Array) ? haystack.include?(needle) : haystack == needle
  5. end
  6. def equals_or_includes_any?(haystack, needles)
  7. needles.any? { |needle| equals_or_includes?(haystack, needle) }
  8. end
  9. def first_of_value(value)
  10. value.is_a?(Array) ? value.first : value
  11. end
  12. # The url attribute can be a string, an array of strings, or an array of objects.
  13. # The objects could include a mimeType. Not-included mimeType means it's text/html.
  14. def url_to_href(value, preferred_type = nil)
  15. single_value = if value.is_a?(Array) && !value.first.is_a?(String)
  16. value.find { |link| preferred_type.nil? || ((link['mimeType'].presence || 'text/html') == preferred_type) }
  17. elsif value.is_a?(Array)
  18. value.first
  19. else
  20. value
  21. end
  22. if single_value.nil? || single_value.is_a?(String)
  23. single_value
  24. else
  25. single_value['href']
  26. end
  27. end
  28. def as_array(value)
  29. value.is_a?(Array) ? value : [value]
  30. end
  31. def value_or_id(value)
  32. value.is_a?(String) || value.nil? ? value : value['id']
  33. end
  34. def supported_context?(json)
  35. !json.nil? && equals_or_includes?(json['@context'], ActivityPub::TagManager::CONTEXT)
  36. end
  37. def unsupported_uri_scheme?(uri)
  38. !uri.start_with?('http://', 'https://')
  39. end
  40. def canonicalize(json)
  41. graph = RDF::Graph.new << JSON::LD::API.toRdf(json, documentLoader: method(:load_jsonld_context))
  42. graph.dump(:normalize)
  43. end
  44. def fetch_resource(uri, id, on_behalf_of = nil)
  45. unless id
  46. json = fetch_resource_without_id_validation(uri, on_behalf_of)
  47. return unless json
  48. uri = json['id']
  49. end
  50. json = fetch_resource_without_id_validation(uri, on_behalf_of)
  51. json.present? && json['id'] == uri ? json : nil
  52. end
  53. def fetch_resource_without_id_validation(uri, on_behalf_of = nil)
  54. build_request(uri, on_behalf_of).perform do |response|
  55. return body_to_json(response.body_with_limit) if response.code == 200
  56. end
  57. # If request failed, retry without doing it on behalf of a user
  58. build_request(uri).perform do |response|
  59. response.code == 200 ? body_to_json(response.body_with_limit) : nil
  60. end
  61. end
  62. def body_to_json(body)
  63. body.is_a?(String) ? Oj.load(body, mode: :strict) : body
  64. rescue Oj::ParseError
  65. nil
  66. end
  67. def merge_context(context, new_context)
  68. if context.is_a?(Array)
  69. context << new_context
  70. else
  71. [context, new_context]
  72. end
  73. end
  74. private
  75. def build_request(uri, on_behalf_of = nil)
  76. request = Request.new(:get, uri)
  77. request.on_behalf_of(on_behalf_of) if on_behalf_of
  78. request.add_headers('Accept' => 'application/activity+json, application/ld+json')
  79. request
  80. end
  81. def load_jsonld_context(url, _options = {}, &_block)
  82. json = Rails.cache.fetch("jsonld:context:#{url}", expires_in: 30.days, raw: true) do
  83. request = Request.new(:get, url)
  84. request.add_headers('Accept' => 'application/ld+json')
  85. request.perform do |res|
  86. raise JSON::LD::JsonLdError::LoadingDocumentFailed unless res.code == 200 && res.mime_type == 'application/ld+json'
  87. res.body_with_limit
  88. end
  89. end
  90. doc = JSON::LD::API::RemoteDocument.new(url, json)
  91. block_given? ? yield(doc) : doc
  92. end
  93. end