rack-linkeddata 2.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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