triannon 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,7 +4,8 @@ module Triannon
4
4
  class LdpWriter
5
5
 
6
6
  # use LDP protocol to create the OpenAnnotation.Annotation in an RDF store
7
- # @param [Triannon::Annotation] anno a Triannon::Annotation object, from which we use the graph
7
+ # @param [Triannon::Annotation] anno a Triannon::Annotation object, from
8
+ # which we use the graph
8
9
  def self.create_anno(anno)
9
10
  if anno && anno.graph
10
11
  # TODO: special case if the Annotation object already has an id --
@@ -12,13 +13,13 @@ module Triannon
12
13
  ldp_writer = Triannon::LdpWriter.new anno
13
14
  id = ldp_writer.create_base
14
15
 
15
- bodies_solns = anno.graph.query([nil, RDF::OpenAnnotation.hasBody, nil])
16
+ bodies_solns = anno.graph.query([nil, RDF::Vocab::OA.hasBody, nil])
16
17
  if bodies_solns.size > 0
17
18
  ldp_writer.create_body_container
18
19
  ldp_writer.create_body_resources
19
20
  end
20
21
 
21
- targets_solns = anno.graph.query([nil, RDF::OpenAnnotation.hasTarget, nil])
22
+ targets_solns = anno.graph.query([nil, RDF::Vocab::OA.hasTarget, nil])
22
23
  # NOTE: Annotation is invalid if there are no target statements
23
24
  if targets_solns.size > 0
24
25
  ldp_writer.create_target_container
@@ -29,39 +30,130 @@ module Triannon
29
30
  end
30
31
  end
31
32
 
32
- # deletes the indicated container and all its child containers from the LDP store
33
- # @param [String] id the unique id for the LDP container for an annotation
34
- # May be a compound id, such as uuid1/t/uuid2, in which case the LDP container object uuid2 and its children
35
- # are deleted from the LDP store, but LDP containers uuid1/t and uuid1 are not deleted from the LDP store.
33
+ # deletes the indicated container and all its child containers from the LDP
34
+ # store
35
+ # @param [String] id the unique id for the LDP container for an annotation.
36
+ # May be a compound id, such as uuid1/t/uuid2, in which case the LDP
37
+ # container object uuid2 and its children are deleted from the LDP
38
+ # store, but LDP containers uuid1/t and uuid1 are not deleted from
39
+ # the LDP store.
36
40
  def self.delete_container id
37
41
  if id && id.size > 0
38
42
  ldpw = Triannon::LdpWriter.new nil
39
43
  ldpw.delete_containers id
40
44
  end
41
45
  end
42
-
43
46
  class << self
44
47
  alias_method :delete_anno, :delete_container
45
48
  end
46
49
 
