ncore 2.2.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4041e91acca8393f599c0efd5688d5ec04446447cf34487b813e2193ecf15677
4
- data.tar.gz: 6308d1af6e0434158667015ee617f5f5f770f43814cf8b71de55c0956ba57db2
3
+ metadata.gz: 79a7f509e1857cf0a4acd6392c2226e9be22b1b55753b63780b58d28651dade5
4
+ data.tar.gz: 85a06a908f8926eac01bebd0d672e57f43ca57ecd05723f3e565d0181998e2d1
5
5
  SHA512:
6
- metadata.gz: b7fc8be7b485530cf6160de45d5d129afa8413b0e15d82a32cfd20748aa932062ce3f6f3f16cddac658db0289f5629167c715cee8c17b0b39249546b1ad5b252
7
- data.tar.gz: 8c441f05e37c0c09d1b82b9da092f0a2df9e48a158bffc516c5a999dcc1497d812b70d23fa4c78dc0055ab4a1e9a8b8b97a22e5feff2aa046ecbe72dc0c0136f
6
+ metadata.gz: 0bf6ddb6c01d38f030566468df96d9027ba57ae3265ffa7974e89cf5645230ccd4691892fbf77fddaded5bf61d005c2a601af7ad4fec83ce6acc8d471875e9cf
7
+ data.tar.gz: e70acae56b27d08249ad2b923d52a0e695323f28a83cf02b5adff2a54d1ee316b2c0de48d9afbbc8dca15f2495e19206ba7b4a910278bd7cec3ca15152d84b89
@@ -1,3 +1,59 @@
1
+ #### 3.2.0
2
+
3
+ - Use system's CA certificate store by default
4
+ To use bundled CAs instead:
5
+ SomeAppName::Api.ssl_cert_bundle = :bundled
6
+ On failure reading specified bundle, raises exception instead of warning
7
+
8
+ #### 3.1.0
9
+
10
+ - Add .bulk_update and bulk_update!
11
+
12
+ #### 3.0.0
13
+
14
+ BREAKING CHANGES
15
+ - Update has_many, belongs_to signatures
16
+ - Rename Base#url -> #resource_path
17
+ - Drop ActiveModel <= 4.1
18
+ - `#errors` is now always an ActiveModel::Errors instance
19
+
20
+ DEPRECATION NOTICE
21
+ - ValidationError is deprecated and will be removed in 3.1.
22
+
23
+ Other changes
24
+ - Add :cache option for requests
25
+ Set default store at MyApi::Api.cache_store=
26
+ See example railtie.rb for auto-config
27
+ Examples:
28
+ SomeResource.all(cache: true)
29
+ uses MyApi::Api.cache_store
30
+ SomeResource.find(id, cache: {expires_in: 5.minutes})
31
+ uses MyApi::Api.cache_store with specified options
32
+ SomeResource.find(id, cache: Dalli::Store.new(...))
33
+ uses provided cache store (with its default options)
34
+ - Make bearer_credential_key allow strings or symbols
35
+ - Warn on attr name collision
36
+ - Update CA certificates
37
+ - Better default output for #as_json
38
+ - Allow ActiveModel/Support 6.0
39
+ - Resolve deprecation messages on Ruby 2.6
40
+ - Add #factory
41
+ - API response :errors may be hash or array
42
+ - Add RecordInvalid#errors
43
+ - Better Ruby and ActiveModel integration
44
+ - #eql?, #==, #hash
45
+ - #model_name
46
+ - #i18n_scope, config via Api.i18n_scope=
47
+ - #cache_key, #cache_version, #cache_key_with_version
48
+
49
+ #### 2.2.2
50
+
51
+ - Update certs
52
+
53
+ #### 2.2.1
54
+
55
+ - Fix decimal attributes on Ruby <= 2.5.x
56
+
1
57
  #### 2.2.0
2
58
 
3
59
  - Allow ActiveSupport 6.0
@@ -83,4 +139,4 @@
83
139
 
84
140
  #### 1.0.0
85
141
 
86
- - Initial release
142
+ - Initial release
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014-2018 Notioneer, Inc.
1
+ Copyright (c) 2014-2020 Notioneer, Inc.
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  NCore is a Ruby gem designed to help build REST API clients. It is not an API
4
4
  client by itself, but provides several useful building blocks to build one.
5
5
 
6
- It relies on `excon` for HTTP handling and `activesupport`.
6
+ It relies on `excon` for HTTP handling and `activemodel`.
7
7
 
