esi 0.4.5 → 0.4.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2917822d51f6a158183715498948e66554cf2ba9
4
- data.tar.gz: 3f0bfedf032f8b29c975e6c7fc0f42eb1914647d
3
+ metadata.gz: 4ef7c659090137fe7ff8581cdb6c6a9e9c240faa
4
+ data.tar.gz: 1e47ffce54eda2555fa7f64e3e841e0507162040
5
5
  SHA512:
6
- metadata.gz: 462fe687f11691c7a8c8fe34deb7ad834152caffa919db850a77a1a0f99958732600d7f8bef5500d913354f912d577eb05cadb2b922de709064127ed77b4ad1f
7
- data.tar.gz: e82ddb4376801d1c28fb420c93a9e383ae46c126cdae35c43a81009920245a5a705a2a383b42201871a8ac7faab8dcfd15fc1c912de3476e013dc639b10e21f5
6
+ metadata.gz: 3354040d05b7f3d674c072081b30815bc63b1ced681d64933e24461b039839f424e29724e2e624320120b652a5e4e43dc7253a5eca4fd3371b0a745bc6db60c1
7
+ data.tar.gz: 318a635d10e008cd0fc1daf06f8cfe028c677f041af49db8261a8f6d08d535862ff1a25ac758835b2397d9d96f0cbc84dce9dbecfffbbe6c5c0f3f85e366d825
data/esi.gemspec CHANGED
@@ -1,26 +1,30 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'esi/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "esi"
8
+ spec.name = 'esi'
8
9
  spec.version = Esi::VERSION
9
- spec.authors = ["Danny Hiemstra"]
10
- spec.email = ["dannyhiemstra@gmail.com"]
10
+ spec.authors = ['Danny Hiemstra', 'Aaron Allen']
11
+ spec.email = ['dannyhiemstra@gmail.com', 'aaronmallen4@gmail.com']
11
12
 
12
- spec.summary = "EVE ESI API wrapper"
13
- spec.description = "EVE ESI API wrapper"
14
- spec.homepage = "https://github.com/dhiemstra/esi"
15
- spec.license = "MIT"
13
+ spec.summary = 'EVE ESI API wrapper'
14
+ spec.description = 'EVE ESI API wrapper'
15
+ spec.homepage = 'https://github.com/dhiemstra/esi'
16
+ spec.license = 'MIT'
16
17
 
17
18
  spec.files = %w(LICENSE.txt README.md esi.gemspec) + Dir['lib/**/*.rb']
18
- spec.require_paths = ["lib"]
19
+ spec.require_paths = ['lib']
19
20
 
20
- spec.add_dependency "oauth2", "~> 1.4"
21
- spec.add_dependency "addressable", "~> 2.3"
22
- spec.add_dependency "activesupport"
23
- spec.add_development_dependency "bundler", "~> 1.14"
24
- spec.add_development_dependency "rake", "~> 10.0"
25
- spec.add_development_dependency "minitest", "~> 5.0"
21
+ spec.add_dependency 'activesupport'
22
+ spec.add_dependency 'addressable', '~> 2.3'
23
+ spec.add_dependency 'oauth2', '~> 1.4'
24
+ spec.add_development_dependency 'bundler', '~> 1.14'
25
+ spec.add_development_dependency 'minitest', '~> 5.0'
26
+ spec.add_development_dependency 'rake', '~> 10.0'
27
+ spec.add_development_dependency 'rubocop', '~> 0.52'
28
+ spec.add_development_dependency 'shoulda', '~> 3.5'
29
+ spec.add_development_dependency 'yard', '~> 0.9'
26
30
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Esi
2
4
  class AccessToken < OAuth2::AccessToken
3
5
  EXPIRES_MARGIN = 30.seconds
@@ -13,7 +15,7 @@ module Esi
13
15
  end
14
16
 
15
17
  def verify
16
- Esi::Response.new(get("/oauth/verify"))
18
+ Esi::Response.new(get('/oauth/verify'))
17
19
  end
18
20
 
