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/lib/folio_client.rb CHANGED
@@ -1,17 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/module/delegation"
4
- require "active_support/core_ext/object/blank"
5
- require "faraday"
6
- require "marc"
7
- require "ostruct"
8
- require "singleton"
9
- require "zeitwerk"
3
+ require 'active_support/core_ext/module/delegation'
4
+ require 'active_support/core_ext/object/blank'
5
+ require 'faraday'
6
+ require 'faraday-cookie_jar'
7
+ require 'marc'
8
+ require 'ostruct'
9
+ require 'singleton'
10
+ require 'zeitwerk'
10
11
 
11
12
  # Load the gem's internal dependencies: use Zeitwerk instead of needing to manually require classes
12
13
  Zeitwerk::Loader.for_gem.setup
13
14
 
14
15
  # Client for interacting with the Folio API
16
+ # rubocop:disable Metrics/ClassLength
15
17
  class FolioClient
16
18
  include Singleton
17
19
 
@@ -40,16 +42,18 @@ class FolioClient
40
42
  class ConflictError < Error; end
41
43
 
42
44
  DEFAULT_HEADERS = {
43
- accept: "application/json, text/plain",
44
- content_type: "application/json"
45
+ accept: 'application/json, text/plain',
46
+ content_type: 'application/json'
45
47
  }.freeze
46
48
 
47
49
  class << self
48
50
  # @param url [String] the folio API URL
49
51
  # @param login_params [Hash] the folio client login params (username:, password:)
50
52
  # @param okapi_headers [Hash] the okapi specific headers to add (X-Okapi-Tenant:, User-Agent:)
53
+ # @param legacy_auth [Boolean] true to use legacy /login rather than Poppy /login-with-expiry endpoint
51
54
  # @return [FolioClient] the configured Singleton class
52
- def configure(url:, login_params:, okapi_headers:, timeout: default_timeout)
55
+ def configure(url:, login_params:, okapi_headers:, timeout: default_timeout, legacy_auth: true)
56
+ # rubocop:disable Style/OpenStructUse
53
57
  instance.config = OpenStruct.new(
54
58
  # For the initial token, use a dummy value to avoid hitting any APIs
55
59
  # during configuration, allowing `with_token_refresh_when_unauthorized` to handle
@@ -61,23 +65,26 @@ class FolioClient
61
65
  # NOTE: `nil` and blank string cannot be used as dummy values here as
62
66
  # they lead to a malformed request to be sent, which triggers an
63
67
  # exception not rescued by `with_token_refresh_when_unauthorized`
64
- token: "a temporary dummy token to avoid hitting the API before it is needed",
68
+ token: 'a temporary dummy token to avoid hitting the API before it is needed',
65
69
  url: url,
66
70
  login_params: login_params,
67
71
  okapi_headers: okapi_headers,
68
- timeout: timeout
72
+ timeout: timeout,
73
+ legacy_auth: legacy_auth # default true until we have new token endpoint enabled in Poppy
69
74
  )
75
+ # rubocop:enable Style/OpenStructUse
70
76
 
71
77
  self
72
78
  end
73
79
 
74
- delegate :config, :connection, :data_import, :default_timeout,
75
- :edit_marc_json, :fetch_external_id, :fetch_hrid, :fetch_instance_info,
76
- :fetch_marc_hash, :fetch_marc_xml, :get, :has_instance_status?,
77
- :http_get_headers, :http_post_and_put_headers, :interface_details,
78
- :job_profiles, :organization_interfaces, :organizations, :users, :user_details,
79
- :post, :put, to:
80
- :instance end
80
+ delegate :config, :connection, :cookie_jar, :data_import, :default_timeout,
81
+ :edit_marc_json, :fetch_external_id, :fetch_hrid,
82
+ :fetch_instance_info, :fetch_marc_hash, :fetch_marc_xml,
83
+ :force_token_refresh!, :get, :has_instance_status?,
84
+ :http_get_headers, :http_post_and_put_headers, :interface_details,
85
+ :job_profiles, :organization_interfaces, :organizations, :users,
86
+ :user_details, :post, :put, to: :instance
87
+ end
81
88
 
