ldp 0.0.3 → 0.0.4

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.
data/lib/ldp/resource.rb CHANGED
@@ -1,20 +1,15 @@
1
1
  module Ldp
2
2
  class Resource
3
+ require 'ldp/resource/binary_source'
4
+ require 'ldp/resource/rdf_source'
3
5
 
4
6
  attr_reader :client, :subject
7
+ attr_accessor :content
5
8
 
6
- ##
7
- # Create a new LDP resource with a blank RDF graph
8
- def self.create client, subject
9
- self.new client, subject, RDF::Graph.new
10
- end
11
-
12
- def initialize client, subject, graph_or_response = nil
9
+ def initialize client, subject, response = nil
13
10
  @client = client
14
11
  @subject = subject
15
-
16
- @graph = graph_or_response if graph_or_response.is_a? RDF::Graph
17
- @get = graph_or_response if graph_or_response.is_a? Ldp::Response
12
+ @get = response if response.is_a? Faraday::Response and current? response
18
13
  end
19
14
 
20
15
  ##
@@ -26,13 +21,15 @@ module Ldp
26
21
  ##
27
22
  # Reload the LDP resource
28
23
  def reload
29
- Ldp::Resource.new client, subject
24
+ self.class.new client, subject, @get
30
25
  end
31
26
 
32
27
  ##
33
28
  # Is the resource new, or does it exist in the LDP server?
34
29
  def new?
35
- get
30
+ subject.nil? || !head
31
+ rescue Ldp::NotFound
32
+ true
36
33
  end
37
34
 
38
35
  ##
@@ -44,9 +41,11 @@ module Ldp
44
41
  ##
45
42
  # Get the resource
46
43
  def get
47
- @get ||= client.get(subject).tap do |result|
48
- raise NotFound if result.status == 404
49
- end
44
+ @get ||= client.get(subject)
45
+ end
46
+
47
+ def head
48
+ @head ||= @get || client.head(subject)
50
49
  end
51
50
 
52
51
  ##
@@ -56,75 +55,45 @@ module Ldp
56
55
  req.headers['If-Match'] = get.etag if retrieved_content?
57
56
  end
58
57
  end
59
-
58
+
59
+ def save
60
+ new? ? create : update
61
+ end
62
+
60
63
  ##
61
64
  # Create a new resource at the URI
62
- def create
63
- raise "" if new?
64
- resp = client.post '', graph.dump(:ttl) do |req|
65
- req.headers['Slug'] = subject
65
+ # @return [RdfSource] the new representation
66
+ def create &block
67
+ raise "Can't call create on an existing resource" unless new?
68
+ resp = client.post((subject || ""), content) do |req|
69
+
70
+ yield req if block_given?
66
71
  end
67
72
 
68
73
  @subject = resp.headers['Location']
69
74
  @subject_uri = nil
75
+ reload
70
76
  end
71
-
77
+
72
78
  ##
73
79
  # Update the stored graph
74
- def update new_graph = nil
75
- new_graph ||= graph
76
- client.put subject, new_graph.dump(:ttl) do |req|
80
+ def update new_content = nil
81
+ new_content ||= content
82
+ client.put subject, new_content do |req|
77
83
  req.headers['If-Match'] = get.etag if retrieved_content?
78
84
  end
79
85
  end
80
86
 
81
- def graph
82
- @graph ||= begin
83
- original_graph = get.graph
84
-
85
- inlinedResources = get.graph.query(:predicate => Ldp.inlinedResource).map { |x| x.object }
86
-
87
- unless inlinedResources.empty?
88
- new_graph = RDF::Graph.new
89
-
90
- original_graph.each_statement do |s|
91
- unless inlinedResources.include? s.subject
92
- new_graph << s
93
- end
94
- end
87
+ def current? response = nil
88
+ response ||= @get
89
+ return true if new? and subject.nil?
90
+
91
+ new_response = client.head(subject)
95
92
 
