sdr-client 2.12.0 → 2.13.0.beta1

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.rubocop_todo.yml +24 -20
  4. data/Gemfile.lock +11 -8
  5. data/exe/sdr_redesigned +10 -0
  6. data/lib/sdr_client/redesigned_client/authenticator.rb +40 -0
  7. data/lib/sdr_client/redesigned_client/cli/config.rb +32 -0
  8. data/lib/sdr_client/redesigned_client/cli/credentials.rb +35 -0
  9. data/lib/sdr_client/redesigned_client/cli/update.rb +186 -0
  10. data/lib/sdr_client/redesigned_client/cli.rb +198 -0
  11. data/lib/sdr_client/redesigned_client/create_resource.rb +71 -0
  12. data/lib/sdr_client/redesigned_client/deposit.rb +115 -0
  13. data/lib/sdr_client/redesigned_client/direct_upload_request.rb +45 -0
  14. data/lib/sdr_client/redesigned_client/direct_upload_response.rb +9 -0
  15. data/lib/sdr_client/redesigned_client/file.rb +100 -0
  16. data/lib/sdr_client/redesigned_client/file_set.rb +53 -0
  17. data/lib/sdr_client/redesigned_client/file_type_file_set_strategy.rb +13 -0
  18. data/lib/sdr_client/redesigned_client/find.rb +42 -0
  19. data/lib/sdr_client/redesigned_client/image_file_set_strategy.rb +13 -0
  20. data/lib/sdr_client/redesigned_client/job_status.rb +74 -0
  21. data/lib/sdr_client/redesigned_client/matching_file_grouping_strategy.rb +19 -0
  22. data/lib/sdr_client/redesigned_client/metadata.rb +64 -0
  23. data/lib/sdr_client/redesigned_client/operations/md5.rb +16 -0
  24. data/lib/sdr_client/redesigned_client/operations/mime_type.rb +17 -0
  25. data/lib/sdr_client/redesigned_client/operations/sha1.rb +16 -0
  26. data/lib/sdr_client/redesigned_client/request_builder.rb +171 -0
  27. data/lib/sdr_client/redesigned_client/single_file_grouping_strategy.rb +14 -0
  28. data/lib/sdr_client/redesigned_client/structural_grouper.rb +72 -0
  29. data/lib/sdr_client/redesigned_client/structural_metadata_builder.rb +51 -0
  30. data/lib/sdr_client/redesigned_client/unexpected_response.rb +25 -0
  31. data/lib/sdr_client/redesigned_client/update_dro_with_file_identifiers.rb +35 -0
  32. data/lib/sdr_client/redesigned_client/update_resource.rb +61 -0
  33. data/lib/sdr_client/redesigned_client/upload_files.rb +71 -0
  34. data/lib/sdr_client/redesigned_client/upload_files_metadata_builder.rb +40 -0
  35. data/lib/sdr_client/redesigned_client.rb +192 -0
  36. data/lib/sdr_client/version.rb +1 -1
  37. data/lib/sdr_client.rb +3 -1
  38. metadata +35 -3
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'timeout'
4
+
5
+ module SdrClient
6
+ class RedesignedClient
7
+ # Wraps operations waiting for results from jobs
8
+ class JobStatus
9
+ def initialize(job_id:)
10
+ @job_id = job_id
11
+ @result = {
12
+ status: 'not started',
13
+ output: {
14
+ errors: nil,
15
+ druid: ''
16
+ }
17
+ }
18
+ end
19
+
20
+ def complete?
21
+ @result = client.get(path: path)
22
+ @result[:status] == 'complete'
23
+ end
24
+
25
+ def druid
26
+ @result[:output][:druid]
27
+ end
28
+
29
+ def errors
30
+ @result[:output][:errors]
31
+ end
32
+
33
+ # Polls using exponential backoff, so as not to overrwhelm the server.
34
+ # @param [Float] secs_between_requests (3.0) initially, how many secs between polling requests
35
+ # @param [Integer] timeout_in_secs (180) timeout after this many secs
36
+ # @param [Float] backoff_factor (2.0) how quickly to backoff. This should be > 1.0 and probably ought to be <= 2.0
37
+ # @return [Boolean] true if successful false if unsuccessful.
38
+ def wait_until_complete(secs_between_requests: 3.0,
39
+ timeout_in_secs: 180,
40
+ backoff_factor: 2.0,
41
+ max_secs_between_requests: 60)
42
+ poll_until_complete(secs_between_requests, timeout_in_secs, backoff_factor, max_secs_between_requests)
43
+ errors.nil?
44
+ end
45
+
46
+ private
47
+
48
+ attr_reader :job_id
49
+
50
+ def client
51
+ SdrClient::RedesignedClient.instance
52
+ end
53
+
54
+ def path
55
+ "/v1/background_job_results/#{job_id}"
56
+ end
57
+
58
+ def poll_until_complete(secs_between_requests, timeout_in_secs, backoff_factor, max_secs_between_requests)
59
+ interval = secs_between_requests
60
+ Timeout.timeout(timeout_in_secs) do
61
+ loop do
62
+ break if complete?
63
+
64
+ sleep(interval)
65
+ # Exponential backoff, limited to max_secs_between_requests
66
+ interval = [interval * backoff_factor, max_secs_between_requests].min
67
+ end
68
+ end
69
+ rescue Timeout::Error
70
+ @result[:output][:errors] = ["Not complete after #{timeout_in_secs} seconds"]
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ class RedesignedClient
5
+ # This strategy is for building one file set per set of similarly prefixed uploaded files
6
+ class MatchingFileGroupingStrategy
7
+ # @param [Array<SdrClient::RedesignedClient::DirectUploadResponse>] uploads the uploaded files to attach.
8
+ # @return [Array<Array<SdrClient::RedesignedClient::DirectUploadResponse>>] uploads the grouped uploaded files.
9
+ def self.run(uploads: [])
10
+ # Call `#values` on the result of the grouping operation because:
11
+ # 1) `StructuralGrouper#build_filesets` expects an array of arrays (not hashes); and
12
+ # 2) the keys aren't used anywhere
13
+ uploads.group_by do |ul|
14
+ ::File.join(::File.dirname(ul.filename), ::File.basename(ul.filename, '.*'))
15
+ end.values
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ class RedesignedClient
5
+ # Build an object and then deposit it
6
+ class Metadata
7
+ def self.deposit(...)
8
+ new(...).deposit
9
+ end
10
+
11
+ # @param [String] apo object ID (druid) of the admin policy/APO
12
+ # @param [String] basepath the base path of the files (to make relative paths absolute)
13
+ # @param [String] source_id source ID
14
+ # @param [Hash] options optional parameters
15
+ # @option options [Array<String>] files a list of relative filepaths to upload
16
+ # @option options [Hash<String, Hash<String, String>>] files_metadata file name, hash of additional file metadata
17
+ def initialize(apo:, basepath:, source_id:, **options)
18
+ @apo = apo
19
+ @basepath = basepath
20
+ @source_id = source_id
21
+ @options = options
22
+ end
23
+
24
+ def deposit # rubocop:disable Metrics/MethodLength
25
+ structural_metadata = SdrClient::RedesignedClient::StructuralMetadataBuilder.build(
26
+ files: files, files_metadata: files_metadata, basepath: basepath
27
+ )
28
+ request_builder = SdrClient::RedesignedClient::RequestBuilder.new(
29
+ apo: apo,
30
+ source_id: source_id,
31
+ files_metadata: structural_metadata,
32
+ **options
33
+ )
34
+ client.deposit_model(
35
+ model: request_builder.to_cocina,
36
+ basepath: basepath,
37
+ files: files,
38
+ request_builder: request_builder,
39
+ **options
40
+ )
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :apo, :basepath, :source_id, :options
46
+
47
+ def client
48
+ SdrClient::RedesignedClient.instance
49
+ end
50
+
51
+ def files
52
+ options.fetch(:files, [])
53
+ end
54
+
55
+ def files_metadata
56
+ options.fetch(:files_metadata, {})
57
+ end
58
+
59
+ def accession
60
+ options.fetch(:accession, false)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ class RedesignedClient
5
+ module Operations
6
+ # MD5 for this file.
7
+ class MD5
8
+ NAME = 'md5'
9
+
10
+ def self.for(filepath:, **)
11
+ Digest::MD5.file(filepath).hexdigest
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ class RedesignedClient
5
+ module Operations
6
+ # Mime-type for this file.
7
+ class MimeType
8
+ NAME = 'mime_type'
9
+
10
+ def self.for(filepath:, **)
11
+ argv = Shellwords.escape(filepath)
12
+ `file --mime-type -b #{argv}`.chomp
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ class RedesignedClient
5
+ module Operations
6
+ # SHA1 for this file.
7
+ class SHA1
8
+ NAME = 'sha1'
9
+
10
+ def self.for(filepath:, **)
11
+ Digest::SHA1.file(filepath).hexdigest
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ class RedesignedClient
5
+ # Builds a Cocina request object from metadata. This is what we send to the server when doing a deposit.
6
+ class RequestBuilder # rubocop:disable Metrics/ClassLength
7
+ # @param [String] apo the object ID of the administrative policy object
8
+ # @param [String] source_id the source ID of the object
9
+ # @param [Hash] options optional parameters
10
+ # @option options [String] label the required object label
11
+ # @option options [String] view the access level for viewing the object
12
+ # @option options [String] download the access level for downloading the object
13
+ # @option options [String] type (https://cocina.sul.stanford.edu/models/object) the required object type.
14
+ # @option options [String] use_and_reproduction the use and reproduction statement
15
+ # @option options [String] copyright the copyright statement
16
+ # @option options [String] collection the object ID of the collection object
17
+ # @option options [String] catkey the catalog key (from now unused Symphony ILS)
18
+ # @option options [String] folio_instance_hrid the instance ID from the Folio ILS
19
+ # @option options [String] viewing_direction the viewing direction (left to right, right to left)
20
+ # @option options [Date|nil] embargo_release_date when to release the embargo (or nil if none)
21
+ # @option options [String] embargo_access the access level for viewing the object after the embargo period
22
+ # @option options [String] embargo_download the access level for downloading the object after the embargo period
23
+ # @option options [Array<FileSet>] file_sets the file sets to attach.
24
+ # @option options [Hash<String, Hash<String, String>>] files_metadata file name, hash of additional file metadata
25
+ # Additional metadata includes access, preserve, shelve, publish, md5, sha1
26
+ def initialize(apo:, source_id:, **options)
27
+ @apo = apo
28
+ @source_id = source_id
29
+ @options = options
30
+ end
31
+
32
+ def to_h
33
+ {
34
+ access: access_struct,
35
+ type: type,
36
+ administrative: administrative,
37
+ identification: identification,
38
+ structural: structural,
39
+ version: 1,
40
+ label: label.nil? ? ':auto' : label
41
+ }
42
+ end
43
+
44
+ def to_cocina
45
+ Cocina::Models.build_request(to_h.with_indifferent_access)
46
+ end
47
+
48
+ # @param [String] filename
49
+ # @return [Hash] the metadata for the file
50
+ def for(filename)
51
+ files_metadata
52
+ .fetch(filename, {})
53
+ .with_indifferent_access
54
+ .tap do |metadata|
55
+ metadata[:view] = view unless metadata.key?(:view)
56
+ metadata[:download] = download unless metadata.key?(:download)
57
+ end
58
+ end
59
+
60
+ def type
61
+ options.fetch(:type, Cocina::Models::ObjectType.object)
62
+ end
63
+
64
+ attr_writer :file_sets
65
+
66
+ private
67
+
68
+ attr_reader :apo, :source_id, :options
69
+
70
+ def administrative
71
+ {
72
+ hasAdminPolicy: apo
73
+ }
74
+ end
75
+
76
+ def identification
77
+ { sourceId: source_id }.tap do |json|
78
+ json[:catalogLinks] = []
79
+ json[:catalogLinks] << { catalog: 'symphony', catalogRecordId: catkey, refresh: true } if catkey
80
+ if folio_instance_hrid
81
+ json[:catalogLinks] << { catalog: 'folio', catalogRecordId: folio_instance_hrid,
82
+ refresh: true }
83
+ end
84
+ json.delete(:catalogLinks) if json[:catalogLinks].empty?
85
+ end
86
+ end
87
+
88
+ def structural
89
+ {}.tap do |json|
90
+ json[:isMemberOf] = [collection] if collection
91
+ json[:contains] = file_sets.map(&:to_h) unless file_sets.empty?
92
+ json[:hasMemberOrders] = [{ viewingDirection: viewing_direction }] if viewing_direction
93
+ end
94
+ end
95
+
96
+ def access_struct # rubocop:disable Metrics/MethodLength
97
+ {
98
+ view: view,
99
+ download: download
100
+ }.tap do |json|
101
+ json[:useAndReproductionStatement] = use_and_reproduction if use_and_reproduction
102
+ json[:copyright] = copyright if copyright
103
+
104
+ if embargo_release_date
105
+ json[:embargo] = {
106
+ releaseDate: embargo_release_date.strftime('%FT%T%:z'),
107
+ view: embargo_access,
108
+ download: embargo_download
109
+ }
110
+ end
111
+ end
112
+ end
113
+
114
+ def view
115
+ options.fetch(:view, 'dark')
116
+ end
117
+
118
+ def download
119
+ options.fetch(:download, 'none')
120
+ end
121
+
122
+ def label
123
+ options[:label]
124
+ end
125
+
126
+ def use_and_reproduction
127
+ options[:use_and_reproduction]
128
+ end
129
+
130
+ def copyright
131
+ options[:copyright]
132
+ end
133
+
134
+ def collection
135
+ options[:collection]
136
+ end
137
+
138
+ def catkey
139
+ options[:catkey]
140
+ end
141
+
142
+ def folio_instance_hrid
143
+ options[:folio_instance_hrid]
144
+ end
145
+
146
+ def viewing_direction
147
+ options[:viewing_direction]
148
+ end
149
+
150
+ def embargo_release_date
151
+ options[:embargo_release_date]
152
+ end
153
+
154
+ def embargo_access
155
+ options.fetch(:embargo_access, 'world')
156
+ end
157
+
158
+ def embargo_download
159
+ options.fetch(:embargo_download, 'world')
160
+ end
161
+
162
+ def file_sets
163
+ @file_sets ||= options.fetch(:file_sets, [])
164
+ end
165
+
166
+ def files_metadata
167
+ options.fetch(:files_metadata, {})
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ class RedesignedClient
5
+ # This strategy is for building one file set per uploaded file
6
+ class SingleFileGroupingStrategy
7
+ # @param [Array<SdrClient::RedesignedClient::DirectUploadResponse>] uploads the uploaded files to attach.
8
+ # @return [Array<Array<SdrClient::RedesignedClient::DirectUploadResponse>>] uploads the grouped uploaded files.
9
+ def self.run(uploads: [])
10
+ uploads.map { |upload| [upload] }
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ class RedesignedClient
5
+ # Builds and constructs the structural metadata given upload responses (file
6
+ # IDs, file set grouping strategies, etc.)
7
+ class StructuralGrouper
8
+ def self.group(...)
9
+ new(...).group
10
+ end
11
+
12
+ # @param [RequestBuilder] request_biulder a request builder instance
13
+ # @param [Array<DirectUploadResponse>] upload_responses upload response instances
14
+ # @param [String] grouping_strategy what strategy will be used to group files
15
+ # @param [String] file_set_strategy what strategy will be used to group file sets
16
+ def initialize(request_builder:, upload_responses:, grouping_strategy:, file_set_strategy: nil) # rubocop:disable Metrics/MethodLength
17
+ @request_builder = request_builder
18
+ @upload_responses = upload_responses
19
+ @grouping_strategy = if grouping_strategy == 'filename'
20
+ SdrClient::RedesignedClient::MatchingFileGroupingStrategy
21
+ else
22
+ SdrClient::RedesignedClient::SingleFileGroupingStrategy
23
+ end
24
+ @file_set_strategy = if file_set_strategy == 'image'
25
+ SdrClient::RedesignedClient::ImageFileSetStrategy
26
+ else
27
+ SdrClient::RedesignedClient::FileTypeFileSetStrategy
28
+ end
29
+ end
30
+
31
+ def group
32
+ request_builder
33
+ .tap { |request| request.file_sets = build_filesets(uploads: upload_responses) }
34
+ .to_cocina
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :request_builder, :file_set_strategy, :grouping_strategy, :upload_responses
40
+
41
+ # @param [Array<SdrClient::RedesignedClient::DirectUploadResponse>] uploads the uploaded files to attach.
42
+ # @return [Array<SdrClient::RedesignedClient::FileSet>] the uploads transformed to filesets
43
+ def build_filesets(uploads:)
44
+ grouping_strategy
45
+ .run(uploads: uploads)
46
+ .map
47
+ .with_index(1) do |upload_group, i|
48
+ SdrClient::RedesignedClient::FileSet.new(uploads: upload_group,
49
+ uploads_metadata: metadata_group(upload_group),
50
+ label: label(i),
51
+ type_strategy: file_set_strategy)
52
+ end
53
+ end
54
+
55
+ def label(index)
56
+ case request_builder.type
57
+ when Cocina::Models::ObjectType.book
58
+ "Page #{index}"
59
+ else
60
+ "Object #{index}"
61
+ end
62
+ end
63
+
64
+ # Get the metadata for the files belonging to a fileset
65
+ def metadata_group(upload_group)
66
+ upload_group.each_with_object({}) do |upload, obj|
67
+ obj[upload.filename] = request_builder.for(upload.filename)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ class RedesignedClient
5
+ # Build basic metadata for files, iterating over a series of operations
6
+ # The available options are here: https://github.com/sul-dlss/sdr-client/blob/v0.8.1/lib/sdr_client/deposit/file.rb#L8-L10
7
+ class StructuralMetadataBuilder
8
+ OPERATIONS = [
9
+ Operations::MimeType,
10
+ Operations::MD5,
11
+ Operations::SHA1
12
+ ].freeze
13
+ private_constant :OPERATIONS
14
+
15
+ # @param (see #initialize)
16
+ # @return (see #build)
17
+ def self.build(files:, files_metadata:, basepath:)
18
+ new(files: files, files_metadata: files_metadata.dup, basepath: basepath).build
19
+ end
20
+
21
+ # @param [Array<String>] files the list of relative filepaths for which to generate metadata
22
+ def initialize(files:, files_metadata:, basepath:)
23
+ @files = files
24
+ @files_metadata = files_metadata
25
+ @basepath = basepath
26
+ end
27
+
28
+ # @return [Hash<String, Hash<String, String>>] a map of relative filepaths to a map of metadata
29
+ def build
30
+ files.each do |filepath|
31
+ OPERATIONS.each do |operation|
32
+ result = operation.for(filepath: absolute_filepath_for(filepath))
33
+ next if result.nil?
34
+
35
+ files_metadata[filepath] ||= {}
36
+ files_metadata[filepath][operation::NAME] = result
37
+ end
38
+ end
39
+ files_metadata
40
+ end
41
+
42
+ private
43
+
44
+ attr_reader :files, :files_metadata, :basepath
45
+
46
+ def absolute_filepath_for(filepath)
47
+ ::File.join(basepath, filepath)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ class RedesignedClient
5
+ # Handles unexpected responses
6
+ class UnexpectedResponse
7
+ # Raised when there is a request error (e.g.: a cocina-models version mismatch)
8
+ class BadRequest < StandardError; end
9
+ # Raised when there is a problem with the credentials
10
+ class Unauthorized < StandardError; end
11
+
12
+ # @param [Faraday::Response] response
13
+ def self.call(response)
14
+ case response.status
15
+ when 400
16
+ raise BadRequest, "There was an error with your request: #{response.body}"
17
+ when 401
18
+ raise Unauthorized, 'There was an error with your credentials.'
19
+ else
20
+ raise "unexpected response: #{response.status} #{response.body}"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ class RedesignedClient
5
+ # Updates a DRO so that the structural metadata references the uploaded file ids
6
+ class UpdateDroWithFileIdentifiers
7
+ # @param [Cocina::Model::RequestDRO] request_dro for depositing
8
+ # @param [Array<DirectUploadResponse>] upload_responses the responses from uploading files
9
+ # @return [Cocina::Models::RequestDRO]
10
+ def self.update(request_dro:, upload_responses:)
11
+ # Manipulating request_dro as hash since immutable
12
+ structural = request_dro.to_h[:structural]
13
+ return request_dro.new({}) unless structural
14
+
15
+ signed_ids = signed_id_map(upload_responses)
16
+ request_dro.new(structural: updated_structural(structural, signed_ids))
17
+ end
18
+
19
+ def self.signed_id_map(upload_responses)
20
+ upload_responses.to_h { |response| [response.filename, response.signed_id] }
21
+ end
22
+ private_class_method :signed_id_map
23
+
24
+ def self.updated_structural(structural, signed_ids)
25
+ structural[:contains].each do |file_set|
26
+ file_set[:structural][:contains].each do |file|
27
+ file[:externalIdentifier] = signed_ids[file[:filename]] if signed_ids.key?(file[:filename])
28
+ end
29
+ end
30
+ structural
31
+ end
32
+ private_class_method :updated_structural
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ class RedesignedClient
5
+ # Updates a resource (metadata) in SDR
6
+ class UpdateResource
7
+ RESOURCE_PATH = '/v1/resources/%<id>s'
8
+
9
+ def self.run(...)
10
+ new(...).run
11
+ end
12
+
13
+ # @param [Cocina::Models::DRO] model
14
+ # @param [String] version_description
15
+ def initialize(model:, version_description: nil)
16
+ @model = model
17
+ @version_description = version_description
18
+ end
19
+
20
+ # @return [String] job id for the background job result
21
+ def run # rubocop:disable Metrics/MethodLength
22
+ json = model.to_json
23
+ logger.debug("Starting update with model: #{json}")
24
+
25
+ response_hash = client.put(
26
+ path: path,
27
+ body: json,
28
+ headers: { 'X-Cocina-Models-Version' => Cocina::Models::VERSION },
29
+ params: request_params,
30
+ expected_status: 202
31
+ )
32
+
33
+ logger.info("Response from server: #{response_hash}")
34
+
35
+ response_hash.fetch('jobId')
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :model, :version_description
41
+
42
+ def client
43
+ SdrClient::RedesignedClient.instance
44
+ end
45
+
46
+ def logger
47
+ SdrClient::RedesignedClient.config.logger
48
+ end
49
+
50
+ def path
51
+ format(RESOURCE_PATH, id: model.externalIdentifier)
52
+ end
53
+
54
+ def request_params
55
+ return { 'versionDescription' => version_description } if version_description
56
+
57
+ {}
58
+ end
59
+ end
60
+ end
61
+ end