50
+ # @param [String] path the path part of the container url, after the ldp base url
51
+ # @return [Boolean] true if container already exists; false otherwise
52
+ def self.container_exist? path
53
+ base_url = Triannon.config[:ldp]['url'].strip
54
+ path.strip!
55
+ separator = (base_url.end_with?('/') || path.start_with?('/')) ? "" : '/'
56
+ conn = Faraday.new url: base_url + separator + path
57
+ resp = conn.head
58
+ if resp.status.between?(400, 600)
59
+ false
60
+ else
61
+ true
62
+ end
63
+ end
64
+
65
+ # Creates an empty LDP BasicContainer in LDP Storage
66
+ # @param [String] parent_path the path part, after the ldp base url -- in
67
+ # essence, the LDP BasicContainer that will be the parent of the
68
+ # to-be-created BasicContainer.
69
+ # @param [String] slug the value to send in Http Header 'Slug' -- this is
70
+ # appended to the parent container's path to become the id of the newly
71
+ # created BasicContainer
72
+ # @return [Boolean] true if the container was created; false otherwise
73
+ def self.create_basic_container parent_path, slug
74
+ if slug.blank?
75
+ puts "create_basic_container called with nil or empty slug, parent_path '#{parent_path}'"
76
+ return false
77
+ end
78
+ base_url = Triannon.config[:ldp]['url'].strip
79
+ base_url.chop! if base_url.end_with?('/')
80
+ slug.strip!
81
+ slug = slug[1..-1] if slug.start_with?('/')
82
+ if parent_path
83
+ parent_path.strip!
84
+ parent_path = parent_path[1..-1] if parent_path.start_with?('/')
85
+ parent_path.chop! if parent_path.end_with?('/')
86
+ end
87
+
88
+ full_path = (parent_path ? parent_path + '/' : "") + slug
89
+ full_url = base_url + '/' + full_path
90
+
91
+ if container_exist? full_path
92
+ puts "Container #{full_url} already exists."
93
+ false
94
+ else
95
+ g = RDF::Graph.new
96
+ null_rel_uri = RDF::URI.new
97
+ g << [null_rel_uri, RDF.type, RDF::Vocab::LDP.BasicContainer]
98
+ conn = Faraday.new url: base_url + (parent_path ? '/' + parent_path : "")
99
+ resp = conn.post do |req|
100
+ # Note from Fcrepo docs:
101
+ # https://wiki.duraspace.org/display/FEDORA41/RESTful+HTTP+API+-+Containers#RESTfulHTTPAPI-Containers-BluePOSTCreatenewresourceswithinaLDPcontainer
102
+ # "If the MIME type corresponds to a supported RDF format or SPARQL-Update, the uploaded content will be
103
+ # parsed as RDF and used to populate the child node properties. RDF will be interpreted using the current
104
+ # resource as the base URI (e.g. <> will be expanded to the current URI)."
105
+ # Thus, the next line is needed even if the body of this POST is empty
106
+ req.headers['Content-Type'] = 'text/turtle'
107
+ req.headers['Slug'] = slug
108
+ req.body = g.to_ttl
109
+ end
110
+
111
+ if resp.status == 201
112
+ new_url = resp.headers['Location'] ? resp.headers['Location'] : resp.headers['location']
113
+ if new_url == full_url
114
+ puts "Created Basic Container #{new_url}"
115
+ true
116
+ else
117
+ puts "Created Basic Container #{new_url} instead of #{full_url}"
118
+ false
119
+ end
120
+ else
121
+ puts "Unable to create Basic Container #{full_url}: LDP Storage response status #{resp.status}; body #{resp.body}"
122
+ false
123
+ end
124
+ end
125
+ end
126
+
127
+
47
128
  # @param [Triannon::Annotation] anno a Triannon::Annotation object
48
- # @param [String] id the unique id for the LDP container for the passed annotation; defaults to nil
49
- def initialize(anno, id=nil)
129
+ # @param [String] id the unique id for the LDP container for the passed
130
+ # annotation; defaults to nil
131
+ def initialize(anno, id = nil)
50
132
  @anno = anno
51
133
  @id = id
52
- @base_uri = Triannon.config[:ldp_url]
134
+ base_url = Triannon.config[:ldp]['url'].strip
135
+ base_url.chop! if base_url.end_with?('/')
136
+ container_path = Triannon.config[:ldp]['uber_container']
137
+ if container_path
138
+ container_path.strip!
139
+ container_path = container_path[1..-1] if container_path.start_with?('/')
140
+ container_path.chop! if container_path.end_with?('/')
141
+ end
142
+ @base_uri = "#{base_url}/#{container_path}"
53
143
  end
54
144
 
55
- # creates a stored LDP container for this object's Annotation, without its targets or bodies (as those are put in descendant containers)
145
+ # creates a stored LDP container for this object's Annotation, without its
146
+ # targets or bodies (as those are put in descendant containers)
56
147
  # SIDE EFFECT: assigns the uuid of the container created to @id
57
- # @return [String] the unique id for the LDP container created for this annotation
148
+ # @return [String] the unique id for the LDP container created for this
149
+ # annotation
58
150
  def create_base
59
151
  if @anno.graph.query([nil, RDF::Triannon.externalReference, nil]).count > 0
60
- raise Triannon::ExternalReferenceError, "Incoming annotations may not have http://triannon.stanford.edu/ns/externalReference as a predicate."
152
+ fail Triannon::ExternalReferenceError, "Incoming annotations may not have http://triannon.stanford.edu/ns/externalReference as a predicate."
61
153
  end
62
154
 
63
155
  if @anno.graph.id_as_url && @anno.graph.id_as_url.size > 0
64
- raise Triannon::ExternalReferenceError, "Incoming new annotations may not have an existing id (yet)."
156
+ fail Triannon::ExternalReferenceError, "Incoming new annotations may not have an existing id (yet)."
65
157
  end
66
158
 
67
159
  # TODO: special case if the Annotation object already has an id --
