folio_client 0.11.0 → 0.12.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/.autoupdate/postupdate +0 -0
- data/.rubocop/custom.yml +17 -3
- data/.rubocop.yml +5 -1
- data/Gemfile.lock +47 -29
- data/README.md +4 -4
- data/lib/folio_client/data_import.rb +10 -6
- data/lib/folio_client/job_status.rb +15 -13
- data/lib/folio_client/version.rb +1 -1
- data/lib/folio_client.rb +46 -11
- metadata +5 -6
- data/lib/folio_client/token_wrapper.rb +0 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7918be772bc1758840166cbb6f0fb66bfbf4765f636118d971efef2192a947df
|
|
4
|
+
data.tar.gz: 86d3c7e5656e849cf3358ddf7e22463bd65527cedf017a9a31dfdd2606bad88a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9471247fec95c9df775cd359896c788d124173477b74f1124d41641f76e6d61438fe7b5e7c10fb3ee7d6efb9f5dba11c9b0b7b45fb9040520f6668cba626fc1d
|
|
7
|
+
data.tar.gz: 4f0adf00bb277b709a68552e7f2b5119e7b8597723ffc3c4f500d850e2c8470df0249a19e35a98bcfcf2ef6daa3befb8155e57d5f69c8d825fe3b1806cd240a5
|
data/.autoupdate/postupdate
CHANGED
|
File without changes
|
data/.rubocop/custom.yml
CHANGED
|
@@ -34,9 +34,9 @@ RSpec/SubjectDeclaration: # new in 2.5
|
|
|
34
34
|
Enabled: true
|
|
35
35
|
RSpec/VerifiedDoubleReference: # new in 2.10.0
|
|
36
36
|
Enabled: true
|
|
37
|
-
|
|
37
|
+
FactoryBot/ConsistentParenthesesStyle: # new in 2.14
|
|
38
38
|
Enabled: true
|
|
39
|
-
|
|
39
|
+
FactoryBot/SyntaxMethods: # new in 2.7
|
|
40
40
|
Enabled: true
|
|
41
41
|
RSpec/Rails/AvoidSetupHook: # new in 2.4
|
|
42
42
|
Enabled: true
|
|
@@ -58,7 +58,7 @@ RSpec/DuplicatedMetadata: # new in 2.16
|
|
|
58
58
|
Enabled: true
|
|
59
59
|
RSpec/PendingWithoutReason: # new in 2.16
|
|
60
60
|
Enabled: true
|
|
61
|
-
|
|
61
|
+
FactoryBot/FactoryNameStyle: # new in 2.16
|
|
62
62
|
Enabled: true
|
|
63
63
|
RSpec/Rails/MinitestAssertions: # new in 2.17
|
|
64
64
|
Enabled: true
|
|
@@ -68,3 +68,17 @@ RSpec/SkipBlockInsideExample: # new in 2.19
|
|
|
68
68
|
Enabled: true
|
|
69
69
|
RSpec/Rails/TravelAround: # new in 2.19
|
|
70
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/.rubocop.yml
CHANGED
|
@@ -3,12 +3,16 @@ inherit_mode:
|
|
|
3
3
|
- Exclude
|
|
4
4
|
|
|
5
5
|
require:
|
|
6
|
+
- standard
|
|
7
|
+
- standard-custom
|
|
8
|
+
- standard-performance
|
|
6
9
|
- rubocop-performance
|
|
7
10
|
- rubocop-rspec
|
|
8
|
-
- standard
|
|
9
11
|
|
|
10
12
|
inherit_gem:
|
|
11
13
|
standard: config/base.yml
|
|
14
|
+
standard-performance: config/base.yml
|
|
15
|
+
standard-custom: config/base.yml
|
|
12
16
|
|
|
13
17
|
inherit_from:
|
|
14
18
|
- .rubocop/custom.yml
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
folio_client (0.
|
|
4
|
+
folio_client (0.12.0)
|
|
5
5
|
activesupport (>= 4.2, < 8)
|
|
6
6
|
dry-monads
|
|
7
7
|
faraday
|
|
@@ -11,82 +11,92 @@ PATH
|
|
|
11
11
|
GEM
|
|
12
12
|
remote: https://rubygems.org/
|
|
13
13
|
specs:
|
|
14
|
-
activesupport (7.0.
|
|
14
|
+
activesupport (7.0.8)
|
|
15
15
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
16
16
|
i18n (>= 1.6, < 2)
|
|
17
17
|
minitest (>= 5.1)
|
|
18
18
|
tzinfo (~> 2.0)
|
|
19
|
-
addressable (2.8.
|
|
19
|
+
addressable (2.8.5)
|
|
20
20
|
public_suffix (>= 2.0.2, < 6.0)
|
|
21
21
|
ast (2.4.2)
|
|
22
|
+
base64 (0.1.1)
|
|
22
23
|
byebug (11.1.3)
|
|
23
24
|
concurrent-ruby (1.2.2)
|
|
24
25
|
crack (0.4.5)
|
|
25
26
|
rexml
|
|
26
27
|
diff-lcs (1.5.0)
|
|
27
28
|
docile (1.4.0)
|
|
28
|
-
dry-core (1.0.
|
|
29
|
+
dry-core (1.0.1)
|
|
29
30
|
concurrent-ruby (~> 1.0)
|
|
30
31
|
zeitwerk (~> 2.6)
|
|
31
32
|
dry-monads (1.6.0)
|
|
32
33
|
concurrent-ruby (~> 1.0)
|
|
33
34
|
dry-core (~> 1.0, < 2)
|
|
34
35
|
zeitwerk (~> 2.6)
|
|
35
|
-
faraday (2.7.
|
|
36
|
+
faraday (2.7.11)
|
|
37
|
+
base64
|
|
36
38
|
faraday-net_http (>= 2.0, < 3.1)
|
|
37
39
|
ruby2_keywords (>= 0.0.4)
|
|
38
40
|
faraday-net_http (3.0.2)
|
|
39
41
|
hashdiff (1.0.1)
|
|
40
|
-
i18n (1.
|
|
42
|
+
i18n (1.14.1)
|
|
41
43
|
concurrent-ruby (~> 1.0)
|
|
42
44
|
json (2.6.3)
|
|
43
45
|
language_server-protocol (3.17.0.3)
|
|
46
|
+
lint_roller (1.1.0)
|
|
44
47
|
marc (1.2.0)
|
|
45
48
|
rexml
|
|
46
49
|
scrub_rb (>= 1.0.1, < 2)
|
|
47
50
|
unf
|
|
48
|
-
minitest (5.
|
|
49
|
-
parallel (1.
|
|
50
|
-
parser (3.2.
|
|
51
|
+
minitest (5.20.0)
|
|
52
|
+
parallel (1.23.0)
|
|
53
|
+
parser (3.2.2.3)
|
|
51
54
|
ast (~> 2.4.1)
|
|
52
|
-
|
|
55
|
+
racc
|
|
56
|
+
public_suffix (5.0.3)
|
|
57
|
+
racc (1.7.1)
|
|
53
58
|
rainbow (3.1.1)
|
|
54
59
|
rake (13.0.6)
|
|
55
|
-
regexp_parser (2.
|
|
56
|
-
rexml (3.2.
|
|
60
|
+
regexp_parser (2.8.1)
|
|
61
|
+
rexml (3.2.6)
|
|
57
62
|
rspec (3.12.0)
|
|
58
63
|
rspec-core (~> 3.12.0)
|
|
59
64
|
rspec-expectations (~> 3.12.0)
|
|
60
65
|
rspec-mocks (~> 3.12.0)
|
|
61
|
-
rspec-core (3.12.
|
|
66
|
+
rspec-core (3.12.2)
|
|
62
67
|
rspec-support (~> 3.12.0)
|
|
63
|
-
rspec-expectations (3.12.
|
|
68
|
+
rspec-expectations (3.12.3)
|
|
64
69
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
65
70
|
rspec-support (~> 3.12.0)
|
|
66
|
-
rspec-mocks (3.12.
|
|
71
|
+
rspec-mocks (3.12.6)
|
|
67
72
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
68
73
|
rspec-support (~> 3.12.0)
|
|
69
|
-
rspec-support (3.12.
|
|
70
|
-
rubocop (1.
|
|
74
|
+
rspec-support (3.12.1)
|
|
75
|
+
rubocop (1.56.3)
|
|
76
|
+
base64 (~> 0.1.1)
|
|
71
77
|
json (~> 2.3)
|
|
78
|
+
language_server-protocol (>= 3.17.0)
|
|
72
79
|
parallel (~> 1.10)
|
|
73
|
-
parser (>= 3.2.
|
|
80
|
+
parser (>= 3.2.2.3)
|
|
74
81
|
rainbow (>= 2.2.2, < 4.0)
|
|
75
82
|
regexp_parser (>= 1.8, < 3.0)
|
|
76
83
|
rexml (>= 3.2.5, < 4.0)
|
|
77
|
-
rubocop-ast (>= 1.
|
|
84
|
+
rubocop-ast (>= 1.28.1, < 2.0)
|
|
78
85
|
ruby-progressbar (~> 1.7)
|
|
79
86
|
unicode-display_width (>= 2.4.0, < 3.0)
|
|
80
|
-
rubocop-ast (1.
|
|
87
|
+
rubocop-ast (1.29.0)
|
|
81
88
|
parser (>= 3.2.1.0)
|
|
82
|
-
rubocop-capybara (2.
|
|
89
|
+
rubocop-capybara (2.18.0)
|
|
83
90
|
rubocop (~> 1.41)
|
|
84
|
-
rubocop-
|
|
91
|
+
rubocop-factory_bot (2.24.0)
|
|
92
|
+
rubocop (~> 1.33)
|
|
93
|
+
rubocop-performance (1.19.1)
|
|
85
94
|
rubocop (>= 1.7.0, < 2.0)
|
|
86
95
|
rubocop-ast (>= 0.4.0)
|
|
87
|
-
rubocop-rspec (2.
|
|
96
|
+
rubocop-rspec (2.24.0)
|
|
88
97
|
rubocop (~> 1.33)
|
|
89
98
|
rubocop-capybara (~> 2.17)
|
|
99
|
+
rubocop-factory_bot (~> 2.22)
|
|
90
100
|
ruby-progressbar (1.13.0)
|
|
91
101
|
ruby2_keywords (0.0.5)
|
|
92
102
|
scrub_rb (1.0.1)
|
|
@@ -96,21 +106,29 @@ GEM
|
|
|
96
106
|
simplecov_json_formatter (~> 0.1)
|
|
97
107
|
simplecov-html (0.12.3)
|
|
98
108
|
simplecov_json_formatter (0.1.4)
|
|
99
|
-
standard (1.
|
|
109
|
+
standard (1.31.1)
|
|
100
110
|
language_server-protocol (~> 3.17.0.2)
|
|
101
|
-
|
|
102
|
-
rubocop
|
|
111
|
+
lint_roller (~> 1.0)
|
|
112
|
+
rubocop (~> 1.56.2)
|
|
113
|
+
standard-custom (~> 1.0.0)
|
|
114
|
+
standard-performance (~> 1.2)
|
|
115
|
+
standard-custom (1.0.2)
|
|
116
|
+
lint_roller (~> 1.0)
|
|
117
|
+
rubocop (~> 1.50)
|
|
118
|
+
standard-performance (1.2.0)
|
|
119
|
+
lint_roller (~> 1.1)
|
|
120
|
+
rubocop-performance (~> 1.19.0)
|
|
103
121
|
tzinfo (2.0.6)
|
|
104
122
|
concurrent-ruby (~> 1.0)
|
|
105
123
|
unf (0.1.4)
|
|
106
124
|
unf_ext
|
|
107
125
|
unf_ext (0.0.8.2)
|
|
108
126
|
unicode-display_width (2.4.2)
|
|
109
|
-
webmock (3.
|
|
127
|
+
webmock (3.19.1)
|
|
110
128
|
addressable (>= 2.8.0)
|
|
111
129
|
crack (>= 0.3.2)
|
|
112
130
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
113
|
-
zeitwerk (2.6.
|
|
131
|
+
zeitwerk (2.6.11)
|
|
114
132
|
|
|
115
133
|
PLATFORMS
|
|
116
134
|
x86_64-darwin-19
|
|
@@ -130,4 +148,4 @@ DEPENDENCIES
|
|
|
130
148
|
webmock
|
|
131
149
|
|
|
132
150
|
BUNDLED WITH
|
|
133
|
-
2.4.
|
|
151
|
+
2.4.13
|
data/README.md
CHANGED
|
@@ -71,15 +71,15 @@ client.fetch_marc_hash(instance_hrid: "a7927874")
|
|
|
71
71
|
[{"003"=>"FOLIO"}....]
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
# Import
|
|
75
|
-
data_importer = client.data_import(
|
|
74
|
+
# Import MARC records into FOLIO
|
|
75
|
+
data_importer = client.data_import(records: [marc_record1, marc_record2], job_profile_id: '4ba4f4ab', job_profile_name: 'ETDs')
|
|
76
76
|
# If called too quickly, might get Failure(:not_found)
|
|
77
77
|
data_importer.status
|
|
78
78
|
=> Failure(:pending)
|
|
79
79
|
data_importer.wait_until_complete
|
|
80
80
|
=> Success()
|
|
81
|
-
data_importer.
|
|
82
|
-
=> Success("in00000000010")
|
|
81
|
+
data_importer.instance_hrids
|
|
82
|
+
=> Success(["in00000000010", "in00000000011"])
|
|
83
83
|
|
|
84
84
|
# Get list of organizations (filtered with an optional query)
|
|
85
85
|
# see https://s3.amazonaws.com/foliodocs/api/mod-organizations/p/organizations.html#organizations_organizations_get
|
|
@@ -14,17 +14,21 @@ class FolioClient
|
|
|
14
14
|
@client = client
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
# @param
|
|
17
|
+
# @param records [Array<MARC::Record>] records to be imported
|
|
18
18
|
# @param job_profile_id [String] job profile id to use for import
|
|
19
19
|
# @param job_profile_name [String] job profile name to use for import
|
|
20
20
|
# @return [JobStatus] a job status instance to get information about the data import job
|
|
21
|
-
def import(
|
|
21
|
+
def import(records:, job_profile_id:, job_profile_name:)
|
|
22
22
|
response_hash = client.post("/data-import/uploadDefinitions", {fileDefinitions: [{name: marc_filename}]})
|
|
23
23
|
upload_definition_id = response_hash.dig("fileDefinitions", 0, "uploadDefinitionId")
|
|
24
24
|
job_execution_id = response_hash.dig("fileDefinitions", 0, "jobExecutionId")
|
|
25
25
|
file_definition_id = response_hash.dig("fileDefinitions", 0, "id")
|
|
26
26
|
|
|
27
|
-
upload_file_response_hash = client.post(
|
|
27
|
+
upload_file_response_hash = client.post(
|
|
28
|
+
"/data-import/uploadDefinitions/#{upload_definition_id}/files/#{file_definition_id}",
|
|
29
|
+
marc_binary(records),
|
|
30
|
+
content_type: "application/octet-stream"
|
|
31
|
+
)
|
|
28
32
|
|
|
29
33
|
client.post(
|
|
30
34
|
"/data-import/uploadDefinitions/#{upload_definition_id}/processFiles",
|
|
@@ -51,16 +55,16 @@ class FolioClient
|
|
|
51
55
|
|
|
52
56
|
private
|
|
53
57
|
|
|
54
|
-
attr_reader :client, :
|
|
58
|
+
attr_reader :client, :job_profile_id, :job_profile_name
|
|
55
59
|
|
|
56
60
|
def marc_filename
|
|
57
61
|
@marc_filename ||= "#{DateTime.now.iso8601}.marc"
|
|
58
62
|
end
|
|
59
63
|
|
|
60
|
-
def marc_binary(
|
|
64
|
+
def marc_binary(records)
|
|
61
65
|
StringIO.open do |io|
|
|
62
66
|
MARC::Writer.new(io) do |writer|
|
|
63
|
-
writer.write(
|
|
67
|
+
records.each { |record| writer.write(record) }
|
|
64
68
|
end
|
|
65
69
|
io.string
|
|
66
70
|
end
|
|
@@ -17,15 +17,17 @@ class FolioClient
|
|
|
17
17
|
@job_execution_id = job_execution_id
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
# @
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
20
|
+
# @todo An "ERROR" approach means one or more records failed, but it does
|
|
21
|
+
# not mean they all fail. We will likely need a more nuanced way to
|
|
22
|
+
# handle this eventually.
|
|
23
|
+
#
|
|
24
|
+
# @return [Dry::Monads::Result] Success() if job is complete,
|
|
25
|
+
# Failure(:pending) if job is still running,
|
|
26
|
+
# Failure(:not_found) if job is not found
|
|
24
27
|
def status
|
|
25
|
-
response_hash = client.get("/
|
|
28
|
+
response_hash = client.get("/change-manager/jobExecutions/#{job_execution_id}")
|
|
26
29
|
|
|
27
|
-
return Failure(:
|
|
28
|
-
return Failure(:pending) if response_hash.dig("sourceRecordSummary", "totalCreatedEntities").zero? && response_hash.dig("sourceRecordSummary", "totalUpdatedEntities").zero?
|
|
30
|
+
return Failure(:pending) if !["COMMITTED", "ERROR"].include?(response_hash["status"])
|
|
29
31
|
|
|
30
32
|
Success()
|
|
31
33
|
rescue ResourceNotFound
|
|
@@ -37,18 +39,18 @@ class FolioClient
|
|
|
37
39
|
wait_with_timeout(wait_secs: wait_secs, timeout_secs: timeout_secs) { status }
|
|
38
40
|
end
|
|
39
41
|
|
|
40
|
-
def
|
|
42
|
+
def instance_hrids
|
|
41
43
|
current_status = status
|
|
42
44
|
return current_status unless current_status.success?
|
|
43
45
|
|
|
44
|
-
@
|
|
46
|
+
@instance_hrids ||= wait_with_timeout do
|
|
45
47
|
response = client
|
|
46
48
|
.get("/metadata-provider/journalRecords/#{job_execution_id}")
|
|
47
49
|
.fetch("journalRecords", [])
|
|
48
|
-
.
|
|
49
|
-
|
|
50
|
+
.select { |journal_record| journal_record["entityType"] == "INSTANCE" && journal_record["actionStatus"] == "COMPLETED" }
|
|
51
|
+
.filter_map { |instance_record| instance_record["entityHrId"] }
|
|
50
52
|
|
|
51
|
-
response.
|
|
53
|
+
response.empty? ? Failure() : Success(response)
|
|
52
54
|
end
|
|
53
55
|
end
|
|
54
56
|
|
|
@@ -61,7 +63,7 @@ class FolioClient
|
|
|
61
63
|
end
|
|
62
64
|
|
|
63
65
|
def default_timeout_secs
|
|
64
|
-
|
|
66
|
+
10 * 60
|
|
65
67
|
end
|
|
66
68
|
|
|
67
69
|
def wait_with_timeout(wait_secs: default_wait_secs, timeout_secs: default_timeout_secs)
|
data/lib/folio_client/version.rb
CHANGED
data/lib/folio_client.rb
CHANGED
|
@@ -47,25 +47,34 @@ class FolioClient
|
|
|
47
47
|
# @param url [String] the folio API URL
|
|
48
48
|
# @param login_params [Hash] the folio client login params (username:, password:)
|
|
49
49
|
# @param okapi_headers [Hash] the okapi specific headers to add (X-Okapi-Tenant:, User-Agent:)
|
|
50
|
+
# @return [FolioClient] the configured Singleton class
|
|
50
51
|
def configure(url:, login_params:, okapi_headers:, timeout: default_timeout)
|
|
51
52
|
instance.config = OpenStruct.new(
|
|
53
|
+
# For the initial token, use a dummy value to avoid hitting any APIs
|
|
54
|
+
# during configuration, allowing `with_token_refresh_when_unauthorized` to handle
|
|
55
|
+
# auto-magic token refreshing. Why not immediately get a valid token? Our apps
|
|
56
|
+
# commonly invoke client `.configure` methods in the initializer in all
|
|
57
|
+
# application environments, even those that are never expected to
|
|
58
|
+
# connect to production APIs, such as local development machines.
|
|
59
|
+
#
|
|
60
|
+
# NOTE: `nil` and blank string cannot be used as dummy values here as
|
|
61
|
+
# they lead to a malformed request to be sent, which triggers an
|
|
62
|
+
# exception not rescued by `with_token_refresh_when_unauthorized`
|
|
63
|
+
token: "a temporary dummy token to avoid hitting the API before it is needed",
|
|
52
64
|
url: url,
|
|
53
65
|
login_params: login_params,
|
|
54
66
|
okapi_headers: okapi_headers,
|
|
55
67
|
timeout: timeout
|
|
56
68
|
)
|
|
57
69
|
|
|
58
|
-
# NOTE: The token cannot be set above, since `#connection` relies on
|
|
59
|
-
# `instance.config` parameters having already been set.
|
|
60
|
-
instance.config.token = Authenticator.token(login_params, connection)
|
|
61
|
-
|
|
62
70
|
self
|
|
63
71
|
end
|
|
64
72
|
|
|
65
|
-
delegate :config, :connection, :data_import, :default_timeout,
|
|
66
|
-
:fetch_external_id, :fetch_hrid, :fetch_instance_info,
|
|
67
|
-
:
|
|
68
|
-
:
|
|
73
|
+
delegate :config, :connection, :data_import, :default_timeout,
|
|
74
|
+
:edit_marc_json, :fetch_external_id, :fetch_hrid, :fetch_instance_info,
|
|
75
|
+
:fetch_marc_hash, :get, :has_instance_status?, :http_get_headers,
|
|
76
|
+
:http_post_and_put_headers, :interface_details, :job_profiles,
|
|
77
|
+
:organization_interfaces, :organizations, :post, :put, to: :instance
|
|
69
78
|
end
|
|
70
79
|
|
|
71
80
|
attr_accessor :config
|
|
@@ -74,7 +83,7 @@ class FolioClient
|
|
|
74
83
|
# @param path [String] the path to the Folio API request
|
|
75
84
|
# @param params [Hash] params to get to the API
|
|
76
85
|
def get(path, params = {})
|
|
77
|
-
response =
|
|
86
|
+
response = with_token_refresh_when_unauthorized do
|
|
78
87
|
connection.get(path, params, {"x-okapi-token": config.token})
|
|
79
88
|
end
|
|
80
89
|
|
|
@@ -91,7 +100,7 @@ class FolioClient
|
|
|
91
100
|
# @param body [Object] body to post to the API as JSON
|
|
92
101
|
def post(path, body = nil, content_type: "application/json")
|
|
93
102
|
req_body = (content_type == "application/json") ? body&.to_json : body
|
|
94
|
-
response =
|
|
103
|
+
response = with_token_refresh_when_unauthorized do
|
|
95
104
|
req_headers = {
|
|
96
105
|
"x-okapi-token": config.token,
|
|
97
106
|
"content-type": content_type
|
|
@@ -112,7 +121,7 @@ class FolioClient
|
|
|
112
121
|
# @param body [Object] body to put to the API as JSON
|
|
113
122
|
def put(path, body = nil, content_type: "application/json")
|
|
114
123
|
req_body = (content_type == "application/json") ? body&.to_json : body
|
|
115
|
-
response =
|
|
124
|
+
response = with_token_refresh_when_unauthorized do
|
|
116
125
|
req_headers = {
|
|
117
126
|
"x-okapi-token": config.token,
|
|
118
127
|
"content-type": content_type
|
|
@@ -218,4 +227,30 @@ class FolioClient
|
|
|
218
227
|
def default_timeout
|
|
219
228
|
120
|
|
220
229
|
end
|
|
230
|
+
|
|
231
|
+
private
|
|
232
|
+
|
|
233
|
+
# Wraps API operations to request new access token if expired.
|
|
234
|
+
# @yieldreturn response [Faraday::Response] the response to inspect
|
|
235
|
+
#
|
|
236
|
+
# @note You likely want to make sure you're wrapping a _single_ HTTP request in this
|
|
237
|
+
# method, because 1) all calls in the block will be retried from the top if there's
|
|
238
|
+
# an authN failure detected, and 2) only the response returned by the block will be
|
|
239
|
+
# inspected for authN failure.
|
|
240
|
+
# Related: consider that the client instance and its token will live across many
|
|
241
|
+
# invocations of the FolioClient methods once the client is configured by a consuming application,
|
|
242
|
+
# since this class is a Singleton. Thus, a token may expire between any two calls (i.e. it
|
|
243
|
+
# isn't necessary for a set of operations to collectively take longer than the token lifetime for
|
|
244
|
+
# expiry to fall in the middle of that related set of HTTP calls).
|
|
245
|
+
def with_token_refresh_when_unauthorized
|
|
246
|
+
response = yield
|
|
247
|
+
|
|
248
|
+
# if unauthorized, token has likely expired. try to get a new token and then retry the same request(s).
|
|
249
|
+
if response.status == 401
|
|
250
|
+
config.token = Authenticator.token(config.login_params, connection)
|
|
251
|
+
response = yield
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
response
|
|
255
|
+
end
|
|
221
256
|
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.
|
|
4
|
+
version: 0.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter Mangiafico
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2023-
|
|
11
|
+
date: 2023-09-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -196,7 +196,6 @@ files:
|
|
|
196
196
|
- lib/folio_client/organizations.rb
|
|
197
197
|
- lib/folio_client/records_editor.rb
|
|
198
198
|
- lib/folio_client/source_storage.rb
|
|
199
|
-
- lib/folio_client/token_wrapper.rb
|
|
200
199
|
- lib/folio_client/unexpected_response.rb
|
|
201
200
|
- lib/folio_client/version.rb
|
|
202
201
|
homepage: https://github.com/sul-dlss/folio_client
|
|
@@ -206,7 +205,7 @@ metadata:
|
|
|
206
205
|
source_code_uri: https://github.com/sul-dlss/folio_client
|
|
207
206
|
changelog_uri: https://github.com/sul-dlss/folio_client/releases
|
|
208
207
|
rubygems_mfa_required: 'true'
|
|
209
|
-
post_install_message:
|
|
208
|
+
post_install_message:
|
|
210
209
|
rdoc_options: []
|
|
211
210
|
require_paths:
|
|
212
211
|
- lib
|
|
@@ -222,7 +221,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
222
221
|
version: '0'
|
|
223
222
|
requirements: []
|
|
224
223
|
rubygems_version: 3.3.7
|
|
225
|
-
signing_key:
|
|
224
|
+
signing_key:
|
|
226
225
|
specification_version: 4
|
|
227
226
|
summary: Interface for interacting with the Folio ILS API.
|
|
228
227
|
test_files: []
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class FolioClient
|
|
4
|
-
# Wraps API operations to request new access token if expired
|
|
5
|
-
class TokenWrapper
|
|
6
|
-
def self.refresh(config, connection)
|
|
7
|
-
yield.tap { |response| UnexpectedResponse.call(response) unless response.success? }
|
|
8
|
-
rescue UnauthorizedError
|
|
9
|
-
config.token = Authenticator.token(config.login_params, connection)
|
|
10
|
-
yield
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|