folio_client 0.14.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
data/folio_client.gemspec CHANGED
@@ -1,24 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- lib = File.expand_path("lib", __dir__)
3
+ lib = File.expand_path('lib', __dir__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require "folio_client/version"
5
+ require 'folio_client/version'
6
6
 
7
7
  Gem::Specification.new do |spec|
8
- spec.name = "folio_client"
8
+ spec.name = 'folio_client'
9
9
  spec.version = FolioClient::VERSION
10
- spec.authors = ["Peter Mangiafico"]
11
- spec.email = ["pmangiafico@stanford.edu"]
10
+ spec.authors = ['Peter Mangiafico']
11
+ spec.email = ['pmangiafico@stanford.edu']
12
12
 
13
- spec.summary = "Interface for interacting with the Folio ILS API."
14
- spec.description = "This provides API interaction with the Folio ILS API"
15
- spec.homepage = "https://github.com/sul-dlss/folio_client"
16
- spec.required_ruby_version = ">= 2.6.0"
13
+ spec.summary = 'Interface for interacting with the Folio ILS API.'
14
+ spec.description = 'This provides API interaction with the Folio ILS API'
15
+ spec.homepage = 'https://github.com/sul-dlss/folio_client'
16
+ spec.required_ruby_version = '>= 3.0.0'
17
17
 
18
- spec.metadata["homepage_uri"] = spec.homepage
19
- spec.metadata["source_code_uri"] = "https://github.com/sul-dlss/folio_client"
20
- spec.metadata["changelog_uri"] = "https://github.com/sul-dlss/folio_client/releases"
21
- spec.metadata["rubygems_mfa_required"] = "true"
18
+ spec.metadata['homepage_uri'] = spec.homepage
19
+ spec.metadata['source_code_uri'] = 'https://github.com/sul-dlss/folio_client'
20
+ spec.metadata['changelog_uri'] = 'https://github.com/sul-dlss/folio_client/releases'
21
+ spec.metadata['rubygems_mfa_required'] = 'true'
22
22
 
23
23
  # Specify which files should be added to the gem when it is released.
24
24
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -27,20 +27,22 @@ Gem::Specification.new do |spec|
27
27
  (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|circleci)|appveyor)})
28
28
  end
29
29
  end
30
- spec.bindir = "exe"
30
+ spec.bindir = 'exe'
31
31
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
- spec.require_paths = ["lib"]
32
+ spec.require_paths = ['lib']
33
33
 
34
- spec.add_dependency "activesupport", ">= 4.2", "< 8"
35
- spec.add_dependency "faraday"
36
- spec.add_dependency "zeitwerk"
37
- spec.add_dependency "marc"
38
- spec.add_dependency "dry-monads"
34
+ spec.add_dependency 'activesupport', '>= 4.2', '< 8'
35
+ spec.add_dependency 'dry-monads'
36
+ spec.add_dependency 'faraday'
37
+ spec.add_dependency 'faraday-cookie_jar'
38
+ spec.add_dependency 'marc'
39
+ spec.add_dependency 'zeitwerk'
39
40
 
40
- spec.add_development_dependency "rake", "~> 13.0"
41
- spec.add_development_dependency "rspec", "~> 3.0"
42
- spec.add_development_dependency "rubocop-rspec"
43
- spec.add_development_dependency "simplecov"
44
- spec.add_development_dependency "standard"
45
- spec.add_development_dependency "webmock"
41
+ spec.add_development_dependency 'rake', '~> 13.0'
42
+ spec.add_development_dependency 'rspec', '~> 3.0'
43
+ spec.add_development_dependency 'rubocop'
44
+ spec.add_development_dependency 'rubocop-performance'
45
+ spec.add_development_dependency 'rubocop-rspec'
46
+ spec.add_development_dependency 'simplecov'
47
+ spec.add_development_dependency 'webmock'
46
48
  end
@@ -3,24 +3,34 @@
3
3
  class FolioClient
4
4
  # Fetch a token from the Folio API using login_params
5
5
  class Authenticator
6
- def self.token(login_params, connection)
7
- new(login_params, connection).token
8
- end
9
-
10
- def initialize(login_params, connection)
11
- @login_params = login_params
12
- @connection = connection
6
+ def self.token
7
+ new.token
13
8
  end