82
89
  attr_accessor :config
83
90
 
@@ -86,7 +93,7 @@ class FolioClient
86
93
  # @param params [Hash] params to get to the API
87
94
  def get(path, params = {})
88
95
  response = with_token_refresh_when_unauthorized do
89
- connection.get(path, params, {"x-okapi-token": config.token})
96
+ connection.get(path, params, { 'x-okapi-token': config.token })
90
97
  end
91
98
 
92
99
  UnexpectedResponse.call(response) unless response.success?
@@ -100,12 +107,13 @@ class FolioClient
100
107
  # If the body is JSON, it will be automatically serialized
101
108
  # @param path [String] the path to the Folio API request
102
109
  # @param body [Object] body to post to the API as JSON
103
- def post(path, body = nil, content_type: "application/json")
104
- req_body = (content_type == "application/json") ? body&.to_json : body
110
+ # rubocop:disable Metrics/MethodLength
111
+ def post(path, body = nil, content_type: 'application/json')
112
+ req_body = content_type == 'application/json' ? body&.to_json : body
105
113
  response = with_token_refresh_when_unauthorized do
106
114
  req_headers = {
107
- "x-okapi-token": config.token,
108
- "content-type": content_type
115
+ 'x-okapi-token': config.token,
116
+ 'content-type': content_type
109
117
  }
110
118
  connection.post(path, req_body, req_headers)
111
119
  end
@@ -116,17 +124,19 @@ class FolioClient
116
124
 
117
125
  JSON.parse(response.body)
118
126
  end
127
+ # rubocop:enable Metrics/MethodLength
119
128
 
120
129
  # Send an authenticated put request
121
130
  # If the body is JSON, it will be automatically serialized
122
131
  # @param path [String] the path to the Folio API request
123
132
  # @param body [Object] body to put to the API as JSON
124
- def put(path, body = nil, content_type: "application/json")
125
- req_body = (content_type == "application/json") ? body&.to_json : body
133
+ # rubocop:disable Metrics/MethodLength
134
+ def put(path, body = nil, content_type: 'application/json')
135
+ req_body = content_type == 'application/json' ? body&.to_json : body
126
136
  response = with_token_refresh_when_unauthorized do
127
137
  req_headers = {
128
- "x-okapi-token": config.token,
129
- "content-type": content_type
138
+ 'x-okapi-token': config.token,
139
+ 'content-type': content_type
130
140
  }
131
141
  connection.put(path, req_body, req_headers)
132
142
  end
@@ -137,14 +147,22 @@ class FolioClient
137
147
 
138
148
  JSON.parse(response.body)
139
149
  end
150
+ # rubocop:enable Metrics/MethodLength
140
151
 
141
152
  # the base connection to the Folio API
142
153
  def connection
143
154
  @connection ||= Faraday.new(
144
155
  url: config.url,
145
156
  headers: DEFAULT_HEADERS.merge(config.okapi_headers || {}),
146
- request: {timeout: config.timeout}
147
- )
157
+ request: { timeout: config.timeout }
158
+ ) do |faraday|
159
+ faraday.use :cookie_jar, jar: cookie_jar
160
+ faraday.adapter Faraday.default_adapter
161
+ end
162
+ end
163
+
164
+ def cookie_jar
165
+ @cookie_jar ||= HTTP::CookieJar.new
148
166
  end
149
167
 
150
168
  # Public methods available on the FolioClient below
@@ -152,98 +170,98 @@ class FolioClient
152
170
  # @see Inventory#fetch_hrid
153
171
  def fetch_hrid(...)
154
172
  Inventory
155
- .new(self)
173
+ .new
156
174
  .fetch_hrid(...)
157
175
  end
158
176
 
159
177
  # @see Inventory#fetch_external_id
