dor-services-client 8.8.0 → 9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d06585e857d803906ba2321f4af8645c9a1caf2c3b1e994e2241703c389d1baf
4
- data.tar.gz: b2aee23e2187304e321ba68dab3f1c72d4f919a7cba0a0d2197dc06c82944c40
3
+ metadata.gz: a4b8ec2c059acbd588a6d64e931035f5552b16bacf9d0e6a1f0b8194fb371bb2
4
+ data.tar.gz: 750be3e2a2a234299c63ee1aeb6b40c4a0a87116785cf085bc359d80676465da
5
5
  SHA512:
6
- metadata.gz: 4c6a3138cb22491bf143bf1cad97d5eebe151df8b889f4f6b62998829062c36905343fa16855438850b36c41be0b5d59e99782ae83098e1750751044898bb523
7
- data.tar.gz: a70f99e2d1e639c7654d512b9f0e79561fa13f2260b97509f86e3a42c007b3a2c2f07ae0fe8091bc0410314a35df3b0cdf78296af51bfa38f11cca540b64d375
6
+ metadata.gz: d0ffa417486e6136c276b2aa289d4f17e01a798ea288e727ce5b2955b722934ce7b992318ff3c88e3060a57a7cfd2a203b29d0302c6862d79cce80e6f4bab1d9
7
+ data.tar.gz: f707910e959af48ab45e29a4328217517b42c50f49314ff14e142afed33688d842b2ecfc9df8ea740e59914c31cbbd4d5b5dbff67cbb7b6e1b64e197a7aa1618
data/.rubocop.yml CHANGED
@@ -12,6 +12,12 @@ Metrics/BlockLength:
12
12
  - 'dor-services-client.gemspec'
13
13
  - 'spec/**/*'
14
14
 
15
+ RSpec/MultipleMemoizedHelpers:
16
+ Max: 10
17
+
18
+ RSpec/ExampleLength:
19
+ Max: 10
20
+
15
21
  Gemspec/DateAssignment: # (new in 1.10)
16
22
  Enabled: true
17
23
  Layout/SpaceBeforeBrackets: # (new in 1.7)
data/.rubocop_todo.yml CHANGED
@@ -25,12 +25,6 @@ RSpec/AnyInstance:
25
25
  - 'spec/dor/services/client/metadata_spec.rb'
26
26
  - 'spec/dor/services/client/object_version_spec.rb'
27
27
 
28
- # Offense count: 1
29
- # Configuration parameters: Max, CountAsOne.
30
- RSpec/ExampleLength:
31
- Exclude:
32
- - 'spec/dor/services/client/metadata_spec.rb'
33
-
34
28
  # Offense count: 6
35
29
  RSpec/IdenticalEqualityAssertion:
36
30
  Exclude:
@@ -46,12 +40,6 @@ RSpec/MultipleExpectations:
46
40
  - 'spec/dor/services/client/object_spec.rb'
47
41
  - 'spec/dor/services/client/response_error_formatter_spec.rb'
48
42
 
49
- # Offense count: 7
50
- # Configuration parameters: AllowSubject, Max.
51
- RSpec/MultipleMemoizedHelpers:
52
- Exclude:
53
- - 'spec/dor/services/client/objects_spec.rb'
54
-
55
43
  # Offense count: 1
56
44
  # Cop supports --auto-correct.
57
45
  RSpec/MultipleSubjects:
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.required_ruby_version = '>= 2.7', '< 4' # dor-services-app needs 2.7 due to fedora3
26
26
 
27
27
  spec.add_dependency 'activesupport', '>= 4.2', '< 8'
28
- spec.add_dependency 'cocina-models', '~> 0.74.0' # leave pinned to patch level until cocina-models hits 1.0
28
+ spec.add_dependency 'cocina-models', '~> 0.75.0' # leave pinned to patch level until cocina-models hits 1.0
29
29
  spec.add_dependency 'deprecation', '>= 0'
30
30
  spec.add_dependency 'faraday', '~> 2.0'
31
31
  spec.add_dependency 'faraday-retry'
@@ -20,9 +20,9 @@ module Dor
20
20
 
21
21
  # This method needs its own exception handling logic due to how the endpoint service (SearchWorks) operates
22
22
  # raise a NotFoundResponse because the resource being requested was not found in the ILS (via dor-services-app)
23
- raise NotFoundResponse, ResponseErrorFormatter.format(response: resp) if resp.success? && resp.body.blank?
23
+ raise NotFoundResponse.new(response: resp) if resp.success? && resp.body.blank?
24
24
 