14
9
 
15
10
  # Request an access_token
16
- def token
17
- response = connection.post("/authn/login", login_params.to_json)
11
+ def token # rubocop:disable Metrics/AbcSize
12
+ response = FolioClient.connection.post(login_endpoint, FolioClient.config.login_params.to_json)
18
13
 
19
14
  UnexpectedResponse.call(response) unless response.success?
20
15
 
21
- JSON.parse(response.body)["okapiToken"]
16
+ # remove legacy_auth once new tokens enabled on Poppy
17
+ if FolioClient.config.legacy_auth
18
+ JSON.parse(response.body)['okapiToken']
19
+ else
20
+ access_cookie = FolioClient.cookie_jar.cookies.find { |cookie| cookie.name == 'folioAccessToken' }
21
+
22
+ raise StandardError, "Problem with folioAccessToken cookie: #{response.headers}, #{response.body}" unless access_cookie
23
+
24
+ access_cookie.value
25
+ end
22
26
  end
23
27
 
24
- attr_reader :login_params, :connection
28
+ private
29
+
30
+ def login_endpoint
31
+ return '/authn/login-with-expiry' unless FolioClient.config.legacy_auth
32
+
33
+ '/authn/login'
34
+ end
25
35
  end
26
36
  end
@@ -1,32 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "date"
4
- require "stringio"
3
+ require 'date'
4
+ require 'stringio'
5
5
 
6
6
  class FolioClient
7
7
  # Imports MARC records into FOLIO
8
8
  class DataImport
9
9
  JOB_PROFILE_ATTRIBUTES = %w[id name description dataType].freeze
10
10
 
11
- # @param client [FolioClient] the configured client
12
- def initialize(client)
13
- @client = client
14
- end
15
-
16
11
  # @param records [Array<MARC::Record>] records to be imported
17
12
  # @param job_profile_id [String] job profile id to use for import
18
13
  # @param job_profile_name [String] job profile name to use for import
19
14
  # @return [JobStatus] a job status instance to get information about the data import job
15
+ # rubocop:disable Metrics/MethodLength
20
16
  def import(records:, job_profile_id:, job_profile_name:)
21
- response_hash = client.post("/data-import/uploadDefinitions", {fileDefinitions: [{name: marc_filename}]})
22
- upload_definition_id = response_hash.dig("fileDefinitions", 0, "uploadDefinitionId")
23
- job_execution_id = response_hash.dig("fileDefinitions", 0, "jobExecutionId")
24
- file_definition_id = response_hash.dig("fileDefinitions", 0, "id")
17
+ response_hash = client.post('/data-import/uploadDefinitions', { fileDefinitions: [{ name: marc_filename }] })
18
+ upload_definition_id = response_hash.dig('fileDefinitions', 0, 'uploadDefinitionId')
19
+ job_execution_id = response_hash.dig('fileDefinitions', 0, 'jobExecutionId')
20
+ file_definition_id = response_hash.dig('fileDefinitions', 0, 'id')
25
21
 
26
22
  upload_file_response_hash = client.post(
27
23
  "/data-import/uploadDefinitions/#{upload_definition_id}/files/#{file_definition_id}",
28
24
  marc_binary(records),
29
- content_type: "application/octet-stream"
25
+ content_type: 'application/octet-stream'
30
26
  )
31
27
 
32
28
  client.post(
@@ -36,25 +32,30 @@ class FolioClient
36
32
  jobProfileInfo: {
37
33
  id: job_profile_id,
38
34
  name: job_profile_name,
39
- dataType: "MARC"
35
+ dataType: 'MARC'
40
36
  }
41
37
  }
42
38
  )
43
39
 
44
- JobStatus.new(client, job_execution_id: job_execution_id)
40
+ JobStatus.new(job_execution_id: job_execution_id)
45
41
  end
42
+ # rubocop:enable Metrics/MethodLength
46
43
 
47
44
  # @return [Array<Hash<String,String>>] a list of job profile hashes
48
45
  def job_profiles
49
46
  client