19
21
  def expired?
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esi
4
+ # ApiError base class
5
+ # @!attribute [r] response
6
+ # @return [Esi::Response] the ApiError Response
7
+ # @!attribute [r] key
8
+ # @return [String] the response.data[:key]
9
+ # @!attribute [r] message
10
+ # @return [String] the response error message
11
+ # @!attribute [r] type
12
+ # @return [String] the response.data[:exceptionType]
13
+ # @!attribute [r] original_exception
14
+ # @return [ExceptionClass|nil] the orginal raised exception
15
+ class ApiError < OAuth2::Error
16
+ attr_reader :response, :key, :message, :type, :original_exception
17
+
18
+ # Create a new instance of ApiError
19
+ # @param [Esi::Response] response the response that generated the exception
20
+ # @param [ExceptionClass|nil] the orginally raised exception
21
+ # @return [Esi::ApiError] an instance of ApiError
22
+ def initialize(response, original_exception = nil)
23
+ super(response.original_response)
24
+
25
+ @response = response
26
+ @original_exception = original_exception
27
+ @code = response.original_response.status
28
+ @key = response.data[:key]
29
+ @message = response.data[:message].presence || response.data[:error] || original_exception.try(:message)
30
+ @type = response.data[:exceptionType]
31
+ end
32
+ end
33
+
34
+ # ApiRequestError Class
35
+ # @!attribute [r] original_exception
36
+ # @return [ExceptionClass|nil] the orginal raised exception
37
+ class ApiRequestError < StandardError
38
+ attr_reader :original_exception
39
+
40
+ # Create a new instance of ApiRequestError
41
+ # @param [ExceptionClass|nil] the orginally raised exception
42
+ # @return [Esi::ApiRequestError] the instance of ApiRequestError
43
+ def initialize(original_exception)
44
+ @original_exception = original_exception
45
+ msg = "#{original_exception.class}: " \
46
+ "#{original_exception.try(:response).try(:status)}" \
47
+ ' - ' \
48
+ "#{original_exception.try(:message)}"
49
+ super(msg)
50
+ end
51
+ end
52
+
53
+ # ApiUnknowntError Class
54
+ # @!attribute [r] response
55
+ # @return [Esi::Response] the ApiError Response
56
+ # @!attribute [r] key
57
+ # @return [String] the response.data[:key]
58
+ # @!attribute [r] message
59
+ # @return [String] the response error message
60
+ # @!attribute [r] type
61
+ # @return [String] the response.data[:exceptionType]
62
+ # @!attribute [r] original_exception
63
+ # @return [ExceptionClass|nil] the orginal raised exception
64
+ class ApiUnknownError < ApiError; end
65
+
66
+ # ApiBadRequestError Class
67
+ # @!attribute [r] response
68
+ # @return [Esi::Response] the ApiError Response
69
+ # @!attribute [r] key
70
+ # @return [String] the response.data[:key]
71
+ # @!attribute [r] message
72
+ # @return [String] the response error message
73
+ # @!attribute [r] type
74
+ # @return [String] the response.data[:exceptionType]
75
+ # @!attribute [r] original_exception
76
+ # @return [ExceptionClass|nil] the orginal raised exception
77
+ class ApiBadRequestError < ApiError; end
78
+
79
+ # ApiRefreshTokenExpiredError Class
80
+ # @!attribute [r] response
81
+ # @return [Esi::Response] the ApiError Response
82
+ # @!attribute [r] key
83
+ # @return [String] the response.data[:key]
84
+ # @!attribute [r] message
85
+ # @return [String] the response error message
86
+ # @!attribute [r] type
87
+ # @return [String] the response.data[:exceptionType]
88
+ # @!attribute [r] original_exception
89
+ # @return [ExceptionClass|nil] the orginal raised exception
90
+ class ApiRefreshTokenExpiredError < ApiError; end
91
+
92
+ # ApiInvalidAppClientKeysError Class
93
+ # @!attribute [r] response
94
+ # @return [Esi::Response] the ApiError Response
95
+ # @!attribute [r] key
96
+ # @return [String] the response.data[:key]
97
+ # @!attribute [r] message
98
+ # @return [String] the response error message
99
+ # @!attribute [r] type
100
+ # @return [String] the response.data[:exceptionType]
101
+ # @!attribute [r] original_exception
102
+ # @return [ExceptionClass|nil] the orginal raised exception
103
+ class ApiInvalidAppClientKeysError < ApiError; end
104
+
105
+ # ApiNotFoundError Class
106
+ # @!attribute [r] response
107
+ # @return [Esi::Response] the ApiError Response
108
+ # @!attribute [r] key
109
+ # @return [String] the response.data[:key]
110
+ # @!attribute [r] message
111
+ # @return [String] the response error message
112
+ # @!attribute [r] type
113
+ # @return [String] the response.data[:exceptionType]
114
+ # @!attribute [r] original_exception
115
+ # @return [ExceptionClass|nil] the orginal raised exception
116
+ class ApiNotFoundError < ApiError; end
117
+
118
+ # ApiForbiddenError Class
119
+ # @!attribute [r] response
120
+ # @return [Esi::Response] the ApiError Response
121
+ # @!attribute [r] key
122
+ # @return [String] the response.data[:key]
123
+ # @!attribute [r] message
124
+ # @return [String] the response error message
125
+ # @!attribute [r] type
126
+ # @return [String] the response.data[:exceptionType]
127
+ # @!attribute [r] original_exception
128
+ # @return [ExceptionClass|nil] the orginal raised exception
129
+ class ApiForbiddenError < ApiError; end
130
+
131
+ # Error Class
132
+ # @see [StandardError](https://ruby-doc.org/core-2.3.2/StandardError.html)
133
+ class Error < StandardError; end
134
+ end
File without changes
data/lib/esi/calls.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Esi
2
4
  class Calls
3
5
  class << self
@@ -29,13 +31,18 @@ module Esi
29
31
  end
30
32
 
31
33
  class Base
34
+ CACHE_NAMESPACE = 'esi'
35
+
32
36
  class_attribute :scope
33
37
  class_attribute :cache_duration
34
38
 
35
39
  attr_accessor :path, :params
36
40
 
37
41
  def cache_key
38
- @cache_key ||= ActiveSupport::Cache.expand_cache_key([path, params].flatten, :esi)
42
+ @cache_key ||= begin
43
+ cache_args = [CACHE_NAMESPACE, path.gsub(%r{^\/}, ''), params.sort].flatten
44
+ ActiveSupport::Cache.expand_cache_key(cache_args)
45
+ end
39
46
  end
40
47
 
41
48
  def method
@@ -52,13 +59,13 @@ module Esi
52
59
  end
53
60
 
54
61
  def paginated?
55
- !!@paginated
62
+ !@paginated
56
63
  end
57
64
  end
58
65
 
59
66
  class Regions < Base
60
67
  def initialize
61
- @path = "/universe/regions/"
68
+ @path = '/universe/regions/'
62
69
  end