160
178
  def fetch_external_id(...)
161
179
  Inventory
162
- .new(self)
180
+ .new
163
181
  .fetch_external_id(...)
164
182
  end
165
183
 
166
184
  # @see Inventory#fetch_instance_info
167
185
  def fetch_instance_info(...)
168
186
  Inventory
169
- .new(self)
187
+ .new
170
188
  .fetch_instance_info(...)
171
189
  end
172
190
 
173
191
  # @see SourceStorage#fetch_marc_hash
174
192
  def fetch_marc_hash(...)
175
193
  SourceStorage
176
- .new(self)
194
+ .new
177
195
  .fetch_marc_hash(...)
178
196
  end
179
197
 
180
198
  # @see SourceStorage#fetch_marc_xml
181
199
  def fetch_marc_xml(...)
182
200
  SourceStorage
183
- .new(self)
201
+ .new
184
202
  .fetch_marc_xml(...)
185
203
  end
186
204
 
187
205
  # @see Inventory#has_instance_status?
188
- def has_instance_status?(...)
206
+ def has_instance_status?(...) # rubocop:disable Naming/PredicateName
189
207
  Inventory
190
- .new(self)
208
+ .new
191
209
  .has_instance_status?(...)
192
210
  end
193
211
 
194
212
  # @ see DataImport#import
195
213
  def data_import(...)
196
214
  DataImport
197
- .new(self)
215
+ .new
198
216
  .import(...)
199
217
  end
200
218
 
201
219
  # @ see DataImport#job_profiles
202
220
  def job_profiles(...)
203
221
  DataImport
204
- .new(self)
222
+ .new
205
223
  .job_profiles(...)
206
224
  end
207
225
 
208
226
  # @see RecordsEditor#edit_marc_json
209
227
  def edit_marc_json(...)
210
228
  RecordsEditor
211
- .new(self)
229
+ .new
212
230
  .edit_marc_json(...)
213
231
  end
214
232
 
215
233
  # @see Organizations#fetch_list
216
234
  def organizations(...)
217
235
  Organizations
218
- .new(self)
236
+ .new
219
237
  .fetch_list(...)
220
238
  end
221
239
 
222
240
  # @see Organizations#fetch_interface_list
223
241
  def organization_interfaces(...)
224
242
  Organizations
225
- .new(self)
243
+ .new
226
244
  .fetch_interface_list(...)
227
245
  end
228
246
 
229
247
  # @see Organizations#fetch_interface_details
230
248
  def interface_details(...)
231
249
  Organizations
232
- .new(self)
250
+ .new
233
251
  .fetch_interface_details(...)
234
252
  end
235
253
 
236
254
  # @see Users#fetch_list
237
255
  def users(...)
238
256
  Users
239
- .new(self)
257
+ .new
240
258
  .fetch_list(...)
241
259
  end
242
260
 
243
261
  # @see Users#fetch_user_details
244
262
  def user_details(...)
245
263
  Users
246
- .new(self)
264
+ .new
247
265
  .fetch_user_details(...)
248
266
  end
249
267
 
@@ -251,6 +269,10 @@ class FolioClient
251
269
  120
252
270
  end
253
271
 
272
+ def force_token_refresh!
273
+ config.token = Authenticator.token
274
+ end
275
+
254
276
  private
255
277
 
256
278
  # Wraps API operations to request new access token if expired.
@@ -269,11 +291,12 @@ class FolioClient
269
291
  response = yield
270
292
 
271
293
  # if unauthorized, token has likely expired. try to get a new token and then retry the same request(s).
272
- if response.status == 401
273
- config.token = Authenticator.token(config.login_params, connection)
294
+ if response.status == 401 || response.status == 403
295
+ force_token_refresh!
274
296
  response = yield
275
297
  end
276
298
 
277
299
  response
278
300
  end
279
301
  end