50
- .get("/data-import-profiles/jobProfiles")
51
- .fetch("jobProfiles", [])
47
+ .get('/data-import-profiles/jobProfiles')
48
+ .fetch('jobProfiles', [])
52
49
  .map { |profile| profile.slice(*JOB_PROFILE_ATTRIBUTES) }
53
50
  end
54
51
 
55
52
  private
56
53
 
57
- attr_reader :client, :job_profile_id, :job_profile_name
54
+ attr_reader :job_profile_id, :job_profile_name
55
+
56
+ def client
57
+ FolioClient.instance
58
+ end
58
59
 
59
60
  def marc_filename
60
61
  @marc_filename ||= "#{DateTime.now.iso8601}.marc"
@@ -3,26 +3,19 @@
3
3
  class FolioClient
4
4
  # Lookup items in the Folio inventory
5
5
  class Inventory
6
- attr_accessor :client
7
-
8
- # @param client [FolioClient] the configured client
9
- def initialize(client)
10
- @client = client
11
- end
12
-
13
6
  # get instance HRID from barcode
14
7
  # @param barcode [String] barcode
15
8
  # @return [String,nil] instance HRID if present, otherwise nil.
16
9
  def fetch_hrid(barcode:)
17
10
  # find the instance UUID for this barcode
18
- instance = client.get("/search/instances", {query: "items.barcode==#{barcode}"})
19
- instance_uuid = instance.dig("instances", 0, "id")
11
+ instance = client.get('/search/instances', { query: "items.barcode==#{barcode}" })
12
+ instance_uuid = instance.dig('instances', 0, 'id')
20
13
 
21
14
  return nil unless instance_uuid
22
15
 
23
16
  # next lookup the instance given the instance_uuid so we can fetch the hrid
24
17
  result = client.get("/inventory/instances/#{instance_uuid}")
25
- result.dig("hrid")
18
+ result['hrid']
26
19
  end
27
20
 
28
21
  # get instance external ID from HRID
@@ -30,12 +23,12 @@ class FolioClient
30
23
  # @return [String,nil] instance external ID if present, otherwise nil.
31
24
  # @raise [ResourceNotFound, MultipleResourcesFound] if search does not return exactly 1 result
32
25
  def fetch_external_id(hrid:)
33
- instance_response = client.get("/search/instances", {query: "hrid==#{hrid}"})
34
- record_count = instance_response["totalRecords"]
35
- raise ResourceNotFound, "No matching instance found for #{hrid}" if instance_response["totalRecords"] == 0
26
+ instance_response = client.get('/search/instances', { query: "hrid==#{hrid}" })
27
+ record_count = instance_response['totalRecords']
28
+ raise ResourceNotFound, "No matching instance found for #{hrid}" if (instance_response['totalRecords']).zero?
36
29
  raise MultipleResourcesFound, "Expected 1 record for #{hrid}, but found #{record_count}" if record_count > 1
37
30
 
38
- instance_response.dig("instances", 0, "id")
31
+ instance_response.dig('instances', 0, 'id')
39
32
  end
40
33
 
41
34
  # Retrieve basic information about a instance record. Example usage: get the external ID and _version for update using
@@ -46,8 +39,8 @@ class FolioClient
46
39
  # @return [Hash] information about the record.
47
40
  # @raise [ArgumentError] if the caller does not provide exactly one of external_id or hrid
48
41
  def fetch_instance_info(external_id: nil, hrid: nil)
49
- raise ArgumentError, "must pass exactly one of external_id or HRID" unless external_id.present? || hrid.present?
50
- raise ArgumentError, "must pass exactly one of external_id or HRID" if external_id.present? && hrid.present?
42
+ raise ArgumentError, 'must pass exactly one of external_id or HRID' unless external_id.present? || hrid.present?
43
+ raise ArgumentError, 'must pass exactly one of external_id or HRID' if external_id.present? && hrid.present?
51
44
 
52
45
  external_id ||= fetch_external_id(hrid: hrid)
53
46
  client.get("/inventory/instances/#{external_id}")
@@ -57,12 +50,12 @@ class FolioClient
57
50
  # @param status_id [String] uuid for an instance status code
58
51
  # @return true if instance status matches the uuid param, false otherwise
59
52
  # @raise [ResourceNotFound] if search by instance HRID returns 0 results