63
70
  end
64
71
 
@@ -70,7 +77,7 @@ module Esi
70
77
 
71
78
  class Constellations < Base
72
79
  def initialize
73
- @path = "/universe/constellations/"
80
+ @path = '/universe/constellations/'
74
81
  end
75
82
  end
76
83
 
@@ -88,7 +95,7 @@ module Esi
88
95
 
89
96
  class SolarSystems < Base
90
97
  def initialize
91
- @path = "/universe/systems/"
98
+ @path = '/universe/systems/'
92
99
  end
93
100
  end
94
101
 
@@ -124,7 +131,7 @@ module Esi
124
131
 
125
132
  class Structures < Base
126
133
  def initialize
127
- @path = "/universe/structures/"
134
+ @path = '/universe/structures/'
128
135
  end
129
136
  end
130
137
 
@@ -136,7 +143,7 @@ module Esi
136
143
 
137
144
  class Types < Base
138
145
  def initialize
139
- @path = "/universe/types"
146
+ @path = '/universe/types'
140
147
  @paginated = true
141
148
  end
142
149
  end
@@ -149,7 +156,7 @@ module Esi
149
156
 
150
157
  class DogmaAttributes < Base
151
158
  def initialize
152
- @path = "/dogma/attributes/"
159
+ @path = '/dogma/attributes/'
153
160
  end
154
161
  end
155
162
 
@@ -161,7 +168,7 @@ module Esi
161
168
 
162
169
  class DogmaEffects < Base
163
170
  def initialize
164
- @path = "/dogma/effects/"
171
+ @path = '/dogma/effects/'
165
172
  end
166
173
  end
167
174
 
@@ -176,7 +183,7 @@ module Esi
176
183
  self.cache_duration = 3600
177
184
 
178
185
  def initialize
179
- @path = "/industry/facilities"
186
+ @path = '/industry/facilities'
180
187
  end
181
188
  end
182
189
 
@@ -185,7 +192,7 @@ module Esi
185
192
  self.cache_duration = 3600
186
193
 
187
194
  def initialize
188
- @path = "/industry/systems"
195
+ @path = '/industry/systems'
189
196
  end
190
197
  end
191
198
 
@@ -194,7 +201,7 @@ module Esi
194
201
  # https://esi.tech.ccp.is/latest/characters/907452336/search/?categories=structure&datasource=tranquility&search=Kamela&strict=false&token=Fp3ThF7wjvYBIDIIrtWE_Ryjt9BhYwUP75y2EL5Eq9mHPm8tYt9I9NwgZz8o26FFQBKoUToh2DYVc-Q5Ws400g2
195
202
 
196
203
  def initialize(character_id: nil, categories:, search:, strict: false)
197
- @path = (character_id ? "/characters/#{character_id}" : '') + "/search"
204
+ @path = (character_id ? "/characters/#{character_id}" : '') + '/search'
198
205
  @params = { categories: categories, search: search, strict: strict }
199
206
  end
200
207
  end
@@ -210,7 +217,7 @@ module Esi
210
217
  self.cache_duration = 3600
211
218
 
212
219
  def initialize(character_ids)
213
- @path = "/characters/names"
220
+ @path = '/characters/names'
214
221
  @params = { character_ids: character_ids.join(',') }
215
222
  end
216
223
  end
@@ -412,7 +419,7 @@ module Esi
412
419
  self.cache_duration = 3600
413
420
 
414
421
  def initialize
415
- @path = "/alliances"
422
+ @path = '/alliances'
416
423
  end
417
424
  end
418
425
 
@@ -420,7 +427,7 @@ module Esi
420
427
  self.cache_duration = 3600
421
428
 
422
429
  def initialize(alliance_ids)
423
- @path = "/alliances/names"
430
+ @path = '/alliances/names'
424
431
  @params = { alliance_ids: alliance_ids.join(',') }
425
432
  end
426
433
  end
@@ -437,7 +444,7 @@ module Esi
437
444
  self.cache_duration = 3600
438
445
 
439
446
  def initialize(corporation_ids)
440
- @path = "/corporations/names"
447
+ @path = '/corporations/names'
441
448
  @params = { corporation_ids: corporation_ids.join(',') }
442
449
  end
443
450
  end
@@ -526,7 +533,7 @@ module Esi
526
533
 
527
534
  class MarketGroups < Base
528
535
  def initialize
529
- @path = "/markets/groups"
536
+ @path = '/markets/groups'
530
537
  @paginated = true
531
538
  end
532
539
  end
@@ -539,7 +546,7 @@ module Esi
539
546
 
540
547
  class MarketPrices < Base
541
548
  def initialize
542
- @path = "/markets/prices"
549
+ @path = '/markets/prices'
543
550
  end
544
551
  end
545
552
 
@@ -605,7 +612,7 @@ module Esi
605
612
  self.scope = 'esi-ui.open_window.v1'
606
613
 
607
614
  def initialize(type_id)
608
- @path = "/ui/openwindow/marketdetails"
615
+ @path = '/ui/openwindow/marketdetails'
609
616
  @method = :post
610
617
  @params = { type_id: type_id }
611
618
  end
