folio_client 0.20.0 → 1.0.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 +4 -4
- data/Gemfile.lock +20 -23
- data/README.md +4 -3
- data/api_test.rb +2 -4
- data/folio_client.gemspec +1 -0
- data/lib/folio_client/authenticator.rb +6 -11
- data/lib/folio_client/version.rb +1 -1
- data/lib/folio_client.rb +209 -153
- metadata +17 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c7ff536f92f06403a2d47dd9ede2dab90d0f20d8b6ad52f9c2bd047dd7eb0650
|
|
4
|
+
data.tar.gz: 3ba0ff3343cbf79bfb383254d18f8cad2bd0c74b0251e4c76bf69b26d6d75ed4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e5e9f3c4b84168f41b6afaacffebcaba29bc732ea338a3834a01621dec956e568d5863789da54b4df297bda2915e9119164c0fbf3e4de1c703fc5317df0c4fb
|
|
7
|
+
data.tar.gz: c9bde04bd107cb50213b2beb8019251dd1cf2452ce842f926420576202df65ee3d54ea1a665afb441ff5d47a367fab8230de0612d88767b73bc9454a35cd9218
|
data/Gemfile.lock
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
folio_client (0.
|
|
4
|
+
folio_client (1.0.0)
|
|
5
5
|
activesupport (>= 4.2)
|
|
6
|
+
deprecation
|
|
6
7
|
dry-monads
|
|
7
8
|
faraday
|
|
8
9
|
faraday-cookie_jar
|
|
@@ -13,7 +14,7 @@ PATH
|
|
|
13
14
|
GEM
|
|
14
15
|
remote: https://rubygems.org/
|
|
15
16
|
specs:
|
|
16
|
-
activesupport (8.1.
|
|
17
|
+
activesupport (8.1.3)
|
|
17
18
|
base64
|
|
18
19
|
bigdecimal
|
|
19
20
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
@@ -26,11 +27,11 @@ GEM
|
|
|
26
27
|
securerandom (>= 0.3)
|
|
27
28
|
tzinfo (~> 2.0, >= 2.0.5)
|
|
28
29
|
uri (>= 0.13.1)
|
|
29
|
-
addressable (2.
|
|
30
|
+
addressable (2.9.0)
|
|
30
31
|
public_suffix (>= 2.0.2, < 8.0)
|
|
31
32
|
ast (2.4.3)
|
|
32
33
|
base64 (0.3.0)
|
|
33
|
-
bigdecimal (4.
|
|
34
|
+
bigdecimal (4.1.1)
|
|
34
35
|
concurrent-ruby (1.3.6)
|
|
35
36
|
connection_pool (3.0.2)
|
|
36
37
|
crack (1.0.1)
|
|
@@ -40,6 +41,8 @@ GEM
|
|
|
40
41
|
debug (1.11.1)
|
|
41
42
|
irb (~> 1.10)
|
|
42
43
|
reline (>= 0.3.8)
|
|
44
|
+
deprecation (1.1.0)
|
|
45
|
+
activesupport
|
|
43
46
|
diff-lcs (1.6.2)
|
|
44
47
|
docile (1.4.1)
|
|
45
48
|
domain_name (0.6.20240107)
|
|
@@ -63,7 +66,7 @@ GEM
|
|
|
63
66
|
faraday-net_http (3.4.2)
|
|
64
67
|
net-http (~> 0.5)
|
|
65
68
|
hashdiff (1.2.1)
|
|
66
|
-
http-cookie (1.1.
|
|
69
|
+
http-cookie (1.1.4)
|
|
67
70
|
domain_name (~> 0.5)
|
|
68
71
|
i18n (1.14.8)
|
|
69
72
|
concurrent-ruby (~> 1.0)
|
|
@@ -73,30 +76,25 @@ GEM
|
|
|
73
76
|
prism (>= 1.3.0)
|
|
74
77
|
rdoc (>= 4.0.0)
|
|
75
78
|
reline (>= 0.4.2)
|
|
76
|
-
json (2.19.
|
|
77
|
-
json-schema (6.2.0)
|
|
78
|
-
addressable (~> 2.8)
|
|
79
|
-
bigdecimal (>= 3.1, < 5)
|
|
79
|
+
json (2.19.3)
|
|
80
80
|
language_server-protocol (3.17.0.5)
|
|
81
81
|
lint_roller (1.1.0)
|
|
82
82
|
logger (1.7.0)
|
|
83
83
|
marc (1.4.0)
|
|
84
84
|
nokogiri (~> 1.0)
|
|
85
85
|
rexml
|
|
86
|
-
|
|
87
|
-
json-schema (>= 4.1)
|
|
88
|
-
minitest (6.0.2)
|
|
86
|
+
minitest (6.0.3)
|
|
89
87
|
drb (~> 2.0)
|
|
90
88
|
prism (~> 1.5)
|
|
91
89
|
net-http (0.9.1)
|
|
92
90
|
uri (>= 0.11.1)
|
|
93
|
-
nokogiri (1.19.
|
|
91
|
+
nokogiri (1.19.2-arm64-darwin)
|
|
94
92
|
racc (~> 1.4)
|
|
95
|
-
nokogiri (1.19.
|
|
93
|
+
nokogiri (1.19.2-x86_64-linux-gnu)
|
|
96
94
|
racc (~> 1.4)
|
|
97
95
|
ostruct (0.6.3)
|
|
98
|
-
parallel (
|
|
99
|
-
parser (3.3.
|
|
96
|
+
parallel (2.0.1)
|
|
97
|
+
parser (3.3.11.1)
|
|
100
98
|
ast (~> 2.4.1)
|
|
101
99
|
racc
|
|
102
100
|
pp (0.6.3)
|
|
@@ -114,7 +112,7 @@ GEM
|
|
|
114
112
|
erb
|
|
115
113
|
psych (>= 4.0.0)
|
|
116
114
|
tsort
|
|
117
|
-
regexp_parser (2.
|
|
115
|
+
regexp_parser (2.12.0)
|
|
118
116
|
reline (0.6.3)
|
|
119
117
|
io-console (~> 0.5)
|
|
120
118
|
rexml (3.4.4)
|
|
@@ -131,19 +129,18 @@ GEM
|
|
|
131
129
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
132
130
|
rspec-support (~> 3.13.0)
|
|
133
131
|
rspec-support (3.13.7)
|
|
134
|
-
rubocop (1.
|
|
132
|
+
rubocop (1.86.1)
|
|
135
133
|
json (~> 2.3)
|
|
136
134
|
language_server-protocol (~> 3.17.0.2)
|
|
137
135
|
lint_roller (~> 1.1.0)
|
|
138
|
-
|
|
139
|
-
parallel (~> 1.10)
|
|
136
|
+
parallel (>= 1.10)
|
|
140
137
|
parser (>= 3.3.0.2)
|
|
141
138
|
rainbow (>= 2.2.2, < 4.0)
|
|
142
139
|
regexp_parser (>= 2.9.3, < 3.0)
|
|
143
140
|
rubocop-ast (>= 1.49.0, < 2.0)
|
|
144
141
|
ruby-progressbar (~> 1.7)
|
|
145
142
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
146
|
-
rubocop-ast (1.49.
|
|
143
|
+
rubocop-ast (1.49.1)
|
|
147
144
|
parser (>= 3.3.7.2)
|
|
148
145
|
prism (~> 1.7)
|
|
149
146
|
rubocop-capybara (2.22.1)
|
|
@@ -179,7 +176,7 @@ GEM
|
|
|
179
176
|
unicode-emoji (~> 4.1)
|
|
180
177
|
unicode-emoji (4.2.0)
|
|
181
178
|
uri (1.1.1)
|
|
182
|
-
webmock (3.26.
|
|
179
|
+
webmock (3.26.2)
|
|
183
180
|
addressable (>= 2.8.0)
|
|
184
181
|
crack (>= 0.3.2)
|
|
185
182
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
@@ -206,4 +203,4 @@ DEPENDENCIES
|
|
|
206
203
|
webmock
|
|
207
204
|
|
|
208
205
|
BUNDLED WITH
|
|
209
|
-
4.0.
|
|
206
|
+
4.0.10
|
data/README.md
CHANGED
|
@@ -25,9 +25,10 @@ require 'folio_client'
|
|
|
25
25
|
|
|
26
26
|
# this will configure the client and request an access token
|
|
27
27
|
client = FolioClient.configure(
|
|
28
|
-
url: 'https://
|
|
28
|
+
url: 'https://folio-dev.stanford.edu',
|
|
29
29
|
login_params: { username: 'xxx', password: 'yyy' },
|
|
30
|
-
|
|
30
|
+
tenant_id: 'sul',
|
|
31
|
+
user_agent: 'FolioApiClient'
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
response = client.get('/organizations/organizations', {query_string_param: 'abcdef'})
|
|
@@ -43,7 +44,7 @@ require 'folio_client'
|
|
|
43
44
|
client = FolioClient.configure(
|
|
44
45
|
url: Settings.okapi.url,
|
|
45
46
|
login_params: Settings.okapi.login_params,
|
|
46
|
-
|
|
47
|
+
...
|
|
47
48
|
)
|
|
48
49
|
```
|
|
49
50
|
|
data/api_test.rb
CHANGED
|
@@ -13,10 +13,8 @@ client =
|
|
|
13
13
|
username: ENV.fetch('OKAPI_USER', nil),
|
|
14
14
|
password: ENV.fetch('OKAPI_PASSWORD', nil)
|
|
15
15
|
},
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
'User-Agent': 'folio_client gem (testing)'
|
|
19
|
-
}
|
|
16
|
+
tenant_id: ENV.fetch('OKAPI_TENANT', nil),
|
|
17
|
+
user_agent: 'folio_client gem (testing)'
|
|
20
18
|
)
|
|
21
19
|
|
|
22
20
|
pp(client.fetch_marc_hash(instance_hrid: 'a666'))
|
data/folio_client.gemspec
CHANGED
|
@@ -32,6 +32,7 @@ Gem::Specification.new do |spec|
|
|
|
32
32
|
spec.require_paths = ['lib']
|
|
33
33
|
|
|
34
34
|
spec.add_dependency 'activesupport', '>= 4.2'
|
|
35
|
+
spec.add_dependency 'deprecation', '>= 0'
|
|
35
36
|
spec.add_dependency 'dry-monads'
|
|
36
37
|
spec.add_dependency 'faraday'
|
|
37
38
|
spec.add_dependency 'faraday-cookie_jar'
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
class FolioClient
|
|
4
4
|
# Fetch a token from the Folio API using login_params
|
|
5
5
|
class Authenticator
|
|
6
|
-
|
|
7
|
-
new.token
|
|
8
|
-
end
|
|
6
|
+
LOGIN_ENDPOINT = '/authn/login-with-expiry'
|
|
9
7
|
|
|
10
8
|
# Request an access_token
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
#
|
|
10
|
+
# @raise [UnauthorizedError] if the response is not successful or if the
|
|
11
|
+
# @return [String] the access token
|
|
12
|
+
def self.refresh_token!
|
|
13
|
+
response = FolioClient.connection.post(LOGIN_ENDPOINT, FolioClient.config.login_params.to_json)
|
|
13
14
|
|
|
14
15
|
UnexpectedResponse.call(response) unless response.success?
|
|
15
16
|
|
|
@@ -23,11 +24,5 @@ class FolioClient
|
|
|
23
24
|
|
|
24
25
|
access_cookie.value
|
|
25
26
|
end
|
|
26
|
-
|
|
27
|
-
private
|
|
28
|
-
|
|
29
|
-
def login_endpoint
|
|
30
|
-
'/authn/login-with-expiry'
|
|
31
|
-
end
|
|
32
27
|
end
|
|
33
28
|
end
|
data/lib/folio_client/version.rb
CHANGED
data/lib/folio_client.rb
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'http/cookie' # Workaround for https://github.com/sparklemotion/http-cookie/issues/62
|
|
3
4
|
require 'active_support/core_ext/module/delegation'
|
|
4
5
|
require 'active_support/core_ext/object/blank'
|
|
6
|
+
require 'deprecation'
|
|
5
7
|
require 'faraday'
|
|
6
8
|
require 'faraday-cookie_jar'
|
|
7
9
|
require 'marc'
|
|
@@ -9,93 +11,102 @@ require 'ostruct'
|
|
|
9
11
|
require 'singleton'
|
|
10
12
|
require 'zeitwerk'
|
|
11
13
|
|
|
12
|
-
#
|
|
14
|
+
# Autoload gem internals.
|
|
13
15
|
Zeitwerk::Loader.for_gem.setup
|
|
14
16
|
|
|
15
|
-
# Client for interacting with the Folio API
|
|
16
|
-
# rubocop:disable Metrics/ClassLength
|
|
17
|
-
class FolioClient
|
|
17
|
+
# Client for interacting with the Folio API.
|
|
18
|
+
class FolioClient # rubocop:disable Metrics/ClassLength
|
|
18
19
|
include Singleton
|
|
19
20
|
|
|
20
|
-
# Base class for all FolioClient errors
|
|
21
|
+
# Base class for all FolioClient errors.
|
|
21
22
|
class Error < StandardError; end
|
|
22
23
|
|
|
23
|
-
#
|
|
24
|
+
# Raised when the Folio Auth API returns 401 Unauthorized.
|
|
24
25
|
class UnauthorizedError < Error; end
|
|
25
26
|
|
|
26
|
-
#
|
|
27
|
+
# Raised when the Folio API returns 404 Not Found, or when 0 results are
|
|
28
|
+
# returned where one was expected.
|
|
27
29
|
class ResourceNotFound < Error; end
|
|
28
30
|
|
|
29
|
-
#
|
|
31
|
+
# Raised when exactly one resource was expected but multiple were returned.
|
|
30
32
|
class MultipleResourcesFound < Error; end
|
|
31
33
|
|
|
32
|
-
#
|
|
34
|
+
# Raised when the Folio API returns 403 Forbidden.
|
|
33
35
|
class ForbiddenError < Error; end
|
|
34
36
|
|
|
35
|
-
#
|
|
37
|
+
# Raised when the Folio API returns 500-level availability errors.
|
|
36
38
|
class ServiceUnavailable < Error; end
|
|
37
39
|
|
|
38
|
-
#
|
|
40
|
+
# Raised when the Folio API returns 422 Unprocessable Entity.
|
|
39
41
|
class ValidationError < Error; end
|
|
40
42
|
|
|
41
|
-
#
|
|
43
|
+
# Raised when the Folio API returns 409 Conflict.
|
|
42
44
|
class ConflictError < Error; end
|
|
43
45
|
|
|
44
|
-
#
|
|
46
|
+
# Raised when the Folio API returns 400 Bad Request.
|
|
45
47
|
class BadRequestError < Error; end
|
|
46
48
|
|
|
47
|
-
DEFAULT_HEADERS = {
|
|
48
|
-
accept: 'application/json, text/plain',
|
|
49
|
-
content_type: 'application/json'
|
|
50
|
-
}.freeze
|
|
51
|
-
|
|
52
49
|
class << self
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
50
|
+
extend Deprecation
|
|
51
|
+
|
|
52
|
+
Config = Struct.new('Config', :url, :login_params, :timeout, :tenant_id, :user_agent) do
|
|
53
|
+
# Build default headers for Folio-bound requests.
|
|
54
|
+
# @return [Hash<Symbol,String>] default request headers
|
|
55
|
+
def headers
|
|
56
|
+
{
|
|
57
|
+
accept: 'application/json, text/plain',
|
|
58
|
+
content_type: 'application/json',
|
|
59
|
+
user_agent: user_agent,
|
|
60
|
+
'X-Okapi-Tenant': tenant_id
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Configure the singleton FolioClient instance.
|
|
66
|
+
# @example
|
|
67
|
+
# FolioClient.configure(
|
|
68
|
+
# url: 'https://folio.example.edu',
|
|
69
|
+
# login_params: { username: 'svc-user', password: 'secret' },
|
|
70
|
+
# tenant_id: 'sul'
|
|
71
|
+
# )
|
|
72
|
+
# @param url [String] Folio API base URL
|
|
73
|
+
# @param login_params [Hash] authentication payload (e.g., +username+, +password+)
|
|
74
|
+
# @param tenant_id [String, nil] Folio tenant identifier
|
|
75
|
+
# @param user_agent [String, nil] user agent string included on outbound requests
|
|
76
|
+
# @param timeout [Integer] request timeout, in seconds
|
|
77
|
+
# @return [Class<FolioClient>] the configured singleton class for chaining
|
|
78
|
+
def configure(url:, login_params:, tenant_id: nil, user_agent: nil, timeout: nil)
|
|
79
|
+
instance.config = Config.new(
|
|
71
80
|
url: url,
|
|
72
81
|
login_params: login_params,
|
|
73
|
-
|
|
74
|
-
timeout: timeout
|
|
82
|
+
tenant_id: tenant_id,
|
|
83
|
+
timeout: timeout || default_timeout,
|
|
84
|
+
user_agent: user_agent || default_user_agent
|
|
75
85
|
)
|
|
76
|
-
# rubocop:enable Style/OpenStructUse
|
|
77
86
|
|
|
78
87
|
self
|
|
79
88
|
end
|
|
80
89
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
# The client is intended to be used as a singleton via {.configure}, but the
|
|
91
|
+
# instance methods are also available on the class itself for convenience.
|
|
92
|
+
# Instead of maintaining a giant list of delegations to the singleton
|
|
93
|
+
# instance, we can just delegate everything. This makes the client easier to
|
|
94
|
+
# extend with additional instance methods without needing to update the
|
|
95
|
+
# delegation list.
|
|
96
|
+
delegate_missing_to :instance
|
|
88
97
|
end
|
|
89
98
|
|
|
99
|
+
# @return [FolioClient::Config, nil] active runtime configuration
|
|
90
100
|
attr_accessor :config
|
|
91
101
|
|
|
92
|
-
# Send an authenticated
|
|
93
|
-
# @param path [String]
|
|
94
|
-
# @param params [Hash]
|
|
95
|
-
# @return [Hash, nil]
|
|
102
|
+
# Send an authenticated GET request.
|
|
103
|
+
# @param path [String] API path relative to configured +url+
|
|
104
|
+
# @param params [Hash] query parameters
|
|
105
|
+
# @return [Hash, Array, nil] parsed JSON body, or +nil+ for empty body
|
|
106
|
+
# @raise [FolioClient::Error] when Folio responds with an unexpected status
|
|
96
107
|
def get(path, params = {})
|
|
97
108
|
response = with_token_refresh_when_unauthorized do
|
|
98
|
-
connection.get(path, params
|
|
109
|
+
connection.get(path, params)
|
|
99
110
|
end
|
|
100
111
|
|
|
101
112
|
UnexpectedResponse.call(response) unless response.success?
|
|
@@ -103,19 +114,18 @@ class FolioClient
|
|
|
103
114
|
JSON.parse(response.body) if response.body.present?
|
|
104
115
|
end
|
|
105
116
|
|
|
106
|
-
# Send an authenticated
|
|
107
|
-
# If
|
|
108
|
-
#
|
|
109
|
-
# @param
|
|
110
|
-
# @
|
|
117
|
+
# Send an authenticated POST request.
|
|
118
|
+
# If +content_type+ is +application/json+, +body+ is serialized with +to_json+.
|
|
119
|
+
# Otherwise +body+ is sent unchanged.
|
|
120
|
+
# @param path [String] API path relative to configured +url+
|
|
121
|
+
# @param body [Hash, String, nil] request payload
|
|
122
|
+
# @param content_type [String] MIME type of request body
|
|
123
|
+
# @return [Hash, Array, nil] parsed JSON body, or +nil+ for empty body
|
|
124
|
+
# @raise [FolioClient::Error] when Folio responds with an unexpected status
|
|
111
125
|
def post(path, body = nil, content_type: 'application/json')
|
|
112
126
|
req_body = content_type == 'application/json' ? body&.to_json : body
|
|
113
127
|
response = with_token_refresh_when_unauthorized do
|
|
114
|
-
|
|
115
|
-
'x-okapi-token': config.token,
|
|
116
|
-
'content-type': content_type
|
|
117
|
-
}
|
|
118
|
-
connection.post(path, req_body, req_headers)
|
|
128
|
+
connection.post(path, req_body, { content_type: content_type })
|
|
119
129
|
end
|
|
120
130
|
|
|
121
131
|
UnexpectedResponse.call(response) unless response.success?
|
|
@@ -123,19 +133,19 @@ class FolioClient
|
|
|
123
133
|
JSON.parse(response.body) if response.body.present?
|
|
124
134
|
end
|
|
125
135
|
|
|
126
|
-
# Send an authenticated
|
|
127
|
-
# If
|
|
128
|
-
#
|
|
129
|
-
# @param
|
|
130
|
-
# @
|
|
136
|
+
# Send an authenticated PUT request.
|
|
137
|
+
# If +content_type+ is +application/json+, +body+ is serialized with +to_json+.
|
|
138
|
+
# Otherwise +body+ is sent unchanged.
|
|
139
|
+
# @param path [String] API path relative to configured +url+
|
|
140
|
+
# @param body [Hash, String, nil] request payload
|
|
141
|
+
# @param content_type [String] MIME type of request body
|
|
142
|
+
# @param exception_args [Hash] supplemental context forwarded to +UnexpectedResponse+
|
|
143
|
+
# @return [Hash, Array, nil] parsed JSON body, or +nil+ for empty body
|
|
144
|
+
# @raise [FolioClient::Error] when Folio responds with an unexpected status
|
|
131
145
|
def put(path, body = nil, content_type: 'application/json', **exception_args)
|
|
132
146
|
req_body = content_type == 'application/json' ? body&.to_json : body
|
|
133
147
|
response = with_token_refresh_when_unauthorized do
|
|
134
|
-
|
|
135
|
-
'x-okapi-token': config.token,
|
|
136
|
-
'content-type': content_type
|
|
137
|
-
}
|
|
138
|
-
connection.put(path, req_body, req_headers)
|
|
148
|
+
connection.put(path, req_body, { content_type: content_type })
|
|
139
149
|
end
|
|
140
150
|
|
|
141
151
|
UnexpectedResponse.call(response, **exception_args) unless response.success?
|
|
@@ -143,11 +153,12 @@ class FolioClient
|
|
|
143
153
|
JSON.parse(response.body) if response.body.present?
|
|
144
154
|
end
|
|
145
155
|
|
|
146
|
-
#
|
|
156
|
+
# Build (or memoize) the base Faraday connection.
|
|
157
|
+
# @return [Faraday::Connection] configured HTTP connection
|
|
147
158
|
def connection
|
|
148
159
|
@connection ||= Faraday.new(
|
|
149
160
|
url: config.url,
|
|
150
|
-
headers:
|
|
161
|
+
headers: config.headers,
|
|
151
162
|
request: { timeout: config.timeout }
|
|
152
163
|
) do |faraday|
|
|
153
164
|
faraday.use :cookie_jar, jar: cookie_jar
|
|
@@ -155,170 +166,215 @@ class FolioClient
|
|
|
155
166
|
end
|
|
156
167
|
end
|
|
157
168
|
|
|
169
|
+
# Build (or memoize) the cookie jar used by Faraday to store authentication cookies.
|
|
170
|
+
# @return [HTTP::CookieJar] cookie storage for session-aware requests
|
|
158
171
|
def cookie_jar
|
|
159
172
|
@cookie_jar ||= HTTP::CookieJar.new
|
|
160
173
|
end
|
|
161
174
|
|
|
162
|
-
#
|
|
163
|
-
|
|
175
|
+
# Fetch a Folio HRID by instance identifier or query context.
|
|
164
176
|
# @see Inventory#fetch_hrid
|
|
177
|
+
# @return [Object] delegated return value from +Inventory#fetch_hrid+
|
|
165
178
|
def fetch_hrid(...)
|
|
166
|
-
|
|
167
|
-
.new
|
|
168
|
-
.fetch_hrid(...)
|
|
179
|
+
inventory.fetch_hrid(...)
|
|
169
180
|
end
|
|
170
181
|
|
|
182
|
+
# Fetch the Folio external id for a matching record.
|
|
171
183
|
# @see Inventory#fetch_external_id
|
|
184
|
+
# @return [Object] delegated return value from +Inventory#fetch_external_id+
|
|
172
185
|
def fetch_external_id(...)
|
|
173
|
-
|
|
174
|
-
.new
|
|
175
|
-
.fetch_external_id(...)
|
|
186
|
+
inventory.fetch_external_id(...)
|
|
176
187
|
end
|
|
177
188
|
|
|
189
|
+
# Fetch inventory instance details.
|
|
178
190
|
# @see Inventory#fetch_instance_info
|
|
191
|
+
# @return [Object] delegated return value from +Inventory#fetch_instance_info+
|
|
179
192
|
def fetch_instance_info(...)
|
|
180
|
-
|
|
181
|
-
.new
|
|
182
|
-
.fetch_instance_info(...)
|
|
193
|
+
inventory.fetch_instance_info(...)
|
|
183
194
|
end
|
|
184
195
|
|
|
196
|
+
# Fetch location details from inventory.
|
|
185
197
|
# @see Inventory#fetch_location
|
|
198
|
+
# @return [Object] delegated return value from +Inventory#fetch_location+
|
|
186
199
|
def fetch_location(...)
|
|
187
|
-
|
|
188
|
-
.new
|
|
189
|
-
.fetch_location(...)
|
|
200
|
+
inventory.fetch_location(...)
|
|
190
201
|
end
|
|
191
202
|
|
|
203
|
+
# Fetch holdings associated with a record.
|
|
192
204
|
# @see Inventory#fetch_holdings
|
|
205
|
+
# @return [Object] delegated return value from +Inventory#fetch_holdings+
|
|
193
206
|
def fetch_holdings(...)
|
|
194
|
-
|
|
195
|
-
.new
|
|
196
|
-
.fetch_holdings(...)
|
|
207
|
+
inventory.fetch_holdings(...)
|
|
197
208
|
end
|
|
198
209
|
|
|
210
|
+
# Update an existing holdings record.
|
|
199
211
|
# @see Inventory#update_holdings
|
|
212
|
+
# @return [Object] delegated return value from +Inventory#update_holdings+
|
|
200
213
|
def update_holdings(...)
|
|
201
|
-
|
|
202
|
-
.new
|
|
203
|
-
.update_holdings(...)
|
|
214
|
+
inventory.update_holdings(...)
|
|
204
215
|
end
|
|
205
216
|
|
|
217
|
+
# Create a new holdings record.
|
|
206
218
|
# @see Inventory#create_holdings
|
|
219
|
+
# @return [Object] delegated return value from +Inventory#create_holdings+
|
|
207
220
|
def create_holdings(...)
|
|
208
|
-
|
|
209
|
-
.new
|
|
210
|
-
.create_holdings(...)
|
|
221
|
+
inventory.create_holdings(...)
|
|
211
222
|
end
|
|
212
223
|
|
|
224
|
+
# Fetch MARC data as a Ruby hash.
|
|
213
225
|
# @see SourceStorage#fetch_marc_hash
|
|
226
|
+
# @return [Object] delegated return value from +SourceStorage#fetch_marc_hash+
|
|
214
227
|
def fetch_marc_hash(...)
|
|
215
|
-
|
|
216
|
-
.new
|
|
217
|
-
.fetch_marc_hash(...)
|
|
228
|
+
source_storage.fetch_marc_hash(...)
|
|
218
229
|
end
|
|
219
230
|
|
|
231
|
+
# Fetch MARC data as XML.
|
|
220
232
|
# @see SourceStorage#fetch_marc_xml
|
|
233
|
+
# @return [Object] delegated return value from +SourceStorage#fetch_marc_xml+
|
|
221
234
|
def fetch_marc_xml(...)
|
|
222
|
-
|
|
223
|
-
.new
|
|
224
|
-
.fetch_marc_xml(...)
|
|
235
|
+
source_storage.fetch_marc_xml(...)
|
|
225
236
|
end
|
|
226
237
|
|
|
238
|
+
# Determine whether an instance has the requested status.
|
|
227
239
|
# @see Inventory#has_instance_status?
|
|
240
|
+
# @return [Boolean] delegated predicate result
|
|
228
241
|
def has_instance_status?(...) # rubocop:disable Naming/PredicatePrefix
|
|
229
|
-
|
|
230
|
-
.new
|
|
231
|
-
.has_instance_status?(...)
|
|
242
|
+
inventory.has_instance_status?(...)
|
|
232
243
|
end
|
|
233
244
|
|
|
234
|
-
#
|
|
245
|
+
# Run an inventory data import workflow.
|
|
246
|
+
# @see DataImport#import
|
|
247
|
+
# @return [Object] delegated return value from +DataImport#import+
|
|
235
248
|
def data_import(...)
|
|
236
|
-
|
|
237
|
-
.new
|
|
238
|
-
.import(...)
|
|
249
|
+
data_import_service.import(...)
|
|
239
250
|
end
|
|
240
251
|
|
|
241
|
-
#
|
|
252
|
+
# List available data-import job profiles.
|
|
253
|
+
# @see DataImport#job_profiles
|
|
254
|
+
# @return [Object] delegated return value from +DataImport#job_profiles+
|
|
242
255
|
def job_profiles(...)
|
|
243
|
-
|
|
244
|
-
.new
|
|
245
|
-
.job_profiles(...)
|
|
256
|
+
data_import_service.job_profiles(...)
|
|
246
257
|
end
|
|
247
258
|
|
|
259
|
+
# Edit MARC-in-JSON records.
|
|
248
260
|
# @see RecordsEditor#edit_marc_json
|
|
261
|
+
# @return [Object] delegated return value from +RecordsEditor#edit_marc_json+
|
|
249
262
|
def edit_marc_json(...)
|
|
250
|
-
|
|
251
|
-
.new
|
|
252
|
-
.edit_marc_json(...)
|
|
263
|
+
records_editor.edit_marc_json(...)
|
|
253
264
|
end
|
|
254
265
|
|
|
266
|
+
# List organizations.
|
|
255
267
|
# @see Organizations#fetch_list
|
|
268
|
+
# @return [Object] delegated return value from +Organizations#fetch_list+
|
|
256
269
|
def organizations(...)
|
|
257
|
-
|
|
258
|
-
.new
|
|
259
|
-
.fetch_list(...)
|
|
270
|
+
organizations_service.fetch_list(...)
|
|
260
271
|
end
|
|
261
272
|
|
|
273
|
+
# List interfaces for organizations.
|
|
262
274
|
# @see Organizations#fetch_interface_list
|
|
275
|
+
# @return [Object] delegated return value from +Organizations#fetch_interface_list+
|
|
263
276
|
def organization_interfaces(...)
|
|
264
|
-
|
|
265
|
-
.new
|
|
266
|
-
.fetch_interface_list(...)
|
|
277
|
+
organizations_service.fetch_interface_list(...)
|
|
267
278
|
end
|
|
268
279
|
|
|
280
|
+
# Fetch detailed interface information for an organization interface.
|
|
269
281
|
# @see Organizations#fetch_interface_details
|
|
282
|
+
# @return [Object] delegated return value from +Organizations#fetch_interface_details+
|
|
270
283
|
def interface_details(...)
|
|
271
|
-
|
|
272
|
-
.new
|
|
273
|
-
.fetch_interface_details(...)
|
|
284
|
+
organizations_service.fetch_interface_details(...)
|
|
274
285
|
end
|
|
275
286
|
|
|
287
|
+
# List users.
|
|
276
288
|
# @see Users#fetch_list
|
|
289
|
+
# @return [Object] delegated return value from +Users#fetch_list+
|
|
277
290
|
def users(...)
|
|
278
|
-
|
|
279
|
-
.new
|
|
280
|
-
.fetch_list(...)
|
|
291
|
+
users_service.fetch_list(...)
|
|
281
292
|
end
|
|
282
293
|
|
|
294
|
+
# Fetch details for a user.
|
|
283
295
|
# @see Users#fetch_user_details
|
|
296
|
+
# @return [Object] delegated return value from +Users#fetch_user_details+
|
|
284
297
|
def user_details(...)
|
|
285
|
-
|
|
286
|
-
.new
|
|
287
|
-
.fetch_user_details(...)
|
|
298
|
+
users_service.fetch_user_details(...)
|
|
288
299
|
end
|
|
289
300
|
|
|
301
|
+
# Force a refresh of the current auth token.
|
|
302
|
+
#
|
|
303
|
+
# @return [Object] return value from +Authenticator.refresh_token!+
|
|
304
|
+
def force_token_refresh!
|
|
305
|
+
Authenticator.refresh_token!
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Default HTTP timeout in seconds.
|
|
309
|
+
#
|
|
310
|
+
# @return [Integer]
|
|
290
311
|
def default_timeout
|
|
291
|
-
|
|
312
|
+
180
|
|
292
313
|
end
|
|
293
314
|
|
|
294
|
-
|
|
295
|
-
|
|
315
|
+
# Default user-agent string used for outbound requests.
|
|
316
|
+
#
|
|
317
|
+
# @return [String]
|
|
318
|
+
def default_user_agent
|
|
319
|
+
"folio_client #{VERSION}"
|
|
296
320
|
end
|
|
297
321
|
|
|
298
322
|
private
|
|
299
323
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
#
|
|
304
|
-
#
|
|
305
|
-
#
|
|
306
|
-
#
|
|
307
|
-
#
|
|
308
|
-
#
|
|
309
|
-
#
|
|
310
|
-
#
|
|
311
|
-
# expiry to fall in the middle of that related set of HTTP calls).
|
|
324
|
+
STATUSES_REQUIRING_TOKEN_REFRESH = [401, 403].freeze
|
|
325
|
+
private_constant :STATUSES_REQUIRING_TOKEN_REFRESH
|
|
326
|
+
|
|
327
|
+
# Wrap API operations to refresh and retry when auth has expired.
|
|
328
|
+
# @yieldreturn [Faraday::Response] response from a single HTTP request
|
|
329
|
+
# @return [Faraday::Response] original or retried response
|
|
330
|
+
# @note Wrap one HTTP call per block. If auth fails, the entire block is retried.
|
|
331
|
+
# Only the final response yielded by the block is inspected for auth failure.
|
|
332
|
+
# @note Because this class is a Singleton, its token can outlive many client calls.
|
|
333
|
+
# Expiration can occur between any two invocations, even when those calls are
|
|
334
|
+
# logically related from the caller's perspective.
|
|
312
335
|
def with_token_refresh_when_unauthorized
|
|
313
336
|
response = yield
|
|
314
337
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
338
|
+
return response unless STATUSES_REQUIRING_TOKEN_REFRESH.include?(response.status)
|
|
339
|
+
|
|
340
|
+
force_token_refresh!
|
|
341
|
+
|
|
342
|
+
yield
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Build an inventory service object.
|
|
346
|
+
# @return [Inventory]
|
|
347
|
+
def inventory
|
|
348
|
+
Inventory.new
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Build a source-storage service object.
|
|
352
|
+
# @return [SourceStorage]
|
|
353
|
+
def source_storage
|
|
354
|
+
SourceStorage.new
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Build a data-import service object.
|
|
358
|
+
# @return [DataImport]
|
|
359
|
+
def data_import_service
|
|
360
|
+
DataImport.new
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Build a records-editor service object.
|
|
364
|
+
# @return [RecordsEditor]
|
|
365
|
+
def records_editor
|
|
366
|
+
RecordsEditor.new
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# Build an organizations service object.
|
|
370
|
+
# @return [Organizations]
|
|
371
|
+
def organizations_service
|
|
372
|
+
Organizations.new
|
|
373
|
+
end
|
|
320
374
|
|
|
321
|
-
|
|
375
|
+
# Build a users service object.
|
|
376
|
+
# @return [Users]
|
|
377
|
+
def users_service
|
|
378
|
+
Users.new
|
|
322
379
|
end
|
|
323
380
|
end
|
|
324
|
-
# rubocop:enable Metrics/ClassLength
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: folio_client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter Mangiafico
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activesupport
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '4.2'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: deprecation
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: dry-monads
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -295,7 +309,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
295
309
|
- !ruby/object:Gem::Version
|
|
296
310
|
version: '0'
|
|
297
311
|
requirements: []
|
|
298
|
-
rubygems_version:
|
|
312
|
+
rubygems_version: 4.0.8
|
|
299
313
|
specification_version: 4
|
|
300
314
|
summary: Interface for interacting with the Folio ILS API.
|
|
301
315
|
test_files: []
|