folio_client 0.6.1 → 0.8.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: db6b3bd02eac883e93ca7a24725f1c3d4f1ef48b34a3aa6e9326070e1903f600
4
- data.tar.gz: e2592286592f8fe064b326373dc3f8ca3123a7fa775fedcb23f00da97dc58638
3
+ metadata.gz: 726191de10d2f5974ac8294f2c4f93a75146631dd2c86200d2de53f9c72cd76f
4
+ data.tar.gz: 2ec95c6e87c4541fb1b2b7050c2bd84d5bd3a46f948b0b711b8f042f58ddf6e5
5
5
  SHA512:
6
- metadata.gz: 7ee03f5f2ae933bab57d6a3c6b2be09d7933b98f51ea66c07ffb09e821c4a5c9a08564125c01cd0858a4347465f8e588314b25bc26296851e61e593f8e1c42bd
7
- data.tar.gz: '0170689c2a3d9bd2547b976cd43b7258cc3dd6c51a5a8caba9ba62774d3425e62f624024c155d3b728a7dea8df30bce23ee7f111f628c5c1649c8327891e1cc9'
6
+ metadata.gz: ce0de774a34bbc7eda7a97d26e9ac12d49297a804994425045fe7a8b85017401f54dd11fd43952e4f75e799e232979475f3881eb6f9a211e6f4e32df48168424
7
+ data.tar.gz: eb31f66db330ab973060794cfcfb30471c02d21529c5242f62fc11a5d97542f1ce13587af5f8d6b2057d5b709951f9c49690725937e39f3d84236df144da3b73
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
- --format documentation
2
1
  --color
3
2
  --require spec_helper
data/.rubocop/custom.yml CHANGED
@@ -9,6 +9,8 @@ AllCops:
9
9
  # Per team developer playbook
10
10
  RSpec/MultipleMemoizedHelpers:
11
11
  Enabled: false
12
+ RSpec/MultipleExpectations:
13
+ Max: 3
12
14
 
13
15
  RSpec/BeEq: # new in 2.9.0
14
16
  Enabled: true
@@ -40,3 +42,21 @@ RSpec/Rails/HaveHttpStatus: # new in 2.12
40
42
  Enabled: true
41
43
  RSpec/Rails/InferredSpecType: # new in 2.14
42
44
  Enabled: true
45
+ Capybara/MatchStyle: # new in 2.17
46
+ Enabled: true
47
+ Capybara/NegationMatcher: # new in 2.14
48
+ Enabled: true
49
+ Capybara/SpecificActions: # new in 2.14
50
+ Enabled: true
51
+ Capybara/SpecificFinders: # new in 2.13
52
+ Enabled: true
53
+ Capybara/SpecificMatcher: # new in 2.12
54
+ Enabled: true
55
+ RSpec/DuplicatedMetadata: # new in 2.16
56
+ Enabled: true
57
+ RSpec/PendingWithoutReason: # new in 2.16
58
+ Enabled: true
59
+ RSpec/FactoryBot/FactoryNameStyle: # new in 2.16
60
+ Enabled: true
61
+ RSpec/Rails/MinitestAssertions: # new in 2.17
62
+ Enabled: true
data/.rubocop_todo.yml CHANGED
@@ -1,30 +1,7 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2023-02-10 00:58:06 UTC using RuboCop version 1.44.1.
3
+ # on 2023-03-03 17:57:09 UTC using RuboCop version 1.44.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
-
9
- # Offense count: 20
10
- # This cop supports safe autocorrection (--autocorrect).
11
- # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces.
12
- # SupportedStyles: space, no_space, compact
13
- # SupportedStylesForEmptyBraces: space, no_space
14
- Layout/SpaceInsideHashLiteralBraces:
15
- Exclude:
16
- - 'lib/folio_client.rb'
17
- - 'spec/folio_client/authenticator_spec.rb'
18
- - 'spec/folio_client_spec.rb'
19
-
20
- # Offense count: 1
21
- # This cop supports safe autocorrection (--autocorrect).
22
- # Configuration parameters: EnforcedStyle.
23
- # SupportedStyles: be, be_nil
24
- RSpec/BeNil:
25
- Exclude:
26
- - 'spec/folio_client_spec.rb'
27
-
28
- # Offense count: 1
29
- RSpec/MultipleExpectations:
30
- Max: 3
data/Gemfile.lock CHANGED
@@ -1,9 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- folio_client (0.6.1)
4
+ folio_client (0.8.0)
5
5
  activesupport (>= 4.2, < 8)
