-
Notifications
You must be signed in to change notification settings - Fork 2
/
symphony_reader.rb
147 lines (112 loc) · 4.09 KB
/
symphony_reader.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# frozen_string_literal: true
# Reader from symphony's JSON API to fetch marcxml given a catkey, or fetch a catkey given a barcode
class SymphonyReader
class ResponseError < StandardError; end
class NotFound < StandardError; end
attr_reader :catkey, :barcode
def self.client
Faraday.new(headers: Settings.catalog.symphony.headers)
end
def initialize(catkey: nil, barcode: nil)
@catkey = catkey
@barcode = barcode
end
def fetch_catkey
return nil unless barcode_json['fields'] && barcode_json['fields']['bib']
barcode_json['fields']['bib']['key']
end
# @raises ResponseError
def to_marc
@catkey = fetch_catkey if catkey.nil? # we need a catkey to do this lookup, so fetch it from the barcode if none exists
record = MARC::Record.new
# NOTE: that new record already has default leader, but we don't want it unless it's from Symphony
record.leader = leader
fields.uniq.each do |field|
record << marc_field(field) unless %w[001 003].include? field['tag'] # explicitly remove all 001 and 003 fields from the record
end
# explicitly inject the catkey into the 001 field
record << marc_field('tag' => '001', 'subfields' => [{ 'code' => '_', 'data' => "a#{catkey}" }])
# explicitly inject SIRSI into the 003 field
record << marc_field('tag' => '003', 'subfields' => [{ 'code' => '_', 'data' => 'SIRSI' }])
record
end
private
# see https://symphony-webservices-dev.stanford.edu/symws/sdk.html for documentation of symphony web services
def client
self.class.client
end
def fetch_barcode_response
raise 'no barcode supplied' unless barcode
url = Settings.catalog.symphony.base_url + Settings.catalog.symphony.barcode_path
symphony_response(format(url, barcode:))
end
def fetch_marc_response
raise 'no catkey supplied' unless catkey
url = Settings.catalog.symphony.base_url + Settings.catalog.symphony.marcxml_path
symphony_response(format(url, catkey:))
end
def symphony_response(url)
resp = client.get(url)
case resp.status
when 200
validate_response(resp)
resp
when 404
raise NotFound, "Record not found in Symphony. Catkey: #{catkey}. API call: #{url}" if resp.status == 404
else
errmsg = "Got HTTP Status-Code #{resp.status} calling #{url}: #{resp.body}"
raise ResponseError, errmsg
end
rescue Faraday::TimeoutError => e
errmsg = "Timeout for Symphony response for API call #{url}: #{e}"
Honeybadger.notify(errmsg)
raise ResponseError, errmsg
end
# expects resp.status to be 200; does not check response code
def validate_response(resp)
# The length for a chunked response is 0, so checking it isn't meaningful.
return resp if resp.headers['Transfer-Encoding'] == 'chunked'
exp_content_length = resp.headers['Content-Length'].to_i
actual_content_length = resp.body.length
return resp if actual_content_length == exp_content_length
errmsg = "Incomplete response received from Symphony for #{@catkey} - expected #{exp_content_length} bytes but got #{actual_content_length}"
Honeybadger.notify(errmsg)
raise ResponseError, errmsg
end
# @raises ResponseError
def marc_json
@marc_json ||= JSON.parse(fetch_marc_response.body)
end
# @raises ResponseError
def barcode_json
@barcode_json ||= JSON.parse(fetch_barcode_response.body)
end
# @raises ResponseError
def bib_record
return {} unless marc_json['fields'] && marc_json['fields']['bib']
marc_json['fields']['bib']
end
def leader
bib_record['leader']
end
def fields
bib_record.fetch('fields', [])
end
def marc_field(field)
if MARC::ControlField.control_tag? field['tag']
marc_control_field(field)
else
marc_data_field(field)
end
end
def marc_control_field(field)
MARC::ControlField.new(field['tag'], field['subfields'].first['data'])
end
def marc_data_field(field)
f = MARC::DataField.new(field['tag'], field['inds'][0], field['inds'][1])
field['subfields'].each do |subfield|
f.append MARC::Subfield.new(subfield['code'], subfield['data'])
end
f
end
end