data/lib/esi/client.rb CHANGED
@@ -1,17 +1,90 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Esi
4
+ # The Esi Client class
5
+ # @!attribute [rw] refresh_callback
6
+ # @return [#callback] the refresh_token callback method
7
+ # @!attribute [rw] access_token
8
+ # @return [String] the esi access_token
9
+ # @!attribute [rw] refresh_token
10
+ # @return [String] the esi refresh_token string
11
+ # @!attribute [rw] expires_at
12
+ # @return [Time] the timestamp of the esi token expire
13
+ # @!attribute [r] logger
14
+ # @return [Logger] the logger class for the gem
15
+ # @!attribute [r] oauth
16
+ # @return [Esi::Oauth] the oauth instance for the client
2
17
  class Client
18
+ # @return [Fixnum] The max amount of request attempst Client will make
3
19
  MAX_ATTEMPTS = 2
4
20
 
5
21
  attr_accessor :refresh_callback, :access_token, :refresh_token, :expires_at
6
22
  attr_reader :logger, :oauth
7
23
 
24
+ # Create a new instance of Client
25
+ # @param [String] token the esi access_token
26
+ # @param [String] refresh_token the esi refresh_token
27
+ # @param [Time] expires_at the time stamp the esi token expires_at
8
28
  def initialize(token: nil, refresh_token: nil, expires_at: nil)
9
29
  @logger = Esi.logger
10
30
  @access_token = token
11
31
  @refresh_token = refresh_token
12
32
  @expires_at = expires_at
33
+ @oauth = init_oauth
34
+ end
35
+
36
+ # Set the current thread's Esi::Client
37
+ # @params [Esi::Client] the client to set
38
+ # @return [Esi::Client] the current thread's Esi::Client
39
+ def self.current=(client)
40
+ Thread.current[:esi_client] = client
41
+ end
42
+
43
+ # Get the current thread's Esi::Client
44
+ # @return [Esi::Client] the current thread's Esi::Client
45
+ def self.current
46
+ Thread.current[:esi_client] ||= new
47
+ end
48
+
49
+ # Switch to default Esi::Client (Esi::Client.new)
50
+ # @return [Esi::Client] the current thread's Esi::Client
51
+ def self.switch_to_default
52
+ self.current = new
53
+ end
54
+
55
+ # Switch current thread's client to instance of Esi::Client
56
+ # @return [self] the instance calling switch to
57
+ def switch_to
58
+ Esi::Client.current = self
59
+ end
60
+
61
+ # Yield block with instance of Esi::Client and revert to
62
+ # previous client or default client
63
+ #
64
+ # @example Call an Esi::Client method using an instance of client
65
+ # new_client = Esi::Client.new(token: 'foo', refresh_token: 'foo', expires_at: 30.minutes.from_now)
66
+ # new_client.with_client do |client|
67
+ # client.character(1234)
68
+ # end
69
+ # #=> Esi::Response<#>
70
+ #
71
+ # @yieldreturn [#block] the passed block.
72
+ def with_client
73
+ initial_client = Esi::Client.current
74
+ switch_to
75
+ yield(self) if block_given?
76
+ ensure
77
+ initial_client.switch_to if initial_client
78
+ Esi::Client.switch_to_default unless initial_client
13
79
  end
14
80
 
81
+ # Intercept Esi::Client method_missing and attempt to call an Esi::Request
82
+ # with an Esi::Calls
83
+ # @param [Symbol|String] name the name of the method called
84
+ # @param [Array] *args the arguments to call the method with
85
+ # @param [#block] &block the block to pass to the underlying method
86
+ # @raise [NameError] If the Esi::Calls does not exist
87
+ # @return [Esi::Response] the response given for the call
15
88
  def method_missing(name, *args, &block)
16
89
  klass = nil
17
90
  ActiveSupport::Notifications.instrument('esi.client.detect_call') do
@@ -25,6 +98,9 @@ module Esi
25
98
  cached_response(klass, *args, &block)
26
99
  end
27
100
 
101
+ # Test if the Esi::Client has a method
102
+ # @param [Symbol] name the name of the method to test
103
+ # @return [Boolean] wether or not the method exists
28
104
  def method?(name)
29
105
  begin
30
106
  klass = Esi::Calls.const_get(method_to_class_name(name))
@@ -34,15 +110,24 @@ module Esi
34
110
  !klass.nil?
35
111
  end
36
112
 
113
+ # Test if the Esi::Client has a pluralized version of a method
114
+ # @param [Symbol] name the name of the method to test
115
+ # @return [Boolean] wether or not the pluralized method exists
37
116
  def plural_method?(name)
38
117
  plural = name.to_s.pluralize.to_sym
39
118
  method? plural
40
119
  end
41
120
 
121
+ # Log a message
122
+ # @param [String] message the message to log
123
+ # @return [void] the Logger.info method with message
42
124
  def log(message)
43
125
  logger.info message
44
126
  end
45
127
 
128
+ # Log a message with debug
129
+ # @param [String] message the message to log
130
+ # @return [void] the Logger.debug method with message
46
131
  def debug(message)
47
132
  logger.debug message
48
133
  end
@@ -64,46 +149,45 @@ module Esi
64
149
  name.dup.to_s.split('_').map(&:capitalize).join
65
150
  end
66
151
 