96
- new_graph
97
- else
98
- original_graph
99
- end
100
- end
101
- end
102
-
103
- def self.check_for_differences_and_reload_resource old_object
104
- new_object = old_object.reload
105
-
106
- bijection = new_object.graph.bijection_to(old_object.graph)
107
- diff = RDF::Graph.new
108
-
109
- old_object.graph.each do |statement|
110
- if statement.has_blank_nodes?
111
- subject = bijection.fetch(statement.subject, false) if statement.subject.node?
112
- object = bijection.fetch(statement.object, false) if statement.object.node?
113
- bijection_statement = RDF::Statement.new :subject => subject || statemnet.subject, :predicate => statement.predicate, :object => object || statement.object
114
-
115
- diff << statement if subject === false or object === false or new_object.graph.has_statement?(bijection_statement)
116
- elsif !new_object.graph.has_statement? statement
117
- diff << statement
118
- end
119
- end
120
-
121
- diff
122
- end
123
-
124
- ##
125
- # Reload this resource as an LDP container
126
- def as_container
127
- Ldp::Container.new client, subject, @graph || @get
93
+ response.headers['ETag'] &&
94
+ response.headers['Last-Modified'] &&
95
+ new_response.headers['ETag'] == response.headers['ETag'] &&
96
+ new_response.headers['Last-Modified'] == response.headers['Last-Modified']
128
97
  end
129
98
  end
130
99
  end
@@ -0,0 +1,57 @@
1
+ module Ldp
2
+ module Response::Paging
3
+ ##
4
+ # Statements about the page
5
+ def page
6
+ @page_graph ||= begin
7
+ g = RDF::Graph.new
8
+
9
+ if resource?
10
+ res = graph.query RDF::Statement.new(page_subject, nil, nil)
11
+
12
+ res.each_statement do |s|
13
+ g << s
14
+ end
15
+ end
16
+
17
+ g
18
+ end
19
+ end
20
+
21
+ ##
22
+ # Get the subject for the response
23
+ def subject
24
+ @subject ||= if has_page?
25
+ graph.first_object [page_subject, Ldp.page_of, nil]
26
+ else
27
+ page_subject
28
+ end
29
+ end
30
+
31
+ ##
32
+ # Is there a next page?
33
+ def has_next?
34
+ next_page != nil
35
+ end
36
+
37
+ ##
38
+ # Get the URI for the next page
39
+ def next_page
40
+ graph.first_object [page_subject, Ldp.nextPage, nil]
41
+ end
42
+
43
+ ##
44
+ # Get the URI to the first page
45
+ def first_page
46
+ if links['first']
47
+ RDF::URI.new links['first']
48
+ elsif graph.has_statement? RDf::Statement.new(page_subject, Ldp.nextPage, nil)
49
+ subject
50
+ end
51
+ end
52
+
53
+ def sort
54
+
55
+ end
56
+ end
57
+ end
data/lib/ldp/response.rb CHANGED
@@ -1,33 +1,62 @@
1
1
  module Ldp
2
2
  module Response
3
+ require 'ldp/response/paging'
3
4
 
4
5
  ##
5
6
  # Wrap the raw Faraday respone with our LDP extensions
6
7
  def self.wrap client, raw_resp
7
8
  raw_resp.send(:extend, Ldp::Response)
8
- raw_resp.ldp_client = client
9
+ raw_resp.send(:extend, Ldp::Response::Paging) if raw_resp.has_page?
9
10
  raw_resp
10
11
  end
11
12
 
12
13
  ##
13
14
  # Extract the Link: headers from the HTTP resource
14
- def self.links raw_resp
15
- h = Hash.new { |hash, key| hash[key] = [] }
16
- Array(raw_resp.headers["Link"]).map { |x| x.split(", ") }.flatten.inject(h) do |memo, header|
17
- v = header.scan(/(.*);\s?rel="([^"]+)"/)
18
-
19
- if v.length == 1
20
- memo[v.first.last] << v.first.first
15
+ def self.links response
16
+ h = {}
17
+ Array(response.headers["Link"]).map { |x| x.split(", ") }.flatten.inject(h) do |memo, header|
18
+ m = header.match(/(?<link>.*);\s?rel="(?<rel>[^"]+)"/)
19
+ if m
20
+ memo[m[:rel]] ||= []
21
+ memo[m[:rel]] << m[:link]
21
22
  end
22
23
 
23
24
  memo
24
25
  end
25
26
  end
27
+
28
+ def self.applied_preferences headers
29
+ h = {}
30
+
31
+ Array(headers).map { |x| x.split(",") }.flatten.inject(h) do |memo, header|
32
+ m = header.match(/(?<key>[^=;]*)(=(?<value>[^;,]*))?(;\s*(?<params>[^,]*))?/)
33
+ includes = (m[:params].match(/include="(?<include>[^"]+)"/)[:include] || "").split(" ")
34
+ omits = (m[:params].match(/omit="(?<omit>[^"]+)"/)[:omit] || "").split(" ")
35
+ memo[m[:key]] = { value: m[:value], includes: includes, omits: omits }
36
+ end
37
+ end
26
38
 