25
- raise UnexpectedResponse, ResponseErrorFormatter.format(response: resp)
25
+ raise UnexpectedResponse.new(response: resp)
26
26
  end
27
27
 
28
28
  # Gets MARCXML corresponding to a barcode or catkey
@@ -45,9 +45,9 @@ module Dor
45
45
  # DOR Services App does not respond with a 404 when no match in Symphony.
46
46
  # Rather, it responds with a 500 containing "Record not found in Symphony" in the body.
47
47
  # raise a NotFoundResponse because the resource being requested was not found in the ILS (via dor-services-app)
48
- raise NotFoundResponse, ResponseErrorFormatter.format(response: resp) if !resp.success? && resp.body.match?(/Record not found in Symphony/)
48
+ raise NotFoundResponse.new(response: resp) if !resp.success? && resp.body.match?(/Record not found in Symphony/)
49
49
 
50
- raise UnexpectedResponse, ResponseErrorFormatter.format(response: resp) unless resp.success?
50
+ raise UnexpectedResponse.new(response: resp) unless resp.success?
51
51
 
52
52
  resp.body
53
53
  end
@@ -26,23 +26,31 @@ module Dor
26
26
  end
27
27
 
28
28
  # Updates the object
29
- # @param [Cocina::Models::DRO,Cocina::Models::Collection,Cocina::Models::AdminPolicy] params model object
29
+ # @param [Cocina::Models::DROWithMetadata|CollectionWithMetadata|AdminPolicyWithMetadata|DRO|Collection|AdminPolicy] params model object
30
+ # @param [boolean] skip_lock do not provide ETag
30
31
  # @raise [NotFoundResponse] when the response is a 404 (object not found)
31
32
  # @raise [UnexpectedResponse] when the response is not successful.
32
- # @return [Cocina::Models::DRO,Cocina::Models::Collection,Cocina::Models::AdminPolicy] the returned model
33
- def update(params:)
33
+ # @raise [BadRequestError] when ETag not provided.
34
+ # @return [Cocina::Models::DROWithMetadata,Cocina::Models::CollectionWithMetadata,Cocina::Models::AdminPolicyWithMetadata] the returned model
35
+ # rubocop:disable Metrics/AbcSize
36
+ def update(params:, skip_lock: false)
37
+ # Raised if Cocina::Models::*WithMetadata not provided.
38
+ raise ArgumentError, 'ETag not provided.' unless skip_lock || params.respond_to?(:lock)
39
+
34
40
  resp = connection.patch do |req|
35
41
  req.url object_path
36
42
  req.headers['Content-Type'] = 'application/json'
37
43
  # asking the service to return JSON (else it'll be plain text)
38
44
  req.headers['Accept'] = 'application/json'
39
- req.body = params.to_json
45
+ req.headers['If-Match'] = params[:lock] unless skip_lock
46
+ req.body = build_json_from_cocina(params)
40
47
  end
41
48
 
42
49
  raise_exception_based_on_response!(resp) unless resp.success?
43
50
 
44
- Cocina::Models.build(JSON.parse(resp.body))
51
+ build_cocina_from_response(resp)
45
52
  end
53
+ # rubocop:enable Metrics/AbcSize
46
54
 
47
55
  # Pull in metadata from Symphony and updates descriptive metadata
48
56
  # @raise [NotFoundResponse] when the response is a 404 (object not found)
@@ -45,9 +45,14 @@ module Dor
45
45
  # Retrieves the Cocina model
46
46
  # @raise [NotFoundResponse] when the response is a 404 (object not found)
47
47
  # @raise [UnexpectedResponse] when the response is not successful.
48
- # @return [Cocina::Models::DRO,Cocina::Models::Collection,Cocina::Models::AdminPolicy] the returned model
48
+ # @return [Cocina::Models::DROWithMetadata,Cocina::Models::CollectionWithMetadata,Cocina::Models::AdminPolicyWithMetadata] the returned model
49
49
  def find
50
- find_with_metadata.first
50
+ resp = connection.get do |req|
51
+ req.url object_path
52
+ end
53
+ raise_exception_based_on_response!(resp) unless resp.success?
54
+
55
+ build_cocina_from_response(resp)
51
56
  end
52
57
 
53
58
  # Retrieves the Cocina model and response metadata
@@ -64,9 +69,12 @@ module Dor
64
69
  model = Cocina::Models.build(JSON.parse(resp.body))