60
- def has_instance_status?(hrid:, status_id:)
53
+ def has_instance_status?(hrid:, status_id:) # rubocop:disable Naming/PredicateName
61
54
  # get the instance record and its statusId
62
- instance = client.get("/inventory/instances", {query: "hrid==#{hrid}"})
63
- raise ResourceNotFound, "No matching instance found for #{hrid}" if instance["totalRecords"] == 0
55
+ instance = client.get('/inventory/instances', { query: "hrid==#{hrid}" })
56
+ raise ResourceNotFound, "No matching instance found for #{hrid}" if (instance['totalRecords']).zero?
64
57
 
65
- instance_status_id = instance.dig("instances", 0, "statusId")
58
+ instance_status_id = instance.dig('instances', 0, 'statusId')
66
59
 
67
60
  return false unless instance_status_id
68
61
 
@@ -70,5 +63,11 @@ class FolioClient
70
63
 
71
64
  false
72
65
  end
66
+
67
+ private
68
+
69
+ def client
70
+ FolioClient.instance
71
+ end
73
72
  end
74
73
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "timeout"
4
- require "dry/monads"
3
+ require 'timeout'
4
+ require 'dry/monads'
5
5
 
6
6
  class FolioClient
7
7
  # Wraps operations waiting for results from jobs
@@ -10,10 +10,8 @@ class FolioClient
10
10
 
11
11
  attr_reader :job_execution_id
12
12
 
13
- # @param client [FolioClient] the configured client
14
13
  # @param job_execution_id [String] ID of the job to be checked on
15
- def initialize(client, job_execution_id:)
16
- @client = client
14
+ def initialize(job_execution_id:)
17
15
  @job_execution_id = job_execution_id
18
16
  end
19
17
 
@@ -27,7 +25,7 @@ class FolioClient
27
25
  def status
28
26
  response_hash = client.get("/change-manager/jobExecutions/#{job_execution_id}")
29
27
 
30
- return Failure(:pending) if !["COMMITTED", "ERROR"].include?(response_hash["status"])
28
+ return Failure(:pending) unless %w[COMMITTED ERROR].include?(response_hash['status'])
31
29
 
32
30
  Success()
33
31
  rescue ResourceNotFound
@@ -35,28 +33,33 @@ class FolioClient
35
33
  Failure(:not_found)
36
34
  end
37
35
 
38
- def wait_until_complete(wait_secs: default_wait_secs, timeout_secs: default_timeout_secs)
39
- wait_with_timeout(wait_secs: wait_secs, timeout_secs: timeout_secs) { status }
36
+ def wait_until_complete(wait_secs: default_wait_secs, timeout_secs: default_timeout_secs,
37
+ max_checks: default_max_checks)
38
+ wait_with_timeout(wait_secs: wait_secs, timeout_secs: timeout_secs, max_checks: max_checks) { status }
40
39
  end
41
40
 
41
+ # rubocop:disable Metrics/AbcSize
42
42
  def instance_hrids
43
43
  current_status = status
44
44
  return current_status unless current_status.success?
45
45
 
46
46
  @instance_hrids ||= wait_with_timeout do
47
47
  response = client
48
- .get("/metadata-provider/journalRecords/#{job_execution_id}")
49
- .fetch("journalRecords", [])
50
- .select { |journal_record| journal_record["entityType"] == "INSTANCE" && journal_record["actionStatus"] == "COMPLETED" }
51
- .filter_map { |instance_record| instance_record["entityHrId"] }
48
+ .get("/metadata-provider/journalRecords/#{job_execution_id}")
49
+ .fetch('journalRecords', [])
50
+ .select { |journal_record| journal_record['entityType'] == 'INSTANCE' && journal_record['actionStatus'] == 'COMPLETED' }
51
+ .filter_map { |instance_record| instance_record['entityHrId'] }
52
52
 
53
53
  response.empty? ? Failure() : Success(response)
54
54
  end
55
55
  end
56
+ # rubocop:enable Metrics/AbcSize
56
57
 
57
58
  private
58
59
 
59
- attr_reader :client
60
+ def client
61
+ FolioClient.instance
62
+ end
60
63
 
61
64
  def default_wait_secs
62
65
  1