@@ -72,7 +164,7 @@ module Triannon
72
164
  @anno.graph.each { |s|
73
165
  g << s
74
166
  }
75
- g = Triannon::Graph.new(g)
167
+ g = OA::Graph.new(g)
76
168
  g.remove_non_base_statements
77
169
  g.make_null_relative_uri_out_of_blank_node
78
170
 
@@ -81,26 +173,27 @@ module Triannon
81
173
 
82
174
  # creates the LDP container for any and all bodies for this annotation
83
175
  def create_body_container
84
- create_direct_container RDF::OpenAnnotation.hasBody
176
+ create_direct_container RDF::Vocab::OA.hasBody
85
177
  end
86
178
 
87
179
  # creates the LDP container for any and all targets for this annotation
88
180
  def create_target_container
89
- create_direct_container RDF::OpenAnnotation.hasTarget
181
+ create_direct_container RDF::Vocab::OA.hasTarget
90
182
  end
91
183
 
92
184
  # create the body resources inside the (already created) body container
93
185
  def create_body_resources
94
- create_resources_in_container RDF::OpenAnnotation.hasBody
186
+ create_resources_in_container RDF::Vocab::OA.hasBody
95
187
  end
96
188
 
97
189
  # create the target resources inside the (already created) target container
98
190
  def create_target_resources
99
- create_resources_in_container RDF::OpenAnnotation.hasTarget
191
+ create_resources_in_container RDF::Vocab::OA.hasTarget
100
192
  end
101
193
 
102
- # @param [Array<String>] ldp_container_uris an Array of ids for LDP containers. (can also be a String)
103
- # e.g. [@base_uri/(uuid1)/t/(uuid2), @base_uri/(uuid1)/t/(uuid3)] or [@base_uri/(uuid)] or (uuid)
194
+ # @param [Array<String>] ldp_container_uris an Array of ids for LDP
195
+ # containers. (can also be a String) e.g. [@base_uri/(uuid1)/t/(uuid2),
196
+ # @base_uri/(uuid1)/t/(uuid3)] or [@base_uri/(uuid)] or (uuid)
104
197
  # @return true if a resource was deleted; false otherwise
105
198
  def delete_containers ldp_container_uris
106
199
  return false if !ldp_container_uris || ldp_container_uris.empty?
@@ -112,7 +205,7 @@ module Triannon
112
205
  ldp_id = uri.to_s.split(@base_uri + '/').last
113
206
  resp = conn.delete { |req| req.url ldp_id }
114
207
  if resp.status != 204
115
- raise Triannon::LDPStorageError.new("Unable to delete LDP container #{ldp_id}", resp.status, resp.body)
208
+ fail Triannon::LDPStorageError.new("Unable to delete LDP container #{ldp_id}", resp.status, resp.body)
116
209
  end
117
210
  something_deleted = true
118
211
  }
@@ -121,36 +214,57 @@ module Triannon
121
214
 
122
215
  protected
123
216
 
124
- # POSTS a ttl representation of a graph to an existing LDP container in the LDP store
125
- # @param [String] ttl a turtle representation of RDF data to be put in the LDP container
126
- # @param [String] parent_path the path portion of the url for the LDP parent container for this resource
127
- # if no path is supplied, then the resource will be created as a child of the root annotation;
128
- # expected paths would also be (anno_id)/t for a target resource (inside the target container of anno_id)
129
- # or (anno_id)/b for a body resource (inside the body container of anno_id)
130
- # @return [String] uuid representing the unique id of the newly created LDP container
217
+ # POSTS a ttl representation of a graph to an existing LDP container in the
218
+ # LDP store
219
+ # @param [String] ttl a turtle representation of RDF data to be put in the
220
+ # LDP container
221
+ # @param [String] parent_path the path portion of the url for the LDP parent
222
+ # container for this resource if no path is supplied, then the resource
223
+ # will be created as a child of the root annotation; expected paths would
224
+ # also be (anno_id)/t for a target resource (inside the target container
225
+ # of anno_id) or (anno_id)/b for a body resource (inside the body
226
+ # container of anno_id)
227
+ # @return [String] path_id representing the unique path of the newly created LDP
228
+ # container
131
229
  def create_resource ttl, parent_path = nil
132
230
  return if !ttl || ttl.empty?