6
+ dry-monads
6
7
  faraday
8
+ marc
7
9
  zeitwerk
8
10
 
9
11
  GEM
@@ -23,6 +25,13 @@ GEM
23
25
  rexml
24
26
  diff-lcs (1.5.0)
25
27
  docile (1.4.0)
28
+ dry-core (1.0.0)
29
+ concurrent-ruby (~> 1.0)
30
+ zeitwerk (~> 2.6)
31
+ dry-monads (1.6.0)
32
+ concurrent-ruby (~> 1.0)
33
+ dry-core (~> 1.0, < 2)
34
+ zeitwerk (~> 2.6)
26
35
  faraday (2.7.4)
27
36
  faraday-net_http (>= 2.0, < 3.1)
28
37
  ruby2_keywords (>= 0.0.4)
@@ -32,9 +41,13 @@ GEM
32
41
  concurrent-ruby (~> 1.0)
33
42
  json (2.6.3)
34
43
  language_server-protocol (3.17.0.3)
35
- minitest (5.17.0)
44
+ marc (1.2.0)
45
+ rexml
46
+ scrub_rb (>= 1.0.1, < 2)
47
+ unf
48
+ minitest (5.18.0)
36
49
  parallel (1.22.1)
37
- parser (3.2.1.0)
50
+ parser (3.2.1.1)
38
51
  ast (~> 2.4.1)
39
52
  public_suffix (5.0.1)
40
53
  rainbow (3.1.1)
@@ -50,7 +63,7 @@ GEM
50
63
  rspec-expectations (3.12.2)
51
64
  diff-lcs (>= 1.2.0, < 2.0)
52
65
  rspec-support (~> 3.12.0)
53
- rspec-mocks (3.12.3)
66
+ rspec-mocks (3.12.4)
54
67
  diff-lcs (>= 1.2.0, < 2.0)
55
68
  rspec-support (~> 3.12.0)
56
69
  rspec-support (3.12.0)
@@ -64,18 +77,19 @@ GEM
64
77
  rubocop-ast (>= 1.24.1, < 2.0)
65
78
  ruby-progressbar (~> 1.7)
66
79
  unicode-display_width (>= 2.4.0, < 3.0)
67
- rubocop-ast (1.26.0)
80
+ rubocop-ast (1.27.0)
68
81
  parser (>= 3.2.1.0)
69
82
  rubocop-capybara (2.17.1)
70
83
  rubocop (~> 1.41)
71
84
  rubocop-performance (1.15.2)
72
85
  rubocop (>= 1.7.0, < 2.0)
73
86
  rubocop-ast (>= 0.4.0)
74
- rubocop-rspec (2.18.1)
87
+ rubocop-rspec (2.19.0)
75
88
  rubocop (~> 1.33)
76
89
  rubocop-capybara (~> 2.17)
77
- ruby-progressbar (1.11.0)
90
+ ruby-progressbar (1.13.0)
78
91
  ruby2_keywords (0.0.5)
92
+ scrub_rb (1.0.1)
79
93
  simplecov (0.22.0)
80
94
  docile (~> 1.1)
81
95
  simplecov-html (~> 0.11)
@@ -88,6 +102,9 @@ GEM
88
102
  rubocop-performance (= 1.15.2)
89
103
  tzinfo (2.0.6)
90
104
  concurrent-ruby (~> 1.0)