65
70
 
66
71
  # Don't use #slice here as Faraday will downcase the keys.
67
- metadata = ObjectMetadata.new(updated_at: resp.headers['Last-Modified'], created_at: resp.headers['X-Created-At'])
72
+ metadata = ObjectMetadata.new(updated_at: resp.headers['Last-Modified'],
73
+ created_at: resp.headers['X-Created-At'],
74
+ etag: resp.headers['ETag'])
68
75
  [model, metadata]
69
76
  end
77
+ deprecation_deprecate find_with_metadata: 'Use find instead with provides models with metadata.'
70
78
 
71
79
  # Get a list of the collections. (Similar to Valkyrie's find_inverse_references_by)
72
80
  # @raise [UnexpectedResponse] if the request is unsuccessful.
@@ -9,11 +9,12 @@ module Dor
9
9
  class ObjectMetadata
10
10
  extend Deprecation
11
11
 
12
- attr_reader :created_at, :updated_at
12
+ attr_reader :created_at, :updated_at, :etag
13
13
 
14
- def initialize(created_at:, updated_at:)
14
+ def initialize(created_at:, updated_at:, etag: nil)
15
15
  @created_at = created_at
16
16
  @updated_at = updated_at
17
+ @etag = etag
17
18
  end
18
19
 
19
20
  def [](key)
@@ -12,20 +12,6 @@ module Dor
12
12
  # @param assign_doi [Boolean]
13
13
  # @return [Cocina::Models::RequestDRO,Cocina::Models::RequestCollection,Cocina::Models::RequestAPO] the returned model
14
14
  def register(params:, assign_doi: false)
15
- json_str = register_response(params: params, assign_doi: assign_doi)
16
- json = JSON.parse(json_str)
17
-
18
- Cocina::Models.build(json)
19
- end
20
-
21
- private
22
-
23
- # make the registration request to the server
24
- # @param params [Hash] optional params (see dor-services-app)
25
- # @param assign_doi [Boolean]
26
- # @raise [UnexpectedResponse] on an unsuccessful response from the server
27
- # @return [String] the raw JSON from the server
28
- def register_response(params:, assign_doi:)
29
15
  resp = connection.post do |req|
30
16
  req.url "#{api_version}/objects"
31
17
  req.headers['Content-Type'] = 'application/json'
@@ -34,9 +20,10 @@ module Dor
34
20
  req.params[:assign_doi] = true if assign_doi
35
21
  req.body = params.to_json
36
22
  end
37
- return resp.body if resp.success?
38
23
 
39
- raise_exception_based_on_response!(resp)
24
+ raise_exception_based_on_response!(resp) unless resp.success?
25
+
26
+ build_cocina_from_response(resp)
40
27
  end
41
28
  end
42
29
  end
@@ -3,7 +3,7 @@
3
3
  module Dor
4
4
  module Services
5
5
  class Client
6
- VERSION = '8.8.0'
6
+ VERSION = '9.1.0'
7
7
  end
8
8
  end
9
9
  end
@@ -5,6 +5,14 @@ module Dor
5
5
  class Client
6
6
  # @abstract API calls to a versioned endpoint
7
7
  class VersionedService
8
+ EXCEPTION_CLASS = {
9
+ 400 => BadRequestError,
10
+ 401 => UnauthorizedResponse,
11
+ 404 => NotFoundResponse,
12
+ 409 => ConflictResponse,
13
+ 412 => PreconditionFailedResponse
14
+ }.freeze
15
+
8
16
  def initialize(connection:, version:)
9
17
  @connection = connection
10
18
  @api_version = version
@@ -19,24 +27,31 @@ module Dor
19
27
 
20
28
  attr_reader :connection, :api_version
21
29
 
22
- # rubocop:disable Metrics/MethodLength
23
30
  def raise_exception_based_on_response!(response, object_identifier = nil)