231
+
232
+ base_url = @base_uri.strip
233
+ base_url.chop! if base_url.end_with?('/')
234
+ if parent_path
235
+ parent_path.strip!
236
+ parent_path = parent_path[1..-1] if parent_path.start_with?('/')
237
+ parent_path.chop! if parent_path.end_with?('/')
238
+ end
239
+
133
240
  resp = conn.post do |req|
134
241
  req.url parent_path if parent_path
135
242
  req.headers['Content-Type'] = 'application/x-turtle'
136
243
  req.body = ttl
137
244
  end
138
245
  if resp.status != 200 && resp.status != 201
139
- raise Triannon::LDPStorageError.new("Unable to create LDP resource in container #{parent_path}; RDF sent: #{ttl}", resp.status, resp.body)
246
+ fail Triannon::LDPStorageError.new("Unable to create LDP resource in container #{parent_path}; RDF sent: #{ttl}", resp.status, resp.body)
140
247
  end
141
248
  new_url = resp.headers['Location'] ? resp.headers['Location'] : resp.headers['location']
142
- new_url.split('/').last if new_url
249
+ if new_url
250
+ new_url = new_url.split(base_url + '/' + parent_path.to_s).last
251
+ new_url = new_url[1..-1] if new_url.start_with?('/')
252
+ end
253
+ new_url
143
254
  end
144
255
 
145
- # Creates an empty LDP DirectContainer in LDP Storage that is a member of the base container and has the memberRelation per the oa_vocab_term
146
- # The id of the created containter will be (base container id)/b if hasBody or (base container id)/t if hasTarget
147
- # @param [RDF::Vocabulary::Term] oa_vocab_term RDF::OpenAnnotation.hasTarget or RDF::OpenAnnotation.hasBody
256
+ # Creates an empty LDP DirectContainer in LDP Storage that is a member of
257
+ # the base container at @id and has the memberRelation per the
258
+ # oa_vocab_term. The id of the created container will be (base container
259
+ # id)/b if hasBody or (base container id)/t if hasTarget
260
+ # @param [RDF::Vocabulary::Term] oa_vocab_term RDF::Vocab::OA.hasTarget or
261
+ # RDF::Vocab::OA.hasBody
148
262
  def create_direct_container oa_vocab_term
149
263
  null_rel_uri = RDF::URI.new
150
264
  g = RDF::Graph.new
151
- g << [null_rel_uri, RDF.type, RDF::LDP.DirectContainer]
152
- g << [null_rel_uri, RDF::LDP.hasMemberRelation, oa_vocab_term]
153
- g << [null_rel_uri, RDF::LDP.membershipResource, RDF::URI.new("#{@base_uri}/#{@id}")]
265
+ g << [null_rel_uri, RDF.type, RDF::Vocab::LDP.DirectContainer]
266
+ g << [null_rel_uri, RDF::Vocab::LDP.hasMemberRelation, oa_vocab_term]
267
+ g << [null_rel_uri, RDF::Vocab::LDP.membershipResource, RDF::URI.new("#{@base_uri}/#{@id}")]
154
268
 
155
269
  resp = conn.post do |req|
156
270
  req.url "#{@id}"
@@ -160,13 +274,15 @@ module Triannon
160
274
  req.body = g.to_ttl
161
275
  end
162
276
  if resp.status != 201
163
- raise Triannon::LDPStorageError.new("Unable to create #{oa_vocab_term.fragment.sub('has', '')} LDP container for anno; RDF sent: #{g.to_ttl}", resp.status, resp.body)
277
+ fail Triannon::LDPStorageError.new("Unable to create #{oa_vocab_term.fragment.sub('has', '')} LDP container for anno; RDF sent: #{g.to_ttl}", resp.status, resp.body)
164
278
  end
165
279
  resp
166
280
  end
167
281
 
168
- # create the target/body resources inside the (already created) target/body container
169
- # @param [RDF::URI] predicate either RDF::OpenAnnotation.hasTarget or RDF::OpenAnnotation.hasBody
282
+ # create the target/body resources inside the (already created) target/body
283
+ # container
284
+ # @param [RDF::URI] predicate either RDF::Vocab::OA.hasTarget or
285
+ # RDF::Vocab::OA.hasBody
170
286
  def create_resources_in_container(predicate)
171
287
  predicate_solns = @anno.graph.query([nil, predicate, nil])
172
288
  resource_ids = []
@@ -174,11 +290,13 @@ module Triannon
174
290
  graph_for_resource = RDF::Graph.new
