borrow_direct 1.1.0 → 1.2.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- 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"]
|