8
8
  If present, uses `multi_json`. Otherwise, the stdlib 'json' is used.
9
9
  'multi_json' with an accelerated json gem is recommended.
@@ -21,6 +21,8 @@ module MyApi
21
21
 
22
22
  self.strict_attributes = false
23
23
 
24
+ self.i18n_scope = :my_api
25
+
24
26
  self.instrument_key = 'request.my_api'
25
27
 
26
28
  self.status_page = 'http://my.api.status.page'
@@ -30,6 +32,14 @@ module MyApi
30
32
  # self.bearer_credential_key = :api_key
31
33
 
32
34
  self.credentials_error_message = %Q{Missing API credentials. Set default credentials using "MyApi.credentials = {api_user: YOUR_API_USER, api_key: YOUR_API_KEY}"}
35
+
36
+ # self.verify_ssl = true
37
+
38
+ # Uses system/OS bundle by default
39
+ # self.ssl_cert_bundle = :bundled # uses excon's included bundle
40
+ # self.ssl_cert_bundle = 'path/to/bundle.crt'
41
+
42
+ # self.logger = Logger.new(STDOUT)
33
43
  end
34
44
 
35
45
  end
@@ -1,6 +1,10 @@
1
1
  module MyApi
2
2
  class Railtie < Rails::Railtie
3
3
 
4
+ config.after_initialize do
5
+ MyApi::Api.cache_store = Rails.cache
6
+ end
7
+
4
8
  initializer "my_api.log_runtime" do |app|
5
9
  require 'my_api/rails/log_subscriber'
6
10
  ActiveSupport.on_load(:action_controller) do
@@ -1,15 +1,43 @@
1
1
  require 'active_support/all'
2
+ require 'active_model'
2
3
  require 'excon'
3
4
  require 'pp'
4
5
 
5
- %w(version builder configuration associations attributes client collection exceptions identity lifecycle util base singleton_base).each do |f|
6
+ %w(
7
+ version
8
+ builder
9
+ configuration
10
+ associations
11
+ attributes
12
+ client
13
+ client_cache
14
+ collection
15
+ exceptions
16
+ identity
17
+ lifecycle
18
+ util
19
+ base
20
+ singleton_base
21
+ ).each do |f|
6
22
  require "ncore/#{f}"
7
23
  end
8
24
 
9
- %w(all build count create delete delete_bulk delete_single find find_single update).each do |f|
25
+ %w(
26
+ all
27
+ build
28
+ count
29
+ create
30
+ delete
31
+ delete_bulk
32
+ delete_single
33
+ find
34
+ find_single
35
+ update
36
+ update_bulk
37
+ ).each do |f|
10
38
  require "ncore/methods/#{f}"
11
39
  end
12
40
 
13
41
  require 'ncore/rails/action_controller' if defined?(::ActionController)
14
- require 'ncore/rails/active_model' if defined?(::ActiveModel)
42
+ require 'ncore/rails/active_model'
15
43
  require 'ncore/rails/module_fix'
@@ -1,17 +1,17 @@
1
1
  module NCore
2
2
  module Associations
3
3
 
4
- def has_many(assoc, klass=nil)
4
+ def has_many(assoc, class_name: nil)
5
5
  assoc = assoc.to_s
6
- klass ||= "#{module_name}::#{assoc.camelize.singularize}"
6
+ klass = class_name || "#{module_name}::#{assoc.camelize.singularize}"
7
7
  key = "#{attrib_name}_id"
8
8
  class_eval <<-M1, __FILE__, __LINE__+1
9
9
  def #{assoc}(params={})
10
10
  return [] unless id
11
11
  reload = params.delete :reload
12
- params = parse_request_params(params).reverse_merge credentials: api_creds
13
12
  cacheable = params.except(:credentials, :request).empty?