175
291
  predicate_obj = predicate_stmt.object
176
292
  if predicate_obj.is_a?(RDF::Node)
177
- # we need to use the null relative URI representation of blank nodes to write to LDP
293
+ # we need to use the null relative URI representation of blank nodes
294
+ # to write to LDP
178
295
  predicate_subject = RDF::URI.new
179
296
  else
180
- # it's already a URI, but we need to use the null relative URI representation so we can
181
- # write out as a Triannon:externalRef property with the URL, and any addl props too.
297
+ # it's already a URI, but we need to use the null relative URI
298
+ # representation so we can write out as a Triannon:externalRef
299
+ # property with the URL, and any addl props too.
182
300
  if predicate_obj.to_str
183
301
  predicate_subject = RDF::URI.new
184
302
  graph_for_resource << RDF::Statement({:subject => predicate_subject,
@@ -198,19 +316,21 @@ module Triannon
198
316
  # add statements with predicate_obj as the subject
199
317
  orig_hash_uri_objs = [] # the orig URI objects from [targetObject, OA.hasSource/.default/.item, (uri)] statements
200
318
  hash_uri_counter = 1
201
- Triannon::Graph.subject_statements(predicate_obj, @anno.graph).each { |s|
319
+ OA::Graph.subject_statements(predicate_obj, @anno.graph).each { |s|
202
320
  if s.subject == predicate_obj
203
321
  # deal with any external URI references which may occur in:
204
- # OA.hasSource (from SpecificResource), OA.default or OA.item (from Choice, Composite, List)
322
+ # OA.hasSource (from SpecificResource), OA.default or
323
+ # OA.item (from Choice, Composite, List)
205
324
  if s.object.is_a?(RDF::URI) && s.object.to_s
206
- # do we need to represent the URL as an externalReference with hash URI
207
- if s.predicate == RDF::OpenAnnotation.hasSource
325
+ # do we need to represent the URL as an externalReference
326
+ # with hash URI?
327
+ if s.predicate == RDF::Vocab::OA.hasSource
208
328
  hash_uri_str = "#source"
209
- elsif s.predicate == RDF::OpenAnnotation.default
329
+ elsif s.predicate == RDF::Vocab::OA.default
210
330
  hash_uri_str = "#default"
211
- elsif s.predicate == RDF::OpenAnnotation.item
331
+ elsif s.predicate == RDF::Vocab::OA.item
212
332
  hash_uri_str = "#item#{hash_uri_counter}"
213
- hash_uri_counter = hash_uri_counter + 1
333
+ hash_uri_counter += 1
214
334
  else
215
335
  # we don't need to represent the object URI as an external ref
216
336
  hash_uri_str = nil
@@ -224,7 +344,7 @@ module Triannon
224
344
  new_hash_uri_obj = RDF::URI.new(hash_uri_str)
225
345
  orig_hash_uri_obj = s.object
226
346
  orig_hash_uri_objs << orig_hash_uri_obj
227
- # add [targetObj, OA.hasSource/.default/.item, (hash URI)] triple to graph
347
+ # add [targetObj, OA.hasSource/.default/.item, (hash URI)]
228
348
  graph_for_resource << RDF::Statement({:subject => predicate_subject,
229
349
  :predicate => s.predicate,
230
350
  :object => new_hash_uri_obj})
@@ -234,7 +354,7 @@ module Triannon
234
354
  :predicate => RDF::Triannon.externalReference,
235
355
  :object => RDF::URI.new(orig_hash_uri_obj.to_s)})
236
356
  # and all of the orig URL's addl props
237
- Triannon::Graph.subject_statements(orig_hash_uri_obj, @anno.graph).each { |ss|
357
+ OA::Graph.subject_statements(orig_hash_uri_obj, @anno.graph).each { |ss|
238
358
  if ss.subject == orig_hash_uri_obj
239
359
  graph_for_resource << RDF::Statement({:subject => new_hash_uri_obj,
240
360
  :predicate => ss.predicate,
@@ -246,7 +366,8 @@ module Triannon
246
366
  end
247
367
  # NOTE: already dealt with case where there is no hash uri above
248
368
  else
249
- # s.object is not a URI, and subject = predicate_subject -- it may be a blank node
369
+ # s.object is not a URI, and subject = predicate_subject -- it may
370
+ # be a blank node
250
371
  graph_for_resource << RDF::Statement({:subject => predicate_subject,
251
372
  :predicate => s.predicate,
252
373
  :object => s.object})
@@ -256,14 +377,14 @@ module Triannon
256
377
  graph_for_resource << s
257
378
  end
258
379
  }
259
- # make sure the graph we will write contains no extraneous statements about URIs
260
- # now represented as hash URIs
380
+ # make sure the graph we will write contains no extraneous statements
381
+ # about URIs now represented as hash URIs
261
382
  orig_hash_uri_objs.each { |uri_node|
262
- Triannon::Graph.subject_statements(uri_node, graph_for_resource).each { |s|
383
+ OA::Graph.subject_statements(uri_node, graph_for_resource).each { |s|
263
384
  graph_for_resource.delete(s)
264
385
  }
265
386
  }
266
- if (predicate == RDF::OpenAnnotation.hasTarget)
387
+ if (predicate == RDF::Vocab::OA.hasTarget)
267
388
  resource_ids << create_resource(graph_for_resource.to_ttl, "#{@id}/t")
268
389
  else
269
390
  resource_ids << create_resource(graph_for_resource.to_ttl, "#{@id}/b")
@@ -279,4 +400,4 @@ module Triannon
279
400
  end
280
401
 
281
402
  end
282
- end
403
+ end
@@ -1,16 +1,16 @@
1
1
  module Triannon
2
2
  class SolrSearcher
3
3
 
4
- # convert RSolr::Response object into an array of Triannon::Graph objects,
4
+ # convert RSolr::Response object into an array of OA::Graph objects,
5
5
  # where each graph object contains a single annotation returned in the response docs
6
6
  # @param [Hash] rsolr_response an RSolr response to a query. It's actually an
7
7
  # RSolr::HashWithResponse but let's not quibble
8
- # @return [Array<Triannon::Graph>]
8
+ # @return [Array<OA::Graph>]
9
9
  def self.anno_graphs_array(rsolr_response)
10
10
  result = []
11
11
  # TODO: deal with Solr pagination
12
12
  rsolr_response['response']['docs'].each { |solr_doc_hash|
13
- result << Triannon::Graph.new(RDF::Graph.new.from_jsonld(solr_doc_hash['anno_jsonld']))
13
+ result << OA::Graph.new(RDF::Graph.new.from_jsonld(solr_doc_hash['anno_jsonld']))
14
14
  }
15
15
  result
16
16
  end
@@ -129,12 +129,12 @@ module Triannon
129
129
  # 2. sends request to Solr
130
130
  # 3. converts Solr response object to array of anno graphs
131
131
  # @param [Hash<String => String>] controller_params params from Controller
132
- # @return [Array<Triannon::Graph>] array of Triannon::Graph objects,
132
+ # @return [Array<OA::Graph>] array of OA::Graph objects,
133
133
  # where each graph object contains a single annotation returned in the response docs
134
134
  def find(controller_params)
135
135
  solr_params = self.class.solr_params(controller_params)
136
136
  solr_response = search(solr_params)
137
- anno_graphs_array = self.class.anno_graphs_array(solr_response)
137
+ self.class.anno_graphs_array(solr_response)
138
138
  end
139
139
 
140
140
 
@@ -149,9 +149,9 @@ module Triannon
149
149
  @logger.debug "#{exception.inspect} on Solr search attempt #{attempt_cnt} for #{solr_params.inspect}"
150
150
  if exception.kind_of?(RSolr::Error::Http)
151
151
  # Note there are extra shenanigans b/c RSolr hijacks the Solr error to return RSolr Error
152
- raise Triannon::SearchError.new("error searching Solr with params #{solr_params.inspect}: #{exception.message}", exception.response[:status], exception.response[:body])
152
+ fail Triannon::SearchError.new("error searching Solr with params #{solr_params.inspect}: #{exception.message}", exception.response[:status], exception.response[:body])
153
153
  elsif exception.kind_of?(StandardError)
154
- raise Triannon::SearchError.new("error searching Solr with params #{solr_params.inspect}: #{exception.message}")
154
+ fail Triannon::SearchError.new("error searching Solr with params #{solr_params.inspect}: #{exception.message}")
155
155
  end
156
156
  end
157
157
 
@@ -170,4 +170,4 @@ module Triannon
170
170
  end
171
171
 
172
172
  end
173
- end
173
+ end