27
39
  ##
28
40
  # Is the response an LDP resource?
29
- def self.resource? raw_resp
30
- links(raw_resp).fetch("type", []).include? Ldp.resource.to_s
41
+
42
+ def self.resource? response
43
+ Array(links(response)["type"]).include? Ldp.resource.to_s
44
+ end
45
+
46
+ ##
47
+ # Is the response an LDP container?
48
+ def self.container? response
49
+ [
50
+ Ldp.basic_container,
51
+ Ldp.direct_container,
52
+ Ldp.indirect_container
53
+ ].any? { |x| Array(links(response)["type"]).include? x.to_s }
54
+ end
55
+
56
+ ##
57
+ # Link: headers from the HTTP response
58
+ def links
59
+ @links ||= Ldp::Response.links(self)
31
60
  end
32
61
 
33
62
  ##
@@ -39,17 +68,16 @@ module Ldp
39
68
  ##
40
69
  # Is the response an LDP container
41
70
  def container?
42
- graph.has_statement? RDF::Statement.new(subject, RDF.type, Ldp.container)
71
+ Ldp::Response.container?(self)
43
72
  end
44
73
 
74
+ def preferences
75
+ Ldp::Resource.applied_preferences(headers["Preference-Applied"])
76
+ end
45
77
  ##
46
78
  # Get the subject for the response
47
79
  def subject
48
- @subject ||= if has_page?
49
- graph.first_object [page_subject, Ldp.page_of, nil]
50
- else
51
- page_subject
52
- end
80
+ page_subject
53
81
  end
54
82
 
55
83
  ##
@@ -59,15 +87,9 @@ module Ldp
59
87
  end
60
88
 
61
89
  ##
62
- # Set the LDP client for this resource
63
- def ldp_client= client
64
- @ldp_client = client
65
- end
66
-
67
- ##
68
- # Get the LDP client
69
- def ldp_client
70
- @ldp_client
90
+ # Is the response paginated?
91
+ def has_page?
92
+ graph.has_statement? RDF::Statement.new(page_subject, RDF.type, Ldp.page)
71
93
  end
72
94
 
73
95
  ##
@@ -101,77 +123,19 @@ module Ldp
101
123
  end
102
124
 
103
125
  ##
104
- # Statements about the page
105
- def page
106
- @page_graph ||= begin
107
- g = RDF::Graph.new
108
-
109
- if resource?
110
- res = graph.query RDF::Statement.new(page_subject, nil, nil)
111
-
112
- res.each_statement do |s|
113
- g << s
114
- end
115
- end
116
-
117
- g
118
- end
126
+ # Extract the Link: rel="type" headers for the resource
127
+ def types
128
+ Array(links["type"])
119
129
  end
120
-
121
- ##
122
- # Is the response paginated?
123
- def has_page?
124
- graph.has_statement? RDF::Statement.new(page_subject, RDF.type, Ldp.page)
125
- end
126
-
127
- ##
128
- # Is there a next page?
129
- def has_next?
130
- next_page != nil
131
- end
132
-
133
- ##
134
- # Get the URI for the next page
135
- def next_page
136
- graph.first_object [page_subject, Ldp.nextPage, nil]
137
- end
138
-
139
- ##
140
- # Get the URI to the first page
141
- def first_page
142
- if links['first']
143
- RDF::URI.new links['first']
144
- elsif graph.has_statement? RDf::Statement.new(page_subject, Ldp.nextPage, nil)
145
- subject
146
- end
147
- end
148
-
149
- ##
150
- # Get a list of inlined resources
151
- def resources
152
- graph.query RDF::Statement.new(page_subject, Ldp.inlinedResource, nil)
153
- end
154
-
155
- ##
156
- # Get a list of member resources
157
- def members
158
- graph.query RDF::Statement.new(page_subject, membership_predicate, nil)
159
- end
160
-
161
- ##
162
- # Predicate to use to determine container membership
163
- def membership_predicate
164
- graph.first_object [page_subject, Ldp.membership_predicate, nil]
130
+
131
+ def includes? preference
132
+ key = Ldp.send("prefer_#{preference}") if Ldp.respond_to("prefer_#{preference}")
133
+ key ||= preference
134
+ preferences["return"][:includes].include?(key) || !preferences["return"][:omits].include?(key)
165
135
  end
