folio_client 0.19.0 → 0.20.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/.rubocop.yml +50 -2
- data/Gemfile +1 -1
- data/Gemfile.lock +46 -22
- data/README.md +80 -0
- data/folio_client.gemspec +2 -1
- data/lib/folio_client/authenticator.rb +5 -1
- data/lib/folio_client/inventory.rb +48 -0
- data/lib/folio_client/job_status.rb +4 -0
- data/lib/folio_client/source_storage.rb +2 -6
- data/lib/folio_client/unexpected_response.rb +7 -7
- data/lib/folio_client/version.rb +1 -1
- data/lib/folio_client.rb +43 -19
- 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: ef1f093f7eb2cfcd38d85fc547d702a9768235b9a4e24f061653225e207978a9
|
|
4
|
+
data.tar.gz: ffe4c65db99fda6d3cece47f00bb84d490794382342ec2b4be6d4d2ff97a4f1e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 514d18193471517981408f5e1193cc13efa0640e3ad3176030aee72629a3b27b0f6ef9b5f9b36ca5197a9280180276d609a7ca0abddd56d5e9885bc6215f81dd
|
|
7
|
+
data.tar.gz: 885859bc1542aa0ceb2d7cf6c939851e2f9774db332f39c2d66b90773d8a6fea6278cf4267a969ece4e492e277deb51ca94337bf02d4395fb00c5a8da3692aa9
|
data/.rubocop.yml
CHANGED
|
@@ -6,7 +6,7 @@ plugins:
|
|
|
6
6
|
- rubocop-rspec
|
|
7
7
|
|
|
8
8
|
AllCops:
|
|
9
|
-
TargetRubyVersion: 3.
|
|
9
|
+
TargetRubyVersion: 3.4
|
|
10
10
|
DisplayCopNames: true
|
|
11
11
|
SuggestExtensions: false
|
|
12
12
|
Exclude:
|
|
@@ -442,4 +442,52 @@ RSpec/IncludeExamples: # new in 3.6
|
|
|
442
442
|
Capybara/FindAllFirst: # new in 2.22
|
|
443
443
|
Enabled: true
|
|
444
444
|
Capybara/NegationMatcherAfterVisit: # new in 2.22
|
|
445
|
-
Enabled: true
|
|
445
|
+
Enabled: true
|
|
446
|
+
Gemspec/AttributeAssignment: # new in 1.77
|
|
447
|
+
Enabled: true
|
|
448
|
+
Layout/EmptyLinesAfterModuleInclusion: # new in 1.79
|
|
449
|
+
Enabled: true
|
|
450
|
+
Style/ArrayIntersectWithSingleElement: # new in 1.81
|
|
451
|
+
Enabled: true
|
|
452
|
+
Style/CollectionQuerying: # new in 1.77
|
|
453
|
+
Enabled: true
|
|
454
|
+
Style/EmptyClassDefinition: # new in 1.84
|
|
455
|
+
Enabled: true
|
|
456
|
+
Style/ModuleMemberExistenceCheck: # new in 1.82
|
|
457
|
+
Enabled: true
|
|
458
|
+
Style/NegativeArrayIndex: # new in 1.84
|
|
459
|
+
Enabled: true
|
|
460
|
+
Style/ReverseFind: # new in 1.84
|
|
461
|
+
Enabled: true
|
|
462
|
+
RSpecRails/HttpStatusNameConsistency: # new in 2.32
|
|
463
|
+
Enabled: true
|
|
464
|
+
RSpec/LeakyLocalVariable: # new in 3.8
|
|
465
|
+
Enabled: true
|
|
466
|
+
RSpec/Output: # new in 3.9
|
|
467
|
+
Enabled: true
|
|
468
|
+
Lint/DataDefineOverride: # new in 1.85
|
|
469
|
+
Enabled: true
|
|
470
|
+
Lint/UnreachablePatternBranch: # new in 1.85
|
|
471
|
+
Enabled: true
|
|
472
|
+
Style/FileOpen: # new in 1.85
|
|
473
|
+
Enabled: true
|
|
474
|
+
Style/MapJoin: # new in 1.85
|
|
475
|
+
Enabled: true
|
|
476
|
+
Style/OneClassPerFile: # new in 1.85
|
|
477
|
+
Enabled: true
|
|
478
|
+
Style/PartitionInsteadOfDoubleSelect: # new in 1.85
|
|
479
|
+
Enabled: true
|
|
480
|
+
Style/PredicateWithKind: # new in 1.85
|
|
481
|
+
Enabled: true
|
|
482
|
+
Style/ReduceToHash: # new in 1.85
|
|
483
|
+
Enabled: true
|
|
484
|
+
Style/RedundantMinMaxBy: # new in 1.85
|
|
485
|
+
Enabled: true
|
|
486
|
+
Style/RedundantStructKeywordInit: # new in 1.85
|
|
487
|
+
Enabled: true
|
|
488
|
+
Style/SelectByKind: # new in 1.85
|
|
489
|
+
Enabled: true
|
|
490
|
+
Style/SelectByRange: # new in 1.85
|
|
491
|
+
Enabled: true
|
|
492
|
+
Style/TallyMethod: # new in 1.85
|
|
493
|
+
Enabled: true
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
folio_client (0.
|
|
4
|
+
folio_client (0.20.0)
|
|
5
5
|
activesupport (>= 4.2)
|
|
6
6
|
dry-monads
|
|
7
7
|
faraday
|
|
8
8
|
faraday-cookie_jar
|
|
9
9
|
marc
|
|
10
|
+
ostruct
|
|
10
11
|
zeitwerk
|
|
11
12
|
|
|
12
13
|
GEM
|
|
@@ -25,18 +26,20 @@ GEM
|
|
|
25
26
|
securerandom (>= 0.3)
|
|
26
27
|
tzinfo (~> 2.0, >= 2.0.5)
|
|
27
28
|
uri (>= 0.13.1)
|
|
28
|
-
addressable (2.8.
|
|
29
|
+
addressable (2.8.9)
|
|
29
30
|
public_suffix (>= 2.0.2, < 8.0)
|
|
30
31
|
ast (2.4.3)
|
|
31
32
|
base64 (0.3.0)
|
|
32
33
|
bigdecimal (4.0.1)
|
|
33
|
-
byebug (13.0.0)
|
|
34
|
-
reline (>= 0.6.0)
|
|
35
34
|
concurrent-ruby (1.3.6)
|
|
36
35
|
connection_pool (3.0.2)
|
|
37
36
|
crack (1.0.1)
|
|
38
37
|
bigdecimal
|
|
39
38
|
rexml
|
|
39
|
+
date (3.5.1)
|
|
40
|
+
debug (1.11.1)
|
|
41
|
+
irb (~> 1.10)
|
|
42
|
+
reline (>= 0.3.8)
|
|
40
43
|
diff-lcs (1.6.2)
|
|
41
44
|
docile (1.4.1)
|
|
42
45
|
domain_name (0.6.20240107)
|
|
@@ -49,7 +52,8 @@ GEM
|
|
|
49
52
|
concurrent-ruby (~> 1.0)
|
|
50
53
|
dry-core (~> 1.1)
|
|
51
54
|
zeitwerk (~> 2.6)
|
|
52
|
-
|
|
55
|
+
erb (6.0.2)
|
|
56
|
+
faraday (2.14.1)
|
|
53
57
|
faraday-net_http (>= 2.0, < 3.5)
|
|
54
58
|
json
|
|
55
59
|
logger
|
|
@@ -64,32 +68,52 @@ GEM
|
|
|
64
68
|
i18n (1.14.8)
|
|
65
69
|
concurrent-ruby (~> 1.0)
|
|
66
70
|
io-console (0.8.2)
|
|
67
|
-
|
|
71
|
+
irb (1.17.0)
|
|
72
|
+
pp (>= 0.6.0)
|
|
73
|
+
prism (>= 1.3.0)
|
|
74
|
+
rdoc (>= 4.0.0)
|
|
75
|
+
reline (>= 0.4.2)
|
|
76
|
+
json (2.19.1)
|
|
77
|
+
json-schema (6.2.0)
|
|
78
|
+
addressable (~> 2.8)
|
|
79
|
+
bigdecimal (>= 3.1, < 5)
|
|
68
80
|
language_server-protocol (3.17.0.5)
|
|
69
81
|
lint_roller (1.1.0)
|
|
70
82
|
logger (1.7.0)
|
|
71
83
|
marc (1.4.0)
|
|
72
84
|
nokogiri (~> 1.0)
|
|
73
85
|
rexml
|
|
74
|
-
|
|
86
|
+
mcp (0.8.0)
|
|
87
|
+
json-schema (>= 4.1)
|
|
88
|
+
minitest (6.0.2)
|
|
89
|
+
drb (~> 2.0)
|
|
75
90
|
prism (~> 1.5)
|
|
76
91
|
net-http (0.9.1)
|
|
77
92
|
uri (>= 0.11.1)
|
|
78
|
-
nokogiri (1.19.
|
|
93
|
+
nokogiri (1.19.1-arm64-darwin)
|
|
79
94
|
racc (~> 1.4)
|
|
80
|
-
nokogiri (1.19.
|
|
81
|
-
racc (~> 1.4)
|
|
82
|
-
nokogiri (1.19.0-x86_64-linux-gnu)
|
|
95
|
+
nokogiri (1.19.1-x86_64-linux-gnu)
|
|
83
96
|
racc (~> 1.4)
|
|
97
|
+
ostruct (0.6.3)
|
|
84
98
|
parallel (1.27.0)
|
|
85
|
-
parser (3.3.10.
|
|
99
|
+
parser (3.3.10.2)
|
|
86
100
|
ast (~> 2.4.1)
|
|
87
101
|
racc
|
|
102
|
+
pp (0.6.3)
|
|
103
|
+
prettyprint
|
|
104
|
+
prettyprint (0.2.0)
|
|
88
105
|
prism (1.9.0)
|
|
89
|
-
|
|
106
|
+
psych (5.3.1)
|
|
107
|
+
date
|
|
108
|
+
stringio
|
|
109
|
+
public_suffix (7.0.5)
|
|
90
110
|
racc (1.8.1)
|
|
91
111
|
rainbow (3.1.1)
|
|
92
112
|
rake (13.3.1)
|
|
113
|
+
rdoc (7.2.0)
|
|
114
|
+
erb
|
|
115
|
+
psych (>= 4.0.0)
|
|
116
|
+
tsort
|
|
93
117
|
regexp_parser (2.11.3)
|
|
94
118
|
reline (0.6.3)
|
|
95
119
|
io-console (~> 0.5)
|
|
@@ -103,14 +127,15 @@ GEM
|
|
|
103
127
|
rspec-expectations (3.13.5)
|
|
104
128
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
105
129
|
rspec-support (~> 3.13.0)
|
|
106
|
-
rspec-mocks (3.13.
|
|
130
|
+
rspec-mocks (3.13.8)
|
|
107
131
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
108
132
|
rspec-support (~> 3.13.0)
|
|
109
133
|
rspec-support (3.13.7)
|
|
110
|
-
rubocop (1.
|
|
134
|
+
rubocop (1.85.1)
|
|
111
135
|
json (~> 2.3)
|
|
112
136
|
language_server-protocol (~> 3.17.0.2)
|
|
113
137
|
lint_roller (~> 1.1.0)
|
|
138
|
+
mcp (~> 0.6)
|
|
114
139
|
parallel (~> 1.10)
|
|
115
140
|
parser (>= 3.3.0.2)
|
|
116
141
|
rainbow (>= 2.2.2, < 4.0)
|
|
@@ -146,6 +171,8 @@ GEM
|
|
|
146
171
|
simplecov_json_formatter (~> 0.1)
|
|
147
172
|
simplecov-html (0.13.2)
|
|
148
173
|
simplecov_json_formatter (0.1.4)
|
|
174
|
+
stringio (3.2.0)
|
|
175
|
+
tsort (0.2.0)
|
|
149
176
|
tzinfo (2.0.6)
|
|
150
177
|
concurrent-ruby (~> 1.0)
|
|
151
178
|
unicode-display_width (3.2.0)
|
|
@@ -156,19 +183,16 @@ GEM
|
|
|
156
183
|
addressable (>= 2.8.0)
|
|
157
184
|
crack (>= 0.3.2)
|
|
158
185
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
159
|
-
zeitwerk (2.7.
|
|
186
|
+
zeitwerk (2.7.5)
|
|
160
187
|
|
|
161
188
|
PLATFORMS
|
|
162
189
|
arm64-darwin-23
|
|
163
190
|
arm64-darwin-24
|
|
164
|
-
|
|
165
|
-
x86_64-darwin-20
|
|
166
|
-
x86_64-darwin-21
|
|
167
|
-
x86_64-darwin-22
|
|
191
|
+
arm64-darwin-25
|
|
168
192
|
x86_64-linux
|
|
169
193
|
|
|
170
194
|
DEPENDENCIES
|
|
171
|
-
|
|
195
|
+
debug
|
|
172
196
|
folio_client!
|
|
173
197
|
rake (~> 13.0)
|
|
174
198
|
rspec (~> 3.0)
|
|
@@ -182,4 +206,4 @@ DEPENDENCIES
|
|
|
182
206
|
webmock
|
|
183
207
|
|
|
184
208
|
BUNDLED WITH
|
|
185
|
-
4.0.
|
|
209
|
+
4.0.7
|
data/README.md
CHANGED
|
@@ -163,6 +163,86 @@ client.user_details(id: 'bbbadd51-c2f1-4107-a54d-52b39087725c')
|
|
|
163
163
|
=> {"username"=>"testing",
|
|
164
164
|
"id"=>"bbbadd51-c2f1-4107-a54d-52b39087725c",
|
|
165
165
|
"externalSystemId"=>"00324439", ... # same response as above, but for single user
|
|
166
|
+
|
|
167
|
+
# Get location details by UUID (useful for checking campusId when creating holdings)
|
|
168
|
+
# see https://s3.amazonaws.com/foliodocs/api/mod-inventory-storage/p/location.html#locations__id__get
|
|
169
|
+
client.fetch_location(location_id: 'd9cd0bed-1b49-4b5e-a7bd-064b8d177231')
|
|
170
|
+
=> {"id"=>"d9cd0bed-1b49-4b5e-a7bd-064b8d177231",
|
|
171
|
+
"name"=>"Miller General Stacks",
|
|
172
|
+
"code"=>"UA/CB/LC/GS",
|
|
173
|
+
"isActive"=>true,
|
|
174
|
+
"description"=>"The very general stacks of Miller",
|
|
175
|
+
"discoveryDisplayName"=>"Miller General",
|
|
176
|
+
"institutionId"=>"4b2a3d97-01c3-4ef3-98a5-ae4e853429b4",
|
|
177
|
+
"campusId"=>"b595d838-b1d5-409e-86ac-af3b41bde0be",
|
|
178
|
+
"libraryId"=>"e2889f93-92f2-4937-b944-5452a575367e",
|
|
179
|
+
"details"=>{"a"=>"b", "foo"=>"bar"},
|
|
180
|
+
"primaryServicePoint"=>"79faacf1-4ba4-42c7-8b2a-566b259e4641",
|
|
181
|
+
"servicePointIds"=>["79faacf1-4ba4-42c7-8b2a-566b259e4641"]}
|
|
182
|
+
|
|
183
|
+
# Get holdings records for an instance by HRID (useful for checking permanentLocationId and discoverySuppress)
|
|
184
|
+
# see https://github.com/folio-org/mod-search#search-api
|
|
185
|
+
client.fetch_holdings(hrid: 'in00000000067')
|
|
186
|
+
=> [{"id"=>"7f89e96c-478c-4ca2-bb85-0a1c5b0c6f3e",
|
|
187
|
+
"instanceId"=>"5108040a-65bc-40ed-bd50-265958301ce4",
|
|
188
|
+
"permanentLocationId"=>"d9cd0bed-1b49-4b5e-a7bd-064b8d177231",
|
|
189
|
+
"discoverySuppress"=>false,
|
|
190
|
+
"hrid"=>"ho00000000010",
|
|
191
|
+
"holdingsTypeId"=>"03c9c400-b9e3-4a07-ac0e-05ab470233ed",
|
|
192
|
+
"callNumber"=>"ABC 123"},
|
|
193
|
+
{"id"=>"8a89e96c-478c-4ca2-bb85-0a1c5b0c6f3f",
|
|
194
|
+
"instanceId"=>"5108040a-65bc-40ed-bd50-265958301ce4",
|
|
195
|
+
"permanentLocationId"=>"b595d838-b1d5-409e-86ac-af3b41bde0be",
|
|
196
|
+
"discoverySuppress"=>true,
|
|
197
|
+
"hrid"=>"ho00000000011",
|
|
198
|
+
"holdingsTypeId"=>"03c9c400-b9e3-4a07-ac0e-05ab470233ed",
|
|
199
|
+
"callNumber"=>"DEF 456"}]
|
|
200
|
+
|
|
201
|
+
# Update a holdings record for an instance HRID
|
|
202
|
+
holdings_record =
|
|
203
|
+
{ 'id' => '7f89e96c-478c-4ca2-bb85-0a1c5b0c6f3e',
|
|
204
|
+
'_version' => 1,
|
|
205
|
+
'sourceId' => 'f32d531e-df79-46b3-8932-cdd35f7a2264',
|
|
206
|
+
'hrid' => 'ah1994253_1',
|
|
207
|
+
'holdingsTypeId' => '5684e4a3-9279-4463-b6ee-20ae21bbec07',
|
|
208
|
+
'instanceId' => '54ec1f1a-d039-5a39-95f2-71df00061664',
|
|
209
|
+
'permanentLocationId' => '4573e824-9273-4f13-972f-cff7bf504217',
|
|
210
|
+
'effectiveLocationId' => '4573e824-9273-4f13-972f-cff7bf504217',
|
|
211
|
+
'discoverySuppress' => false }
|
|
212
|
+
client.update_holdings(holdings_id: '7f89e96c-478c-4ca2-bb85-0a1c5b0c6f3e', holdings_record:)
|
|
213
|
+
|
|
214
|
+
#Create a holdings record
|
|
215
|
+
holdings_record =
|
|
216
|
+
{ "instance_id" => "f1b301ce-f5d2-53b5-85eb-e4452bb5a591",
|
|
217
|
+
"permanent_location_id" => '1b14e21c-8d47-45c7-bc49-456a0086422b',
|
|
218
|
+
"source_id" => "f32d531e-df79-46b3-8932-cdd35f7a2264",
|
|
219
|
+
"holdings_type_id" => "996f93e2-5b5e-4cf2-9168-33ced1f95eed",
|
|
220
|
+
"discovery_suppress" => false }
|
|
221
|
+
client.create_holdings(holdings_record:)
|
|
222
|
+
=> {
|
|
223
|
+
"id" => "c65bb9dc-ebca-41fc-9c50-0d39085c1987",
|
|
224
|
+
"_version" => 1,
|
|
225
|
+
"sourceId" => "f32d531e-df79-46b3-8932-cdd35f7a2264",
|
|
226
|
+
"hrid" => "ho00000927052",
|
|
227
|
+
"holdingsTypeId" => "996f93e2-5b5e-4cf2-9168-33ced1f95eed",
|
|
228
|
+
"formerIds" => [],
|
|
229
|
+
"instanceId" => "54ec1f1a-d039-5a39-95f2-71df00061664",
|
|
230
|
+
"permanentLocationId" => "1b14e21c-8d47-45c7-bc49-456a0086422b",
|
|
231
|
+
"effectiveLocationId" => "1b14e21c-8d47-45c7-bc49-456a0086422b",
|
|
232
|
+
"electronicAccess" => [],
|
|
233
|
+
"administrativeNotes" => [],
|
|
234
|
+
"notes" => [],
|
|
235
|
+
"holdingsStatements" => [],
|
|
236
|
+
"holdingsStatementsForIndexes" => [],
|
|
237
|
+
"holdingsStatementsForSupplements" => [],
|
|
238
|
+
"discoverySuppress" => false,
|
|
239
|
+
"statisticalCodeIds" => [],
|
|
240
|
+
"metadata" =>
|
|
241
|
+
{"createdDate" => "2026-03-12T18:58:03.576+00:00",
|
|
242
|
+
"createdByUserId" => "709fdac6-d3f3-5784-8839-fe36ad6ed0b3",
|
|
243
|
+
"updatedDate" => "2026-03-12T18:58:03.576+00:00",
|
|
244
|
+
"updatedByUserId" => "709fdac6-d3f3-5784-8839-fe36ad6ed0b3"}
|
|
245
|
+
}
|
|
166
246
|
```
|
|
167
247
|
|
|
168
248
|
## Development
|
data/folio_client.gemspec
CHANGED
|
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
|
|
|
13
13
|
spec.summary = 'Interface for interacting with the Folio ILS API.'
|
|
14
14
|
spec.description = 'This provides API interaction with the Folio ILS API'
|
|
15
15
|
spec.homepage = 'https://github.com/sul-dlss/folio_client'
|
|
16
|
-
spec.required_ruby_version = '>= 3.
|
|
16
|
+
spec.required_ruby_version = '>= 3.4'
|
|
17
17
|
|
|
18
18
|
spec.metadata['homepage_uri'] = spec.homepage
|
|
19
19
|
spec.metadata['source_code_uri'] = 'https://github.com/sul-dlss/folio_client'
|
|
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
|
|
|
36
36
|
spec.add_dependency 'faraday'
|
|
37
37
|
spec.add_dependency 'faraday-cookie_jar'
|
|
38
38
|
spec.add_dependency 'marc'
|
|
39
|
+
spec.add_dependency 'ostruct'
|
|
39
40
|
spec.add_dependency 'zeitwerk'
|
|
40
41
|
|
|
41
42
|
spec.add_development_dependency 'rake', '~> 13.0'
|
|
@@ -15,7 +15,11 @@ class FolioClient
|
|
|
15
15
|
|
|
16
16
|
access_cookie = FolioClient.cookie_jar.cookies.find { |cookie| cookie.name == 'folioAccessToken' }
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
# NOTE: The client typically delegates raising exceptions (based on HTTP
|
|
19
|
+
# responses) to the UnexpectedResponse class, but this call in
|
|
20
|
+
# Authenticator is a one-off, unlike any other in the app, so we
|
|
21
|
+
# allow it to customize its exception handling.
|
|
22
|
+
raise UnauthorizedError, "Problem with folioAccessToken cookie: #{response.headers}, #{response.body}" unless access_cookie
|
|
19
23
|
|
|
20
24
|
access_cookie.value
|
|
21
25
|
end
|
|
@@ -64,6 +64,54 @@ class FolioClient
|
|
|
64
64
|
false
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
# Get location details by UUID
|
|
68
|
+
# @param location_id [String] UUID of the location
|
|
69
|
+
# @return [Hash] location data including campusId and other location information
|
|
70
|
+
# @raise [ResourceNotFound] if location with the given UUID is not found
|
|
71
|
+
def fetch_location(location_id:)
|
|
72
|
+
client.get("/locations/#{location_id}")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Get holdings records for an instance by HRID
|
|
76
|
+
# @see https://s3.amazonaws.com/foliodocs/api/mod-inventory-storage/p/inventory-view.html
|
|
77
|
+
# @param hrid [String] instance HRID
|
|
78
|
+
# @return [Array<Hash>] array of holdings records with permanentLocationId, _version, discoverySuppress, and other fields
|
|
79
|
+
def fetch_holdings(hrid:)
|
|
80
|
+
instance_uuid = fetch_external_id(hrid: hrid)
|
|
81
|
+
instance_response = client.get('/inventory-view/instances', { query: "id==#{instance_uuid}" })
|
|
82
|
+
|
|
83
|
+
instance_response.dig('instances', 0, 'holdingsRecords') || []
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Put an updated holdings record
|
|
87
|
+
# @see https://s3.amazonaws.com/foliodocs/api/mod-inventory/p/inventory.html#inventory_holdings__holdingsid__put
|
|
88
|
+
# @param holdings_id [String] UUID of the holdings record to update
|
|
89
|
+
# @param holdings_record [Hash] holdings record
|
|
90
|
+
# @raise [ResourceNotFound] if holdings record with the given UUID is not found
|
|
91
|
+
# @raise [BadRequestError] may occur if the update includes invalid fields or values
|
|
92
|
+
# @raise [UnexpectedResponse] if the API returns some other error response
|
|
93
|
+
def update_holdings(holdings_id:, holdings_record:)
|
|
94
|
+
client.put("/inventory/holdings/#{holdings_id}", holdings_record, exception_subject: "holdings record with ID #{holdings_id}")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Post a new holdings record
|
|
98
|
+
# @param holdings_record [Hash] holdings record
|
|
99
|
+
# @return [Hash] the created holdings record, including its id and other fields
|
|
100
|
+
# @see https://s3.amazonaws.com/foliodocs/api/mod-inventory-storage/p/holdings-storage.html#holdings_storage_holdings_post
|
|
101
|
+
def create_holdings(holdings_record:)
|
|
102
|
+
required = %w[instance_id permanent_location_id source_id holdings_type_id]
|
|
103
|
+
missing = required.select { |field| !holdings_record.key?(field) || holdings_record[field].blank? }
|
|
104
|
+
raise ArgumentError, "Missing required fields: #{missing.join(', ')}" if missing.any?
|
|
105
|
+
|
|
106
|
+
client.post('/holdings-storage/holdings', {
|
|
107
|
+
instanceId: holdings_record['instance_id'],
|
|
108
|
+
permanentLocationId: holdings_record['permanent_location_id'],
|
|
109
|
+
sourceId: holdings_record['source_id'],
|
|
110
|
+
holdingsTypeId: holdings_record['holdings_type_id'],
|
|
111
|
+
discoverySuppress: false
|
|
112
|
+
})
|
|
113
|
+
end
|
|
114
|
+
|
|
67
115
|
private
|
|
68
116
|
|
|
69
117
|
def client
|
|
@@ -98,6 +98,10 @@ class FolioClient
|
|
|
98
98
|
def check_not_found(result, index, max_checks)
|
|
99
99
|
return unless result.failure? && result.failure == :not_found && index > max_checks
|
|
100
100
|
|
|
101
|
+
# NOTE: The client typically delegates raising exceptions (based on HTTP
|
|
102
|
+
# responses) to the UnexpectedResponse class, but the interaction in
|
|
103
|
+
# JobStatus is more complex due to waits/loops, so we allow this
|
|
104
|
+
# class to do some of its own exception handling.
|
|
101
105
|
raise ResourceNotFound,
|
|
102
106
|
"Job #{job_execution_id} not found after #{index} retries. The data import job may still have completed."
|
|
103
107
|
end
|
|
@@ -21,7 +21,7 @@ class FolioClient
|
|
|
21
21
|
"Expected 1 record for #{instance_hrid}, but found #{record_count}"
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
response_hash
|
|
24
|
+
response_hash.dig('sourceRecords', 0, 'parsedRecord', 'content')
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# get marc bib data as MARCXML from folio given an instance HRID
|
|
@@ -30,9 +30,7 @@ class FolioClient
|
|
|
30
30
|
# @return [String] MARCXML string
|
|
31
31
|
# @raise [ResourceNotFound]
|
|
32
32
|
# @raise [MultipleResourcesFound]
|
|
33
|
-
# rubocop:disable Metrics/MethodLength
|
|
34
|
-
# rubocop:disable Metrics/AbcSize
|
|
35
|
-
def fetch_marc_xml(instance_hrid: nil, barcode: nil)
|
|
33
|
+
def fetch_marc_xml(instance_hrid: nil, barcode: nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
36
34
|
if barcode.nil? && instance_hrid.nil?
|
|
37
35
|
raise ArgumentError,
|
|
38
36
|
'Either a barcode or a Folio instance HRID must be provided'
|
|
@@ -62,8 +60,6 @@ class FolioClient
|
|
|
62
60
|
updated_marc.fields << MARC::ControlField.new('003', 'FOLIO')
|
|
63
61
|
updated_marc.to_xml.to_s
|
|
64
62
|
end
|
|
65
|
-
# rubocop:enable Metrics/MethodLength
|
|
66
|
-
# rubocop:enable Metrics/AbcSize
|
|
67
63
|
|
|
68
64
|
private
|
|
69
65
|
|
|
@@ -4,16 +4,18 @@ 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
|
-
|
|
9
|
-
|
|
7
|
+
def self.call(response, **kwargs) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
8
|
+
subject = kwargs.fetch(:exception_subject, 'resource')
|
|
9
|
+
|
|
10
10
|
case response.status
|
|
11
|
+
when 400
|
|
12
|
+
raise BadRequestError, "Bad request for #{subject}: #{response.body}"
|
|
11
13
|
when 401
|
|
12
14
|
raise UnauthorizedError, "There was a problem with the access token: #{response.body}"
|
|
13
15
|
when 403
|
|
14
16
|
raise ForbiddenError, "The operation requires privileges which the client does not have: #{response.body}"
|
|
15
17
|
when 404
|
|
16
|
-
raise ResourceNotFound, "Endpoint not found or
|
|
18
|
+
raise ResourceNotFound, "Endpoint not found or #{subject} does not exist: #{response.body}"
|
|
17
19
|
when 409
|
|
18
20
|
raise ConflictError, "Resource cannot be updated: #{response.body}"
|
|
19
21
|
when 422
|
|
@@ -21,10 +23,8 @@ class FolioClient
|
|
|
21
23
|
when 500
|
|
22
24
|
raise ServiceUnavailable, "The remote server returned an internal server error: #{response.body}"
|
|
23
25
|
else
|
|
24
|
-
raise
|
|
26
|
+
raise Error, "Unexpected response: #{response.status} #{response.body}"
|
|
25
27
|
end
|
|
26
28
|
end
|
|
27
29
|
end
|
|
28
|
-
# rubocop:enable Metrics/MethodLength
|
|
29
|
-
# rubocop:enable Metrics/AbcSize
|
|
30
30
|
end
|
data/lib/folio_client/version.rb
CHANGED
data/lib/folio_client.rb
CHANGED
|
@@ -41,6 +41,9 @@ class FolioClient
|
|
|
41
41
|
# Error raised when the Folio API returns a 409 Conflict
|
|
42
42
|
class ConflictError < Error; end
|
|
43
43
|
|
|
44
|
+
# Error raised when the Folio API returns a 400 Bad Request
|
|
45
|
+
class BadRequestError < Error; end
|
|
46
|
+
|
|
44
47
|
DEFAULT_HEADERS = {
|
|
45
48
|
accept: 'application/json, text/plain',
|
|
46
49
|
content_type: 'application/json'
|
|
@@ -75,12 +78,12 @@ class FolioClient
|
|
|
75
78
|
self
|
|
76
79
|
end
|
|
77
80
|
|
|
78
|
-
delegate :config, :connection, :cookie_jar, :data_import, :default_timeout,
|
|
79
|
-
:edit_marc_json, :fetch_external_id, :fetch_hrid,
|
|
80
|
-
:fetch_instance_info, :fetch_marc_hash, :fetch_marc_xml,
|
|
81
|
+
delegate :config, :connection, :cookie_jar, :create_holdings, :data_import, :default_timeout,
|
|
82
|
+
:edit_marc_json, :fetch_external_id, :fetch_holdings, :fetch_hrid,
|
|
83
|
+
:fetch_instance_info, :fetch_location, :fetch_marc_hash, :fetch_marc_xml,
|
|
81
84
|
:force_token_refresh!, :get, :has_instance_status?,
|
|
82
85
|
:http_get_headers, :http_post_and_put_headers, :interface_details,
|
|
83
|
-
:job_profiles, :organization_interfaces, :organizations, :users,
|
|
86
|
+
:job_profiles, :organization_interfaces, :organizations, :update_holdings, :users,
|
|
84
87
|
:user_details, :post, :put, to: :instance
|
|
85
88
|
end
|
|
86
89
|
|
|
@@ -89,6 +92,7 @@ class FolioClient
|
|
|
89
92
|
# Send an authenticated get request
|
|
90
93
|
# @param path [String] the path to the Folio API request
|
|
91
94
|
# @param params [Hash] params to get to the API
|
|
95
|
+
# @return [Hash, nil] the parsed response body or nil
|
|
92
96
|
def get(path, params = {})
|
|
93
97
|
response = with_token_refresh_when_unauthorized do
|
|
94
98
|
connection.get(path, params, { 'x-okapi-token': config.token })
|
|
@@ -96,16 +100,14 @@ class FolioClient
|
|
|
96
100
|
|
|
97
101
|
UnexpectedResponse.call(response) unless response.success?
|
|
98
102
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
JSON.parse(response.body)
|
|
103
|
+
JSON.parse(response.body) if response.body.present?
|
|
102
104
|
end
|
|
103
105
|
|
|
104
106
|
# Send an authenticated post request
|
|
105
107
|
# If the body is JSON, it will be automatically serialized
|
|
106
108
|
# @param path [String] the path to the Folio API request
|
|
107
109
|
# @param body [Object] body to post to the API as JSON
|
|
108
|
-
#
|
|
110
|
+
# @return [Hash, nil] the parsed response body or nil
|
|
109
111
|
def post(path, body = nil, content_type: 'application/json')
|
|
110
112
|
req_body = content_type == 'application/json' ? body&.to_json : body
|
|
111
113
|
response = with_token_refresh_when_unauthorized do
|
|
@@ -118,18 +120,15 @@ class FolioClient
|
|
|
118
120
|
|
|
119
121
|
UnexpectedResponse.call(response) unless response.success?
|
|
120
122
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
JSON.parse(response.body)
|
|
123
|
+
JSON.parse(response.body) if response.body.present?
|
|
124
124
|
end
|
|
125
|
-
# rubocop:enable Metrics/MethodLength
|
|
126
125
|
|
|
127
126
|
# Send an authenticated put request
|
|
128
127
|
# If the body is JSON, it will be automatically serialized
|
|
129
128
|
# @param path [String] the path to the Folio API request
|
|
130
129
|
# @param body [Object] body to put to the API as JSON
|
|
131
|
-
#
|
|
132
|
-
def put(path, body = nil, content_type: 'application/json')
|
|
130
|
+
# @return [Hash, nil] the parsed response body or nil
|
|
131
|
+
def put(path, body = nil, content_type: 'application/json', **exception_args)
|
|
133
132
|
req_body = content_type == 'application/json' ? body&.to_json : body
|
|
134
133
|
response = with_token_refresh_when_unauthorized do
|
|
135
134
|
req_headers = {
|
|
@@ -139,13 +138,10 @@ class FolioClient
|
|
|
139
138
|
connection.put(path, req_body, req_headers)
|
|
140
139
|
end
|
|
141
140
|
|
|
142
|
-
UnexpectedResponse.call(response) unless response.success?
|
|
143
|
-
|
|
144
|
-
return nil if response.body.blank?
|
|
141
|
+
UnexpectedResponse.call(response, **exception_args) unless response.success?
|
|
145
142
|
|
|
146
|
-
JSON.parse(response.body)
|
|
143
|
+
JSON.parse(response.body) if response.body.present?
|
|
147
144
|
end
|
|
148
|
-
# rubocop:enable Metrics/MethodLength
|
|
149
145
|
|
|
150
146
|
# the base connection to the Folio API
|
|
151
147
|
def connection
|
|
@@ -186,6 +182,34 @@ class FolioClient
|
|
|
186
182
|
.fetch_instance_info(...)
|
|
187
183
|
end
|
|
188
184
|
|
|
185
|
+
# @see Inventory#fetch_location
|
|
186
|
+
def fetch_location(...)
|
|
187
|
+
Inventory
|
|
188
|
+
.new
|
|
189
|
+
.fetch_location(...)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# @see Inventory#fetch_holdings
|
|
193
|
+
def fetch_holdings(...)
|
|
194
|
+
Inventory
|
|
195
|
+
.new
|
|
196
|
+
.fetch_holdings(...)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# @see Inventory#update_holdings
|
|
200
|
+
def update_holdings(...)
|
|
201
|
+
Inventory
|
|
202
|
+
.new
|
|
203
|
+
.update_holdings(...)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# @see Inventory#create_holdings
|
|
207
|
+
def create_holdings(...)
|
|
208
|
+
Inventory
|
|
209
|
+
.new
|
|
210
|
+
.create_holdings(...)
|
|
211
|
+
end
|
|
212
|
+
|
|
189
213
|
# @see SourceStorage#fetch_marc_hash
|
|
190
214
|
def fetch_marc_hash(...)
|
|
191
215
|
SourceStorage
|
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: 0.20.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter Mangiafico
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-03-13 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activesupport
|
|
@@ -79,6 +79,20 @@ dependencies:
|
|
|
79
79
|
- - ">="
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: '0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: ostruct
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
82
96
|
- !ruby/object:Gem::Dependency
|
|
83
97
|
name: zeitwerk
|
|
84
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -274,7 +288,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
274
288
|
requirements:
|
|
275
289
|
- - ">="
|
|
276
290
|
- !ruby/object:Gem::Version
|
|
277
|
-
version: 3.
|
|
291
|
+
version: '3.4'
|
|
278
292
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
279
293
|
requirements:
|
|
280
294
|
- - ">="
|