rdf-ldp 0.1.0 → 0.2.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 +4 -4
- data/AUTHORS +1 -0
- data/CREDITS +0 -0
- data/README.md +43 -21
- data/UNLICENSE +24 -0
- data/VERSION +1 -0
- data/lib/rack/ldp.rb +121 -0
- data/lib/rdf/ldp/container.rb +143 -0
- data/lib/rdf/ldp/direct_container.rb +112 -0
- data/lib/rdf/ldp/indirect_container.rb +13 -0
- data/lib/rdf/ldp/non_rdf_source.rb +18 -0
- data/lib/rdf/ldp/rdf_source.rb +190 -0
- data/lib/rdf/ldp/resource.rb +318 -0
- data/lib/rdf/ldp/version.rb +19 -1
- data/lib/rdf/ldp.rb +87 -2
- metadata +207 -34
- data/.gitignore +0 -15
- data/.travis.yml +0 -13
- data/Gemfile +0 -4
- data/LICENSE +0 -13
- data/Rakefile +0 -15
- data/lib/rdf/ldp/helper.rb +0 -27
- data/lib/rdf/ldp/vocab.rb +0 -149
- data/rdf-ldp.gemspec +0 -27
- data/spec/fixtures/open_anno_ldp_container.ttl +0 -29
- data/spec/helper_spec.rb +0 -27
- data/spec/spec_helper.rb +0 -97
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module RDF::LDP
|
4
|
+
##
|
5
|
+
# The base class for all directly usable LDP Resources that *are not*
|
6
|
+
# `NonRDFSources`. RDFSources are implemented as a resource with:
|
7
|
+
#
|
8
|
+
# - a `#subject_uri` identifying the RDFSource (see: {RDF::LDP::Resource}).
|
9
|
+
# - a `#graph` representing the "entire persistent state"
|
10
|
+
# - a `#metagraph` containing internal properties of the RDFSource
|
11
|
+
#
|
12
|
+
# Persistence schemes must be able to reconstruct both `#graph` and
|
13
|
+
# `#metagraph` accurately and separately (e.g. by saving them as distinct
|
14
|
+
# named graphs). Statements in `#metagraph` are considered canonical for the
|
15
|
+
# purposes of server-side operations; in the `RDF::LDP` core, this means they
|
16
|
+
# determine interaction model.
|
17
|
+
#
|
18
|
+
# Note that the contents of `#metagraph`'s are *not* the same as
|
19
|
+
# LDP-server-managed triples. `#metagraph` contains statements internal
|
20
|
+
# properties of the RDFSource which are necessary for the server's management
|
21
|
+
# purposes, but MAY be absent from the representation of its state in `#graph`.
|
22
|
+
# `#metagraph` is invisible to the client unless the implementation mirrors
|
23
|
+
# its contents in `#graph`.
|
24
|
+
#
|
25
|
+
# @see http://www.w3.org/TR/ldp/#dfn-linked-data-platform-rdf-source definition
|
26
|
+
# of ldp:RDFSource in the LDP specification
|
27
|
+
class RDFSource < Resource
|
28
|
+
attr_accessor :graph
|
29
|
+
|
30
|
+
class << self
|
31
|
+
##
|
32
|
+
# @return [RDF::URI] uri with lexical representation
|
33
|
+
# 'http://www.w3.org/ns/ldp#RDFSource'
|
34
|
+
#
|
35
|
+
# @see http://www.w3.org/TR/ldp/#dfn-linked-data-platform-rdf-source
|
36
|
+
def to_uri
|
37
|
+
RDF::Vocab::LDP.RDFSource
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(subject_uri, data = RDF::Repository.new)
|
42
|
+
@graph = RDF::Graph.new(subject_uri, data: data)
|
43
|
+
super
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Creates the RDFSource, populating its graph from the input given
|
49
|
+
#
|
50
|
+
# @param [IO, File, #to_s] input input (usually from a Rack env's
|
51
|
+
# `rack.input` key) used to determine the Resource's initial state.
|
52
|
+
# @param [#to_s] content_type a MIME content_type used to read the graph.
|
53
|
+
#
|
54
|
+
# @raise [RDF::LDP::RequestError]
|
55
|
+
# @raise [RDF::LDP::UnsupportedMediaType] if no reader can be found for the
|
56
|
+
# graph
|
57
|
+
# @raise [RDF::LDP::BadRequest] if the identified reader can't parse the
|
58
|
+
# graph
|
59
|
+
# @raise [RDF::LDP::Conflict] if the RDFSource already exists
|
60
|
+
#
|
61
|
+
# @return [RDF::LDP::Resource] self
|
62
|
+
def create(input, content_type, &block)
|
63
|
+
super
|
64
|
+
statements = parse_graph(input, content_type)
|
65
|
+
yield statements if block_given?
|
66
|
+
graph << statements
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Updates the resource. Replaces the contents of `graph` with the parsed
|
72
|
+
# input.
|
73
|
+
#
|
74
|
+
# @param [IO, File, #to_s] input input (usually from a Rack env's
|
75
|
+
# `rack.input` key) used to determine the Resource's new state.
|
76
|
+
# @param [#to_s] content_type a MIME content_type used to interpret the
|
77
|
+
# input.
|
78
|
+
#
|
79
|
+
# @return [RDF::LDP::Resource] self
|
80
|
+
def update(input, content_type, &block)
|
81
|
+
return create(input, content_type) unless exists?
|
82
|
+
statements = parse_graph(input, content_type)
|
83
|
+
yield statements if block_given?
|
84
|
+
graph.clear!
|
85
|
+
graph << statements
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Clears the graph and marks as destroyed.
|
91
|
+
#
|
92
|
+
# @see RDF::LDP::Resource#destroy
|
93
|
+
def destroy
|
94
|
+
@graph.clear
|
95
|
+
super
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Returns an Etag. This may be a strong or a weak ETag.
|
100
|
+
#
|
101
|
+
# @return [String] an HTTP Etag
|
102
|
+
#
|
103
|
+
# @note the current implementation is a naive one that combines a couple of
|
104
|
+
# blunt heurisitics.
|
105
|
+
#
|
106
|
+
# @todo add an efficient hash function for RDF Graphs to RDF.rb and use that
|
107
|
+
# here?
|
108
|
+
#
|
109
|
+
# @see http://ceur-ws.org/Vol-1259/proceedings.pdf#page=65 for a recent
|
110
|
+
# treatment of digests for RDF graphs
|
111
|
+
#
|
112
|
+
# @see http://www.w3.org/TR/ldp#h-ldpr-gen-etags LDP ETag clause for GET
|
113
|
+
# @see http://www.w3.org/TR/ldp#h-ldpr-put-precond LDP ETag clause for PUT
|
114
|
+
# @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3
|
115
|
+
# description of strong vs. weak validators
|
116
|
+
def etag
|
117
|
+
subs = graph.subjects.map { |s| s.node? ? nil : s.to_s }
|
118
|
+
.compact.sort.join()
|
119
|
+
"\"#{Digest::MD5.base64digest(subs)}#{graph.statements.count}\""
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# @param [String] tag a tag to compare to `#etag`
|
124
|
+
# @return [Boolean] whether the given tag matches `#etag`
|
125
|
+
def match?(tag)
|
126
|
+
# return false unless tag.split('==').last == graph.statements.count.to_s
|
127
|
+
tag == etag
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# @return [Boolean] whether this is an ldp:RDFSource
|
132
|
+
def rdf_source?
|
133
|
+
true
|
134
|
+
end
|
135
|
+
|
136
|
+
##
|
137
|
+
# @return [RDF::URI] the subject URI for this resource
|
138
|
+
def to_uri
|
139
|
+
subject_uri
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# Returns the graph representing this resource's state, without the graph
|
144
|
+
# context.
|
145
|
+
def to_response
|
146
|
+
RDF::Graph.new << graph
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
##
|
152
|
+
# Process & generate response for PUT requsets.
|
153
|
+
def put(status, headers, env)
|
154
|
+
raise PreconditionFailed.new('Etag invalid') if
|
155
|
+
env.has_key?('HTTP_IF_MATCH') && !match?(env['HTTP_IF_MATCH'])
|
156
|
+
|
157
|
+
if exists?
|
158
|
+
update(env['rack.input'], env['CONTENT_TYPE'])
|
159
|
+
headers = update_headers(headers)
|
160
|
+
[200, headers, self]
|
161
|
+
else
|
162
|
+
create(env['rack.input'], env['CONTENT_TYPE'])
|
163
|
+
[201, update_headers(headers), self]
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
##
|
168
|
+
# Finds an {RDF::Reader} appropriate for the given content_type and attempts
|
169
|
+
# to parse the graph string.
|
170
|
+
#
|
171
|
+
# @param [IO, File, String] graph an input stream to parse
|
172
|
+
# @param [#to_s] content_type the content type for the reader
|
173
|
+
#
|
174
|
+
# @return [RDF::Enumerable] the statements in the resulting graph
|
175
|
+
#
|
176
|
+
# @raise [RDF::LDP::UnsupportedMediaType] if no appropriate reader is found
|
177
|
+
#
|
178
|
+
# @todo handle cases where no content type is given? Does RDF::Reader have
|
179
|
+
# tools to help us here?
|
180
|
+
def parse_graph(graph, content_type)
|
181
|
+
reader = RDF::Reader.for(content_type: content_type.to_s)
|
182
|
+
raise(RDF::LDP::UnsupportedMediaType, content_type) if reader.nil?
|
183
|
+
begin
|
184
|
+
RDF::Graph.new << reader.new(graph, base_uri: subject_uri)
|
185
|
+
rescue RDF::ReaderError => e
|
186
|
+
raise RDF::LDP::BadRequest, e.message
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,318 @@
|
|
1
|
+
require 'link_header'
|
2
|
+
|
3
|
+
module RDF::LDP
|
4
|
+
class Resource
|
5
|
+
attr_reader :subject_uri
|
6
|
+
attr_accessor :metagraph
|
7
|
+
|
8
|
+
class << self
|
9
|
+
##
|
10
|
+
# @return [RDF::URI] uri with lexical representation
|
11
|
+
# 'http://www.w3.org/ns/ldp#Resource'
|
12
|
+
#
|
13
|
+
# @see http://www.w3.org/TR/ldp/#dfn-linked-data-platform-resource
|
14
|
+
def to_uri
|
15
|
+
RDF::Vocab::LDP.Resource
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Creates an unique id (URI Slug) for a resource.
|
20
|
+
#
|
21
|
+
# @note the current implementation uses {SecureRandom#uuid}.
|
22
|
+
#
|
23
|
+
# @return [String] a unique ID
|
24
|
+
def gen_id
|
25
|
+
SecureRandom.uuid
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Finds an existing resource and
|
30
|
+
#
|
31
|
+
# @param [RDF::URI] uri the URI for the resource to be found
|
32
|
+
# @param [RDF::Repository] data a repostiory instance in which to find
|
33
|
+
# the resource.
|
34
|
+
#
|
35
|
+
# @raise [RDF::LDP::NotFound] when the resource doesn't exist
|
36
|
+
#
|
37
|
+
# @return [RDF::LDP::Resource] a resource instance matching the given URI;
|
38
|
+
# usually of a subclass
|
39
|
+
# from the interaction models.
|
40
|
+
def find(uri, data)
|
41
|
+
graph = RDF::Graph.new(uri / '#meta', data: data)
|
42
|
+
raise NotFound if graph.empty?
|
43
|
+
|
44
|
+
rdf_class = graph.query([uri, RDF.type, :o]).first
|
45
|
+
klass = INTERACTION_MODELS[rdf_class.object] if rdf_class
|
46
|
+
klass ||= RDFSource
|
47
|
+
|
48
|
+
klass.new(uri, data)
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Retrieves the correct interaction model from the Link headers.
|
53
|
+
#
|
54
|
+
# Headers are handled intelligently, e.g. if a client sends a request with
|
55
|
+
# Resource, RDFSource, and BasicContainer headers, the server gives a
|
56
|
+
# BasicContainer. An error is thrown if the headers contain conflicting
|
57
|
+
# types (i.e. NonRDFSource and another Resource class).
|
58
|
+
#
|
59
|
+
# @param [String] link_header a string containing Link headers from an
|
60
|
+
# HTTP request (Rack env)
|
61
|
+
#
|
62
|
+
# @return [Class] a subclass of {RDF::LDP::Resource} matching the
|
63
|
+
# requested interaction model;
|
64
|
+
def interaction_model(link_header)
|
65
|
+
models = LinkHeader.parse(link_header)
|
66
|
+
.links.select { |link| link['rel'].downcase == 'type' }
|
67
|
+
.map { |link| link.href }
|
68
|
+
|
69
|
+
return RDFSource if models.empty?
|
70
|
+
match = INTERACTION_MODELS.keys.reverse.find { |u| models.include? u }
|
71
|
+
|
72
|
+
if match == RDF::LDP::NonRDFSource.to_uri
|
73
|
+
raise NotAcceptable if
|
74
|
+
models.include?(RDF::LDP::RDFSource.to_uri) ||
|
75
|
+
models.include?(RDF::LDP::Container.to_uri) ||
|
76
|
+
models.include?(RDF::LDP::DirectContainer.to_uri) ||
|
77
|
+
models.include?(RDF::LDP::IndirectContainer.to_uri) ||
|
78
|
+
models.include?(RDF::URI('http://www.w3.org/ns/ldp#BasicContainer'))
|
79
|
+
end
|
80
|
+
|
81
|
+
INTERACTION_MODELS[match] || RDFSource
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def initialize(subject_uri, data = RDF::Repository.new)
|
86
|
+
@subject_uri = RDF::URI(subject_uri)
|
87
|
+
@data = data
|
88
|
+
@metagraph = RDF::Graph.new(metagraph_name, data: data)
|
89
|
+
yield self if block_given?
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# @abstract creates the resource
|
94
|
+
#
|
95
|
+
# @param [IO, File, #to_s] input input (usually from a Rack env's
|
96
|
+
# `rack.input` key) used to determine the Resource's initial state.
|
97
|
+
# @param [#to_s] content_type a MIME content_type used to interpret the
|
98
|
+
# input. This MAY be used as a content type for the created Resource
|
99
|
+
# (especially for `LDP::NonRDFSource`s).
|
100
|
+
#
|
101
|
+
# @raise [RDF::LDP::RequestError] when creation fails. May raise various
|
102
|
+
# subclasses for the appropriate response codes.
|
103
|
+
#
|
104
|
+
# @return [RDF::LDP::Resource] self
|
105
|
+
def create(input, content_type)
|
106
|
+
raise Conflict if exists?
|
107
|
+
metagraph << RDF::Statement(subject_uri, RDF.type, self.class.to_uri)
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# @abstract update the resource
|
113
|
+
#
|
114
|
+
# @param [IO, File, #to_s] input input (usually from a Rack env's
|
115
|
+
# `rack.input` key) used to determine the Resource's new state.
|
116
|
+
# @param [#to_s] content_type a MIME content_type used to interpret the
|
117
|
+
# input.
|
118
|
+
#
|
119
|
+
# @raise [RDF::LDP::RequestError] when update fails. May raise various
|
120
|
+
# subclasses for the appropriate response codes.
|
121
|
+
#
|
122
|
+
# @return [RDF::LDP::Resource] self
|
123
|
+
def update(input, content_type)
|
124
|
+
raise NotImplementedError
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Mark the resource as destroyed.
|
129
|
+
#
|
130
|
+
# This adds a statment to the metagraph expressing that the resource has
|
131
|
+
# been deleted
|
132
|
+
#
|
133
|
+
# @return [RDF::LDP::Resource] self
|
134
|
+
#
|
135
|
+
# @todo Use of owl:Nothing is probably problematic. Define an internal
|
136
|
+
# namespace and class represeting deletion status as a stateful property.
|
137
|
+
def destroy
|
138
|
+
containers.each { |con| con.remove(self) if con.container? }
|
139
|
+
@metagraph << RDF::Statement(subject_uri, RDF.type, RDF::OWL.Nothing)
|
140
|
+
self
|
141
|
+
end
|
142
|
+
|
143
|
+
##
|
144
|
+
# @return [Boolean] true if the resource exists within the repository
|
145
|
+
def exists?
|
146
|
+
@data.has_context? metagraph.context
|
147
|
+
end
|
148
|
+
|
149
|
+
##
|
150
|
+
# @return [Boolean] true if resource has been destroyed
|
151
|
+
def destroyed?
|
152
|
+
!(@metagraph.query([subject_uri, RDF.type, RDF::OWL.Nothing]).empty?)
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
# @return [Array<Symbol>] a list of HTTP methods allowed by this resource.
|
157
|
+
def allowed_methods
|
158
|
+
[:GET, :POST, :PUT, :DELETE, :PATCH, :OPTIONS, :HEAD].select do |m|
|
159
|
+
respond_to?(m.downcase, true)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
##
|
164
|
+
# @return [Boolean] whether this is an ldp:Resource
|
165
|
+
def ldp_resource?
|
166
|
+
true
|
167
|
+
end
|
168
|
+
|
169
|
+
##
|
170
|
+
# @return [Boolean] whether this is an ldp:Container
|
171
|
+
def container?
|
172
|
+
false
|
173
|
+
end
|
174
|
+
|
175
|
+
##
|
176
|
+
# @return [Boolean] whether this is an ldp:NonRDFSource
|
177
|
+
def non_rdf_source?
|
178
|
+
false
|
179
|
+
end
|
180
|
+
|
181
|
+
##
|
182
|
+
# @return [Boolean] whether this is an ldp:RDFSource
|
183
|
+
def rdf_source?
|
184
|
+
false
|
185
|
+
end
|
186
|
+
|
187
|
+
##
|
188
|
+
# @return [Array<RDF::LDP::Resource>] the container for this resource
|
189
|
+
def containers
|
190
|
+
@data.query([:s, RDF::Vocab::LDP.contains, subject_uri]).map do |st|
|
191
|
+
RDF::LDP::Resource.find(st.subject, @data)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
##
|
196
|
+
# Runs the request and returns the object's desired HTTP response body,
|
197
|
+
# conforming to the Rack interfare.
|
198
|
+
#
|
199
|
+
# @see http://www.rubydoc.info/github/rack/rack/master/file/SPEC#The_Body
|
200
|
+
# for Rack body documentation
|
201
|
+
def to_response
|
202
|
+
[]
|
203
|
+
end
|
204
|
+
alias_method :each, :to_response
|
205
|
+
|
206
|
+
##
|
207
|
+
# Build the response for the HTTP `method` given.
|
208
|
+
#
|
209
|
+
# The method passed in is symbolized, downcased, and sent to `self` with the
|
210
|
+
# other three parameters.
|
211
|
+
#
|
212
|
+
# Request methods are expected to return an Array appropriate for a Rack
|
213
|
+
# response; to return this object (e.g. for a sucessful GET) the response
|
214
|
+
# may be `[status, headers, self]`.
|
215
|
+
#
|
216
|
+
# If the method given is unimplemented, we understand it to require an HTTP
|
217
|
+
# 405 response, and throw the appropriate error.
|
218
|
+
#
|
219
|
+
# @param [#to_sym] method the HTTP request method of the response; this
|
220
|
+
# message will be downcased and sent to the object.
|
221
|
+
# @param [Fixnum] status an HTTP response code; this status should be sent
|
222
|
+
# back to the caller or altered, as appropriate.
|
223
|
+
# @param [Hash<String, String>] headers a hash mapping HTTP headers
|
224
|
+
# built for the response to their contents; these headers should be sent
|
225
|
+
# back to the caller or altered, as appropriate.
|
226
|
+
# @param [] env the Rack env for the request
|
227
|
+
#
|
228
|
+
# @return [Array<Fixnum, Hash<String, String>, #each] a new Rack response
|
229
|
+
# array.
|
230
|
+
def request(method, status, headers, env)
|
231
|
+
raise Gone if destroyed?
|
232
|
+
begin
|
233
|
+
send(method.to_sym.downcase, status, headers, env)
|
234
|
+
rescue NotImplementedError => e
|
235
|
+
raise MethodNotAllowed, method
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
##
|
242
|
+
# Generate response for GET requests. Returns existing status and headers,
|
243
|
+
# with `self` as the body.
|
244
|
+
def get(status, headers, env)
|
245
|
+
[status, update_headers(headers), self]
|
246
|
+
end
|
247
|
+
|
248
|
+
##
|
249
|
+
# Generate response for HEAD requsets. Adds appropriate headers and returns
|
250
|
+
# an empty body.
|
251
|
+
def head(status, headers, env)
|
252
|
+
[status, update_headers(headers), []]
|
253
|
+
end
|
254
|
+
|
255
|
+
##
|
256
|
+
# Generate response for OPTIONS requsets. Adds appropriate headers and
|
257
|
+
# returns an empty body.
|
258
|
+
def options(status, headers, env)
|
259
|
+
[status, update_headers(headers), []]
|
260
|
+
end
|
261
|
+
|
262
|
+
##
|
263
|
+
# Process & generate response for DELETE requests.
|
264
|
+
def delete(status, headers, env)
|
265
|
+
[204, headers, destroy]
|
266
|
+
end
|
267
|
+
|
268
|
+
##
|
269
|
+
# @return [RDF::URI] the name for this resource's metagraph
|
270
|
+
def metagraph_name
|
271
|
+
subject_uri / '#meta'
|
272
|
+
end
|
273
|
+
|
274
|
+
##
|
275
|
+
# @param [Hash<String, String>] headers
|
276
|
+
# @return [Hash<String, String>] the updated headers
|
277
|
+
def update_headers(headers)
|
278
|
+
headers['Link'] =
|
279
|
+
([headers['Link']] + link_headers).compact.join(",")
|
280
|
+
|
281
|
+
headers['Allow'] = allowed_methods.join(', ')
|
282
|
+
headers['Accept-Post'] = accept_post if respond_to?(:post, true)
|
283
|
+
|
284
|
+
headers['ETag'] ||= etag if respond_to?(:etag)
|
285
|
+
headers
|
286
|
+
end
|
287
|
+
|
288
|
+
##
|
289
|
+
# @return [String] the Accept-Post headers
|
290
|
+
def accept_post
|
291
|
+
RDF::Reader.map { |klass| klass.format.content_type }.flatten.join(', ')
|
292
|
+
end
|
293
|
+
|
294
|
+
##
|
295
|
+
# @return [Array<String>] an array of link headers to add to the
|
296
|
+
# existing ones
|
297
|
+
#
|
298
|
+
# @see http://www.w3.org/TR/ldp/#h-ldpr-gen-linktypehdr
|
299
|
+
# @see http://www.w3.org/TR/ldp/#h-ldprs-are-ldpr
|
300
|
+
# @see http://www.w3.org/TR/ldp/#h-ldpnr-type
|
301
|
+
# @see http://www.w3.org/TR/ldp/#h-ldpc-linktypehdr
|
302
|
+
def link_headers
|
303
|
+
return [] unless is_a? RDF::LDP::Resource
|
304
|
+
headers = [link_type_header(RDF::LDP::Resource.to_uri)]
|
305
|
+
headers << link_type_header(RDF::LDP::RDFSource.to_uri) if rdf_source?
|
306
|
+
headers << link_type_header(RDF::LDP::NonRDFSource.to_uri) if
|
307
|
+
non_rdf_source?
|
308
|
+
headers << link_type_header(container_class) if container?
|
309
|
+
headers
|
310
|
+
end
|
311
|
+
|
312
|
+
##
|
313
|
+
# @return [String] a string to insert into a Link header
|
314
|
+
def link_type_header(uri)
|
315
|
+
"<#{uri}>;rel=\"type\""
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
data/lib/rdf/ldp/version.rb
CHANGED
@@ -1 +1,19 @@
|
|
1
|
-
|
1
|
+
module RDF::LDP
|
2
|
+
module VERSION
|
3
|
+
FILE = File.expand_path('../../../../VERSION', __FILE__)
|
4
|
+
MAJOR, MINOR, TINY, EXTRA = File.read(FILE).chomp.split('.')
|
5
|
+
STRING = [MAJOR, MINOR, TINY, EXTRA].compact.join('.').freeze
|
6
|
+
|
7
|
+
##
|
8
|
+
# @return [String]
|
9
|
+
def self.to_s() STRING end
|
10
|
+
|
11
|
+
##
|
12
|
+
# @return [String]
|
13
|
+
def self.to_str() STRING end
|
14
|
+
|
15
|
+
##
|
16
|
+
# @return [Array(Integer, Integer, Integer)]
|
17
|
+
def self.to_a() [MAJOR, MINOR, TINY] end
|
18
|
+
end
|
19
|
+
end
|
data/lib/rdf/ldp.rb
CHANGED
@@ -1,2 +1,87 @@
|
|
1
|
-
require 'rdf
|
2
|
-
require 'rdf/
|
1
|
+
require 'rdf'
|
2
|
+
require 'rdf/vocab'
|
3
|
+
|
4
|
+
require 'rdf/ldp/version'
|
5
|
+
|
6
|
+
require 'rdf/ldp/resource'
|
7
|
+
require 'rdf/ldp/rdf_source'
|
8
|
+
require 'rdf/ldp/non_rdf_source'
|
9
|
+
require 'rdf/ldp/container'
|
10
|
+
require 'rdf/ldp/direct_container'
|
11
|
+
require 'rdf/ldp/indirect_container'
|
12
|
+
|
13
|
+
module RDF
|
14
|
+
module LDP
|
15
|
+
##
|
16
|
+
# Interaction models are in reverse order of preference for POST/PUT
|
17
|
+
# requests; e.g. if a client sends a request with Resource, RDFSource, and
|
18
|
+
# BasicContainer headers, the server gives a basic container.
|
19
|
+
INTERACTION_MODELS = {
|
20
|
+
RDF::Vocab::LDP.Resource => RDF::LDP::RDFSource,
|
21
|
+
RDF::LDP::RDFSource.to_uri => RDF::LDP::RDFSource,
|
22
|
+
RDF::LDP::Container.to_uri => RDF::LDP::Container,
|
23
|
+
RDF::URI('http://www.w3.org/ns/ldp#BasicContainer') => RDF::LDP::Container,
|
24
|
+
RDF::LDP::DirectContainer.to_uri => RDF::LDP::DirectContainer,
|
25
|
+
RDF::LDP::IndirectContainer.to_uri => RDF::LDP::IndirectContainer,
|
26
|
+
RDF::LDP::NonRDFSource.to_uri => RDF::LDP::NonRDFSource
|
27
|
+
}.freeze
|
28
|
+
|
29
|
+
CONTAINER_CLASSES = {
|
30
|
+
basic: RDF::Vocab::LDP.BasicContainer,
|
31
|
+
direct: RDF::LDP::DirectContainer.to_uri,
|
32
|
+
indirect: RDF::LDP::IndirectContainer.to_uri
|
33
|
+
}
|
34
|
+
|
35
|
+
CONSTRAINED_BY = RDF::Vocab::LDP.constrainedBy
|
36
|
+
|
37
|
+
##
|
38
|
+
# A base class for HTTP request errors.
|
39
|
+
#
|
40
|
+
# This and its subclasses are caught and handled by Rack::LDP middleware.
|
41
|
+
class RequestError < RuntimeError
|
42
|
+
STATUS = 500
|
43
|
+
|
44
|
+
def status
|
45
|
+
self.class::STATUS
|
46
|
+
end
|
47
|
+
|
48
|
+
def headers
|
49
|
+
uri =
|
50
|
+
'https://github.com/no-reply/rdf-ldp/blob/master/CONSTRAINED_BY.md'
|
51
|
+
{ 'Link' => "<#{uri}>;rel=\"#{CONSTRAINED_BY}\"" }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class BadRequest < RequestError
|
56
|
+
STATUS = 400
|
57
|
+
end
|
58
|
+
|
59
|
+
class NotFound < RequestError
|
60
|
+
STATUS = 404
|
61
|
+
end
|
62
|
+
|
63
|
+
class MethodNotAllowed < RequestError
|
64
|
+
STATUS = 405
|
65
|
+
end
|
66
|
+
|
67
|
+
class NotAcceptable < RequestError
|
68
|
+
STATUS = 406
|
69
|
+
end
|
70
|
+
|
71
|
+
class Conflict < RequestError
|
72
|
+
STATUS = 409
|
73
|
+
end
|
74
|
+
|
75
|
+
class Gone < RequestError
|
76
|
+
STATUS = 410
|
77
|
+
end
|
78
|
+
|
79
|
+
class PreconditionFailed < RequestError
|
80
|
+
STATUS = 412
|
81
|
+
end
|
82
|
+
|
83
|
+
class UnsupportedMediaType < RequestError
|
84
|
+
STATUS = 415
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|