@@ -66,13 +69,19 @@ class FolioClient
66
69
  10 * 60
67
70
  end
68
71
 
69
- def wait_with_timeout(wait_secs: default_wait_secs, timeout_secs: default_timeout_secs)
72
+ def default_max_checks
73
+ # arbitrary best guess at number of times to check for job status before erroring
74
+ 10
75
+ end
76
+
77
+ def wait_with_timeout(wait_secs: default_wait_secs, timeout_secs: default_timeout_secs,
78
+ max_checks: default_max_checks)
70
79
  Timeout.timeout(timeout_secs) do
71
80
  loop.with_index do |_, i|
72
81
  result = yield
73
82
 
74
83
  # If a 404, wait a bit longer before raising an error.
75
- check_not_found(result, i)
84
+ check_not_found(result, i, max_checks)
76
85
  return result if done_waiting?(result)
77
86
 
78
87
  sleep(wait_secs)
@@ -86,10 +95,11 @@ class FolioClient
86
95
  result.success? || (result.failure? && result.failure == :error)
87
96
  end
88
97
 
89
- def check_not_found(result, index)
90
- return unless result.failure? && result.failure == :not_found && index > 2
98
+ def check_not_found(result, index, max_checks)
99
+ return unless result.failure? && result.failure == :not_found && index > max_checks
91
100
 
92
- raise ResourceNotFound, "Job #{job_execution_id} not found after #{index} retries"
101
+ raise ResourceNotFound,
102
+ "Job #{job_execution_id} not found after #{index} retries. The data import job may still have completed."
93
103
  end
94
104
  end
95
105
  end
@@ -5,39 +5,38 @@ class FolioClient
5
5
  # https://s3.amazonaws.com/foliodocs/api/mod-organizations/p/organizations.html
6
6
  # https://s3.amazonaws.com/foliodocs/api/mod-organizations-storage/p/interface.html
7
7
  class Organizations
8
- attr_accessor :client
9
-
10
- # @param client [FolioClient] the configured client
11
- def initialize(client)
12
- @client = client
13
- end
14
-
15
8
  # @param query [String] an optional query to limit the number of organizations returned
16
9
  # @param limit [Integer] the number of results to return (defaults to 10,000)
17
10
  # @param offset [Integer] the offset for results returned (defaults to 0)
18
11
  # @param lang [String] language code for returned results (defaults to 'en')
19
- def fetch_list(query: nil, limit: 10000, offset: 0, lang: "en")
20
- params = {limit: limit, offset: offset, lang: lang}
12
+ def fetch_list(query: nil, limit: 10_000, offset: 0, lang: 'en')
13
+ params = { limit: limit, offset: offset, lang: lang }
21
14
  params[:query] = query if query
22
- client.get("/organizations/organizations", params)
15
+ client.get('/organizations/organizations', params)
23
16
  end
24
17
 
25
18
  # @param query [String] an optional query to limit the number of organization interfaces returned
26
19
  # @param limit [Integer] the number of results to return (defaults to 10,000)
27
20
  # @param offset [Integer] the offset for results returned (defaults to 0)
28
21
  # @param lang [String] language code for returned results (defaults to 'en')
29
- def fetch_interface_list(query: nil, limit: 10000, offset: 0, lang: "en")
30
- params = {limit: limit, offset: offset, lang: lang}
22
+ def fetch_interface_list(query: nil, limit: 10_000, offset: 0, lang: 'en')
23
+ params = { limit: limit, offset: offset, lang: lang }
31
24
  params[:query] = query if query
32
- client.get("/organizations-storage/interfaces", params)
25
+ client.get('/organizations-storage/interfaces', params)
33
26
  end
34
27
 
35
28
  # @param id [String] id for requested storage interface
36
29
  # @param lang [String] language code for returned result (defaults to 'en')
37
- def fetch_interface_details(id:, lang: "en")
30
+ def fetch_interface_details(id:, lang: 'en')
38
31
  client.get("/organizations-storage/interfaces/#{id}", {
39
- lang: lang
40
- })
32
+ lang: lang
33
+ })
34
+ end
35
+
36
+ private
37
+
38
+ def client
39
+ FolioClient.instance
41
40
  end
42
41
  end
