Class: JSON::LD::ContentNegotiation

Inherits:
Object
  • Object
show all
Defined in:
lib/json/ld/conneg.rb

Overview

Rack middleware for JSON-LD content negotiation.

Uses HTTP Content Negotiation to serialize ‘Array` and `Hash` results as JSON-LD using ’profile’ accept-params to invoke appropriate JSON-LD API methods.

Allows black-listing and white-listing of two-part profiles where the second part denotes a URL of a context or frame. (See Writer.accept?)

Works along with ‘rack-linkeddata` for serializing data which is not in the form of an `RDF::Repository`.

Constant Summary collapse

VARY =
{'Vary' => 'Accept'}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ ContentNegotiation



39
40
41
# File 'lib/json/ld/conneg.rb', line 39

def initialize(app)
  @app = app
end

Instance Attribute Details

#app#call (readonly)



26
27
28
# File 'lib/json/ld/conneg.rb', line 26

def app
  @app
end

Class Method Details

.registered(app) ⇒ void

This method returns an undefined value.

  • Registers JSON::LD::Rack, suitable for Sinatra application

  • adds helpers



34
35
36
37
# File 'lib/json/ld/conneg.rb', line 34

def self.registered(app)
  options = {}
  app.use(JSON::LD::Rack, **options)
end

Instance Method Details

#call(env) ⇒ Array(Integer, Hash, #each)

Handles a Rack protocol request. Parses Accept header to find appropriate mime-type and sets content_type accordingly.



50
51
52
53
54
55
56
57
58
59
# File 'lib/json/ld/conneg.rb', line 50

def call(env)
  response = app.call(env)
  body = response[2].respond_to?(:body) ? response[2].body : response[2]
  case body
    when Array, Hash
      response[2] = body  # Put it back in the response, it might have been a proxy
      serialize(env, *response)
    else response
  end
end

#serialize(env, status, headers, body) ⇒ Array(Integer, Hash, #each)

Serializes objects as JSON-LD. Defaults to expanded form, other forms determined by presense of ‘profile` in accept-parms.



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/json/ld/conneg.rb', line 70

def serialize(env, status, headers, body)
  # This will only return json-ld content types, possibly with parameters
  content_types = parse_accept_header(env['HTTP_ACCEPT'] || 'application/ld+json')
  content_types = content_types.select do |content_type|
    _, *params = content_type.split(';').map(&:strip)
    accept_params = params.inject({}) do |memo, pv|
      p, v = pv.split('=').map(&:strip)
      memo.merge(p.downcase.to_sym => v.sub(/^["']?([^"']*)["']?$/, '\1'))
    end
    JSON::LD::Writer.accept?(accept_params)
  end
  if content_types.empty?
    not_acceptable("No appropriate combinaion of media-type and parameters found")
  else
    ct, *params = content_types.first.split(';').map(&:strip)
    accept_params = params.inject({}) do |memo, pv|
      p, v = pv.split('=').map(&:strip)
      memo.merge(p.downcase.to_sym => v.sub(/^["']?([^"']*)["']?$/, '\1'))
    end

    # Determine API method from profile
    profile = accept_params[:profile].to_s.split(' ')

    # Get context from Link header
    links = LinkHeader.parse(env['HTTP_LINK'])
    context = links.find_link(['rel', JSON_LD_NS+"context"]).href rescue nil
    frame = links.find_link(['rel', JSON_LD_NS+"frame"]).href rescue nil

    if profile.include?(JSON_LD_NS+"framed") && frame.nil?
      return not_acceptable("framed profile without a frame")
    end

    # accept? already determined that there are appropriate contexts
    # If profile also includes a URI which is not a namespace, use it for compaction.
    context ||= Writer.default_context if profile.include?(JSON_LD_NS+"compacted")

    result = if profile.include?(JSON_LD_NS+"flattened")
      API.flatten(body, context)
    elsif profile.include?(JSON_LD_NS+"framed")
      API.frame(body, frame)
    elsif context
      API.compact(body, context)
    elsif profile.include?(JSON_LD_NS+"expanded")
      API.expand(body)
    else
      body
    end

    headers = headers.merge(VARY).merge('Content-Type' => ct)
    [status, headers, [result.to_json]]
  end
rescue
  http_error(500, $!.message)
end