triannon 1.0.1 → 1.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.
@@ -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