24
- exception_class = case response.status
25
- when 400
26
- BadRequestError
27
- when 401
28
- UnauthorizedResponse
29
- when 404
30
- NotFoundResponse
31
- when 409
32
- ConflictResponse
33
- else
34
- UnexpectedResponse
35
- end
36
- raise exception_class,
37
- ResponseErrorFormatter.format(response: response, object_identifier: object_identifier)
31
+ data = if response.headers['content-type'] == 'application/json'
32
+ JSON.parse(response.body)
33
+ else
34
+ {}
35
+ end
36
+ exception_class = EXCEPTION_CLASS.fetch(response.status, UnexpectedResponse)
37
+ raise exception_class.new(response: response,
38
+ object_identifier: object_identifier,
39
+ errors: data.fetch('errors', []))
40
+ end
41
+
42
+ def build_cocina_from_response(response)
43
+ cocina_object = Cocina::Models.build(JSON.parse(response.body))
44
+ Cocina::Models.with_metadata(cocina_object, response.headers['ETag'], created: date_from_header(response, 'X-Created-At'),
45
+ modified: date_from_header(response, 'Last-Modified'))
46
+ end
47
+
48
+ def build_json_from_cocina(cocina_object)
49
+ Cocina::Models.without_metadata(cocina_object).to_json
50
+ end
51
+
52
+ def date_from_header(response, key)
53
+ response.headers[key]&.to_datetime
38
54
  end
39
- # rubocop:enable Metrics/MethodLength
40
55
  end
41
56
  end
42
57
  end
@@ -38,7 +38,26 @@ module Dor
38
38
 
39
39
  # Error that is raised when the remote server returns some unexpected response
40
40
  # this could be any 4xx or 5xx status (except the ones that are direct children of the Error class above)
41
- class UnexpectedResponse < Error; end
41
+ class UnexpectedResponse < Error
42
+ # @param [Faraday::Response] response
43
+ # @param [String] object_identifier (nil)
44
+ # @param [Hash<String,Object>] errors (nil) the JSON-API errors object
45
+ # rubocop:disable Lint/MissingSuper
46
+ def initialize(response:, object_identifier: nil, errors: nil)
47
+ @response = response
48
+ @object_identifier = object_identifier
49
+ @errors = errors
50
+ end
51
+ # rubocop:enable Lint/MissingSuper
52
+
53
+ attr_accessor :errors
54
+
55
+ def to_s
56
+ return errors.map { |e| "#{e['title']} (#{e['detail']})" }.join(', ') if errors.present?
57
+
58
+ ResponseErrorFormatter.format(response: @response, object_identifier: @object_identifier)
59
+ end
60
+ end
42
61
 
43
62
  # Error that is raised when the remote server returns a 401 Unauthorized
44
63
  class UnauthorizedResponse < UnexpectedResponse; end
@@ -46,6 +65,10 @@ module Dor
46
65
  # Error that is raised when the remote server returns a 409 Conflict
47
66
  class ConflictResponse < UnexpectedResponse; end
48
67
 
68
+ # Error that is raised when the remote server returns a 412 Precondition Failed.
69
+ # This occurs when you sent an etag with If-Match, but the etag didn't match the latest version
70
+ class PreconditionFailedResponse < UnexpectedResponse; end
71
+
49
72
  # Error that is raised when the remote server returns a 400 Bad Request; apps should not retry the request
50
73
  class BadRequestError < UnexpectedResponse; end
51
74
 
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dor-services-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.8.0
4
+ version: 9.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Coyne
8
8
  - Michael Giarlo
9
- autorequire:
9
+ autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2022-04-15 00:00:00.000000000 Z
12
+ date: 2022-04-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -37,14 +37,14 @@ dependencies:
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.74.0
40
+ version: 0.75.0
41
41
  type: :runtime
42
42
  prerelease: false
43
43
  version_requirements: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.74.0
47
+ version: 0.75.0
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: deprecation
50
50
  requirement: !ruby/object:Gem::Requirement
@@ -213,7 +213,7 @@ dependencies:
213
213
  - - ">="
214
214
  - !ruby/object:Gem::Version
215
215
  version: '0'
216
- description:
216
+ description:
217
217
  email:
218
218
  - jcoyne@justincoyne.com
219
219
  - leftwing@alumni.rutgers.edu
@@ -262,7 +262,7 @@ homepage: https://github.com/sul-dlss/dor-services-client
262
262
  licenses: []
263
263
  metadata:
264
264
  rubygems_mfa_required: 'true'
265
- post_install_message:
265
+ post_install_message:
266
266
  rdoc_options: []
267
267
  require_paths:
268
268
  - lib
@@ -280,8 +280,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
280
280
  - !ruby/object:Gem::Version
281
281
  version: '0'
282
282
  requirements: []
283
- rubygems_version: 3.1.4
284
- signing_key:
283
+ rubygems_version: 3.3.7
284
+ signing_key:
285
285
  specification_version: 4
286
286
  summary: A client for dor-services-app
287
287
  test_files: []