166
-
167
- def sort
168
-
169
- end
170
-
171
- ##
172
- # Link: headers from the HTTP response
173
- def links
174
- Ldp::Response.links(self)
136
+
137
+ def minimal?
138
+ preferences["return"][:value] == "minimal"
175
139
  end
176
140
  end
177
- end
141
+ end
data/lib/ldp/uri.rb CHANGED
@@ -12,6 +12,22 @@ module Ldp::Uri
12
12
  uri("Container")
13
13
  end
14
14
 
15
+ def basic_container
16
+ uri("BasicContainer")
17
+ end
18
+
19
+ def direct_container
20
+ uri("DirectContainer")
21
+ end
22
+
23
+ def indirect_container
24
+ uri("IndirectContainer")
25
+ end
26
+
27
+ def contains
28
+ uri("contains")
29
+ end
30
+
15
31
  def page
16
32
  uri("Page")
17
33
  end
@@ -24,12 +40,28 @@ module Ldp::Uri
24
40
  uri("nextPage")
25
41
  end
26
42
 
27
- def inlinedResource
28
- uri("inlinedResource")
29
- end
30
-
31
43
  def membership_predicate
32
44
  uri("membershipPredicate")
33
45
  end
46
+
47
+ def prefer_empty_container
48
+ uri("PreferEmptyContainer")
49
+ end
50
+
51
+ def prefer_membership
52
+ uri("PreferMembership")
53
+ end
54
+
55
+ def prefer_containment
56
+ uri("PreferContainment")
57
+ end
58
+
59
+ def has_member_relation
60
+ uri("hasMemberRelation")
61
+ end
62
+
63
+ def member
64
+ uri("member")
65
+ end
34
66
 
35
- end
67
+ end
data/lib/ldp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ldp
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
data/lib/ldp.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'ldp/version'
2
2
  require 'linkeddata'
3
3
  require 'logger'
4
+ require 'http_logger'
4
5
 
5
6
  module Ldp
6
7
  RDF::Graph.send(:include, RDF::Isomorphic)
@@ -16,7 +17,8 @@ module Ldp
16
17
 
17
18
  autoload :Orm, 'ldp/orm'
18
19
 
19
- class NotFound < StandardError; end
20
+ class HttpError < RuntimeError; end
21
+ class NotFound < HttpError; end
20
22
 
21
23
  class << self
22
24
  def logger
@@ -24,6 +26,14 @@ module Ldp
24
26
  log.level = Logger::WARN
25
27
  end
26
28
  end
29
+
30
+ def instrument *args, &block
31
+ if defined?(::ActiveSupport) && defined?(::ActiveSupport::Notifications)
32
+ ActiveSupport::Notifications.instrument *args, &block
33
+ else
34
+ yield
35
+ end
36
+ end
27
37
 
28
38
  attr_writer :logger
29
39
  end
@@ -1,45 +1,51 @@
1
1
  require 'spec_helper'
2
2
  describe "Ldp::Client" do
3
3
 
4
- let(:simple_graph) do
5
- graph = RDF::Graph.new << [RDF::URI.new(""), RDF::DC.title, "Hello, world!"]
6
- graph.dump(:ttl)
7
- end
4
+ let(:simple_graph) do
5
+ graph = RDF::Graph.new << [RDF::URI.new(""), RDF::DC.title, "Hello, world!"]
6
+ graph.dump(:ttl)
7
+ end
8
8
 
9
9
 
10
- let(:paginatedGraph) do
11
- graph = RDF::Graph.new << [RDF::URI.new(""), RDF::DC.title, "Hello, world!"]
12
- graph << [RDF::URI.new("?firstPage"), RDF.type, Ldp.page]
13
- graph << [RDF::URI.new("?firstPage"), Ldp.page_of, RDF::URI.new("")]
14
- graph.dump(:ttl)
15
- end
10
+ let(:paginatedGraph) do
11
+ graph = RDF::Graph.new << [RDF::URI.new(""), RDF::DC.title, "Hello, world!"]
12
+ graph << [RDF::URI.new("?firstPage"), RDF.type, Ldp.page]
13
+ graph << [RDF::URI.new("?firstPage"), Ldp.page_of, RDF::URI.new("")]
14
+ graph.dump(:ttl)
15
+ end
16
16
 
17
- let(:simple_container_graph) do
18
- graph = RDF::Graph.new << [RDF::URI.new(""), RDF.type, Ldp.container]
19
- graph.dump(:ttl)
20
- end
17
+ let(:simple_container_graph) do
18
+ graph = RDF::Graph.new << [RDF::URI.new(""), RDF.type, Ldp.container]
19
+ graph.dump(:ttl)
20
+ end
21
21
 