67
- def oauth
152
+ def init_oauth
68
153
  @oauth ||= OAuth.new(
69
154
  access_token: @access_token,
70
155
  refresh_token: @refresh_token,
71
156
  expires_at: @expires_at,
72
- callback: -> (token, expires_at) {
157
+ callback: lambda { |token, expires_at|
73
158
  @access_token = token
74
159
  @expires_at = expires_at
75
- if refresh_callback.respond_to?(:call)
76
- refresh_callback.call(token, expires_at)
77
- end
160
+ refresh_callback.call(token, expires_at) if refresh_callback.respond_to?(:call)
78
161
  }
79
162
  )
80
163
  end
81
164
 
82
165
  def request_paginated(call, &block)
166
+ call.page = 1
83
167
  response = nil
84
- page = 1
85
-
86
168
  ActiveSupport::Notifications.instrument('esi.client.request.paginated') do
87
- loop do
88
- call.page = page
89
- page_response = request(call, &block)
90
-
91
- if page_response.data.blank?
92
- break
93
- elsif response
94
- response.merge(page_response)
95
- else
96
- response = page_response
97
- end
98
-
99
- page += 1
100
- end
169
+ response = paginated_response(response, call, &block)
101
170
  end
102
-
103
171
  response
104
172
  end
105
173
 
174
+ def paginated_response(response, call, &block)
175
+ loop do
176
+ page_response = request(call, &block)
177
+ break response if page_response.data.blank?
178
+ response = response ? response.merge(page_response) : page_response
179
+ call.page += 1
180
+ end
181
+ end
182
+
106
183
  # FIXME: esi should not retry
184
+ # FIXME: make rubocop compliant
185
+ # rubocop:disable Lint/ShadowedException
186
+ # rubocop:disable Metrics/AbcSize
187
+ # rubocop:disable Metrics/BlockLength
188
+ # rubocop:disable Metrics/CyclomaticComplexity
189
+ # rubocop:disable Metrics/MethodLength
190
+ # rubocop:disable Metrics/PerceivedComplexity
107
191
  def request(call, &block)
108
192
  response = nil
109
193
  last_ex = nil
@@ -113,12 +197,12 @@ module Esi
113
197
  debug "Starting request: #{url}"
114
198
 
115
199
  ActiveSupport::Notifications.instrument('esi.client.request') do
116
- 1.upto(MAX_ATTEMPTS) do |try|
200
+ 1.upto(MAX_ATTEMPTS) do |_try|
117
201
  last_ex = nil
118
202
  response = nil
119
203
 
120
204
  begin
121
- response = Timeout::timeout(Esi.config.timeout) do
205
+ response = Timeout.timeout(Esi.config.timeout) do
122
206
  oauth.request(call.method, url, options)
123
207
  end
124
208
  rescue Faraday::SSLError, Faraday::ConnectionFailed, Timeout::Error, Net::ReadTimeout => e
@@ -136,7 +220,7 @@ module Esi
136
220
  sleep 5
137
221
  next
138
222
  when 503 # Rate Limit
139
- logger.error "RateLimit error, sleeping for 5 seconds"
223
+ logger.error 'RateLimit error, sleeping for 5 seconds'
140
224
  sleep 5
141
225
  next
142
226
  when 404 # Not Found
@@ -166,34 +250,44 @@ module Esi
166
250
  break if response
167
251
  end
168
252
  end
253
+ # rubocop:enable Lint/ShadowedException
254
+ # rubocop:enable Metrics/AbcSize
255
+ # rubocop:enable Metrics/BlockLength
256
+ # rubocop:enable Metrics/CyclomaticComplexity
257
+ # rubocop:enable Metrics/MethodLength
258
+ # rubocop:enable Metrics/PerceivedComplexity
169
259
 
170
260
  if last_ex
171
261
  logger.error "Request failed with #{last_ex.class}"
172
262
  debug_error(last_ex.class, url, response)
173
- raise Esi::ApiRequestError.new(last_ex)
263
+ raise Esi::ApiRequestError, last_ex
174
264
  end
175
265
 
176
- debug "Request successful"
266
+ debug 'Request successful'
177
267
 
178
268
  ActiveSupport::Notifications.instrument('esi.client.response.render') do
179
269
  response = Response.new(response, call)
180
270
  response.save
181
271
  end
182
272
  ActiveSupport::Notifications.instrument('esi.client.response.callback') do
183
- response.data.each { |item| block.call(item) } if block
273
+ response.data.each { |item| yield(item) } if block
184
274
  end
185
275
  response
186
276
  end
187
277
 
188
278
  def debug_error(klass, url, response)
189
279
  [
190
- '-'*60,
280
+ '-' * 60,
191
281
  "#{klass}(#{response.error})",
192
282
  "STATUS: #{response.status}",
193
- "MESSAGE: #{response.respond_to?(:data) ? (response.data[:message].presence || response.data[:error]) : response.try(:body)}",
283
+ "MESSAGE: #{debug_message_for_response(response)}",
194
284
  "URL: #{url}",
195
- '-'*60,
285
+ '-' * 60
196
286
  ].each { |msg| logger.error(msg) }
197
287
  end
288
+
289
+ def debug_message_for_response(response)
290
+ response.respond_to?(:data) ? (response.data[:message].presence || response.data[:error]) : response.try(:body)
291
+ end
198
292
  end
199
293
  end
data/lib/esi/o_auth.rb CHANGED
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Esi
2
4
  class OAuth
3
5
  extend Forwardable
4
6
 
5
7
  attr_reader :access_token, :refresh_token, :expires_at
6
- def_delegators :token, :get, :post, :delete, :patch, :put
8
+ def_delegators :token, :request, :get, :post, :delete, :patch, :put
7
9
 
8
10
  class << self
9
11
  def authorize_url(redirect_uri:, scopes: nil)
@@ -20,15 +22,13 @@ module Esi
20
22
  def client
21
23
  @client ||= OAuth2::Client.new(
22
24
  Esi.config.client_id, Esi.config.client_secret,
23
- { site: Esi.config.oauth_host }
25
+ site: Esi.config.oauth_host
24
26
  )
25
27
  end
26
28
  end
27
29
 
28
30
  def initialize(access_token:, refresh_token:, expires_at:, callback: nil)
29
- if callback && !callback.respond_to?(:call)
30
- raise Esi::Error.new("Callback should be a callable Proc")
31
- end
31
+ raise Esi::Error, 'Callback should be a callable Proc' if callback && !callback.respond_to?(:call)
32
32
 
33
33
  @access_token = access_token
34
34
  @refresh_token = refresh_token
@@ -36,28 +36,21 @@ module Esi
36
36
  @callback = callback if callback
37
37
  end
38
38
 
39
- def request(*args)
40
- token.request(*args)
41
- end
42
-
43
39
  private
44
40
 
45
41
  def token
46
- @token = Esi::AccessToken.new(OAuth.client, @access_token, {
47
- refresh_token: @refresh_token, expires_at: @expires_at
48
- })
42
+ @token = Esi::AccessToken.new(OAuth.client, @access_token,
43
+ refresh_token: @refresh_token, expires_at: @expires_at)
49
44
  refresh_access_token if @token.expired?
50
45
  @token
51
46
  end
52
47
 
53
48
  def refresh_access_token
54
- Esi.logger.debug "Refreshing access token"
55
-
56
49
  ActiveSupport::Notifications.instrument('esi.oauth.refresh_token') do
57
50
  @token = @token.refresh!
58
51
  @access_token = @token.token
59
52
  @expires_at = @token.expires_at.integer? ? Time.at(@token.expires_at) : @token.expires_at
60
- @callback.call(@access_token, @expires_at) if @callback
53
+ @callback&.call(@access_token, @expires_at)
61
54
  end
62
55
  end
63
56
  end
data/lib/esi/response.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Esi
2
4
  class Response
3
5
  extend Forwardable
@@ -6,10 +8,10 @@ module Esi
6
8
  def_delegators :original_response, :status, :body, :headers
7
9
  def_delegators :data, :each
8
10
 
9
- def initialize(response, call=nil)
11
+ def initialize(response, call = nil)
10
12
  @original_response = response
13
+ @data = normalize_response_body
11
14
  @call = call
12
- @data = MultiJson.load(body || {}, symbolize_keys: true, object_class: OpenStruct) # rescue OpenStruct.new
13
15
  end
14
16
 
15
17
  def merge(other_response)
@@ -29,18 +31,40 @@ module Esi
29
31
  @cached_until ||= headers[:expires] ? Time.parse(headers[:expires]) : nil
30
32
  end
31
33
 
34
+ def response_json
35
+ @response_json ||= begin
36
+ MultiJson.load(body, symbolize_keys: true)
37
+ rescue StandardError
38
+ {}
39
+ end
40
+ end
41
+
32
42
  def save
33
- return if call.nil?
34
- return if Esi.config.response_log_path.blank? || !Dir.exists?(Esi.config.response_log_path)
43
+ return unless should_log_response?
44
+ File.write(log_directroy.join("#{Time.now.to_i}.json"), to_json)
45
+ end
35
46
 
36
- call_name = call.class.to_s.split('::').last.underscore
37
- folder = Pathname.new(Esi.config.response_log_path).join(call_name)
38
- FileUtils.mkdir_p(folder)
39
- File.write(folder.join("#{Time.now.to_i.to_s}.json"), to_json)
47
+ def log_directory
48
+ call.class.to_s.split('::').last.underscore
49
+ dir = Pathname.new(Esi.config.response_log_path).join(call_name)
50
+ FileUtils.mkdir_p(dir)
51
+ dir
52
+ end
53
+
54
+ def should_log_response?
55
+ return false if call.nil?
56
+ return false if Esi.config.response_log_path.blank? || !Dir.exist?(Esi.config.response_log_path)
57
+ true
40
58
  end
41
59
 
42
60
  def method_missing(method, *args, &block)
43
61
  data.send(method, *args, &block)
44
62
  end
63
+
64
+ private
65
+
66
+ def normalize_response_body
67
+ MultiJson.load(body || {}, symbolize_keys: true, object_class: OpenStruct)
68
+ end
45
69
  end
46
70
  end
data/lib/esi/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Esi
2
- VERSION = "0.4.5"
4
+ VERSION = '0.4.6'
3
5
  end
data/lib/esi.rb CHANGED
@@ -1,12 +1,19 @@
1
- require "oauth2"
2
- require "forwardable"
3
- require "ostruct"
4
- require "addressable/uri"
5
- require "active_support/cache"
6
- require "active_support/notifications"
1
+ # frozen_string_literal: true
2
+
3
+ require 'oauth2'
4
+ require 'forwardable'
5
+ require 'ostruct'
6
+ require 'addressable/uri'
7
+ require 'active_support/cache'
8
+ require 'active_support/notifications'
7
9
  require 'active_support/core_ext/string'
8
- require "active_support/core_ext/class/attribute"
10
+ require 'active_support/core_ext/class/attribute'
9
11
 
12
+ # The main Esi Module
13
+ # @!attribute [w] api_version
14
+ # @return [Symbol] the Esi Api version used by the gem
15
+ # @!attribute [w] logger
16
+ # @return [Logger] the logger class for the gem
10
17
  module Esi
11
18
  autoload :VERSION, 'esi/version'
12
19
  autoload :AccessToken, 'esi/access_token'
@@ -15,6 +22,10 @@ module Esi
15
22
  autoload :Client, 'esi/client'
16
23
  autoload :Response, 'esi/response'
17
24
 
25
+ require_relative 'esi/api_error'
26
+
27
+ # Default ESI access scopes
28
+ # @return [Array<String>] the default scopes
18
29
  SCOPES = %w(
19
30
  esi-assets.read_assets.v1
20
31
  esi-bookmarks.read_character_bookmarks.v1
@@ -61,7 +72,10 @@ module Esi
61
72
  esi-universe.read_structures.v1
62
73
  esi-wallet.read_character_wallet.v1
63
74
  esi-wallet.read_corporation_wallets.v1
64
- )
75
+ ).freeze
76
+
77
+ # The default Esi gem configuration
78
+ # @return [Hash{Symbol => Symbol|String|Fixnum|Object|Array}] the default configuration
65
79
  DEFAULT_CONFIG = {
66
80
  datasource: :tranquility,
67
81
  oauth_host: 'https://login.eveonline.com',
@@ -75,21 +89,27 @@ module Esi
75
89
  client_secret: nil,
76
90
  cache: ActiveSupport::Cache::MemoryStore.new,
77
91
  scopes: SCOPES
78
- }
92
+ }.freeze
79
93
 