302
+ # rubocop:enable Metrics/ClassLength
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.14.0
4
+ version: 0.16.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-11-09 00:00:00.000000000 Z
11
+ date: 2024-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -30,6 +30,20 @@ dependencies:
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '8'
33
+ - !ruby/object:Gem::Dependency
34
+ name: dry-monads
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
33
47
  - !ruby/object:Gem::Dependency
34
48
  name: faraday
35
49
  requirement: !ruby/object:Gem::Requirement
@@ -45,7 +59,7 @@ dependencies:
45
59
  - !ruby/object:Gem::Version
46
60
  version: '0'
47
61
  - !ruby/object:Gem::Dependency
48
- name: zeitwerk
62
+ name: faraday-cookie_jar
49
63
  requirement: !ruby/object:Gem::Requirement
50
64
  requirements:
51
65
  - - ">="
@@ -73,7 +87,7 @@ dependencies:
73
87
  - !ruby/object:Gem::Version
74
88
  version: '0'
75
89
  - !ruby/object:Gem::Dependency
76
- name: dry-monads
90
+ name: zeitwerk
77
91
  requirement: !ruby/object:Gem::Requirement
78
92
  requirements:
79
93
  - - ">="
@@ -115,7 +129,7 @@ dependencies:
115
129
  - !ruby/object:Gem::Version
116
130
  version: '3.0'
117
131
  - !ruby/object:Gem::Dependency
118
- name: rubocop-rspec
132
+ name: rubocop
119
133
  requirement: !ruby/object:Gem::Requirement
120
134
  requirements:
121
135
  - - ">="
@@ -129,7 +143,7 @@ dependencies:
129
143
  - !ruby/object:Gem::Version
130
144
  version: '0'
131
145
  - !ruby/object:Gem::Dependency
132
- name: simplecov
146
+ name: rubocop-performance
133
147
  requirement: !ruby/object:Gem::Requirement
134
148
  requirements:
135
149
  - - ">="
@@ -143,7 +157,21 @@ dependencies:
143
157
  - !ruby/object:Gem::Version
144
158
  version: '0'
145
159
  - !ruby/object:Gem::Dependency
146
- name: standard
160
+ name: rubocop-rspec
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ - !ruby/object:Gem::Dependency
174
+ name: simplecov
147
175
  requirement: !ruby/object:Gem::Requirement
148
176
  requirements:
149
177
  - - ">="
@@ -177,11 +205,8 @@ executables: []
177
205
  extensions: []
178
206
  extra_rdoc_files: []
179
207
  files:
180
- - ".autoupdate/postupdate"
181
208
  - ".rspec"
182
209
  - ".rubocop.yml"
183
- - ".rubocop/custom.yml"
184
- - ".standard.yml"
185
210
  - Gemfile
186
211
  - Gemfile.lock
187
212
  - LICENSE
@@ -215,14 +240,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
215
240
  requirements:
216
241
  - - ">="
217
242
  - !ruby/object:Gem::Version
218
- version: 2.6.0
243
+ version: 3.0.0
219
244
  required_rubygems_version: !ruby/object:Gem::Requirement
220
245
  requirements:
221
246
  - - ">="
222
247
  - !ruby/object:Gem::Version
223
248
  version: '0'
224
249
  requirements: []
225
- rubygems_version: 3.4.21
250
+ rubygems_version: 3.4.22
226
251
  signing_key:
227
252
  specification_version: 4
228
253
  summary: Interface for interacting with the Folio ILS API.
