rack-linkeddata 2.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 09fce0e2585a150bd2ad4344612ca71216188d7e
4
- data.tar.gz: 552a7b73cdb1c8546f248b711cb1ab375b9b529a
2
+ SHA256:
3
+ metadata.gz: 966d05516d9e34419506a0fdaa47a04e61ba51b18630f98f2590fc7d82c1a00b
4
+ data.tar.gz: 2a616206491f89c318b16beb61a35688965958271dce37b5a0ce412a17568507
5
5
  SHA512:
6
- metadata.gz: 2a4b51266cce02045005de02a975ce705c5fc33203f813e5afc6b10b68b00e4a5fa259aba9aa0868938d9c0db40cdb33aa114acb78461b0c3b60898169a905de
7
- data.tar.gz: 1a3a2359a061e14cca2d0bc9c5fd253a8100cd72fd83a49ecbbc36d723cca90b36cca068df285cd1dd6a0fbb7e70858ebd189f19e436be4ff6e04a42f6326548
6
+ metadata.gz: 86a3666ae1e6e3e0ef7bc0bb730883ccce7063ebe1f7053eead450ad7498dff6aecf9cd2fdadd00f3da9ab5d3acef68696899feb045e8c139c8e1342fcd5e8bc
7
+ data.tar.gz: e98c08daae3e76e3b3c7c8037b718b5be537be1e1e74578d36f2addaa99d38ec1a1aeb1dcd94eb3c566823d700d13023aad107cd28613663ed263ae8f6c55d8c
data/AUTHORS CHANGED
@@ -1 +1,2 @@
1
1
  * Arto Bendiken <arto.bendiken@gmail.com>
2
+ * Gregg Kellogg <gregg@greggkellogg.net>
data/README.md CHANGED
@@ -85,7 +85,7 @@ take care of serializing your response into whatever RDF format the HTTP
85
85
  client requested and understands.
86
86
 
87
87
  The middleware queries [RDF.rb][] for the MIME content types of known RDF
88
- serialization formats, so it will work with whatever serialization plugins
88
+ serialization formats, so it will work with whatever serialization extensions
89
89
  that are currently available for RDF.rb. (At present, this includes support
90
90
  for N-Triples, N-Quads, Turtle, RDF/XML, RDF/JSON, JSON-LD, RDFa, TriG and TriX.)
91
91
 
@@ -98,8 +98,8 @@ for N-Triples, N-Quads, Turtle, RDF/XML, RDF/JSON, JSON-LD, RDFa, TriG and TriX.
98
98
 
99
99
  ## Dependencies
100
100
 