80
94
  class << self
81
95
  attr_writer :api_version, :config, :logger, :cache
82
96
 
97
+ # The Esi Configuration
98
+ # @return [OpenStruct] the configuration object
83
99
  def config
84
100
  @config ||= OpenStruct.new(DEFAULT_CONFIG)
85
101
  end
86
102
 
103
+ # The Esi logger class instance
104
+ # @return [MyLoggerInstance|Logger] an instance of the logger class
87
105
  def logger
88
106
  @logger ||= Esi.config.logger || Logger.new(Esi.config.log_target).tap do |l|
89
107
  l.level = Logger.const_get(Esi.config.log_level.upcase)
90
108
  end
91
109
  end
92
110
 
111
+ # The Esi cache class instance
112
+ # @return [ActiveSupport::Cache::Store] an instance of cache
93
113
  def cache
94
114
  if Esi.config.cache.nil?
95
115
  @cache ||= ActiveSupport::Cache::NullStore.new
@@ -98,54 +118,35 @@ module Esi
98
118
  end
99
119
  end
100
120
 
121
+ # The Esi Api version to interface with
122
+ # @return [Symbol] the esi api version
101
123
  def api_version
102
124
  @api_version || :latest
103
125
  end
104
126
 
105
- def generate_url(path, params={})
106
- path = path[1..-1] if path.start_with?('/')
107
- path += "/" unless path.end_with?('/')
108
-
109
- url = [config.api_host, config.api_version, path].join('/')
127
+ # Generate an Esi url for a given path
128
+ # @param [String] path the path to generate an esi url for
129
+ # @param [Hash{Symbol => String|Fixnum}] params the params for the url query
130
+ # @return [String] the generated url
131
+ def generate_url(path, params = {})
132
+ url = url_for_path(path)
110
133
  uri = Addressable::URI.parse(url)