@@ -1,19 +0,0 @@
1
- #!/bin/bash --login
2
-
3
- # This script is called by our weekly dependency update job in Jenkins after updating Ruby and other deps
4
-
5
- # Switch to Ruby 3.1 for FolioClient (3.0 is default in Jenkinsfile)
6
- rvm use 3.1.2@folio_client --create &&
7
- gem install bundler &&
8
- bundle install --gemfile Gemfile
9
-
10
- standardrb --fix > folio_client_standard.txt
11
-
12
- retVal=$?
13
-
14
- git commit -am "Update to latest standard style guide"
15
-
16
- if [ $retVal -ne 0 ]; then
17
- echo "ERROR UPDATING RUBY TO STANDARD STYLE (folio_client)"
18
- cat folio_client_standard.txt
19
- fi
data/.rubocop/custom.yml DELETED
@@ -1,84 +0,0 @@
1
- AllCops:
2
- TargetRubyVersion: 3.0
3
- DisplayCopNames: true
4
- SuggestExtensions: false
5
- Exclude:
6
- - bin/**
7
- - vendor/bundle/**/*
8
-
9
- # Per team developer playbook
10
- RSpec/MultipleMemoizedHelpers:
11
- Enabled: false
12
- RSpec/MultipleExpectations:
13
- Max: 3
14
- RSpec/ExampleLength:
15
- Max: 15
16
-
17
- RSpec/BeEq: # new in 2.9.0
18
- Enabled: true
19
- RSpec/BeNil: # new in 2.9.0
20
- Enabled: true
21
- RSpec/ChangeByZero: # new in 2.11
22
- Enabled: true
23
- RSpec/ClassCheck: # new in 2.13
24
- Enabled: true
25
- RSpec/ExcessiveDocstringSpacing: # new in 2.5
26
- Enabled: true
27
- RSpec/IdenticalEqualityAssertion: # new in 2.4
28
- Enabled: true
29
- RSpec/NoExpectationExample: # new in 2.13
30
- Enabled: true
31
- RSpec/SortMetadata: # new in 2.14
32
- Enabled: true
33
- RSpec/SubjectDeclaration: # new in 2.5
34
- Enabled: true
35
- RSpec/VerifiedDoubleReference: # new in 2.10.0
36
- Enabled: true
37
- FactoryBot/ConsistentParenthesesStyle: # new in 2.14
38
- Enabled: true
39
- FactoryBot/SyntaxMethods: # new in 2.7
40
- Enabled: true
41
- RSpec/Rails/AvoidSetupHook: # new in 2.4
42
- Enabled: true
43
- RSpec/Rails/HaveHttpStatus: # new in 2.12
44
- Enabled: true
45
- RSpec/Rails/InferredSpecType: # new in 2.14
46
- Enabled: true
47
- Capybara/MatchStyle: # new in 2.17
48
- Enabled: true
49
- Capybara/NegationMatcher: # new in 2.14
50
- Enabled: true
51
- Capybara/SpecificActions: # new in 2.14
52
- Enabled: true
53
- Capybara/SpecificFinders: # new in 2.13
54
- Enabled: true
55
- Capybara/SpecificMatcher: # new in 2.12
56
- Enabled: true
57
- RSpec/DuplicatedMetadata: # new in 2.16
58
- Enabled: true
59
- RSpec/PendingWithoutReason: # new in 2.16
60
- Enabled: true
61
- FactoryBot/FactoryNameStyle: # new in 2.16
62
- Enabled: true
63
- RSpec/Rails/MinitestAssertions: # new in 2.17
64
- Enabled: true
65
- RSpec/RedundantAround: # new in 2.19
66
- Enabled: true
67
- RSpec/SkipBlockInsideExample: # new in 2.19
68
- Enabled: true
69
- RSpec/Rails/TravelAround: # new in 2.19
70
- Enabled: true
71
- FactoryBot/AssociationStyle: # new in 2.23
72
- Enabled: true
73
- FactoryBot/FactoryAssociationWithStrategy: # new in 2.23
74
- Enabled: true
75
- FactoryBot/RedundantFactoryOption: # new in 2.23
76
- Enabled: true
77
- RSpec/BeEmpty: # new in 2.20
78
- Enabled: true
79
- RSpec/ContainExactly: # new in 2.19
80
- Enabled: true
81
- RSpec/IndexedLet: # new in 2.20
82
- Enabled: true
83
- RSpec/MatchArray: # new in 2.19
84
- Enabled: true
data/.standard.yml DELETED
@@ -1 +0,0 @@
1
- parallel: true