borrow_direct 1.1.0 → 1.2.0.pre1
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 +5 -13
- data/.travis.yml +6 -0
- data/Gemfile +2 -0
- data/README.md +19 -6
- data/bd_metrics/finditem_measure.rb +8 -1
- data/borrow_direct.gemspec +1 -1
- data/lib/borrow_direct/authentication.rb +16 -8
- data/lib/borrow_direct/defaults.rb +4 -1
- data/lib/borrow_direct/encryption.rb +34 -0
- data/lib/borrow_direct/error.rb +17 -0
- data/lib/borrow_direct/find_item.rb +32 -18
- data/lib/borrow_direct/pickup_location.rb +32 -0
- data/lib/borrow_direct/request.rb +30 -8
- data/lib/borrow_direct/request_item.rb +15 -7
- data/lib/borrow_direct/request_query.rb +18 -4
- data/lib/borrow_direct/version.rb +1 -1
- data/test/authentication_test.rb +40 -12
- data/test/encryption_test.rb +58 -0
- data/test/find_item_test.rb +66 -11
- data/test/request_item_test.rb +65 -8
- data/test/request_query_test.rb +23 -27
- data/test/request_test.rb +40 -27
- data/test/test_helper.rb +10 -1
- data/test/vcr_cassettes/Authentication/Makes_a_request_succesfully.yml +11 -23
- data/test/vcr_cassettes/Authentication/Raises_for_bad_library_symbol.yml +12 -23
- data/test/vcr_cassettes/Authentication/Raises_for_bad_patron_barcode.yml +12 -24
- data/test/vcr_cassettes/Authentication/get_auth_id/Raises_for_bad_api_key.yml +41 -0
- data/test/vcr_cassettes/Authentication/get_auth_id/raises_for_a_bad_library_symbol.yml +12 -23
- data/test/vcr_cassettes/Authentication/get_auth_id/raises_for_a_bad_patron_barcode.yml +12 -24
- data/test/vcr_cassettes/Authentication/get_auth_id/returns_an_auth_id_for_a_good_request.yml +11 -23
- data/test/vcr_cassettes/Authentication/get_auth_id/returns_auth_id_with_API_key_from_defaults.yml +40 -0
- data/test/vcr_cassettes/Authentication/raw_request_to_verify_HTTP_api/works.yml +11 -23
- data/test/vcr_cassettes/FindItem/_find_item_request/Raises_with_bad_api_key.yml +41 -0
- data/test/vcr_cassettes/FindItem/_find_item_request/finds_a_locally_available_item.yml +47 -18
- data/test/vcr_cassettes/FindItem/_find_item_request/finds_a_requestable_item.yml +66 -18
- data/test/vcr_cassettes/FindItem/_find_item_request/finds_an_item_that_does_not_exist_in_BD.yml +66 -19
- data/test/vcr_cassettes/FindItem/_find_item_request/raises_proper_error_on_bad_AID.yml +40 -0
- data/test/vcr_cassettes/FindItem/_find_item_request/uses_manually_set_auth_id.yml +97 -0
- data/test/vcr_cassettes/FindItem/_find_item_request/with_expected_error_PUBFI002/returns_result.yml +46 -9
- data/test/vcr_cassettes/FindItem/_find_item_request/works_with_multiple_values.yml +66 -18
- data/test/vcr_cassettes/FindItem/find_with_Response/_pickup_location_data/returns_array_of_PickupLocations.yml +97 -0
- data/test/vcr_cassettes/FindItem/find_with_Response/has_an_auth_id.yml +66 -18
- data/test/vcr_cassettes/FindItem/find_with_Response/has_nil_pickup_locations_when_BD_doesn_t_want_to_give_us_them.yml +46 -9
- data/test/vcr_cassettes/FindItem/find_with_Response/has_pickup_locations.yml +66 -18
- data/test/vcr_cassettes/FindItem/find_with_Response/knows_locally_available_.yml +47 -18
- data/test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_BD_returns_PUBFI002.yml +46 -9
- data/test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_does_not_exist_in_BD.yml +46 -19
- data/test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_no_libraries_will_lend.yml +275 -18
- data/test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_locally_available_item.yml +47 -18
- data/test/vcr_cassettes/FindItem/find_with_Response/requestable_for_requestable_item.yml +66 -18
- data/test/vcr_cassettes/FindItem/find_with_Response/requestable_with_multiple_items_if_at_least_one_is_requestable.yml +66 -18
- data/test/vcr_cassettes/Request/authentication_id/automatically_fetches_one_when_needed.yml +47 -22
- data/test/vcr_cassettes/Request/authentication_id/can_refetch_when_instructed.yml +47 -22
- data/test/vcr_cassettes/Request/authentication_id/manually_set_one_will_be_used_without_fetch.yml +40 -0
- data/test/vcr_cassettes/Request/authentication_id/starts_out_nil.yml +40 -0
- data/test/vcr_cassettes/Request/authentication_id/takes_with_auth_id.yml +40 -0
- data/test/vcr_cassettes/Request/can_make_a_succesful_request_with_AID.yml +97 -0
- data/test/vcr_cassettes/Request/gets_BD_error_info.yml +49 -12
- data/test/vcr_cassettes/Request/gets_BD_error_info_from_a_bad_AID.yml +77 -0
- data/test/vcr_cassettes/Request/raises_exception_on_timeout_live.yml +40 -0
- data/test/vcr_cassettes/Request/raises_on_bad_path.yml +61 -24
- data/test/vcr_cassettes/Request/raises_on_bad_request_hash.yml +50 -35
- data/test/vcr_cassettes/Request/uses_timeout_for_HttpClient.yml +40 -0
- data/test/vcr_cassettes/Request/with_expected_errors/still_returns_result.yml +47 -10
- data/test/vcr_cassettes/RequestItem/make_request/make_request_for_a_locally_available_item.yml +21 -33
- data/test/vcr_cassettes/RequestItem/make_request/make_request_for_a_requestable_item.yml +20 -32
- data/test/vcr_cassettes/RequestItem/make_request/make_request_for_an_unrequestable_item.yml +21 -33
- data/test/vcr_cassettes/RequestItem/make_request/says_no_for_item_that_BD_returns_PUBRI003.yml +77 -0
- data/test/vcr_cassettes/RequestItem/make_request/sets_an_auth_id.yml +77 -0
- data/test/vcr_cassettes/RequestItem/make_request_/raises_for_unrequestable.yml +21 -33
- data/test/vcr_cassettes/RequestItem/make_request_/returns_number_for_succesful_request.yml +20 -32
- data/test/vcr_cassettes/RequestItem/raises_proper_error_on_bad_AID.yml +40 -0
- data/test/vcr_cassettes/RequestItem/raw_RequestItem_sanity_check.yml +134 -0
- data/test/vcr_cassettes/RequestItem/raw_requests_an_unrequestable_item.yml +21 -33
- data/test/vcr_cassettes/RequestItem/uses_manually_set_auth_id.yml +20 -32
- data/test/vcr_cassettes/RequestItem/with_pickup_location_and_requestable_item/works_with_String_pickup_location.yml +78 -0
- data/test/vcr_cassettes/RequestItem/with_pickup_location_and_requestable_item/works_with_structured_PickupLocation.yml +77 -0
- data/test/vcr_cassettes/RequestQuery/raises_proper_error_on_bad_AID.yml +40 -0
- data/test/vcr_cassettes/RequestQuery/raw_request_query_request/returns_results.yml +117 -325
- data/test/vcr_cassettes/RequestQuery/raw_request_to_verify_the_BD_HTTP_API.yml +30 -325
- data/test/vcr_cassettes/RequestQuery/requests/fetches_default_records.yml +117 -328
- data/test/vcr_cassettes/RequestQuery/requests/fetches_full_records.yml +148 -425
- metadata +67 -38
- data/test/vcr_cassettes/Authentication/raw_request_to_verify_HTTP_api/.yml +0 -52
- data/test/vcr_cassettes/FindItem/find_with_Response/has_nil_auth_id_when_BD_doesn_t_want_to_give_us_one.yml +0 -40
- data/test/vcr_cassettes/Request/can_make_a_succesful_request.yml +0 -49
- data/test/vcr_cassettes/RequestItem/make_request/raises_for_unrequestable.yml +0 -91
- data/test/vcr_cassettes/RequestItem/make_request/returns_number_for_succesful_request.yml +0 -89
- data/test/vcr_cassettes/RequestItem/make_request/says_no_for_item_that_BD_returns_PUBRI004.yml +0 -89
- data/test/vcr_cassettes/RequestItem/with_pickup_location_and_requestable_item/still_works.yml +0 -90
- data/test/vcr_cassettes/top_level_describe/an_inner_describe/.yml +0 -76
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
ODVhYjgxMjhkZTA1MjhjYTNlOTE1OWEwN2Y5NDVlYmZmM2M3ZjQxOA==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f894588c25613d6e9423de67e272784e910b6c8a
|
4
|
+
data.tar.gz: f75996b16e159cf14fe0a7454447fd13bc8a1253
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
ZjU2M2FhYzg3ODQzYmExNzViMTI0Y2UwYjQzYjJlYmNiYmYyMmI3NDBjYTIy
|
11
|
-
OWRiYzdiM2M5MGFjZDRmOTkxNWQ2ZWNhMjhkN2ZhYjQ4M2VmOTc=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
YTA4NzMxODE4OTgzMDg2ZmZjN2I0NTE0MmM1NjZlOGQwZjRiMmJkNDhjMzg3
|
14
|
-
Yjc0ZTg3N2I5OWYwMjQwNGRkNDc1YzIxMjk4YmRmMjcxZjY4YWNjNGM0MzIx
|
15
|
-
ZmVhYjc2OGU5NGY0NzBjMWY2ZDdhODU5MzM3NDU0NzUyMjBjZDE=
|
6
|
+
metadata.gz: b21a6e68e0bd31b355da430b08b0ff0dea9e46bbb660ea8741c0b6a041045261b3f89ce426e0a2377b2a25aefb88edbc2f1056f9658f7d3dcdd3b0bc168899f0
|
7
|
+
data.tar.gz: 579a230114fb40f0661d25f02ba15be1d366ad1a7e534abe8e134f1aadf1e7c54c19fbe82710372e3783e047d708f520e38ec58776f11a54838907771a6aaa99
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -14,6 +14,14 @@ May also work with other Relais D2D setups with configuration or changes, no ide
|
|
14
14
|
Some configuration at boot, perhaps in a Rails initializer:
|
15
15
|
|
16
16
|
~~~ruby
|
17
|
+
# REQUIRED: Set your BD api_key
|
18
|
+
BorrowDirect::Defaults.api_key = "your bd api key"
|
19
|
+
|
20
|
+
# Or, you likely have a different api key for production and
|
21
|
+
# testing/dev, if in Rails this is one way to handle that:
|
22
|
+
BorrowDirect::Defaults.api_key = Rails.env.production? ? "production_bd_api" : "dev_bd_api"
|
23
|
+
|
24
|
+
|
17
25
|
# Uses BD Test system by defualt, if you want to use production system instead
|
18
26
|
BorrowDirect::Defaults.api_base = BorrowDirect::Defaults::PRODUCTION_API_BASE
|
19
27
|
|
@@ -71,13 +79,13 @@ BorrowDirect::RequestQuery.new(patron_barcode).requests("open")
|
|
71
79
|
|
72
80
|
### AuthID's
|
73
81
|
|
74
|
-
|
75
|
-
API still accepts a barcode
|
76
|
-
|
82
|
+
All BD API's will requires an AuthorizationID as of late summer/fall 2015.
|
83
|
+
Our ruby API still accepts a barcode/library symbol pair instead, with both values possibly
|
84
|
+
coming from configured local deaults. The ruby code will make a separate request to retrieve
|
85
|
+
the AuthorizationID behind the scenes, so it can use it.
|
77
86
|
|
78
|
-
If you already have an AuthorizationID, you can set it to
|
79
|
-
|
80
|
-
how often they expire, it might be less efficient than simply requesting a new one)
|
87
|
+
If you already have an AuthorizationID, you can set pass it in to re-use, and avoid
|
88
|
+
the extra call, using #with_auth_id on any BD API request.
|
81
89
|
|
82
90
|
~~~ruby
|
83
91
|
response = BorrowDirect::FindItem.new(patron_barcode).find(:isbn => isbn)
|
@@ -86,6 +94,11 @@ auth_id = response.auth_id
|
|
86
94
|
BorrowDirect::RequestItem.new(patron_barcode).with_auth_id(auth_id).make_request(pickup_location, :isbn => isbn)
|
87
95
|
~~~
|
88
96
|
|
97
|
+
If you pass in an expired or bad AID, we should raise a BorrowDirect::InvalidAidError.
|
98
|
+
(Some unpredictability and inconsistency in remote system error messages may
|
99
|
+
prevent us from catching and classing as an InvalidAidError, if you notice report
|
100
|
+
and we'll try to fix or report upstream.)
|
101
|
+
|
89
102
|
### Generate a query into BorrowDirect
|
90
103
|
|
91
104
|
Sometimes you may want to send the user to specific search results inside the standard BorrowDirect HTML interface. We include a helper class for generating such queries.
|
@@ -8,6 +8,13 @@ require 'borrow_direct'
|
|
8
8
|
require 'borrow_direct/find_item'
|
9
9
|
require 'date'
|
10
10
|
|
11
|
+
if ENV["ENV"].upcase == "PRODUCTION"
|
12
|
+
BorrowDirect::Defaults.api_base = BorrowDirect::Defaults::PRODUCTION_API_BASE
|
13
|
+
puts "BD PRODUCTION: #{BorrowDirect::Defaults::PRODUCTION_API_BASE}"
|
14
|
+
end
|
15
|
+
|
16
|
+
BorrowDirect::Defaults.api_key = ENV['BD_API_KEY']
|
17
|
+
|
11
18
|
key = "isbn"
|
12
19
|
sourcefile = ARGV[0] || File.expand_path("../isbn-bd-test-200.txt", __FILE__)
|
13
20
|
|
@@ -19,7 +26,7 @@ timeout = 20
|
|
19
26
|
# wait one to 7 minutes.
|
20
27
|
delay = 60..420
|
21
28
|
|
22
|
-
puts "#{ENV['BD_LIBRARY_SYMBOL']}: #{key}: #{sourcefile}: #{Time.now.localtime}"
|
29
|
+
puts "#{ENV['BD_LIBRARY_SYMBOL']}: #{key}: #{sourcefile}: #{Time.now.localtime}: Testing at #{BorrowDirect::Defaults.api_base}"
|
23
30
|
|
24
31
|
identifiers = File.readlines(sourcefile) #.shuffle
|
25
32
|
|
data/borrow_direct.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Jonathan Rochkind"]
|
10
10
|
spec.email = ["jonathan@dnil.net"]
|
11
11
|
spec.summary = %q{Ruby tools for interacting with the Borrow Direct consortial services}
|
12
|
-
spec.homepage = ""
|
12
|
+
spec.homepage = "https://github.com/jrochkind/borrow_direct"
|
13
13
|
spec.license = "MIT"
|
14
14
|
|
15
15
|
spec.files = `git ls-files -z`.split("\x0")
|
@@ -9,16 +9,23 @@ module BorrowDirect
|
|
9
9
|
class Authentication < Request
|
10
10
|
attr_reader :patron_barcode, :patron_library_symbol
|
11
11
|
|
12
|
-
@@api_path = "/portal-service/user/authentication
|
12
|
+
@@api_path = "/portal-service/user/authentication"
|
13
13
|
|
14
14
|
# BorrowDirect::Authentication.new(barcode)
|
15
15
|
# BorrowDirect::Authentication.new(barcode, library_symbol)
|
16
16
|
def initialize(patron_barcode,
|
17
|
-
patron_library_symbol = Defaults.library_symbol
|
17
|
+
patron_library_symbol = Defaults.library_symbol,
|
18
|
+
api_key = Defaults.api_key)
|
18
19
|
super(@@api_path)
|
19
20
|
|
20
21
|
@patron_barcode = patron_barcode
|
21
22
|
@patron_library_symbol = patron_library_symbol
|
23
|
+
|
24
|
+
@api_key = api_key
|
25
|
+
|
26
|
+
unless @api_key
|
27
|
+
raise ArgumentError, "BorrowDirect::Authentication requires an api key as third paramter or set in BorrowDirect::Defaults.api_key"
|
28
|
+
end
|
22
29
|
end
|
23
30
|
|
24
31
|
# Returns raw Hash results of the Authentication request
|
@@ -34,8 +41,8 @@ module BorrowDirect
|
|
34
41
|
def get_auth_id
|
35
42
|
response = authentication_request
|
36
43
|
|
37
|
-
if response["
|
38
|
-
return response["
|
44
|
+
if response["AuthorizationId"]
|
45
|
+
return response["AuthorizationId"]
|
39
46
|
else
|
40
47
|
raise BorrowDirect::Error.new("Could not obtain AId from Authorization API call: #{response.inspect}")
|
41
48
|
end
|
@@ -43,10 +50,11 @@ module BorrowDirect
|
|
43
50
|
|
44
51
|
def authentication_request_hash(patron_barcode, library_symbol)
|
45
52
|
{
|
46
|
-
"
|
47
|
-
|
48
|
-
|
49
|
-
|
53
|
+
"ApiKey" => @api_key,
|
54
|
+
"PartnershipId" => BorrowDirect::Defaults.partnership_id,
|
55
|
+
"UserGroup" => "patron",
|
56
|
+
"LibrarySymbol" => library_symbol,
|
57
|
+
"PatronId" => patron_barcode
|
50
58
|
}
|
51
59
|
end
|
52
60
|
|
@@ -13,7 +13,7 @@ module BorrowDirect
|
|
13
13
|
# To set a default generic patron barcode to use for FindItem requests
|
14
14
|
# BorrowDirect::Defaults.find_item_patron_barcode = "99999999999"
|
15
15
|
class Defaults
|
16
|
-
TEST_API_BASE = "https://
|
16
|
+
TEST_API_BASE = "https://rc.relais-host.com/"
|
17
17
|
PRODUCTION_API_BASE = "https://borrow-direct.relais-host.com/"
|
18
18
|
|
19
19
|
TEST_HTML_BASE = "https://bdtest.relaisd2d.com/service-proxy?command=query"
|
@@ -22,6 +22,7 @@ module BorrowDirect
|
|
22
22
|
class << self
|
23
23
|
attr_accessor :api_base, :partnership_id, :find_item_patron_barcode, :library_symbol
|
24
24
|
attr_accessor :html_base_url
|
25
|
+
attr_accessor :api_key
|
25
26
|
|
26
27
|
# used for HTTPClient send, connection, AND receive timeouts, so
|
27
28
|
# theoretically could take 3x this, but unlikely, usually it's just
|
@@ -34,5 +35,7 @@ module BorrowDirect
|
|
34
35
|
self.timeout = 30
|
35
36
|
self.html_base_url = TEST_HTML_BASE
|
36
37
|
|
38
|
+
self.api_key = nil
|
39
|
+
|
37
40
|
end
|
38
41
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module BorrowDirect
|
5
|
+
# Implements Relais' encryption protocol
|
6
|
+
# https://relais.atlassian.net/wiki/display/ILL/Encryption
|
7
|
+
#
|
8
|
+
# value = BorrowDirect::Encryption.new(public_key_string).encode_with_ts(api_key_or_other_data)
|
9
|
+
class Encryption
|
10
|
+
attr_reader :public_key_str
|
11
|
+
|
12
|
+
def initialize(a_public_key)
|
13
|
+
@public_key_str = a_public_key
|
14
|
+
end
|
15
|
+
|
16
|
+
# Will add on timestamp according to Relais protocol, encrypt,
|
17
|
+
# and Base64-encode, all per Relais protocol.
|
18
|
+
def encode_with_ts(value)
|
19
|
+
# Not sure if this object is thread-safe, so we re-create
|
20
|
+
# each time.
|
21
|
+
|
22
|
+
public_key = OpenSSL::PKey::RSA.new(self.public_key_str)
|
23
|
+
|
24
|
+
payload = "#{value}|#{self.now_timestamp}"
|
25
|
+
|
26
|
+
return Base64.encode64(public_key.public_encrypt(payload))
|
27
|
+
end
|
28
|
+
|
29
|
+
# Timestamp formatted how Relais wants it, in UTC
|
30
|
+
def now_timestamp
|
31
|
+
Time.now.getutc.strftime("%Y%m%d %H%M%S")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/borrow_direct/error.rb
CHANGED
@@ -10,9 +10,15 @@ module BorrowDirect
|
|
10
10
|
super(msg)
|
11
11
|
end
|
12
12
|
|
13
|
+
# Different services use different error codes for 'invalid aid'
|
14
|
+
# Not sure we can actually catch them all, but we'll try.
|
15
|
+
def self.invalid_aid_code?(bd_code)
|
16
|
+
["PUBFI003", "PUBRI002"].include?(bd_code)
|
17
|
+
end
|
13
18
|
end
|
14
19
|
|
15
20
|
class HttpError < Error ; end
|
21
|
+
|
16
22
|
class HttpTimeoutError < HttpError
|
17
23
|
attr_reader :timeout
|
18
24
|
def initialize(msg, timeout=nil)
|
@@ -20,4 +26,15 @@ module BorrowDirect
|
|
20
26
|
super(msg)
|
21
27
|
end
|
22
28
|
end
|
29
|
+
|
30
|
+
class InvalidAidError < Error
|
31
|
+
attr_reader :aid
|
32
|
+
def initialize(msg, bd_code = nil, aid = nil)
|
33
|
+
msg += " (aid: #{aid})" if aid
|
34
|
+
super(msg, bd_code)
|
35
|
+
@aid = aid
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
23
40
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'borrow_direct'
|
2
2
|
require 'borrow_direct/request'
|
3
|
+
require 'borrow_direct/pickup_location'
|
3
4
|
|
4
5
|
module BorrowDirect
|
5
6
|
# The BorrowDirect FindItem service, for discovering item availability
|
@@ -10,6 +11,7 @@ module BorrowDirect
|
|
10
11
|
# # optional and use a default patron barcode
|
11
12
|
#
|
12
13
|
# You can also use #find_item_request to get the raw BD response as a ruby hash
|
14
|
+
#
|
13
15
|
class FindItem < Request
|
14
16
|
attr_reader :patron_barcode, :patron_library_symbol
|
15
17
|
|
@@ -24,7 +26,7 @@ module BorrowDirect
|
|
24
26
|
@patron_barcode = patron_barcode
|
25
27
|
@patron_library_symbol = patron_library_symbol
|
26
28
|
|
27
|
-
# BD sometimes unpredictably returns
|
29
|
+
# BD sometimes unpredictably returns one of these errors when it means
|
28
30
|
# "no results", other times it doens't. We don't want to raise on it.
|
29
31
|
self.expected_error_codes << "PUBFI002"
|
30
32
|
end
|
@@ -56,7 +58,7 @@ module BorrowDirect
|
|
56
58
|
raise ArgumentError.new("Missing valid search type and value: '#{options}'")
|
57
59
|
end
|
58
60
|
|
59
|
-
request exact_search_request_hash(search_type, search_value)
|
61
|
+
request exact_search_request_hash(search_type, search_value), need_auth_id(self.patron_barcode, self.patron_library_symbol)
|
60
62
|
end
|
61
63
|
|
62
64
|
# need to send a key and value for a valid exact_search type
|
@@ -65,7 +67,7 @@ module BorrowDirect
|
|
65
67
|
# Returns a BorrowDirect::FindItem::Response object, from which you
|
66
68
|
# can find out requestability, list of pickup locations, etc.
|
67
69
|
def find(options)
|
68
|
-
BorrowDirect::FindItem::Response.new find_item_request(options)
|
70
|
+
BorrowDirect::FindItem::Response.new find_item_request(options), self.auth_id
|
69
71
|
end
|
70
72
|
|
71
73
|
protected
|
@@ -79,10 +81,6 @@ module BorrowDirect
|
|
79
81
|
|
80
82
|
hash = {
|
81
83
|
"PartnershipId" => Defaults.partnership_id,
|
82
|
-
"Credentials" => {
|
83
|
-
"LibrarySymbol" => self.patron_library_symbol,
|
84
|
-
"Barcode" => self.patron_barcode
|
85
|
-
},
|
86
84
|
"ExactSearch" => []
|
87
85
|
}
|
88
86
|
|
@@ -101,8 +99,9 @@ module BorrowDirect
|
|
101
99
|
|
102
100
|
attr_reader :response_hash
|
103
101
|
|
104
|
-
def initialize(hash)
|
102
|
+
def initialize(hash, auth_id)
|
105
103
|
@response_hash = hash
|
104
|
+
@auth_id = auth_id
|
106
105
|
end
|
107
106
|
|
108
107
|
|
@@ -123,7 +122,7 @@ module BorrowDirect
|
|
123
122
|
return false
|
124
123
|
end
|
125
124
|
|
126
|
-
return response_hash["
|
125
|
+
return response_hash["Available"].to_s == "true"
|
127
126
|
end
|
128
127
|
|
129
128
|
# BD thinks the item is locally available at patron's home library,
|
@@ -131,22 +130,37 @@ module BorrowDirect
|
|
131
130
|
# Items that are available locally, and thus not requestable via BD, can
|
132
131
|
# only be found by looking at the RequestMessage, bah
|
133
132
|
def locally_available?
|
134
|
-
|
135
|
-
|
133
|
+
h = response_hash["RequestLink"]
|
134
|
+
# Message seems to sometimes have period sometimes not.
|
135
|
+
return !! (h && h["RequestMessage"] =~ /\AThis item is available locally\.?\Z/)
|
136
136
|
end
|
137
|
-
|
138
|
-
# Returns the AuthorizationID returned by FindItem API call,
|
139
|
-
# or nil if none is available. Nil _can_ be returned, for
|
140
|
-
# instance when BD returns a NotFound error instead of a good
|
141
|
-
# response.
|
137
|
+
|
142
138
|
def auth_id
|
143
|
-
|
139
|
+
@auth_id
|
144
140
|
end
|
145
141
|
|
146
142
|
# Can be nil in some cases if not requestable?
|
147
143
|
# if requestable?, should be an array of Strings.
|
144
|
+
#
|
145
|
+
# This just returns BD location labels, see also #pickup_location_data to
|
146
|
+
# return labels and codes.
|
148
147
|
def pickup_locations
|
149
|
-
|
148
|
+
response_hash["PickupLocation"] && response_hash["PickupLocation"].collect {|h| h["PickupLocationDescription"] }
|
149
|
+
end
|
150
|
+
|
151
|
+
# Can be nil if not requestable, otherwise an array of BorrowDirect::PickupLocation
|
152
|
+
#
|
153
|
+
# See also #pickup_locations to return simply string location descriptions.
|
154
|
+
# It's perhaps more careful code to use the codes too, as in this method,
|
155
|
+
# although Relais says just using labels and submitting them to RequestItem
|
156
|
+
# as a pickup location should work too.
|
157
|
+
def pickup_location_data
|
158
|
+
unless defined? @pickup_location_data
|
159
|
+
@pickup_location_data = response_hash["PickupLocation"] && response_hash["PickupLocation"].collect do |hash|
|
160
|
+
BorrowDirect::PickupLocation.new(hash)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
return @pickup_location_data
|
150
164
|
end
|
151
165
|
|
152
166
|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module BorrowDirect
|
2
|
+
# Returned from FindItem::Response.pickup_locations , contains
|
3
|
+
# a #code and a #description . #to_a returns a handy duple
|
4
|
+
# suitable for passing to Rails options_for_select
|
5
|
+
class PickupLocation
|
6
|
+
attr_reader :response_hash
|
7
|
+
def initialize(bd_hash)
|
8
|
+
if bd_hash["PickupLocationCode"].empty? || bd_hash["PickupLocationDescription"].empty?
|
9
|
+
raise ArgumentError, "PickupLocation requires a hash with PickupLocationCode and PickupLocationDescription, not `#{bd_hash}`"
|
10
|
+
end
|
11
|
+
|
12
|
+
@response_hash = bd_hash
|
13
|
+
end
|
14
|
+
|
15
|
+
def code
|
16
|
+
@response_hash["PickupLocationCode"]
|
17
|
+
end
|
18
|
+
|
19
|
+
def description
|
20
|
+
@response_hash["PickupLocationDescription"]
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
self.response_hash
|
25
|
+
end
|
26
|
+
|
27
|
+
# Handy for passing to Rails options_for_select
|
28
|
+
def to_a
|
29
|
+
[self.description, self.code]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -55,22 +55,29 @@ module BorrowDirect
|
|
55
55
|
@http_method = :post
|
56
56
|
end
|
57
57
|
|
58
|
-
|
58
|
+
# First param is request hash, will be query param for GET or JSON body for POST
|
59
|
+
# Second param is optional AuthenticationID used by BD system -- if given,
|
60
|
+
# will be added to URI as "?aid=$AID", even for POST. Yep, that's Relais
|
61
|
+
# documented protocol eg https://relais.atlassian.net/wiki/display/ILL/Find+Item
|
62
|
+
def request(hash, aid = nil)
|
59
63
|
http = http_client
|
60
|
-
|
61
64
|
|
62
|
-
|
63
|
-
|
65
|
+
uri = @api_uri
|
66
|
+
if aid
|
67
|
+
uri += "?aid=#{CGI.escape aid}"
|
68
|
+
end
|
64
69
|
|
70
|
+
# Mostly for debugging, store these
|
71
|
+
@last_request_uri = uri
|
65
72
|
|
66
73
|
start_time = Time.now
|
67
74
|
|
68
75
|
if self.http_method == :post
|
69
76
|
@last_request_json = json_request = JSON.generate(hash)
|
70
|
-
http_response = http.post
|
77
|
+
http_response = http.post uri, json_request, self.request_headers
|
71
78
|
elsif self.http_method == :get
|
72
79
|
@last_request_query_params = hash
|
73
|
-
http_response = http.get
|
80
|
+
http_response = http.get uri, hash, self.request_headers
|
74
81
|
else
|
75
82
|
raise ArgumentError.new("BorrowDirect::Request only understands http_method :get and :post, not `#{self.http_method}`")
|
76
83
|
end
|
@@ -83,14 +90,18 @@ module BorrowDirect
|
|
83
90
|
rescue JSON::ParserError => json_parse_exception
|
84
91
|
nil
|
85
92
|
end
|
86
|
-
|
93
|
+
|
87
94
|
# will be nil if we have none
|
88
95
|
einfo = error_info(response_hash)
|
89
96
|
expected_error = (einfo && self.expected_error_codes.include?(einfo.number))
|
90
97
|
|
91
98
|
|
92
99
|
if einfo && (! expected_error)
|
93
|
-
|
100
|
+
if BorrowDirect::Error.invalid_aid_code?(einfo.number)
|
101
|
+
raise BorrowDirect::InvalidAidError.new(einfo.message, einfo.number, aid)
|
102
|
+
else
|
103
|
+
raise BorrowDirect::Error.new(einfo.message, einfo.number)
|
104
|
+
end
|
94
105
|
elsif http_response.code != 200 && (! expected_error)
|
95
106
|
raise BorrowDirect::HttpError.new("HTTP Error: #{http_response.code}: #{http_response.body}")
|
96
107
|
elsif response_hash.nil?
|
@@ -179,6 +190,17 @@ module BorrowDirect
|
|
179
190
|
return OpenStruct.new(:number => e["Problem"]["Code"], :message => e["Problem"]["Message"])
|
180
191
|
end
|
181
192
|
|
193
|
+
# And one more for Auth errors! With no error number, hooray.
|
194
|
+
if hash && (e = hash["AuthorizationState"]) && e["State"] == "failed"
|
195
|
+
return OpenStruct.new(:number => nil, :message => "AuthorizationState: State: failed")
|
196
|
+
end
|
197
|
+
|
198
|
+
# And yet another way!
|
199
|
+
if hash && (e = hash["Problem"])
|
200
|
+
# Code/Message appear unpredictably at different keys?
|
201
|
+
return OpenStruct.new(:number => e["ErrorCode"] || e["Code"], :message => e["ErrorMessage"] || e["Message"])
|
202
|
+
end
|
203
|
+
|
182
204
|
return nil
|
183
205
|
end
|
184
206
|
end
|
@@ -22,9 +22,10 @@ module BorrowDirect
|
|
22
22
|
@patron_barcode = patron_barcode
|
23
23
|
@patron_library_symbol = patron_library_symbol
|
24
24
|
|
25
|
-
# BD sometimes unpredictably returns
|
26
|
-
# "no results", other times it
|
27
|
-
self.expected_error_codes << "PUBRI004"
|
25
|
+
# BD sometimes unpredictably returns one of these errors when it means
|
26
|
+
# "no results", other times it doesn't. We don't want to raise on it.
|
27
|
+
self.expected_error_codes << "PUBRI004"
|
28
|
+
self.expected_error_codes << "PUBRI003"
|
28
29
|
end
|
29
30
|
|
30
31
|
# need to send a key and value for a valid exact_search type
|
@@ -34,6 +35,11 @@ module BorrowDirect
|
|
34
35
|
# PickupLocation to BD, which it seems to accept, not sure what it
|
35
36
|
# does with it.
|
36
37
|
#
|
38
|
+
# pickup_location can be a BorrowDirect::PickupLocation object,
|
39
|
+
# or a string. If a string, BD recommends it be a CODE returned
|
40
|
+
# from FindItem, rather than DESCRIPTION as in the past, but we
|
41
|
+
# think description still works?
|
42
|
+
#
|
37
43
|
# Returns the actual complete BD response hash. You may want
|
38
44
|
# #make_request instead
|
39
45
|
#
|
@@ -55,7 +61,11 @@ module BorrowDirect
|
|
55
61
|
raise ArgumentError.new("Missing valid search type and value: '#{options}'")
|
56
62
|
end
|
57
63
|
|
58
|
-
|
64
|
+
if pickup_location.kind_of?(BorrowDirect::PickupLocation)
|
65
|
+
pickup_location = pickup_location.code
|
66
|
+
end
|
67
|
+
|
68
|
+
request exact_search_request_hash(pickup_location, search_type, search_value), need_auth_id(self.patron_barcode, self.patron_library_symbol)
|
59
69
|
end
|
60
70
|
|
61
71
|
# Pass in a BD exact search and pickup location eg
|
@@ -91,14 +101,12 @@ module BorrowDirect
|
|
91
101
|
protected
|
92
102
|
|
93
103
|
def extract_request_number(resp)
|
94
|
-
return
|
104
|
+
return resp["RequestNumber"]
|
95
105
|
end
|
96
106
|
|
97
107
|
def exact_search_request_hash(pickup_location, type, value)
|
98
108
|
hash = {
|
99
109
|
"PartnershipId" => Defaults.partnership_id,
|
100
|
-
"AuthorizationId" => need_auth_id(self.patron_barcode, self.patron_library_symbol),
|
101
|
-
"PickupLocation" => pickup_location,
|
102
110
|
"ExactSearch" => [
|
103
111
|
{
|
104
112
|
"Type" => type.to_s.upcase,
|
@@ -55,7 +55,16 @@ module BorrowDirect
|
|
55
55
|
"fullRecord" => (full_record ? "1" : "0")
|
56
56
|
}
|
57
57
|
|
58
|
-
request query_params
|
58
|
+
response = request query_params
|
59
|
+
|
60
|
+
# RequestQuery sometimes returns odd 200 OK response with
|
61
|
+
# AuthFailed, at least as of 29 Sep 2015. Catch it and
|
62
|
+
# raise it properly.
|
63
|
+
if response["AuthorizationState"] && response["AuthorizationState"]["State"] == false
|
64
|
+
raise BorrowDirect::InvalidAidError.new("API returned AuthorizationState.State == false", nil, response["AuthorizationState"]["AuthorizationId"])
|
65
|
+
end
|
66
|
+
|
67
|
+
return response
|
59
68
|
end
|
60
69
|
|
61
70
|
# Returns an array of BorrowDirect::RequestQuery::Item
|
@@ -68,7 +77,7 @@ module BorrowDirect
|
|
68
77
|
|
69
78
|
results = []
|
70
79
|
|
71
|
-
response["
|
80
|
+
response["MyRequestRecords"].each do |item_hash|
|
72
81
|
results << BorrowDirect::RequestQuery::Item.new(item_hash)
|
73
82
|
end
|
74
83
|
|
@@ -89,11 +98,16 @@ module BorrowDirect
|
|
89
98
|
# basic record values
|
90
99
|
@request_number = hash["RequestNumber"]
|
91
100
|
@title = hash["Title"]
|
92
|
-
|
101
|
+
if hash["ISO8601DateSubmitted"]
|
102
|
+
@date_submitted = DateTime.iso8601 hash["ISO8601DateSubmitted"]
|
103
|
+
end
|
93
104
|
@allow_renew = hash["AllowRenew"]
|
94
105
|
@allow_cancel = hash["AllowCancel"]
|
95
106
|
@request_status = hash["RequestStatus"]
|
96
|
-
|
107
|
+
|
108
|
+
if hash["ISO8601RequestStatusDate"]
|
109
|
+
@request_status_date = DateTime.iso8601 hash["ISO8601RequestStatusDate"]
|
110
|
+
end
|
97
111
|
|
98
112
|
# full record values
|
99
113
|
@publicaition_type = hash["PublicationType"]
|