43
42
  end
@@ -1,14 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class FolioClient
4
+ # Edit MARC JSON records in Folio
4
5
  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
6
  # Given an HRID, retrieves the associated MARC JSON, yields it to the caller as a hash,
13
7
  # and attempts to re-save it, using optimistic locking to prevent accidental overwrite,
14
8
  # in case another user or process has updated the record in the time between retrieval and
@@ -18,24 +12,32 @@ class FolioClient
18
12
  # HRID; the updated hash will be saved when control is returned from the block.
19
13
  # @note in limited manual testing, optimistic locking behaved like so when two edit attempts collided:
20
14
  # * 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\"'.
15
+ # * The other updating client would raise a StandardError, with a message like 'duplicate key value violates unique
16
+ # constraint \"idx_records_matched_id_gen\"'.
22
17
  # This client would fail to write.
23
18
  # * As opposed to the expected behavior of the "winner" getting a 200 ok response, and the "loser" getting a 409 conflict response.
24
19
  # @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
20
  def edit_marc_json(hrid:)
26
21
  instance_info = client.fetch_instance_info(hrid: hrid)
27
22
 
28
- version = instance_info["_version"]
29
- external_id = instance_info["id"]
23
+ version = instance_info['_version']
24
+ external_id = instance_info['id']
30
25
 
31
- record_json = client.get("/records-editor/records", {externalId: external_id})
26
+ record_json = client.get('/records-editor/records', { externalId: external_id })
32
27
 
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
28
+ parsed_record_id = record_json['parsedRecordId']
29
+ # setting this field on the JSON we send back is what will allow optimistic locking to catch stale updates
30
+ record_json['relatedRecordVersion'] = version
35
31
 
36
32
  yield record_json
37
33
 
38
34
  client.put("/records-editor/records/#{parsed_record_id}", record_json)
39
35
  end
36
+
37
+ private
38
+
39
+ def client
40
+ FolioClient.instance
41
+ end
40
42
  end
41
43
  end
@@ -5,26 +5,23 @@ class FolioClient
5
5
  class SourceStorage
6
6
  FIELDS_TO_REMOVE = %w[001 003].freeze
7
7
 
8
- attr_accessor :client
9
-
10
- # @param client [FolioClient] the configured client
11
- def initialize(client)
12
- @client = client
13
- end
14
-
15
8
  # get marc bib data from folio given an instance HRID
16
9
  # @param instance_hrid [String] the key to use for MARC lookup
17
10
  # @return [Hash] hash representation of the MARC. should be usable by MARC::Record.new_from_hash (from ruby-marc gem)
18
11
  # @raise [ResourceNotFound]
19
12
  # @raise [MultipleResourcesFound]
20
13
  def fetch_marc_hash(instance_hrid:)
21
- response_hash = client.get("/source-storage/source-records", {instanceHrid: instance_hrid})
14
+ response_hash = client.get('/source-storage/source-records', { instanceHrid: instance_hrid })
22
15
 
23
- record_count = response_hash["totalRecords"]
16
+ record_count = response_hash['totalRecords']
24
17
  raise ResourceNotFound, "No records found for #{instance_hrid}" if record_count.zero?
25
- raise MultipleResourcesFound, "Expected 1 record for #{instance_hrid}, but found #{record_count}" if record_count > 1
26
18
 
27
- response_hash["sourceRecords"].first["parsedRecord"]["content"]
19
+ if record_count > 1
20
+ raise MultipleResourcesFound,
21
+ "Expected 1 record for #{instance_hrid}, but found #{record_count}"
22
+ end
23
+
24
+ response_hash['sourceRecords'].first['parsedRecord']['content']
28
25
  end
29
26
 
30
27
  # get marc bib data as MARCXML from folio given an instance HRID
@@ -33,12 +30,20 @@ class FolioClient
33
30
  # @return [String] MARCXML string
34
31
  # @raise [ResourceNotFound]
35
32
  # @raise [MultipleResourcesFound]
33
+ # rubocop:disable Metrics/MethodLength
34
+ # rubocop:disable Metrics/AbcSize
36
35
  def fetch_marc_xml(instance_hrid: nil, barcode: nil)