105
+ unf (0.1.4)
106
+ unf_ext
107
+ unf_ext (0.0.8.2)
91
108
  unicode-display_width (2.4.2)
92
109
  webmock (3.18.1)
93
110
  addressable (>= 2.8.0)
data/README.md CHANGED
@@ -70,6 +70,27 @@ client.fetch_marc_hash(instance_hrid: "a7927874")
70
70
  => {"fields"=>
71
71
  [{"003"=>"FOLIO"}....]
72
72
  }
73
+
74
+ # Import a MARC record
75
+ data_importer = client.data_import(marc: my_marc, job_profile_id: '4ba4f4ab', job_profile_name: 'ETDs')
76
+ # If called too quickly, might get Failure(:not_found)
77
+ data_importer.status
78
+ => Failure(:pending)
79
+ data_importer.wait_until_complete
80
+ => Success()
81
+ data_importer.instance_hrid
82
+ => Success("in00000000010")
83
+
84
+ # Create a Holdings record
85
+ holdings_client = client.holdings(instance_id: "99a6d818-d523-42f3-9844-81cf3187dbad")
86
+ holdings_client.create(permanent_location_id: "1b14e21c-8d47-45c7-bc49-456a0086422b", holdings_type_id: "996f93e2-5b5e-4cf2-9168-33ced1f95eed")
87
+ => {
88
+ "id" => "581f6289-001f-49d1-bab7-035f4d878cbd",
89
+ "_version" => 1,
90
+ "hrid" => "ho00000000065",
91
+ "holdingsTypeId" => "996f93e2-5b5e-4cf2-9168-33ced1f95eed"
92
+ ...
93
+ }
73
94
  ```
74
95
 
75
96
  ## Development
data/Rakefile CHANGED
@@ -2,11 +2,9 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rspec/core/rake_task"
5
-
6
- RSpec::Core::RakeTask.new(:spec)
7
-
8
5
  require "rubocop/rake_task"
9
6
 
7
+ RSpec::Core::RakeTask.new(:spec)
10
8
  RuboCop::RakeTask.new
11
9
 
12
- task default: %i[spec rubocop]
10
+ task default: %i[rubocop spec]
data/folio_client.gemspec CHANGED
@@ -34,6 +34,8 @@ Gem::Specification.new do |spec|
34
34
  spec.add_dependency "activesupport", ">= 4.2", "< 8"
35
35
  spec.add_dependency "faraday"
36
36
  spec.add_dependency "zeitwerk"
37
+ spec.add_dependency "marc"
38
+ spec.add_dependency "dry-monads"
37
39
 
38
40
  spec.add_development_dependency "rake", "~> 13.0"
39
41
  spec.add_development_dependency "rspec", "~> 3.0"
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "marc"
5
+ require "stringio"
6
+
7
+ class FolioClient
8
+ # Imports MARC records into FOLIO
9
+ class DataImport
10
+ # @param client [FolioClient] the configured client
11
+ def initialize(client)
12
+ @client = client
13
+ end
14
+
15
+ # @param record [MARC::Record] record to be imported
16
+ # @param job_profile_id [String] job profile id to use for import
17
+ # @param job_profile_name [String] job profile name to use for import
18
+ def import(marc:, job_profile_id:, job_profile_name:)
19
+ response_hash = client.post("/data-import/uploadDefinitions", {fileDefinitions: [{name: marc_filename}]})
20
+ upload_definition_id = response_hash.dig("fileDefinitions", 0, "uploadDefinitionId")
21
+ job_execution_id = response_hash.dig("fileDefinitions", 0, "jobExecutionId")
22
+ file_definition_id = response_hash.dig("fileDefinitions", 0, "id")
23
+
24
+ upload_file_response_hash = client.post("/data-import/uploadDefinitions/#{upload_definition_id}/files/#{file_definition_id}", marc_binary(marc), content_type: "application/octet-stream")
25
+
26
+ client.post(
27
+ "/data-import/uploadDefinitions/#{upload_definition_id}/processFiles",
28
+ {
29
+ uploadDefinition: upload_file_response_hash,
30
+ jobProfileInfo: {
31
+ id: job_profile_id,
32
+ name: job_profile_name,
33
+ dataType: "MARC"
34
+ }
35
+ }
36
+ )
37
+
38
+ JobStatus.new(client, job_execution_id: job_execution_id)
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :client, :marc, :job_profile_id, :job_profile_name
44
+
45
+ def marc_filename
46
+ @marc_filename ||= "#{DateTime.now.iso8601}.marc"
47
+ end
48
+
49
+ def marc_binary(marc)
50
+ StringIO.open do |io|
51
+ MARC::Writer.new(io) do |writer|
52
+ writer.write(marc)
53
+ end
54
+ io.string
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FolioClient
4
+ # Manage holdings records in the Folio inventory
5
+ class Holdings
6
+ attr_accessor :client, :instance_id
7
+
8
+ # @param client [FolioClient] the configured client
9
+ # @param instance_id [String] the UUID of the instance to which the holdings records belongs
10
+ def initialize(client, instance_id:)
11
+ @client = client
12
+ @instance_id = instance_id
13
+ end
14
+
15
+ # @param permanent_location_id [String] the UUID of the permanent location
16
+ # @param holdings_type_id [String] the UUID of the holdings type
17
+ def create(holdings_type_id:, permanent_location_id:)
18
+ client.post("/holdings-storage/holdings", {
19
+ instanceId: instance_id,
20
+ permanentLocationId: permanent_location_id,
21
+ holdingsTypeId: holdings_type_id
22
+ })
23
+ end
24
+ end
25
+ end
@@ -24,6 +24,33 @@ class FolioClient
24
24
  result.dig("hrid")
25
25
  end
26
26
 
27
+ # @param hrid [String] HRID to search by to fetch the external ID
28
+ # @return [String,nil] external ID if present, otherwise nil.
29
+ # @raise [ResourceNotFound, MultipleResourcesFound] if search does not return exactly 1 result
30
+ def fetch_external_id(hrid:)
31
+ instance_response = client.get("/search/instances", {query: "hrid==#{hrid}"})
32
+ record_count = instance_response["totalRecords"]
33
+ raise ResourceNotFound, "No matching instance found for #{hrid}" if instance_response["totalRecords"] == 0
34
+ raise MultipleResourcesFound, "Expected 1 record for #{hrid}, but found #{record_count}" if record_count > 1
35
+
36
+ instance_response.dig("instances", 0, "id")
37
+ end
38
+
39
+ # Retrieve basic information about a record. Example usage: get the external ID and _version for update using
40
+ # optimistic locking when the HRID is available: `fetch_instance_info(hrid: 'a1234').slice('id', '_version')`
41
+ # (or vice versa if the external ID is available).
42
+ # @param external_id [String] an external ID for looking up info about the record
43
+ # @param hrid [String] an HRID for looking up info about the record
44
+ # @return [Hash] information about the record.
45
+ # @raise [ArgumentError] if the caller does not provide exactly one of external_id or hrid
46
+ def fetch_instance_info(external_id: nil, hrid: nil)
47
+ raise ArgumentError, "must pass exactly one of external_id or HRID" unless external_id.present? || hrid.present?
48
+ raise ArgumentError, "must pass exactly one of external_id or HRID" if external_id.present? && hrid.present?
49
+
50
+ external_id ||= fetch_external_id(hrid: hrid)
51
+ client.get("/inventory/instances/#{external_id}")
52
+ end
53
+
27
54
  # @param hrid [String] folio instance HRID
28
55
  # @param status_id [String] uuid for an instance status code
29
56
  # @raise [ResourceNotFound] if search by hrid returns 0 results
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timeout"
4
+ require "dry/monads"
5
+
6
+ class FolioClient
7
+ # Wraps operations waiting for results from jobs
8
+ class JobStatus
9
+ include Dry::Monads[:result]
10
+
11
+ attr_reader :job_execution_id
12
+
13
+ # @param client [FolioClient] the configured client
14
+ # @param job_execution_id [String] ID of the job to be checked on
15
+ def initialize(client, job_execution_id:)
16
+ @client = client
17
+ @job_execution_id = job_execution_id
18
+ end
19
+
20
+ # @return [Dry::Monads::Result] Success if job is complete,
21
+ # Failure(:pending) if job is still running,
22
+ # Failure(:error) if job has errors
23
+ # Failure(:not_found) if job is not found
24
+ def status
25
+ response_hash = client.get("/metadata-provider/jobSummary/#{job_execution_id}")
26
+
27
+ return Failure(:error) if response_hash["totalErrors"].positive?
28
+ return Failure(:pending) if response_hash.dig("sourceRecordSummary", "totalCreatedEntities").zero? && response_hash.dig("sourceRecordSummary", "totalUpdatedEntities").zero?
29
+
30
+ Success()
31
+ rescue ResourceNotFound
32
+ # Checking the status immediately after starting the import may result in a 404.
33
+ Failure(:not_found)
34
+ end
35
+
36
+ def wait_until_complete(wait_secs: default_wait_secs, timeout_secs: default_timeout_secs)
37
+ wait_with_timeout(wait_secs: wait_secs, timeout_secs: timeout_secs) { status }
38
+ end
39
+
40
+ def instance_hrid
41
+ current_status = status
42
+ return current_status unless current_status.success?
43
+
44
+ @instance_hrid ||= wait_with_timeout do
45
+ response = client
46
+ .get("/metadata-provider/journalRecords/#{job_execution_id}")
47
+ .fetch("journalRecords", [])
48
+ .find { |journal_record| journal_record["entityType"] == "INSTANCE" }
49
+ &.fetch("entityHrId", nil)
50
+
51
+ response.nil? ? Failure() : Success(response)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ attr_reader :client
58
+
59
+ def default_wait_secs
60
+ 1
61
+ end
62
+
63
+ def default_timeout_secs
64
+ 5 * 60
65
+ end
66
+
67
+ def wait_with_timeout(wait_secs: default_wait_secs, timeout_secs: default_timeout_secs)
68
+ Timeout.timeout(timeout_secs) do
69
+ loop.with_index do |_, i|
70
+ result = yield
71
+
72
+ # If a 404, wait a bit longer before raising an error.
73
+ check_not_found(result, i)
74
+ return result if done_waiting?(result)
75
+
76
+ sleep(wait_secs)
77
+ end
78
+ end
79
+ rescue Timeout::Error
80
+ Failure(:timeout)
81
+ end
82
+
83
+ def done_waiting?(result)
84
+ result.success? || (result.failure? && result.failure == :error)
85
+ end
86
+
87
+ def check_not_found(result, index)
88
+ return unless result.failure? && result.failure == :not_found && index > 2
89
+
90
+ raise ResourceNotFound, "Job not found"
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FolioClient
4
+ class RecordsEditor
5
+ attr_accessor :client
6
+
7
+ # @param client [FolioClient] the configured client
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ # Given an HRID, retrieves the associated MARC JSON, yields it to the caller as a hash,
13
+ # and attempts to re-save it, using optimistic locking to prevent accidental overwrite,
14
+ # in case another user or process has updated the record in the time between retrieval and
15
+ # attempted save.
16
+ # @param hrid [String] the HRID of the MARC record to be edited and saved
17
+ # @yieldparam record_json [Hash] a hash representation of the MARC JSON for the
18
+ # HRID; the updated hash will be saved when control is returned from the block.
19
+ # @note in limited manual testing, optimistic locking behaved like so when two edit attempts collided:
20
+ # * One updating client would eventually raise a timeout. This updating client would actually write successfully, and version the record.
21
+ # * The other updating client would raise a StandardError, with a message like 'duplicate key value violates unique constraint \"idx_records_matched_id_gen\"'.
22
+ # This client would fail to write.
23
+ # * As opposed to the expected behavior of the "winner" getting a 200 ok response, and the "loser" getting a 409 conflict response.
24
+ # @todo If this is a problem in practice, see if it's possible to have Folio respond in a more standard way; or, workaround with error handling.
25
+ def edit_marc_json(hrid:)
26
+ instance_info = client.fetch_instance_info(hrid: hrid)
27
+
28
+ version = instance_info["_version"]
29
+ external_id = instance_info["id"]
30
+
31
+ record_json = client.get("/records-editor/records", {externalId: external_id})
32
+
33
+ parsed_record_id = record_json["parsedRecordId"]
34
+ record_json["relatedRecordVersion"] = version # setting this field on the JSON we send back is what will allow optimistic locking to catch stale updates
35
+
36
+ yield record_json
37
+
38
+ client.put("/records-editor/records/#{parsed_record_id}", record_json)
39
+ end
40
+ end
41
+ end
@@ -13,7 +13,7 @@ class FolioClient
13
13
  when 404
14
14
  raise ResourceNotFound, "Endpoint not found or resource does not exist: #{response.body}"
15
15
  when 422
16
- raise UnauthorizedError, "There was a problem fetching the access token: #{response.body} "
16
+ raise ValidationError, "There was a validation problem with the request: #{response.body} "
17
17
  when 500
18
18
  raise ServiceUnavailable, "The remote server returned an internal server error."
19
19
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class FolioClient
4
- VERSION = "0.6.1"
4
+ VERSION = "0.8.0"
5
5
  end
data/lib/folio_client.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/module/delegation"
4
+ require "active_support/core_ext/object/blank"
4
5
  require "faraday"
5
6
  require "singleton"
6
7
  require "ostruct"
@@ -16,7 +17,7 @@ class FolioClient
16
17
  # Base class for all FolioClient errors
17
18
  class Error < StandardError; end
18
19
 
19
- # Error raised by the Folio Auth API returns a 422 Unauthorized
20
+ # Error raised by the Folio Auth API returns a 401 Unauthorized
20
21
  class UnauthorizedError < Error; end
21
22
 
22
23
  # Error raised when the Folio API returns a 404 NotFound, or returns 0 results when one was expected
@@ -31,6 +32,9 @@ class FolioClient
31
32
  # Error raised when the Folio API returns a 500
32
33
  class ServiceUnavailable < Error; end
33
34
 
35
+ # Error raised when the Folio API returns a 422 Unprocessable Entity
36
+ class ValidationError < Error; end
37
+
34
38
  DEFAULT_HEADERS = {
35
39
  accept: "application/json, text/plain",
36
40
  content_type: "application/json"
@@ -48,8 +52,8 @@ class FolioClient
48
52
  self
49
53
  end
50
54
 
51
- delegate :config, :connection, :get, :post, to: :instance
52
- delegate :fetch_hrid, :fetch_marc_hash, :has_instance_status?, to: :instance
55
+ delegate :config, :connection, :get, :post, :put, to: :instance
56
+ delegate :fetch_hrid, :fetch_external_id, :fetch_instance_info, :fetch_marc_hash, :has_instance_status?, :data_import, :holdings, :edit_marc_json, to: :instance
53
57
  end
54
58
 
55
59
  attr_accessor :config
@@ -64,19 +68,50 @@ class FolioClient
64
68
 
65
69
  UnexpectedResponse.call(response) unless response.success?
66
70
 
71
+ return nil if response.body.blank?
72
+
67
73
  JSON.parse(response.body)
68
74
  end
69
75
 
70
76
  # Send an authenticated post request
77
+ # If the body is JSON, it will be automatically serialized
71
78
  # @param path [String] the path to the Folio API request
72
- # @param request [json] request body to post to the API
73
- def post(path, request = nil)
79
+ # @param body [Object] body to post to the API as JSON
80
+ def post(path, body = nil, content_type: "application/json")
81
+ req_body = (content_type == "application/json") ? body&.to_json : body
74
82
  response = TokenWrapper.refresh(config, connection) do
75
- connection.post(path, request, {"x-okapi-token": config.token})
83
+ req_headers = {
84
+ "x-okapi-token": config.token,
85
+ "content-type": content_type
86
+ }
87
+ connection.post(path, req_body, req_headers)
76
88
  end
77
89
 
78
90
  UnexpectedResponse.call(response) unless response.success?
79
91
 
92
+ return nil if response.body.blank?
93
+
94
+ JSON.parse(response.body)
95
+ end
96
+
97
+ # Send an authenticated put request
98
+ # If the body is JSON, it will be automatically serialized
99
+ # @param path [String] the path to the Folio API request
100
+ # @param body [Object] body to put to the API as JSON
101
+ def put(path, body = nil, content_type: "application/json")
102
+ req_body = (content_type == "application/json") ? body&.to_json : body
103
+ response = TokenWrapper.refresh(config, connection) do
104
+ req_headers = {
105
+ "x-okapi-token": config.token,
106
+ "content-type": content_type
107
+ }
108
+ connection.put(path, req_body, req_headers)
109
+ end
110
+
111
+ UnexpectedResponse.call(response) unless response.success?
112
+
113
+ return nil if response.body.blank?
114
+
80
115
  JSON.parse(response.body)
81
116
  end
82
117
 
@@ -90,17 +125,49 @@ class FolioClient
90
125
 
91
126
  # Public methods available on the FolioClient below
92
127
  def fetch_hrid(...)
93
- inventory = Inventory.new(self)
94
- inventory.fetch_hrid(...)
128
+ Inventory
129
+ .new(self)
130
+ .fetch_hrid(...)
131
+ end
132
+
133
+ def fetch_external_id(...)
134
+ Inventory
135
+ .new(self)
136
+ .fetch_external_id(...)
137
+ end
138
+
139
+ def fetch_instance_info(...)
140
+ Inventory
141
+ .new(self)
142
+ .fetch_instance_info(...)
95
143
  end
96
144
 
97
145
  def fetch_marc_hash(...)
98
- source_storage = SourceStorage.new(self)
99
- source_storage.fetch_marc_hash(...)
146
+ SourceStorage
147
+ .new(self)
148
+ .fetch_marc_hash(...)
100
149
  end
101
150
 
102
151
  def has_instance_status?(...)
103
- inventory = Inventory.new(self)
104
- inventory.has_instance_status?(...)
152
+ Inventory
153
+ .new(self)
154
+ .has_instance_status?(...)
155
+ end
156
+
157
+ def data_import(...)
158
+ DataImport
159
+ .new(self)
160
+ .import(...)
161
+ end
162
+
163
+ def holdings(...)
164
+ Holdings
165
+ .new(self, ...)
166
+ end
167
+
168
+ def edit_marc_json(...)
169
+ RecordsEditor
170
+ .new(self)
171
+ .edit_marc_json(...)
105
172
  end
106
173
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: folio_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Mangiafico
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-03-02 00:00:00.000000000 Z
11
+ date: 2023-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -58,6 +58,34 @@ dependencies:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
60
  version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: marc
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: dry-monads
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
61
89
  - !ruby/object:Gem::Dependency
62
90
  name: rake
63
91
  requirement: !ruby/object:Gem::Requirement
@@ -162,7 +190,11 @@ files:
162
190
  - folio_client.gemspec
163
191
  - lib/folio_client.rb
164
192
  - lib/folio_client/authenticator.rb
193
+ - lib/folio_client/data_import.rb
194
+ - lib/folio_client/holdings.rb
165
195
  - lib/folio_client/inventory.rb
196
+ - lib/folio_client/job_status.rb
197
+ - lib/folio_client/records_editor.rb
166
198
  - lib/folio_client/source_storage.rb
167
199
  - lib/folio_client/token_wrapper.rb
168
200
  - lib/folio_client/unexpected_response.rb