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 +5 -5
- data/AUTHORS +1 -0
- data/README.md +3 -3
- data/VERSION +1 -1
- data/lib/rack/linkeddata.rb +4 -4
- data/lib/rack/linkeddata/conneg.rb +92 -37
- metadata +29 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 966d05516d9e34419506a0fdaa47a04e61ba51b18630f98f2590fc7d82c1a00b
|
4
|
+
data.tar.gz: 2a616206491f89c318b16beb61a35688965958271dce37b5a0ce412a17568507
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86a3666ae1e6e3e0ef7bc0bb730883ccce7063ebe1f7053eead450ad7498dff6aecf9cd2fdadd00f3da9ab5d3acef68696899feb045e8c139c8e1342fcd5e8bc
|
7
|
+
data.tar.gz: e98c08daae3e76e3b3c7c8037b718b5be537be1e1e74578d36f2addaa99d38ec1a1aeb1dcd94eb3c566823d700d13023aad107cd28613663ed263ae8f6c55d8c
|
data/AUTHORS
CHANGED
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
|
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) (~>
|
102
|
-
* [Linked Data](http://rubygems.org/gems/linkeddata) (~>
|
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
|
-
|
1
|
+
3.1.0
|
data/lib/rack/linkeddata.rb
CHANGED
@@ -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!(
|
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}") ||
|
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}") ||
|
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
|
-
# @
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
# @
|
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)
|
101
|
-
|
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,
|
106
|
-
|
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
|
-
#
|
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
|
-
# @
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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.
|
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' => "
|
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:
|
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:
|
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: '
|
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: '
|
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: '
|
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: '
|
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:
|
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:
|
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.
|
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.
|
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: '
|
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: '
|
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.
|
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
|
-
|
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
|