111
- uri.query_values = {datasource: config.datasource}.merge(params.to_h)
134
+ uri.query_values = { datasource: config.datasource }.merge(params.to_h)
112
135
  uri.to_s
113
136
  end
114
137
 
138
+ # The current Esi client
139
+ # @return [Esi::Client] the current client
115
140
  def client
116
- @client ||= Client.new
117
- end
118
- end
119
-
120
- class ApiError < OAuth2::Error
121
- attr_reader :response, :key, :message, :type, :original_exception
122
-
123
- def initialize(response, original_exception=nil)
124
- super(response.original_response)
125
-
126
- @response = response
127
- @original_exception = original_exception
128
- @code = response.original_response.status
129
- @key = response.data[:key]
130
- @message = response.data[:message].presence || response.data[:error] || original_exception.try(:message)
131
- @type = response.data[:exceptionType]
141
+ @client ||= Client.current
132
142
  end
133
- end
134
143
 
135
- class ApiRequestError < StandardError
136
- attr_reader :original_exception
144
+ private
137
145
 
138
- def initialize(original_exception)
139
- @original_exception = original_exception
140
- super("#{original_exception.class}: #{original_exception.try(:response).try(:status)} - #{original_exception.try(:message)}")
146
+ def url_for_path(path)
147
+ path = path[1..-1] if path.start_with?('/')
148
+ path += '/' unless path.end_with?('/')
149
+ [config.api_host, config.api_version, path].join('/')
141
150
  end
142
151
  end
143
-
144
- class ApiUnknownError < ApiError; end
145
- class ApiBadRequestError < ApiError; end
146
- class ApiRefreshTokenExpiredError < ApiError; end
147
- class ApiInvalidAppClientKeysError < ApiError; end
148
- class ApiNotFoundError < ApiError; end
149
- class ApiForbiddenError < ApiError; end
150
- class Error < StandardError; end
151
152
  end
data/lib/omniauth/esi.rb CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'omniauth/strategies/esi'
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'omniauth/strategies/oauth2'
2
4
 
3
5
  module OmniAuth
4
6
  module Strategies
5
7
  class Esi < OmniAuth::Strategies::OAuth2
6
8
  option :name, 'esi'
7
- option :client_options, { site: ::Esi.config.oauth_host, verify_url: '/oauth/verify' }
8
- option :authorize_options, [:scope, :callback_url]
9
+ option :client_options, site: ::Esi.config.oauth_host, verify_url: '/oauth/verify'
10
+ option :authorize_options, %i(scope callback_url)
9
11
 
