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.

115 lines
3.4 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, compare_id: nil)
  63. json = body.is_a?(String) ? Oj.load(body, mode: :strict) : body
  64. return if compare_id.present? && json['id'] != compare_id
  65. json
  66. rescue Oj::ParseError
  67. nil
  68. end
  69. def merge_context(context, new_context)
  70. if context.is_a?(Array)
  71. context << new_context
  72. else
  73. [context, new_context]
  74. end
  75. end
  76. private
  77. def build_request(uri, on_behalf_of = nil)
  78. request = Request.new(:get, uri)
  79. request.on_behalf_of(on_behalf_of) if on_behalf_of
  80. request.add_headers('Accept' => 'application/activity+json, application/ld+json')
  81. request
  82. end
  83. def load_jsonld_context(url, _options = {}, &_block)
  84. json = Rails.cache.fetch("jsonld:context:#{url}", expires_in: 30.days, raw: true) do
  85. request = Request.new(:get, url)
  86. request.add_headers('Accept' => 'application/ld+json')
  87. request.perform do |res|
  88. raise JSON::LD::JsonLdError::LoadingDocumentFailed unless res.code == 200 && res.mime_type == 'application/ld+json'
  89. res.body_with_limit
  90. end
  91. end
  92. doc = JSON::LD::API::RemoteDocument.new(url, json)
  93. block_given? ? yield(doc) : doc
  94. end
  95. end