101
- * [Rack](http://rubygems.org/gems/rack) (~> 1.6)
102
- * [Linked Data](http://rubygems.org/gems/linkeddata) (~> 2.0)
101
+ * [Rack](http://rubygems.org/gems/rack) (~> 2.0)
102
+ * [Linked Data](http://rubygems.org/gems/linkeddata) (~> 3.1)
103
103
 
104
104
  ## Installation
105
105
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0
1
+ 3.1.0
@@ -9,18 +9,18 @@ module Rack
9
9
  ##
10
10
  # Registers all known RDF formats with Rack's MIME types registry.
11
11
  #
12
+ # @param [Boolean] :overwrite (false)
12
13
  # @param [Hash{Symbol => Object}] options
13
- # @option options [Boolean] :overwrite (false)
14
14
  # @return [void]
15
- def self.register_mime_types!(options = {})
15
+ def self.register_mime_types!(overwrite: false, **options)
16
16
  if defined?(Rack::Mime::MIME_TYPES)
17
17
  RDF::Format.each do |format|
18
- if !Rack::Mime::MIME_TYPES.has_key?(file_ext = ".#{format.to_sym}") || options[:overwrite]
18
+ if !Rack::Mime::MIME_TYPES.has_key?(file_ext = ".#{format.to_sym}") || overwrite
19
19
  Rack::Mime::MIME_TYPES.merge!(file_ext => format.content_type.first)
20
20
  end
21
21
  end
22
22
  RDF::Format.file_extensions.each do |file_ext, formats|
23
- if !Rack::Mime::MIME_TYPES.has_key?(file_ext = ".#{file_ext}") || options[:overwrite]
23
+ if !Rack::Mime::MIME_TYPES.has_key?(file_ext = ".#{file_ext}") || overwrite
24
24
  Rack::Mime::MIME_TYPES.merge!(file_ext => formats.first.content_type.first)
25
25
  end
26
26
  end
@@ -17,6 +17,7 @@ module Rack; module LinkedData
17
17
  # use Rack::LinkedData::ContentNegotiation, :default => 'application/rdf+xml'
18
18
  #
19
19
  # @see http://www4.wiwiss.fu-berlin.de/bizer/pub/LinkedDataTutorial/
20
+ # @see https://www.rubydoc.info/github/rack/rack/master/file/SPEC
20
21
  class ContentNegotiation
21
22
  DEFAULT_CONTENT_TYPE = "application/n-triples" # N-Triples
22
23
  VARY = {'Vary' => 'Accept'}.freeze
@@ -31,9 +32,9 @@ module Rack; module LinkedData
31
32
  # @param [#call] app
32
33
  # @param [Hash{Symbol => Object}] options
33
34
  # Other options passed to writer.
34
- # @option options [String] :default (DEFAULT_CONTENT_TYPE) Specific content type
35
+ # @param [String] :default (DEFAULT_CONTENT_TYPE) Specific content type
35
36
  # @option options [RDF::Format, #to_sym] :format Specific RDF writer format to use
36
- def initialize(app, options = {})
37
+ def initialize(app, options)
37
38
  @app, @options = app, options
38
39
  @options[:default] = (@options[:default] || DEFAULT_CONTENT_TYPE).to_s
39
40
  end
@@ -45,7 +46,7 @@ module Rack; module LinkedData
45
46
  # Inserts ordered content types into the environment as `ORDERED_CONTENT_TYPES` if an Accept header is present
46
47
  #
47
48
  # @param [Hash{String => String}] env
48
- # @return [Array(Integer, Hash, #each)]
49
+ # @return [Array(Integer, Hash, #each)] Status, Headers and Body
49
50
  # @see http://rack.rubyforge.org/doc/SPEC.html
50
51
  def call(env)
51
52
  env['ORDERED_CONTENT_TYPES'] = parse_accept_header(env['HTTP_ACCEPT']) if env.has_key?('HTTP_ACCEPT')
@@ -63,28 +64,43 @@ module Rack; module LinkedData
63
64
  # Serializes an `RDF::Enumerable` response into a Rack protocol
64
65
  # response using HTTP content negotiation rules or a specified Content-Type.
65
66
  #
67
+ # Passes parameters from Accept header, and Link header to writer.
68
+ #
66
69
  # @param [Hash{String => String}] env
67
70
  # @param [Integer] status
68
71
  # @param [Hash{String => Object}] headers
69
72
  # @param [RDF::Enumerable] body
70
- # @return [Array(Integer, Hash, #each)]
73
+ # @return [Array(Integer, Hash, #each)] Status, Headers and Body
71
74
  def serialize(env, status, headers, body)
72
- begin
73
- writer, content_type = find_writer(env, headers)
74
- if writer
75
- # FIXME: don't overwrite existing Vary headers
76
- headers = headers.merge(VARY).merge('Content-Type' => content_type)
77
- [status, headers, [writer.dump(body, nil, @options)]]
78
- else
79
- not_acceptable
75
+ result, content_type = nil, nil
76
+ find_writer(env, headers) do |writer, ct, accept_params = {}|
77
+ begin
78
+ # Passes content_type as writer option to allow parameters to be extracted.
79
+ writer_options = @options.merge(
80
+ accept_params: accept_params,
81
+ link: env['HTTP_LINK']
82
+ )
83
+ result, content_type = writer.dump(body, nil, **writer_options), ct.split(';').first
84
+ break
85
+ rescue RDF::WriterError
86
+ # Continue to next writer
87
+ ct
88
+ rescue
89
+ ct
80
90
  end
81
- rescue RDF::WriterError => e
91
+ end
92
+
93
+ if result
94
+ headers = headers.merge(VARY).merge('Content-Type' => content_type)
95
+ [status, headers, [result]]
96
+ else
82
97
  not_acceptable
83
98
  end
84
99
  end
85
100
 
101
+ protected
86
102
  ##
87
- # Returns an `RDF::Writer` class for the given `env`.
103
+ # Yields an `RDF::Writer` class for the given `env`.
88
104
  #
89
105
  # If options contain a `:format` key, it identifies the specific format to use;
90
106
  # otherwise, if the environment has an HTTP_ACCEPT header, use it to find a writer;
@@ -92,46 +108,56 @@ module Rack; module LinkedData
92
108
  #
93
109
  # @param [Hash{String => String}] env
94
110
  # @param [Hash{String => Object}] headers
95
- # @return [Array(Class, String)]
111
+ # @yield |writer, content_type|
112
+ # @yield_param [RDF::Writer] writer
113
+ # @yield_param [String] content_type from accept media-range without parameters
114
+ # @yield_param [Hash{Symbol => String}] accept_params from accept media-range
96
115
  # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
97
116
  def find_writer(env, headers)
98
117
  if @options[:format]
99
118
  format = @options[:format]
100
- writer = RDF::Writer.for(format.to_sym) unless format.is_a?(RDF::Format)
101
- return [writer, writer.format.content_type.first] if writer
119
+ writer = RDF::Writer.for(format.to_sym)
120
+ yield(writer, writer.format.content_type.first) if writer
102
121
  elsif env.has_key?('HTTP_ACCEPT')
103
122
  content_types = parse_accept_header(env['HTTP_ACCEPT'])
104
123
  content_types.each do |content_type|
105
- writer, content_type = find_writer_for_content_type(content_type)
106
- return [writer, content_type] if writer
124
+ find_writer_for_content_type(content_type) do |writer, ct, accept_params|
125
+ # Yields content type with parameters
126
+ yield(writer, ct, accept_params)
127
+ end
107
128
  end
108
- return nil
109
129
  else
110
130
  # HTTP/1.1 §14.1: "If no Accept header field is present, then it is
111
131
  # assumed that the client accepts all media types"
112
- find_writer_for_content_type(options[:default])
132
+ find_writer_for_content_type(options[:default]) do |writer, ct|
133
+ # Yields content type with parameters
134
+ yield(writer, ct)
135
+ end
113
136
  end
114
137
  end
115
138
 
116
139
  ##
117
- # Returns an `RDF::Writer` class for the given `content_type`.
140
+ # Yields an `RDF::Writer` class for the given `content_type`.
141
+ #
142
+ # Calls `Writer#accept?(content_type)` for matched content type to allow writers to further discriminate on how if to accept content-type with specified parameters.
118
143
  #
119
144
  # @param [String, #to_s] content_type
120
- # @return [Array(Class, String)]
145
+ # @yield |writer, content_type|
146
+ # @yield_param [RDF::Writer] writer
147
+ # @yield_param [String] content_type (including media-type parameters)
121
148
  def find_writer_for_content_type(content_type)
122
- writer = case content_type.to_s
123
- when '*/*'
124
- RDF::Writer.for(:content_type => (content_type = options[:default]))
125
- when /^([^\/]+)\/\*$/
126
- nil # TODO: match subtype wildcards
127
- else
128
- RDF::Writer.for(:content_type => content_type)
149
+ ct, *params = content_type.split(';').map(&:strip)
150
+ accept_params = params.inject({}) do |memo, pv|
151
+ p, v = pv.split('=').map(&:strip)
152
+ memo.merge(p.downcase.to_sym => v.sub(/^["']?([^"']*)["']?$/, '\1'))
153
+ end
154
+ formats = RDF::Format.each(content_type: ct, has_writer: true).to_a.reverse
155
+ formats.each do |format|
156
+ yield format.writer, (ct || format.content_type.first), accept_params if
157
+ format.writer.accept?(accept_params)
129
158
  end
130
- writer ? [writer, content_type] : nil
131
159
  end
132
160
 
133
- protected
134
-
135
161
  ##
136
162
  # Parses an HTTP `Accept` header, returning an array of MIME content
137
163
  # types ordered by the precedence rules defined in HTTP/1.1 §14.1.
@@ -141,14 +167,43 @@ module Rack; module LinkedData
141
167
  # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
142
168
  def parse_accept_header(header)
143
169
  entries = header.to_s.split(',')
144
- entries.map { |e| accept_entry(e) }.sort_by(&:last).map(&:first)
170
+ entries = entries.map { |e| accept_entry(e) }.sort_by(&:last).map(&:first)
171
+ entries.map { |e| find_content_type_for_media_range(e) }.flatten
145
172
  end
146
173
 
174
+ # Returns pair of content_type (including non-'q' parameters)
175
+ # and array of quality, number of '*' in content-type, and number of non-'q' parameters
147
176
  def accept_entry(entry)
148
- type, *options = entry.delete(' ').split(';')
177
+ type, *options = entry.split(';').map(&:strip)
149
178
  quality = 0 # we sort smallest first
150
179
  options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' }
151
- [type, [quality, type.count('*'), 1 - options.size]]
180
+ [options.unshift(type).join(';'), [quality, type.count('*'), 1 - options.size]]
181
+ end
182
+
183
+ ##
184
+ # Returns a content type appropriate for the given `media_range`,
185
+ # returns `nil` if `media_range` contains a wildcard subtype
186
+ # that is not mapped.
187
+ #
188
+ # @param [String, #to_s] media_range
189
+ # @return [String, nil]
190
+ def find_content_type_for_media_range(media_range)
191
+ case media_range.to_s
192
+ when '*/*'
193
+ options[:default]
194
+ when 'text/*'
195
+ 'text/turtle'
196
+ when 'application/*'
197
+ 'application/ld+json'
198
+ when 'application/json'
199
+ 'application/ld+json'
200
+ when 'application/xml'
201
+ 'application/rdf+xml'
202
+ when /^([^\/]+)\/\*$/
203
+ nil
204
+ else
205
+ media_range.to_s
206
+ end
152
207
  end
153
208
 
154
209
  ##
@@ -169,7 +224,7 @@ module Rack; module LinkedData
169
224
  # @return [Array(Integer, Hash, #each)]
170
225
  def http_error(code, message = nil, headers = {})
171
226
  message = http_status(code) + (message.nil? ? "\n" : " (#{message})\n")
172
- [code, {'Content-Type' => "#{DEFAULT_CONTENT_TYPE}; charset=utf-8"}.merge(headers), [message]]
227
+ [code, {'Content-Type' => "text/plain"}.merge(headers), [message]]
173
228
  end
174
229
 
175
230
  ##
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-linkeddata
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arto Bendiken
8
+ - Gregg Kellogg
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2016-04-10 00:00:00.000000000 Z
12
+ date: 2019-12-16 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: linkeddata
@@ -16,70 +17,84 @@ dependencies:
16
17
  requirements:
17
18
  - - "~>"
18
19
  - !ruby/object:Gem::Version
19
- version: '2.0'
20
+ version: '3.1'
20
21
  type: :runtime
21
22
  prerelease: false
22
23
  version_requirements: !ruby/object:Gem::Requirement
23
24
  requirements:
24
25
  - - "~>"
25
26
  - !ruby/object:Gem::Version
26
- version: '2.0'
27
+ version: '3.1'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rdf
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '3.1'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '3.1'
27
42
  - !ruby/object:Gem::Dependency
28
43
  name: rack
29
44
  requirement: !ruby/object:Gem::Requirement
30
45
  requirements:
31
46
  - - "~>"
32
47
  - !ruby/object:Gem::Version
33
- version: '1.6'
48
+ version: '2.0'
34
49
  type: :runtime
35
50
  prerelease: false
36
51
  version_requirements: !ruby/object:Gem::Requirement
37
52
  requirements:
38
53
  - - "~>"
39
54
  - !ruby/object:Gem::Version
40
- version: '1.6'
55
+ version: '2.0'
41
56
  - !ruby/object:Gem::Dependency
42
57
  name: yard
43
58
  requirement: !ruby/object:Gem::Requirement
44
59
  requirements:
45
60
  - - "~>"
46
61
  - !ruby/object:Gem::Version
47
- version: '0.8'
62
+ version: 0.9.20
48
63
  type: :development
49
64
  prerelease: false
50
65
  version_requirements: !ruby/object:Gem::Requirement
51
66
  requirements:
52
67
  - - "~>"
53
68
  - !ruby/object:Gem::Version
54
- version: '0.8'
69
+ version: 0.9.20
55
70
  - !ruby/object:Gem::Dependency
56
71
  name: rspec
57
72
  requirement: !ruby/object:Gem::Requirement
58
73
  requirements:
59
74
  - - "~>"
60
75
  - !ruby/object:Gem::Version
61
- version: '3.4'
76
+ version: '3.9'
62
77
  type: :development
63
78
  prerelease: false
64
79
  version_requirements: !ruby/object:Gem::Requirement
65
80
  requirements:
66
81
  - - "~>"
67
82
  - !ruby/object:Gem::Version
68
- version: '3.4'
83
+ version: '3.9'
69
84
  - !ruby/object:Gem::Dependency
70
85
  name: rack-test
71
86
  requirement: !ruby/object:Gem::Requirement
72
87
  requirements:
73
88
  - - "~>"
74
89
  - !ruby/object:Gem::Version
75
- version: '0.6'
90
+ version: '1.1'
76
91
  type: :development
77
92
  prerelease: false
78
93
  version_requirements: !ruby/object:Gem::Requirement
79
94
  requirements:
80
95
  - - "~>"
81
96
  - !ruby/object:Gem::Version
82
- version: '0.6'
97
+ version: '1.1'
83
98
  description: Rack middleware for Linked Data content negotiation.
84
99
  email: public-rdf-ruby@w3.org
85
100
  executables: []
@@ -106,17 +121,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
106
121
  requirements:
107
122
  - - ">="
108
123
  - !ruby/object:Gem::Version
109
- version: '2.0'
124
+ version: '2.4'
110
125
  required_rubygems_version: !ruby/object:Gem::Requirement
111
126
  requirements:
112
127
  - - ">="
113
128
  - !ruby/object:Gem::Version
114
129
  version: '0'
115
130
  requirements: []
116
- rubyforge_project: datagraph
117
- rubygems_version: 2.4.8
131
+ rubygems_version: 3.0.6
118
132
  signing_key:
119
133
  specification_version: 4
120
134
  summary: Linked Data content negotiation for Rack applications.
121
135
  test_files: []
122
- has_rdoc: false