22
- let(:conn_stubs) do
23
- stubs = Faraday::Adapter::Test::Stubs.new do |stub|
24
- stub.get('/a_resource') {[ 200, {"Link" => "http://www.w3.org/ns/ldp#Resource;rel=\"type\""}, simple_graph ]}
25
- stub.get('/a_container') {[ 200, {"Link" => "http://www.w3.org/ns/ldp#Resource;rel=\"type\""}, simple_container_graph ]}
26
- stub.put("/a_resource") { [204]}
27
- stub.delete("/a_resource") { [204]}
28
- stub.post("/a_container") { [201, {"Location" => "http://example.com/a_container/subresource"}]}
29
- end
22
+ let(:conn_stubs) do
23
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
24
+ stub.head('/a_resource') {[ 200 ]}
25
+ stub.get('/a_resource') {[ 200, {"Link" => "http://www.w3.org/ns/ldp#Resource;rel=\"type\""}, simple_graph ]}
26
+ stub.get('/a_container') {[ 200, {"Link" => ["http://www.w3.org/ns/ldp#Resource;rel=\"type\"","http://www.w3.org/ns/ldp#BasicContainer;rel=\"type\""]}, simple_container_graph ]}
27
+ stub.head('/a_binary_resource') { [200]}
28
+ stub.get('/a_binary_resource') { [200, {}, ""]}
29
+ stub.put("/a_resource") { [204]}
30
+ stub.delete("/a_resource") { [204]}
31
+ stub.head('/a_container') {[ 200 ]}
32
+ stub.post("/a_container") { [201, {"Location" => "http://example.com/a_container/subresource"}]}
33
+ stub.get("/test:1") { [200] }
34
+ stub.get("http://test:8080/abc") { [200] }
30
35
  end
36
+ end
31
37
 
32
- let(:mock_conn) do
33
- test = Faraday.new do |builder|
34
- builder.adapter :test, conn_stubs do |stub|
35
- end
38
+ let(:mock_conn) do
39
+ test = Faraday.new do |builder|
40
+ builder.adapter :test, conn_stubs do |stub|
36
41
  end
37
-
38
42
  end
39
43
 
40
- subject do
41
- Ldp::Client.new mock_conn
42
- end
44
+ end
45
+
46
+ subject do
47
+ Ldp::Client.new mock_conn
48
+ end
43
49
 
44
50
  describe "initialize" do
45
51
  it "should accept an existing Faraday connection" do
@@ -66,6 +72,36 @@ describe "Ldp::Client" do
66
72
  it "should accept a block to change the HTTP request" do
67
73
  expect { |b| subject.get "a_resource", &b }.to yield_control
68
74
  end
75
+
76
+ context "should provide convenient accessors for LDP Prefer headers" do
77
+ it "should set the minimal header" do
78
+ subject.get "a_resource", minimal: true do |req|
79
+ expect(req.headers["Prefer"]).to eq "return=minimal"
80
+ end
81
+ end
82
+ it "should set the include parameter" do
83
+ subject.get "a_resource", include: "membership" do |req|
84
+ expect(req.headers["Prefer"]).to match "include=\"#{Ldp.prefer_membership}\""
85
+ end
86
+ end
87
+ it "should set the omit parameter" do
88
+ subject.get "a_resource", omit: "containment" do |req|
89
+ expect(req.headers["Prefer"]).to match "omit=\"#{Ldp.prefer_containment}\""
90
+ end
91
+ end
92
+ end
93
+
94
+ context "with an invalid relative uri" do
95
+ it "should work" do
96
+ subject.get "test:1"
97
+ end
98
+ end
99
+
100
+ context "with an absolute uri" do
101
+ it "should work" do
102
+ subject.get "http://test:8080/abc"
103
+ end
104
+ end
69
105
  end
70
106
 
71
107
  describe "delete" do
@@ -143,6 +179,11 @@ describe "Ldp::Client" do
143
179
  expect(resource).to be_a_kind_of(Ldp::Resource)
144
180
  expect(resource).to be_a_kind_of(Ldp::Container)
145
181
  end
182
+
183
+ it "should be a binary resource" do
184
+ resource = subject.find_or_initialize "a_binary_resource"
185
+ expect(resource).to be_a_kind_of(Ldp::Resource::BinarySource)
186
+ end
146
187
 
147
188
  end
148
189
  end