10
12
  uid { extra_info[:character_id] }
11
13
 
@@ -19,11 +21,11 @@ module OmniAuth
19
21
  end
20
22
 
21
23
  credentials do
22
- hash = {token: access_token.token}
23
- hash.merge!(refresh_token: access_token.refresh_token) if access_token.refresh_token
24
- hash.merge!(expires_at: access_token.expires_at) if access_token.expires?
25
- hash.merge!(expires: access_token.expires?)
26
- hash.merge!(scopes: extra_info[:scopes].split(' ')) if extra_info[:scopes]
24
+ hash = { token: access_token.token }
25
+ hash[:refresh_token] = access_token.refresh_token if access_token.refresh_token
26
+ hash[:expires_at] = access_token.expires_at if access_token.expires?
27
+ hash[:expires] = access_token.expires?
28
+ hash[:scopes] = extra_info[:scopes].split(' ') if extra_info[:scopes]
27
29
  hash
28
30
  end
29
31
 
@@ -32,7 +34,9 @@ module OmniAuth
32
34
  end
33
35
 
34
36
  def extra_info
35
- @extra_info ||= deep_symbolize(access_token.get(options.client_options.verify_url).parsed.transform_keys!(&:underscore))
37
+ @extra_info ||= deep_symbolize(
38
+ access_token.get(options.client_options.verify_url).parsed.transform_keys!(&:underscore)
39
+ )
36
40
  end
37
41
 
38
42
  def authorize_params
metadata CHANGED
@@ -1,29 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: esi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.5
4
+ version: 0.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danny Hiemstra
8
+ - Aaron Allen
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2018-02-14 00:00:00.000000000 Z
12
+ date: 2018-02-22 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
- name: oauth2
15
+ name: activesupport
15
16
  requirement: !ruby/object:Gem::Requirement
16
17
  requirements:
17
- - - "~>"
18
+ - - ">="
18
19
  - !ruby/object:Gem::Version
19
- version: '1.4'
20
+ version: '0'
20
21
  type: :runtime
21
22
  prerelease: false
22
23
  version_requirements: !ruby/object:Gem::Requirement
23
24
  requirements:
24
- - - "~>"
25
+ - - ">="
25
26
  - !ruby/object:Gem::Version
26
- version: '1.4'
27
+ version: '0'
27
28
  - !ruby/object:Gem::Dependency
28
29
  name: addressable
29
30
  requirement: !ruby/object:Gem::Requirement
@@ -39,19 +40,19 @@ dependencies:
39
40
  - !ruby/object:Gem::Version
40
41
  version: '2.3'
41
42
  - !ruby/object:Gem::Dependency
42
- name: activesupport
43
+ name: oauth2
43
44
  requirement: !ruby/object:Gem::Requirement
44
45
  requirements:
45
- - - ">="
46
+ - - "~>"
46
47
  - !ruby/object:Gem::Version
47
- version: '0'
48
+ version: '1.4'
48
49
  type: :runtime
49
50
  prerelease: false
50
51
  version_requirements: !ruby/object:Gem::Requirement
51
52
  requirements:
52
- - - ">="
53
+ - - "~>"
53
54
  - !ruby/object:Gem::Version
54
- version: '0'
55
+ version: '1.4'
55
56
  - !ruby/object:Gem::Dependency
56
57
  name: bundler
57
58
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +67,20 @@ dependencies:
66
67
  - - "~>"
67
68
  - !ruby/object:Gem::Version
68
69
  version: '1.14'
70
+ - !ruby/object:Gem::Dependency
71
+ name: minitest
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '5.0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '5.0'
69
84
  - !ruby/object:Gem::Dependency
70
85
  name: rake
71
86
  requirement: !ruby/object:Gem::Requirement
@@ -81,22 +96,51 @@ dependencies:
81
96
  - !ruby/object:Gem::Version
82
97
  version: '10.0'
83
98
  - !ruby/object:Gem::Dependency
84
- name: minitest
99
+ name: rubocop
85
100
  requirement: !ruby/object:Gem::Requirement
86
101
  requirements:
87
102
  - - "~>"
88
103
  - !ruby/object:Gem::Version
89
- version: '5.0'
104
+ version: '0.52'
90
105
  type: :development
91
106
  prerelease: false
92
107
  version_requirements: !ruby/object:Gem::Requirement
93
108
  requirements:
94
109
  - - "~>"
95
110
  - !ruby/object:Gem::Version
96
- version: '5.0'
111
+ version: '0.52'
112
+ - !ruby/object:Gem::Dependency
113
+ name: shoulda
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '3.5'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '3.5'
126
+ - !ruby/object:Gem::Dependency
127
+ name: yard
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: '0.9'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: '0.9'
97
140
  description: EVE ESI API wrapper
98
141
  email:
99
142
  - dannyhiemstra@gmail.com
143
+ - aaronmallen4@gmail.com
100
144
  executables: []
101
145
  extensions: []
102
146
  extra_rdoc_files: []
@@ -106,7 +150,9 @@ files:
106
150
  - esi.gemspec
107
151
  - lib/esi.rb
108
152
  - lib/esi/access_token.rb
153
+ - lib/esi/api_error.rb
109
154
  - lib/esi/calls.rb
155
+ - lib/esi/calls/info.rb
110
156
  - lib/esi/client.rb
111
157
  - lib/esi/o_auth.rb
112
158
  - lib/esi/response.rb