37
- raise ArgumentError, "Either a barcode or a Folio instance HRID must be provided" if barcode.nil? && instance_hrid.nil?
36
+ if barcode.nil? && instance_hrid.nil?
37
+ raise ArgumentError,
38
+ 'Either a barcode or a Folio instance HRID must be provided'
39
+ end
38
40
 
39
41
  instance_hrid ||= client.fetch_hrid(barcode: barcode)
40
42
 
41
- raise ResourceNotFound, "Catalog record not found. HRID: #{instance_hrid} | Barcode: #{barcode}" if instance_hrid.blank?
43
+ if instance_hrid.blank?
44
+ raise ResourceNotFound,
45
+ "Catalog record not found. HRID: #{instance_hrid} | Barcode: #{barcode}"
46
+ end
42
47
 
43
48
  marc_record = MARC::Record.new_from_hash(
44
49
  fetch_marc_hash(instance_hrid: instance_hrid)
@@ -52,10 +57,18 @@ class FolioClient
52
57
  updated_marc.fields << field
53
58
  end
54
59
  # explicitly inject the instance_hrid into the 001 field
55
- updated_marc.fields << MARC::ControlField.new("001", instance_hrid)
60
+ updated_marc.fields << MARC::ControlField.new('001', instance_hrid)
56
61
  # explicitly inject FOLIO into the 003 field
57
- updated_marc.fields << MARC::ControlField.new("003", "FOLIO")
62
+ updated_marc.fields << MARC::ControlField.new('003', 'FOLIO')
58
63
  updated_marc.to_xml.to_s
59
64
  end
65
+ # rubocop:enable Metrics/MethodLength
66
+ # rubocop:enable Metrics/AbcSize
67
+
68
+ private
69
+
70
+ def client
71
+ FolioClient.instance
72
+ end
60
73
  end
61
74
  end
@@ -4,6 +4,8 @@ class FolioClient
4
4
  # Handles unexpected responses when communicating with Folio
5
5
  class UnexpectedResponse
6
6
  # @param [Faraday::Response] response
7
+ # rubocop:disable Metrics/MethodLength
8
+ # rubocop:disable Metrics/AbcSize
7
9
  def self.call(response)
8
10
  case response.status
9
11
  when 401
@@ -23,4 +25,6 @@ class FolioClient
23
25
  end
24
26
  end
25
27
  end
28
+ # rubocop:enable Metrics/MethodLength
29
+ # rubocop:enable Metrics/AbcSize
26
30
  end
@@ -4,29 +4,28 @@ class FolioClient
4
4
  # Query user records in Folio; see
5
5
  # https://s3.amazonaws.com/foliodocs/api/mod-users/r/users.html
6
6
  class Users
7
- attr_accessor :client
8
-
9
- # @param client [FolioClient] the configured client
10
- def initialize(client)
11
- @client = client
12
- end
13
-
14
7
  # @param query [String] an optional query to limit the number of users returned
15
8
  # @param limit [Integer] the number of results to return (defaults to 10,000)
16
9
  # @param offset [Integer] the offset for results returned (defaults to 0)
17
10
  # @param lang [String] language code for returned results (defaults to 'en')
18
- def fetch_list(query: nil, limit: 10000, offset: 0, lang: "en")
19
- params = {limit: limit, offset: offset, lang: lang}
11
+ def fetch_list(query: nil, limit: 10_000, offset: 0, lang: 'en')
12
+ params = { limit: limit, offset: offset, lang: lang }
20
13
  params[:query] = query if query
21
- client.get("/users", params)
14
+ client.get('/users', params)
22
15
  end
23
16
 
24
17
  # @param id [String] id for requested user
25
18
  # @param lang [String] language code for returned results (defaults to 'en')
26
- def fetch_user_details(id:, lang: "en")
19
+ def fetch_user_details(id:, lang: 'en')
27
20
  client.get("/users/#{id}", {
28
- lang: lang
29
- })
21
+ lang: lang
22
+ })
23
+ end
24
+
25
+ private
26
+
27
+ def client
28
+ FolioClient.instance
30
29
  end
31
30
  end
32
31
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class FolioClient
4
- VERSION = "0.14.0"
4
+ VERSION = '0.16.0'
5
5
  end