14
- params.merge! #{key}: id
13
+ params = parse_request_params(params).reverse_merge credentials: api_creds
14
+ params[:#{key}] = id
15
15
  if cacheable
16
16
  # only cache unfiltered, default api call
17
17
  @attribs[:#{assoc}] = (!reload && @attribs[:#{assoc}]) || #{klass}.all(params)
@@ -24,7 +24,7 @@ module NCore
24
24
  def find_#{assoc.singularize}(aid, params={})
25
25
  raise UnsavedObjectError unless id
26
26
  params = parse_request_params(params).reverse_merge credentials: api_creds
27
- params.merge! #{key}: id
27
+ params[:#{key}] = id
28
28
  #{klass}.find(aid, params)
29
29
  end
30
30
  M2
@@ -33,7 +33,7 @@ module NCore
33
33
  def create_#{assoc.singularize}(params={})
34
34
  raise UnsavedObjectError unless id
35
35
  params = parse_request_params(params).reverse_merge credentials: api_creds
36
- params.merge! #{key}: id
36
+ params[:#{key}] = id
37
37
  #{klass}.create(params)
38
38
  end
39
39
  M3
@@ -42,7 +42,7 @@ module NCore
42
42
  def update_#{assoc.singularize}(aid, params={})
43
43
  raise UnsavedObjectError unless id
44
44
  params = parse_request_params(params).reverse_merge credentials: api_creds
45
- params.merge! #{key}: id
45
+ params[:#{key}] = id
46
46
  #{klass}.update(aid, params)
47
47
  end
48
48
  M4
@@ -50,7 +50,7 @@ module NCore
50
50
  def create_#{assoc.singularize}!(params={})
51
51
  raise UnsavedObjectError unless id
52
52
  params = parse_request_params(params).reverse_merge credentials: api_creds
53
- params.merge! #{key}: id
53
+ params[:#{key}] = id
54
54
  #{klass}.create!(params)
55
55
  end
56
56
  M5
@@ -58,7 +58,7 @@ module NCore
58
58
  def update_#{assoc.singularize}!(aid, params={})
59
59
  raise UnsavedObjectError unless id
60
60
  params = parse_request_params(params).reverse_merge credentials: api_creds
61
- params.merge! #{key}: id
61
+ params[:#{key}] = id
62
62
  #{klass}.update!(aid, params)
63
63
  end
64
64
  M6
@@ -67,7 +67,7 @@ module NCore
67
67
  def delete_#{assoc.singularize}(aid, params={})
68
68
  raise UnsavedObjectError unless id
69
69
  params = parse_request_params(params).reverse_merge credentials: api_creds
70
- params.merge! #{key}: id
70
+ params[:#{key}] = id
71
71
  #{klass}.delete(aid, params)
72
72
  end
73
73
  M7
@@ -75,15 +75,15 @@ module NCore
75
75
  def delete_#{assoc.singularize}!(aid, params={})
76
76
  raise UnsavedObjectError unless id
77
77
  params = parse_request_params(params).reverse_merge credentials: api_creds
78
- params.merge! #{key}: id
78
+ params[:#{key}] = id
79
79
  #{klass}.delete!(aid, params)
80
80
  end
81
81
  M8
82
82
  end
83
83
 
84
- def belongs_to(assoc, klass=nil)
84
+ def belongs_to(assoc, class_name: nil)
85
85
  assoc = assoc.to_s
86
- klass ||= "#{module_name}::#{assoc.camelize}"
86
+ klass = class_name || "#{module_name}::#{assoc.camelize}"
87
87
  class_eval <<-M1, __FILE__, __LINE__+1
88
88
  attr :#{assoc}_id
89
89
  def #{assoc}(params={})
@@ -10,6 +10,7 @@ module NCore
10
10
  module ClassMethods
11
11
  def attr(*attrs)
12
12
  attrs.each do |attr|
13
+ check_existing_method(attr)
13
14
  class_eval <<-AR, __FILE__, __LINE__+1
14
15
  def #{attr}
15
16
  self[:#{attr}]
@@ -20,6 +21,7 @@ module NCore
20
21
 
21
22
  def attr_datetime(*attrs)
22
23
  attrs.each do |attr|
24
+ check_existing_method(attr)
23
25
  class_eval <<-AD, __FILE__, __LINE__+1
24
26
  def #{attr}
25
27
  case self[:#{attr}]
@@ -39,6 +41,7 @@ module NCore
39
41
 
40
42
  def attr_decimal(*attrs)
41
43
  attrs.each do |attr|
44
+ check_existing_method(attr)
42
45
  class_eval <<-AD, __FILE__, __LINE__+1
43
46
  def #{attr}
44
47
  case self[:#{attr}]
@@ -52,10 +55,19 @@ module NCore
52
55
  end
53
56
  end
54
57
 
58
+ def check_existing_method(attr)
59
+ if method_defined?(attr) || private_method_defined?(attr)
60
+ sc = self
61
+ sc = sc.superclass while sc.superclass != Object
62
+ warn "Warning: Existing method #{sc.name}##{attr} being overwritten at #{caller[3]}"
63
+ end
64
+ end
65
+
55
66
  def parse_request_params(params={}, opts={})
56
67
  params = params.with_indifferent_access
57
68
  req = params.delete(:request)
58
69
  creds = params.delete(:credentials)
70
+ cache = params.delete(:cache)
59
71
  if opts[:json_root]
60
72
  if params.key?(opts[:json_root])
61
73
  o = params
@@ -67,6 +79,7 @@ module NCore
67
79
  end
68
80
  o[:request] = req if req
69
81
  o[:credentials] = creds if creds
82
+ o[:cache] = cache if cache
70
83
  o
71
84
  end
72
85
  end
@@ -86,23 +99,18 @@ module NCore
86
99
  creds_attr = attribs.delete(:credentials)
87
100
  @api_creds = api_creds || creds_attr
88
101
 
89
- if attribs.keys.sort == %w(data error metadata)
90
- load_attrs = attribs
91
- else
92
- load_attrs = {
93
- metadata: attribs.delete(:metadata),
94
- errors: attribs.delete(:errors),
95
- data: attribs.delete(:data) || attribs
96
- }
97
- end
98
- load(load_attrs)
102
+ load(
103
+ metadata: attribs.delete(:metadata),
104
+ errors: attribs.delete(:errors),
105
+ data: attribs.delete(:data) || attribs
106
+ )
99
107
  end
100
108
 
101
109
 
102
110
  def attributes
103
111
  Util.deep_clone(@attribs)
104
112
  end
105
-
113
+ alias_method :as_json, :attributes
106
114
 
107
115
  def [](attr)
108
116
  @attribs[attr]
@@ -150,10 +158,10 @@ module NCore
150
158
  end
151
159
 
152
160
 
153
- def load(parsed)
154
- self.metadata = parsed[:metadata] || {}.with_indifferent_access
155
- self.errors = parsed[:errors] || {}.with_indifferent_access
156
- parsed[:data].each do |k,v|
161
+ def load(data:, errors: nil, metadata: nil)
162
+ self.metadata = metadata || {}.with_indifferent_access
163
+ self.errors = parse_errors(errors)
164
+ data.each do |k,v|
157
165
  if respond_to? "#{k}="
158
166
  send "#{k}=", self.class.interpret_type(v, api_creds)
159
167
  else
@@ -163,13 +171,24 @@ module NCore
163
171
  self
164
172
  end
165
173
 
174
+ def parse_errors(errors)
175
+ errors ||= []
176
+ if errors.is_a?(::ActiveModel::Errors)
177
+ errors
178
+ else
179
+ ::ActiveModel::Errors.new(self).tap do |e0|
180
+ errors.each{|msg| e0.add :base, msg }
181
+ end
182
+ end
183
+ end
184
+
166
185
  end
167
186
 
168
187
 
169
188
  class BigMoney < SimpleDelegator
170
189
 
171
- def initialize(*args, **kwargs)
172
- __setobj__(BigDecimal(*args, **kwargs))
190
+ def initialize(*args)
191
+ __setobj__(BigDecimal(*args))
173
192
  end
174
193
 
175
194
  def to_s
@@ -4,8 +4,10 @@ module NCore
4
4
 
5
5
  included do
6
6
  extend Associations
7
+ include ActiveModel
7
8
  include Attributes
8
9
  include Client
10
+ include Client::Cache
9
11
  include Identity
10
12
  include Lifecycle
11
13
  include Util
@@ -21,15 +23,16 @@ module NCore
21
23
  include DeleteBulk if types.include? :delete_bulk
22
24
  include Find if types.include? :find
23
25
  include Update if types.include? :update
26
+ include UpdateBulk if types.include? :update_bulk
24
27
  end
25
28
 
26
- def url
29
+ def resource_path
27
30
  class_name.underscore.pluralize
28
31
  end
29
32
  end
30
33
 
31
- def url
32
- "#{self.class.url}/#{CGI.escape((id||'-').to_s)}"
34
+ def resource_path
35
+ "#{self.class.resource_path}/#{CGI.escape((id||'-').to_s)}"
33
36
  end
34
37
 
35
38
  end
@@ -6,11 +6,12 @@ module NCore
6
6
 
7
7
  module ClassMethods
8
8
 
9
- # opts - {params: {}, headers: {}, credentials: {}}
9
+ # opts - {params: {}, headers: {}, credentials: {}, cache: {}}
10
10
  # unknown keys assumed to be :params if :params is missing
11
11
  def request(method, url, opts={})
12
12
  opts = opts.with_indifferent_access
13
13
  request_credentials = opts.delete 'credentials'
14
+ cache_opts = opts.delete 'cache'
14
15
  headers = opts.delete('headers') || {}
15
16
  params = opts['params'] || opts
16
17
 
@@ -48,7 +49,7 @@ module NCore
48
49
  write_timeout: 50,
49
50
  }
50
51
 
51
- response = execute_request(rest_opts)
52
+ response = execute_request(rest_opts, cache_opts)
52
53
  parsed = parse_response(response)
53
54
  [parsed, request_credentials]
54
55
  end
@@ -58,7 +59,7 @@ module NCore
58
59
 
59
60
  def retrieve_credentials
60
61
  if credentials.blank?
61
- raise parent::Error, credentials_error_message
62
+ raise module_parent::Error, credentials_error_message
62
63
  end
63
64
  credentials
64
65
  end
@@ -69,7 +70,7 @@ module NCore
69
70
 
70
71
  def retrieve_default_url
71
72
  if default_url.blank?
72
- raise parent::Error, credentials_error_message
73
+ raise module_parent::Error, credentials_error_message
73
74
  end
74
75
  default_url
75
76
  end
@@ -87,12 +88,12 @@ module NCore
87
88
 
88
89
  def build_query_string(params)
89
90
  if params.any?
90
- query_string = params.map do |k,v|
91
+ query_string = params.sort.map do |k,v|
91
92
  if v.is_a?(Array)
92
93
  if v.empty?
93
94
  "#{k.to_s}[]="
94
95
  else
95
- v.map do |v2|
96
+ v.sort.map do |v2|
96
97
  "#{k.to_s}[]=#{CGI::escape(v2.to_s)}"
97
98
  end.join('&')
98
99
  end
@@ -163,7 +164,7 @@ module NCore
163
164
  end
164
165
 
165
166
 
166
- def execute_request(rest_opts)
167
+ def execute_request(rest_opts, _)
167
168
  debug_request rest_opts if debug
168
169
 
169
170
  tries = 0
@@ -175,7 +176,8 @@ module NCore
175
176
  begin
176
177
  tries += 1
177
178
  response = connection.request rest_opts.except(:url)
178
- rescue Excon::Errors::SocketError, Excon::Error::Timeout, Errno::EADDRNOTAVAIL => e
179
+ rescue Excon::Error::Socket, Excon::Errors::SocketError, Excon::Error::Timeout,
180
+ Errno::EADDRNOTAVAIL => e
179
181
  # retry when keepalive was closed
180
182
  if tries <= 1 #&& e.message =~ /end of file reached/
181
183
  retry
@@ -187,30 +189,30 @@ module NCore
187
189
  debug_response response if debug
188
190
  end
189
191
  rescue Errno::ECONNRESET
190
- raise parent::ConnectionError, "Connection reset for #{host_for_error rest_opts[:url]} : check network or visit #{status_page}."
192
+ raise module_parent::ConnectionError, "Connection reset for #{host_for_error rest_opts[:url]} : check network or visit #{status_page}."
191
193
  rescue Errno::ECONNREFUSED
192
- raise parent::ConnectionError, "Connection error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
194
+ raise module_parent::ConnectionError, "Connection error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
193
195
  rescue Excon::Error::Timeout => e
194
196
  case e.message
195
197
  when /timeout reached/
196
- raise parent::ConnectionError, "Connection error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
198
+ raise module_parent::ConnectionError, "Connection error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
197
199
  else
198
200
  raise e
199
201
  end
200
202
  rescue Excon::Errors::SocketError => e
201
203
  case e.message
202
204
  when /Unable to verify certificate/
203
- raise parent::ConnectionError, "Unable to verify certificate for #{host_for_error rest_opts[:url]} : verify URL or disable SSL certificate verification (insecure)."
205
+ raise module_parent::ConnectionError, "Unable to verify certificate for #{host_for_error rest_opts[:url]} : verify URL, set ssl_cert_bundle=, or disable SSL certificate verification (insecure)."
204
206
  when /Name or service not known/, /No address associated with hostname/
205
- raise parent::ConnectionError, "DNS error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
207
+ raise module_parent::ConnectionError, "DNS error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
206
208
  when /Errno::ECONNREFUSED/
207
- raise parent::ConnectionError, "Connection error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
209
+ raise module_parent::ConnectionError, "Connection error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
208
210
  else
209
211
  raise e
210
212
  end
211
213
  rescue SocketError => e
212
214
  if e.message =~ /nodename nor servname provided/
213
- raise parent::ConnectionError, "DNS error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
215
+ raise module_parent::ConnectionError, "DNS error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
214
216
  else
215
217
  raise e
216
218
  end
@@ -218,21 +220,21 @@ module NCore
218
220
 
219
221
  case response.status
220
222
  when 401 # API auth valid; API call itself is an auth-related call and failed
221
- raise parent::AuthenticationFailed
223
+ raise module_parent::AuthenticationFailed
222
224
  when 402
223
- raise parent::AccountInactive, "Account inactive; login to portal to check account status."
225
+ raise module_parent::AccountInactive, "Account inactive; login to portal to check account status."
224
226
  when 403 # API auth failed or insufficient permissions
225
- raise parent::AccessDenied, "Access denied; check your API credentials and permissions."
227
+ raise module_parent::AccessDenied, "Access denied; check your API credentials and permissions."
226
228
  when 404
227
- raise parent::RecordNotFound
229
+ raise module_parent::RecordNotFound
228
230
  when 409, 422
229
231
  # pass through
230
232
  when 429
231
- raise parent::RateLimited
233
+ raise module_parent::RateLimited
232
234
  when 400..499
233
- raise parent::Error, "Client error: #{response.status}\n #{response.body}"
235
+ raise module_parent::Error, "Client error: #{response.status}\n #{response.body}"
234
236
  when 500..599
235
- raise parent::Error, "Server error: #{response.status}\n #{response.body}"
237
+ raise module_parent::Error, "Server error: #{response.status}\n #{response.body}"
236
238
  end
237
239
  response
238
240
  end
@@ -245,20 +247,20 @@ module NCore
245
247
  begin
246
248
  json = MultiJson.load(response.body, symbolize_keys: false) || {}
247
249
  rescue MultiJson::ParseError
248
- raise parent::Error, "Unable to parse API response; HTTP status: #{response.status}; body: #{response.body.inspect}"
250
+ raise module_parent::Error, "Unable to parse API response; HTTP status: #{response.status}; body: #{response.body.inspect}"
249
251
  end
250
252
  else
251
253
  begin
252
254
  json = JSON.parse(response.body, symbolize_names: false) || {}
253
255
  rescue JSON::ParserError
254
- raise parent::Error, "Unable to parse API response; HTTP status: #{response.status}; body: #{response.body.inspect}"
256
+ raise module_parent::Error, "Unable to parse API response; HTTP status: #{response.status}; body: #{response.body.inspect}"
255
257
  end
256
258
  end
257
259
  end
258
260
  json = json.with_indifferent_access
259
261
  errors = json.delete(:errors) || []
260
262
  if errors.any?
261
- errors = errors.values.flatten
263
+ errors = errors.values.flatten if errors.is_a?(Hash)
262
264
  metadata, json = json, {}
263
265
  else
264
266
  errors = []
@@ -295,17 +297,16 @@ module NCore
295
297
 
296
298
  def verify_ssl_cert?
297
299
  return @verify_ssl_cert unless @verify_ssl_cert.nil?
298
- bundle_readable = File.readable?(ssl_cert_bundle)
299
- if verify_ssl && bundle_readable
300
+ if verify_ssl
301
+ if ssl_cert_bundle
302
+ bundle_readable = File.readable?(ssl_cert_bundle) rescue false
303
+ unless bundle_readable
304
+ raise module_parent::CertificateError, "Unable to read SSL cert bundle #{ssl_cert_bundle}."
305
+ end
306
+ end
300
307
  @verify_ssl_cert = true
301
308
  else
302
- m = 'WARNNG: SSL cert verification is disabled.'
303
- unless verify_ssl
304
- m += " Enable verification with: #{parent}::Api.verify_ssl = true."
305
- end
306
- unless bundle_readable
307
- m += " Unable to read CA bundle #{ssl_cert_bundle}."
308
- end
309
+ m = "WARNNG: SSL cert verification is disabled. Enable verification with: #{module_parent}::Api.verify_ssl = true."
309
310
  $stderr.puts m
310
311
  @verify_ssl_cert = false
311
312
  end