rdf-ldp 0.9.2 → 2.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/CHANGELOG.md +10 -0
- data/CREDITS +1 -0
- data/README.md +66 -10
- data/UNLICENSE +1 -1
- data/VERSION +1 -1
- data/app/lamprey.rb +119 -40
- data/bin/lamprey +15 -1
- data/lib/rack/ldp.rb +23 -25
- data/lib/rdf/ldp/container.rb +32 -22
- data/lib/rdf/ldp/direct_container.rb +9 -7
- data/lib/rdf/ldp/indirect_container.rb +8 -6
- data/lib/rdf/ldp/interaction_model.rb +39 -29
- data/lib/rdf/ldp/non_rdf_source.rb +10 -6
- data/lib/rdf/ldp/rdf_source.rb +3 -3
- data/lib/rdf/ldp/resource.rb +33 -31
- data/lib/rdf/ldp/spec/container.rb +337 -0
- data/lib/rdf/ldp/spec/direct_container.rb +245 -0
- data/lib/rdf/ldp/spec/indirect_container.rb +152 -0
- data/lib/rdf/ldp/spec/non_rdf_source.rb +138 -0
- data/lib/rdf/ldp/spec/rdf_source.rb +370 -0
- data/lib/rdf/ldp/spec/resource.rb +242 -0
- data/lib/rdf/ldp/spec.rb +6 -0
- data/lib/rdf/ldp/storage_adapters/file_storage_adapter.rb +4 -4
- data/lib/rdf/ldp/version.rb +16 -3
- data/lib/rdf/ldp.rb +4 -3
- metadata +89 -55
@@ -0,0 +1,242 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'rdf/spec'
|
3
|
+
require 'rdf/spec/matchers'
|
4
|
+
require 'timecop'
|
5
|
+
|
6
|
+
shared_examples 'a Resource' do
|
7
|
+
describe '.to_uri' do
|
8
|
+
it { expect(described_class.to_uri).to be_a RDF::URI }
|
9
|
+
end
|
10
|
+
|
11
|
+
subject { described_class.new(uri) }
|
12
|
+
let(:uri) { RDF::URI 'http://example.org/moomin' }
|
13
|
+
|
14
|
+
it { is_expected.to be_ldp_resource }
|
15
|
+
it { is_expected.to respond_to :container? }
|
16
|
+
it { is_expected.to respond_to :rdf_source? }
|
17
|
+
it { is_expected.to respond_to :non_rdf_source? }
|
18
|
+
|
19
|
+
it { subject.send(:set_last_modified) }
|
20
|
+
|
21
|
+
describe '#exists?' do
|
22
|
+
it 'does not exist' do
|
23
|
+
expect(subject).not_to exist
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'while existing' do
|
27
|
+
before { subject.create(StringIO.new, 'application/n-triples') }
|
28
|
+
|
29
|
+
subject { described_class.new(uri, repository) }
|
30
|
+
let(:repository) { RDF::Repository.new }
|
31
|
+
|
32
|
+
it 'exists' do
|
33
|
+
expect(subject).to exist
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'is different from same URI with trailing /' do
|
37
|
+
expect(described_class.new(uri + '/', repository)).not_to exist
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#allowed_methods' do
|
43
|
+
it 'responds to all methods returned' do
|
44
|
+
subject.allowed_methods.each do |method|
|
45
|
+
expect(subject.respond_to?(method.downcase, true)).to be true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'includes the MUST methods' do
|
50
|
+
expect(subject.allowed_methods).to include(:GET, :OPTIONS, :HEAD)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#create' do
|
55
|
+
it 'accepts two args' do
|
56
|
+
expect(described_class.instance_method(:create).arity).to eq 2
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'modified time' do
|
60
|
+
before { Timecop.freeze }
|
61
|
+
after { Timecop.return }
|
62
|
+
|
63
|
+
it 'sets last_modified' do
|
64
|
+
subject.create(StringIO.new, 'text/turtle')
|
65
|
+
expect(subject.last_modified).to eq DateTime.now
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'adds a type triple to metagraph' do
|
70
|
+
subject.create(StringIO.new, 'application/n-triples')
|
71
|
+
expect(subject.metagraph)
|
72
|
+
.to have_statement RDF::Statement(subject.subject_uri,
|
73
|
+
RDF.type,
|
74
|
+
described_class.to_uri)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'yields a transaction' do
|
78
|
+
expect { |b| subject.create(StringIO.new, 'application/n-triples', &b) }
|
79
|
+
.to yield_with_args(be_kind_of(RDF::Transaction))
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'marks resource as existing' do
|
83
|
+
expect { subject.create(StringIO.new, 'application/n-triples') }
|
84
|
+
.to change { subject.exists? }.from(false).to(true)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'returns self' do
|
88
|
+
expect(subject.create(StringIO.new, 'application/n-triples'))
|
89
|
+
.to eq subject
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'raises Conlict when already exists' do
|
93
|
+
subject.create(StringIO.new, 'application/n-triples')
|
94
|
+
expect { subject.create(StringIO.new, 'application/n-triples') }
|
95
|
+
.to raise_error RDF::LDP::Conflict
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#update' do
|
100
|
+
it 'accepts two args' do
|
101
|
+
expect(described_class.instance_method(:update).arity).to eq 2
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'returns self' do
|
105
|
+
expect(subject.update(StringIO.new, 'application/n-triples'))
|
106
|
+
.to eq subject
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'yields a changeset' do
|
110
|
+
expect { |b| subject.update(StringIO.new, 'application/n-triples', &b) }
|
111
|
+
.to yield_with_args(be_kind_of(RDF::Transaction))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe '#destroy' do
|
116
|
+
it 'accepts no args' do
|
117
|
+
expect(described_class.instance_method(:destroy).arity).to eq 0
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe '#metagraph' do
|
122
|
+
it 'returns a graph' do
|
123
|
+
expect(subject.metagraph).to be_a RDF::Graph
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'has the metagraph name for the resource' do
|
127
|
+
expect(subject.metagraph.graph_name).to eq subject.subject_uri / '#meta'
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe '#etag' do
|
132
|
+
before { subject.create(StringIO.new, 'application/n-triples') }
|
133
|
+
|
134
|
+
it 'has an etag' do
|
135
|
+
expect(subject.etag).to be_a String
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'updates etag on change' do
|
139
|
+
expect { subject.update(StringIO.new, 'application/n-triples') }
|
140
|
+
.to change { subject.etag }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe '#last_modified' do
|
145
|
+
it 'returns nil when no dc:modified triple is present' do
|
146
|
+
expect(subject.last_modified).to be_nil
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'raises an error when exists without dc:modified triple is present' do
|
150
|
+
allow(subject).to receive(:exists?).and_return true
|
151
|
+
expect { subject.last_modified }.to raise_error RDF::LDP::RequestError
|
152
|
+
end
|
153
|
+
|
154
|
+
context 'with dc:modified triple' do
|
155
|
+
before do
|
156
|
+
subject.metagraph.update([subject.subject_uri,
|
157
|
+
RDF::Vocab::DC.modified,
|
158
|
+
datetime])
|
159
|
+
end
|
160
|
+
|
161
|
+
let(:datetime) { DateTime.now }
|
162
|
+
|
163
|
+
it 'returns date in `dc:modified`' do
|
164
|
+
expect(subject.last_modified).to eq datetime
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe '#to_response' do
|
170
|
+
it 'returns an object that responds to #each' do
|
171
|
+
expect(subject.to_response).to respond_to :each
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe '#request' do
|
176
|
+
it 'sends the message to itself' do
|
177
|
+
expect(subject).to receive(:blah)
|
178
|
+
subject.request(:BLAH, 200, {}, {})
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'raises MethodNotAllowed when method is unimplemented' do
|
182
|
+
allow(subject).to receive(:not_implemented)
|
183
|
+
.and_raise NotImplementedError
|
184
|
+
expect { subject.request(:not_implemented, 200, {}, {}) }
|
185
|
+
.to raise_error(RDF::LDP::MethodNotAllowed)
|
186
|
+
end
|
187
|
+
|
188
|
+
[:GET, :OPTIONS, :HEAD].each do |method|
|
189
|
+
it "responds to #{method}" do
|
190
|
+
expect(subject.request(method, 200, {}, {}).size).to eq 3
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
[:PATCH, :POST, :PUT, :DELETE, :TRACE, :CONNECT].each do |method|
|
195
|
+
it "responds to or errors on #{method}" do
|
196
|
+
g = RDF::Graph.new << [RDF::Node.new, RDF.type, 'moomin']
|
197
|
+
env = { 'CONTENT_TYPE' => 'application/n-triples',
|
198
|
+
'rack.input' => StringIO.new(g.dump(:ntriples)) }
|
199
|
+
|
200
|
+
begin
|
201
|
+
response = subject.request(method, 200, {}, env)
|
202
|
+
expect(response.size).to eq 3
|
203
|
+
rescue => e
|
204
|
+
expect(e).to be_a RDF::LDP::RequestError
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe 'HTTP headers' do
|
210
|
+
before { subject.create(StringIO.new, 'text/turtle') }
|
211
|
+
let(:headers) { subject.request(:GET, 200, {}, {})[1] }
|
212
|
+
|
213
|
+
it 'has ETag' do
|
214
|
+
expect(headers['ETag']).to eq subject.etag
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'has Last-Modified' do
|
218
|
+
expect(headers['Last-Modified']).to eq subject.last_modified.httpdate
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'has Allow' do
|
222
|
+
expect(headers['Allow']).to be_a String
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'has Link' do
|
226
|
+
expect(headers['Link']).to be_a String
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'responds to :GET' do
|
231
|
+
expect { subject.request(:GET, 200, {}, {}) }.not_to raise_error
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'responds to :HEAD' do
|
235
|
+
expect { subject.request(:OPTIONS, 200, {}, {}) }.not_to raise_error
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'responds to :OPTIONS' do
|
239
|
+
expect { subject.request(:OPTIONS, 200, {}, {}) }.not_to raise_error
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
data/lib/rdf/ldp/spec.rb
ADDED
@@ -63,7 +63,7 @@ module RDF::LDP
|
|
63
63
|
#
|
64
64
|
# @return [IO] an object conforming to the Ruby IO interface
|
65
65
|
def io(&block)
|
66
|
-
FileUtils.mkdir_p(path_dir) unless Dir.
|
66
|
+
FileUtils.mkdir_p(path_dir) unless Dir.exist?(path_dir)
|
67
67
|
FileUtils.touch(path) unless file_exists?
|
68
68
|
|
69
69
|
File.open(path, 'r+b', &block)
|
@@ -72,7 +72,7 @@ module RDF::LDP
|
|
72
72
|
##
|
73
73
|
# @return [Boolean] 1 if the file has been deleted, otherwise false
|
74
74
|
def delete
|
75
|
-
return false unless File.
|
75
|
+
return false unless File.exist?(path)
|
76
76
|
File.delete(path)
|
77
77
|
end
|
78
78
|
|
@@ -81,7 +81,7 @@ module RDF::LDP
|
|
81
81
|
##
|
82
82
|
# @return [Boolean]
|
83
83
|
def file_exists?
|
84
|
-
File.
|
84
|
+
File.exist?(path)
|
85
85
|
end
|
86
86
|
|
87
87
|
##
|
@@ -99,4 +99,4 @@ module RDF::LDP
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
end
|
102
|
-
end
|
102
|
+
end
|
data/lib/rdf/ldp/version.rb
CHANGED
@@ -1,4 +1,11 @@
|
|
1
1
|
module RDF::LDP
|
2
|
+
##
|
3
|
+
# Version Number
|
4
|
+
#
|
5
|
+
# @example getting a version string
|
6
|
+
# RDF::LDP::VERSION # or
|
7
|
+
# "#{RDF::LDP::VERSION}"
|
8
|
+
#
|
2
9
|
module VERSION
|
3
10
|
FILE = File.expand_path('../../../../VERSION', __FILE__)
|
4
11
|
MAJOR, MINOR, TINY, EXTRA = File.read(FILE).chomp.split('.')
|
@@ -6,14 +13,20 @@ module RDF::LDP
|
|
6
13
|
|
7
14
|
##
|
8
15
|
# @return [String]
|
9
|
-
def self.to_s
|
16
|
+
def self.to_s
|
17
|
+
STRING
|
18
|
+
end
|
10
19
|
|
11
20
|
##
|
12
21
|
# @return [String]
|
13
|
-
def self.to_str
|
22
|
+
def self.to_str
|
23
|
+
STRING
|
24
|
+
end
|
14
25
|
|
15
26
|
##
|
16
27
|
# @return [Array(Integer, Integer, Integer)]
|
17
|
-
def self.to_a
|
28
|
+
def self.to_a
|
29
|
+
[MAJOR, MINOR, TINY]
|
30
|
+
end
|
18
31
|
end
|
19
32
|
end
|
data/lib/rdf/ldp.rb
CHANGED
@@ -19,14 +19,15 @@ module RDF
|
|
19
19
|
# Rack servers.
|
20
20
|
#
|
21
21
|
# @see RDF::LDP::Resource
|
22
|
-
# @see
|
22
|
+
# @see https://www.w3.org/TR/ldp/ for the Linked Data platform specification
|
23
23
|
module LDP
|
24
24
|
InteractionModel.register(RDF::LDP::RDFSource, default: true)
|
25
|
-
InteractionModel.register(RDF::LDP::Container,
|
25
|
+
InteractionModel.register(RDF::LDP::Container,
|
26
|
+
for: RDF::Vocab::LDP.BasicContainer)
|
26
27
|
InteractionModel.register(RDF::LDP::DirectContainer)
|
27
28
|
InteractionModel.register(RDF::LDP::IndirectContainer)
|
28
29
|
InteractionModel.register(RDF::LDP::NonRDFSource)
|
29
|
-
|
30
|
+
|
30
31
|
CONTAINER_CLASSES = {
|
31
32
|
basic: RDF::Vocab::LDP.BasicContainer.freeze,
|
32
33
|
direct: RDF::LDP::DirectContainer.to_uri.freeze,
|