nypl_sierra_api_client 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8936adc2234429844a356e11687cb660933b0ad20f22acac447a09f9595af0d0
4
+ data.tar.gz: e14ab0690f2d3b8b9a340a1248a4746a9c9c418298c8028e9be096d380a204c5
5
+ SHA512:
6
+ metadata.gz: a50a652ee9278599ca077c5fa3a197128163e0f91eb8d2bd57db3152a31bdea413004541abf49d060eb907fae2a3fd8edf0dfcf0fd0d60b893b1beb69a4600df
7
+ data.tar.gz: 7dd271f819f77d1f58cdb4aa0b12c61921cb41ec7cf29eeaf6d637444e251328a89136479f7a9f888f9d92b5758de77c8e63517db78db86671ce67371301edd1
data/lib/errors.rb ADDED
@@ -0,0 +1,13 @@
1
+ class SierraApiClientError < StandardError
2
+ end
3
+
4
+ class SierraApiClientTokenError < SierraApiClientError
5
+ end
6
+
7
+ class SierraApiResponseError < StandardError
8
+ attr_reader :response
9
+
10
+ def initialize(response)
11
+ @response = response
12
+ end
13
+ end
@@ -0,0 +1,139 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'uri'
4
+ require 'nypl_log_formatter'
5
+
6
+ require_relative 'errors'
7
+ require_relative 'sierra_api_response'
8
+
9
+ class SierraApiClient
10
+ def initialize(config = {})
11
+ @config = {
12
+ base_url: ENV['SIERRA_API_BASE_URL'],
13
+ client_id: ENV['SIERRA_OAUTH_ID'],
14
+ client_secret: ENV['SIERRA_OAUTH_SECRET'],
15
+ oauth_url: ENV['SIERRA_OAUTH_URL'],
16
+ log_level: 'info'
17
+ }.merge config
18
+
19
+ raise SierraApiClientError.new 'Missing config: neither config.base_url nor ENV.SIERRA_API_BASE_URL are set' unless @config[:base_url]
20
+ raise SierraApiClientError.new 'Missing config: neither config.client_id nor ENV.SIERRA_OAUTH_ID are set' unless @config[:client_id]
21
+ raise SierraApiClientError.new 'Missing config: neither config.client_secret nor ENV.SIERRA_OAUTH_SECRET are set ' unless @config[:client_secret]
22
+ raise SierraApiClientError.new 'Missing config: neither config.oauth_url nor ENV.SIERRA_OAUTH_URL are set ' unless @config[:oauth_url]
23
+ end
24
+
25
+ def get (path, options = {})
26
+ options = parse_http_options options
27
+
28
+ do_request 'get', path, options
29
+ end
30
+
31
+ def post (path, body, options = {})
32
+ options = parse_http_options options
33
+
34
+ # Default to POSTing JSON unless explicitly stated otherwise
35
+ options[:headers]['Content-Type'] = 'application/json' unless options[:headers]['Content-Type']
36
+
37
+ do_request 'post', path, options do |request|
38
+ request.body = body
39
+ request.body = request.body.to_json unless options[:headers]['Content-Type'] != 'application/json'
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def do_request (method, path, options = {})
46
+ # For now, these are the methods we support:
47
+ raise SierraApiClientError, "Unsupported method: #{method}" unless ['get', 'post'].include? method.downcase
48
+
49
+ authenticate! if options[:authenticated]
50
+
51
+ uri = URI.parse("#{@config[:base_url]}#{path}")
52
+
53
+ http = Net::HTTP.new(uri.host, uri.port)
54
+ http.use_ssl = uri.scheme === 'https'
55
+
56
+ # Build request headers:
57
+ request_headers = {}
58
+ request_headers['Content-Type'] = options[:headers]['Content-Type'] unless options.dig(:headers, 'Content-Type').nil?
59
+
60
+ # Create HTTP::Get or HTTP::Post
61
+ request = Net::HTTP.const_get(method.capitalize).new(uri.path, request_headers)
62
+
63
+ # Add bearer token header
64
+ request['Authorization'] = "Bearer #{@access_token}" if options[:authenticated]
65
+
66
+ # Allow caller to modify the request before we send it off:
67
+ yield request if block_given?
68
+
69
+ logger.debug "SierraApiClient: #{method} to Sierra api", { uri: uri, body: request.body }
70
+
71
+ begin
72
+ # Execute request:
73
+ response = http.request(request)
74
+ rescue => e
75
+ raise SierraApiClientError.new(e), "Failed to #{method} to #{path}: #{e.message}"
76
+ end
77
+
78
+ logger.debug "SierraApiClient: Got Sierra api response", { code: response.code, body: response.body }
79
+
80
+ parse_response response
81
+ end
82
+
83
+ def parse_response (response)
84
+ if response.code == "401"
85
+ # Likely an expired access-token; Wipe it for next run
86
+ # TODO: Implement token refresh
87
+ @access_token = nil
88
+ raise SierraApiClientTokenError.new("Got a 401: #{response.body}")
89
+ end
90
+
91
+ SierraApiResponse.new(response)
92
+ end
93
+
94
+ def parse_http_options (_options)
95
+ options = {
96
+ authenticated: true
97
+ }.merge _options
98
+
99
+ options[:headers] = {
100
+ }.merge(_options[:headers] || {})
101
+ .transform_keys(&:to_s)
102
+
103
+ options
104
+ end
105
+
106
+ # Authorizes the request.
107
+ def authenticate!
108
+ # NOOP if we've already authenticated
109
+ return nil if ! @access_token.nil?
110
+
111
+ logger.debug "SierraApiClient: Authenticating with client_id #{@config[:client_id]}"
112
+
113
+ uri = URI.parse("#{@config[:oauth_url]}")
114
+ request = Net::HTTP::Post.new(uri)
115
+ request.basic_auth(@config[:client_id], @config[:client_secret])
116
+ request.set_form_data(
117
+ "grant_type" => "client_credentials"
118
+ )
119
+
120
+ req_options = {
121
+ use_ssl: uri.scheme == "https",
122
+ request_timeout: 500
123
+ }
124
+
125
+ response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
126
+ http.request(request)
127
+ end
128
+
129
+ if response.code == '200'
130
+ @access_token = JSON.parse(response.body)["access_token"]
131
+ else
132
+ nil
133
+ end
134
+ end
135
+
136
+ def logger
137
+ @logger ||= NyplLogFormatter.new(STDOUT, level: @config[:log_level])
138
+ end
139
+ end
@@ -0,0 +1,34 @@
1
+ class SierraApiResponse
2
+ attr_accessor :response
3
+
4
+ def initialize(response)
5
+ @response = response
6
+ end
7
+
8
+ def code
9
+ @response.code.to_i
10
+ end
11
+
12
+ def error?
13
+ code >= 400
14
+ end
15
+
16
+ def success?
17
+ (200...300).include? code
18
+ end
19
+
20
+ def body
21
+ return @response.body if @response.code == '204'
22
+
23
+ # If response Content-Type indicates body is json
24
+ if /^application\/json/ =~ @response['Content-Type']
25
+ begin
26
+ JSON.parse(@response.body)
27
+ rescue => e
28
+ raise SierraApiResponseError.new(response), "Error parsing response (#{response.code}): #{response.body}"
29
+ end
30
+ else
31
+ @response.body
32
+ end
33
+ end
34
+ end
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nypl_sierra_api_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - nonword
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-04-21 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Client for querying Sierra API
14
+ email: paulbeaudoin@nypl.org
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/errors.rb
20
+ - lib/sierra_api_client.rb
21
+ - lib/sierra_api_response.rb
22
+ homepage: https://github.com/NYPL/ruby-sierra-api-client
23
+ licenses:
24
+ - MIT
25
+ metadata: {}
26
+ post_install_message:
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ requirements: []
41
+ rubygems_version: 3.0.8
42
+ signing_key:
43
+ specification_version: 4
44
+ summary: NYPL Sierra API client
45
+ test_files: []