zendesk_api 1.16.0 → 1.37.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/LICENSE +176 -0
- data/lib/zendesk_api/actions.rb +35 -0
- data/lib/zendesk_api/association.rb +1 -7
- data/lib/zendesk_api/client.rb +29 -4
- data/lib/zendesk_api/collection.rb +57 -13
- data/lib/zendesk_api/configuration.rb +15 -1
- data/lib/zendesk_api/error.rb +14 -2
- data/lib/zendesk_api/helpers.rb +21 -0
- data/lib/zendesk_api/middleware/request/etag_cache.rb +4 -0
- data/lib/zendesk_api/middleware/request/raise_rate_limited.rb +31 -0
- data/lib/zendesk_api/middleware/request/retry.rb +22 -5
- data/lib/zendesk_api/middleware/request/upload.rb +5 -2
- data/lib/zendesk_api/middleware/response/raise_error.rb +1 -1
- data/lib/zendesk_api/resource.rb +17 -5
- data/lib/zendesk_api/resources.rb +116 -120
- data/lib/zendesk_api/search.rb +51 -0
- data/lib/zendesk_api/version.rb +1 -1
- metadata +30 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5c5a2ced45e0201f6f9a45e5b8f7cf6951ca92c15ce893283c2289d0b260dc77
|
4
|
+
data.tar.gz: 31fa7566865e7191f49dadf6a5e01573264f4fc62ddfd4f635f8f7edd4be9ab2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1b355c67c8126f69f32ff6cd24d03b81b75eec27a0a72044383d0e06a871ba5bd18778119523a013725162048393d7a6bf58f075013e852303255b78bccb73f
|
7
|
+
data.tar.gz: 78ecdd8fd4d2fd889df06aea1ac98df7c68e6f89378f1358594b30853b23b3179f304ee4d87a781bda0047bc1970b8c28dfd147907d74223e28ffceb1676d1b1
|
data/LICENSE
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
Apache License
|
2
|
+
Version 2.0, January 2004
|
3
|
+
http://www.apache.org/licenses/
|
4
|
+
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
6
|
+
|
7
|
+
1. Definitions.
|
8
|
+
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
11
|
+
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
13
|
+
the copyright owner that is granting the License.
|
14
|
+
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
16
|
+
other entities that control, are controlled by, or are under common
|
17
|
+
control with that entity. For the purposes of this definition,
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
19
|
+
direction or management of such entity, whether by contract or
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
22
|
+
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
24
|
+
exercising permissions granted by this License.
|
25
|
+
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
27
|
+
including but not limited to software source code, documentation
|
28
|
+
source, and configuration files.
|
29
|
+
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
31
|
+
transformation or translation of a Source form, including but
|
32
|
+
not limited to compiled object code, generated documentation,
|
33
|
+
and conversions to other media types.
|
34
|
+
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
36
|
+
Object form, made available under the License, as indicated by a
|
37
|
+
copyright notice that is included in or attached to the work
|
38
|
+
(an example is provided in the Appendix below).
|
39
|
+
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
46
|
+
the Work and Derivative Works thereof.
|
47
|
+
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
49
|
+
the original version of the Work and any modifications or additions
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
61
|
+
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
64
|
+
subsequently incorporated within the Work.
|
65
|
+
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
72
|
+
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
78
|
+
where such license applies only to those patent claims licensable
|
79
|
+
by such Contributor that are necessarily infringed by their
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
82
|
+
institute patent litigation against any entity (including a
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
85
|
+
or contributory patent infringement, then any patent licenses
|
86
|
+
granted to You under this License for that Work shall terminate
|
87
|
+
as of the date such litigation is filed.
|
88
|
+
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
91
|
+
modifications, and in Source or Object form, provided that You
|
92
|
+
meet the following conditions:
|
93
|
+
|
94
|
+
(a) You must give any other recipients of the Work or
|
95
|
+
Derivative Works a copy of this License; and
|
96
|
+
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
98
|
+
stating that You changed the files; and
|
99
|
+
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
102
|
+
attribution notices from the Source form of the Work,
|
103
|
+
excluding those notices that do not pertain to any part of
|
104
|
+
the Derivative Works; and
|
105
|
+
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
108
|
+
include a readable copy of the attribution notices contained
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
111
|
+
of the following places: within a NOTICE text file distributed
|
112
|
+
as part of the Derivative Works; within the Source form or
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
114
|
+
within a display generated by the Derivative Works, if and
|
115
|
+
wherever such third-party notices normally appear. The contents
|
116
|
+
of the NOTICE file are for informational purposes only and
|
117
|
+
do not modify the License. You may add Your own attribution
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
120
|
+
that such additional attribution notices cannot be construed
|
121
|
+
as modifying the License.
|
122
|
+
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
124
|
+
may provide additional or different license terms and conditions
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
128
|
+
the conditions stated in this License.
|
129
|
+
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
133
|
+
this License, without any additional terms or conditions.
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
135
|
+
the terms of any separate license agreement you may have executed
|
136
|
+
with Licensor regarding such Contributions.
|
137
|
+
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
140
|
+
except as required for reasonable and customary use in describing the
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
142
|
+
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
152
|
+
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
158
|
+
incidental, or consequential damages of any character arising as a
|
159
|
+
result of this License or out of the use or inability to use the
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
162
|
+
other commercial damages or losses), even if such Contributor
|
163
|
+
has been advised of the possibility of such damages.
|
164
|
+
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
168
|
+
or other liability obligations and/or rights consistent with this
|
169
|
+
License. However, in accepting such obligations, You may act only
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
174
|
+
of your accepting any such warranty or additional liability.
|
175
|
+
|
176
|
+
END OF TERMS AND CONDITIONS
|
data/lib/zendesk_api/actions.rb
CHANGED
@@ -181,6 +181,41 @@ module ZendeskAPI
|
|
181
181
|
end
|
182
182
|
end
|
183
183
|
|
184
|
+
module CreateOrUpdate
|
185
|
+
# Creates or updates resource using the create_or_update endpoint.
|
186
|
+
# @param [Client] client The {Client} object to be used
|
187
|
+
# @param [Hash] attributes The attributes to create.
|
188
|
+
def create_or_update!(client, attributes, association = Association.new(:class => self))
|
189
|
+
response = client.connection.post("#{association.generate_path}/create_or_update") do |req|
|
190
|
+
req.body = { singular_resource_name => attributes }
|
191
|
+
|
192
|
+
yield req if block_given?
|
193
|
+
end
|
194
|
+
|
195
|
+
new_from_response(client, response, Array(association.options[:include]))
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
module CreateOrUpdateMany
|
200
|
+
# Creates or updates multiple resources using the create_or_update_many endpoint.
|
201
|
+
#
|
202
|
+
# @param [Client] client The {Client} object to be used
|
203
|
+
# @param [Array<Hash>] attributes The attributes to update resources with
|
204
|
+
#
|
205
|
+
# @return [JobStatus] the {JobStatus} instance for this destroy job
|
206
|
+
def create_or_update_many!(client, attributes)
|
207
|
+
association = Association.new(:class => self)
|
208
|
+
|
209
|
+
response = client.connection.post("#{association.generate_path}/create_or_update_many") do |req|
|
210
|
+
req.body = { resource_name => attributes }
|
211
|
+
|
212
|
+
yield req if block_given?
|
213
|
+
end
|
214
|
+
|
215
|
+
JobStatus.new_from_response(client, response)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
184
219
|
module Destroy
|
185
220
|
def self.included(klass)
|
186
221
|
klass.extend(ClassMethod)
|
@@ -19,13 +19,8 @@ module ZendeskAPI
|
|
19
19
|
nil
|
20
20
|
end
|
21
21
|
|
22
|
-
# 1.9+ changed default to search ancestors, added flag to disable behavior.
|
23
22
|
def module_defines_class?(mod, klass_as_string)
|
24
|
-
|
25
|
-
mod.const_defined?(klass_as_string)
|
26
|
-
else
|
27
|
-
mod.const_defined?(klass_as_string, false)
|
28
|
-
end
|
23
|
+
mod.const_defined?(klass_as_string, false)
|
29
24
|
end
|
30
25
|
end
|
31
26
|
|
@@ -58,7 +53,6 @@ module ZendeskAPI
|
|
58
53
|
|
59
54
|
namespace = @options[:class].to_s.split("::")
|
60
55
|
namespace[-1] = @options[:class].resource_path
|
61
|
-
|
62
56
|
# Remove components without path information
|
63
57
|
ignorable_namespace_strings.each { |ns| namespace.delete(ns) }
|
64
58
|
has_parent = namespace.size > 1 || (options[:with_parent] && @options.parent)
|
data/lib/zendesk_api/client.rb
CHANGED
@@ -8,6 +8,7 @@ require 'zendesk_api/lru_cache'
|
|
8
8
|
require 'zendesk_api/silent_mash'
|
9
9
|
require 'zendesk_api/middleware/request/etag_cache'
|
10
10
|
require 'zendesk_api/middleware/request/retry'
|
11
|
+
require 'zendesk_api/middleware/request/raise_rate_limited'
|
11
12
|
require 'zendesk_api/middleware/request/upload'
|
12
13
|
require 'zendesk_api/middleware/request/encode_json'
|
13
14
|
require 'zendesk_api/middleware/request/url_based_access_token'
|
@@ -39,6 +40,11 @@ module ZendeskAPI
|
|
39
40
|
method = method.to_s
|
40
41
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
41
42
|
|
43
|
+
unless config.use_resource_cache
|
44
|
+
raise "Resource for #{method} does not exist" unless method_as_class(method)
|
45
|
+
return ZendeskAPI::Collection.new(self, method_as_class(method), options)
|
46
|
+
end
|
47
|
+
|
42
48
|
@resource_cache[method] ||= { :class => nil, :cache => ZendeskAPI::LRUCache.new }
|
43
49
|
if !options.delete(:reload) && (cached = @resource_cache[method][:cache].read(options.hash))
|
44
50
|
cached
|
@@ -93,6 +99,8 @@ module ZendeskAPI
|
|
93
99
|
|
94
100
|
config.retry = !!config.retry # nil -> false
|
95
101
|
|
102
|
+
set_raise_error_when_rated_limited
|
103
|
+
|
96
104
|
set_token_auth
|
97
105
|
|
98
106
|
set_default_logger
|
@@ -157,7 +165,7 @@ module ZendeskAPI
|
|
157
165
|
|
158
166
|
# request
|
159
167
|
if config.access_token && !config.url_based_access_token
|
160
|
-
builder.authorization
|
168
|
+
builder.request(:authorization, "Bearer", config.access_token)
|
161
169
|
elsif config.access_token
|
162
170
|
builder.use ZendeskAPI::Middleware::Request::UrlBasedAccessToken, config.access_token
|
163
171
|
else
|
@@ -171,7 +179,12 @@ module ZendeskAPI
|
|
171
179
|
builder.use ZendeskAPI::Middleware::Request::Upload
|
172
180
|
builder.request :multipart
|
173
181
|
builder.use ZendeskAPI::Middleware::Request::EncodeJson
|
174
|
-
|
182
|
+
|
183
|
+
# Should always be first in the stack
|
184
|
+
builder.use ZendeskAPI::Middleware::Request::Retry, :logger => config.logger, :retry_codes => config.retry_codes, :retry_on_exception => config.retry_on_exception if config.retry
|
185
|
+
if config.raise_error_when_rate_limited
|
186
|
+
builder.use ZendeskAPI::Middleware::Request::RaiseRateLimited, :logger => config.logger
|
187
|
+
end
|
175
188
|
|
176
189
|
builder.adapter(*adapter)
|
177
190
|
end
|
@@ -185,13 +198,25 @@ module ZendeskAPI
|
|
185
198
|
end
|
186
199
|
|
187
200
|
def check_url
|
188
|
-
if !config.allow_http && config.url
|
201
|
+
if !config.allow_http && !config.url.start_with?('https://')
|
189
202
|
raise ArgumentError, "zendesk_api is ssl only; url must begin with https://"
|
190
203
|
end
|
191
204
|
end
|
192
205
|
|
206
|
+
def set_raise_error_when_rated_limited
|
207
|
+
config.raise_error_when_rate_limited = if config.retry
|
208
|
+
false
|
209
|
+
else
|
210
|
+
!!config.raise_error_when_rate_limited
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
193
214
|
def set_token_auth
|
194
|
-
if config.
|
215
|
+
return if config.password || config.token.nil?
|
216
|
+
|
217
|
+
if config.username.nil?
|
218
|
+
raise ArgumentError, "you need to provide a username when using API token auth"
|
219
|
+
else
|
195
220
|
config.password = config.token
|
196
221
|
config.username += "/token" unless config.username.end_with?("/token")
|
197
222
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'zendesk_api/resource'
|
2
2
|
require 'zendesk_api/resources'
|
3
|
+
require 'zendesk_api/search'
|
3
4
|
|
4
5
|
module ZendeskAPI
|
5
6
|
# Represents a collection of resources. Lazily loaded, resources aren't
|
@@ -28,7 +29,7 @@ module ZendeskAPI
|
|
28
29
|
# @param [String] resource The resource being collected.
|
29
30
|
# @param [Hash] options Any additional options to be passed in.
|
30
31
|
def initialize(client, resource, options = {})
|
31
|
-
@client, @resource_class, @resource = client, resource, resource.
|
32
|
+
@client, @resource_class, @resource = client, resource, resource.resource_path
|
32
33
|
@options = SilentMash.new(options)
|
33
34
|
|
34
35
|
set_association_from_options
|
@@ -47,7 +48,7 @@ module ZendeskAPI
|
|
47
48
|
end
|
48
49
|
|
49
50
|
# Methods that take a Hash argument
|
50
|
-
methods = %w{create find update update_many destroy}
|
51
|
+
methods = %w{create find update update_many destroy create_or_update}
|
51
52
|
methods += methods.map { |method| method + "!" }
|
52
53
|
methods.each do |deferrable|
|
53
54
|
# Passes arguments and the proper path to the resource class method.
|
@@ -185,16 +186,22 @@ module ZendeskAPI
|
|
185
186
|
elsif association && association.options.parent && association.options.parent.new_record?
|
186
187
|
return (@resources = [])
|
187
188
|
end
|
189
|
+
path_query_link = (@query || path)
|
188
190
|
|
189
|
-
@response = get_response(
|
190
|
-
|
191
|
+
@response = get_response(path_query_link)
|
192
|
+
|
193
|
+
if path_query_link == "search/export"
|
194
|
+
handle_cursor_response(@response.body)
|
195
|
+
else
|
196
|
+
handle_response(@response.body)
|
197
|
+
end
|
191
198
|
|
192
199
|
@resources
|
193
200
|
end
|
194
201
|
|
195
202
|
def fetch(*args)
|
196
203
|
fetch!(*args)
|
197
|
-
rescue Faraday::
|
204
|
+
rescue Faraday::ClientError => e
|
198
205
|
@error = e
|
199
206
|
|
200
207
|
[]
|
@@ -248,7 +255,7 @@ module ZendeskAPI
|
|
248
255
|
if @options["page"]
|
249
256
|
clear_cache
|
250
257
|
@options["page"] += 1
|
251
|
-
elsif @query = @next_page
|
258
|
+
elsif (@query = @next_page)
|
252
259
|
fetch(true)
|
253
260
|
else
|
254
261
|
clear_cache
|
@@ -264,7 +271,7 @@ module ZendeskAPI
|
|
264
271
|
if @options["page"] && @options["page"] > 1
|
265
272
|
clear_cache
|
266
273
|
@options["page"] -= 1
|
267
|
-
elsif @query = @prev_page
|
274
|
+
elsif (@query = @prev_page)
|
268
275
|
fetch(true)
|
269
276
|
else
|
270
277
|
clear_cache
|
@@ -313,12 +320,35 @@ module ZendeskAPI
|
|
313
320
|
end
|
314
321
|
end
|
315
322
|
|
316
|
-
alias
|
323
|
+
alias to_str to_s
|
317
324
|
|
318
325
|
def to_param
|
319
326
|
map(&:to_param)
|
320
327
|
end
|
321
328
|
|
329
|
+
def more_results?(response)
|
330
|
+
response["meta"].present? && response["results"].present?
|
331
|
+
end
|
332
|
+
alias_method :has_more_results?, :more_results? # For backward compatibility with 1.33.0 and 1.34.0
|
333
|
+
|
334
|
+
def get_response_body(link)
|
335
|
+
@client.connection.send("get", link).body
|
336
|
+
end
|
337
|
+
|
338
|
+
def get_next_page_data(original_response_body)
|
339
|
+
link = original_response_body["links"]["next"]
|
340
|
+
|
341
|
+
while link
|
342
|
+
response = get_response_body(link)
|
343
|
+
|
344
|
+
original_response_body["results"] = original_response_body["results"] + response["results"]
|
345
|
+
|
346
|
+
link = response["meta"]["has_more"] ? response["links"]["next"] : nil
|
347
|
+
end
|
348
|
+
|
349
|
+
original_response_body
|
350
|
+
end
|
351
|
+
|
322
352
|
private
|
323
353
|
|
324
354
|
def set_page_and_count(body)
|
@@ -372,8 +402,6 @@ module ZendeskAPI
|
|
372
402
|
result
|
373
403
|
end
|
374
404
|
|
375
|
-
## Initialize
|
376
|
-
|
377
405
|
def join_special_params
|
378
406
|
# some params use comma-joined strings instead of query-based arrays for multiple values
|
379
407
|
@options.each do |k, v|
|
@@ -389,12 +417,9 @@ module ZendeskAPI
|
|
389
417
|
association_options = { :path => @options.delete(:path) }
|
390
418
|
association_options[:path] ||= @collection_path.join("/") if @collection_path
|
391
419
|
@association = @options.delete(:association) || Association.new(association_options.merge(:class => @resource_class))
|
392
|
-
|
393
420
|
@collection_path ||= [@resource]
|
394
421
|
end
|
395
422
|
|
396
|
-
## Fetch
|
397
|
-
|
398
423
|
def get_response(path)
|
399
424
|
@error = nil
|
400
425
|
@response = @client.connection.send(@verb || "get", path) do |req|
|
@@ -410,6 +435,25 @@ module ZendeskAPI
|
|
410
435
|
end
|
411
436
|
end
|
412
437
|
|
438
|
+
def handle_cursor_response(response_body)
|
439
|
+
unless response_body.is_a?(Hash)
|
440
|
+
raise ZendeskAPI::Error::NetworkError, @response.env
|
441
|
+
end
|
442
|
+
|
443
|
+
response_body = get_next_page_data(response_body) if more_results?(response_body)
|
444
|
+
|
445
|
+
body = response_body.dup
|
446
|
+
results = body.delete(@resource_class.model_key) || body.delete("results")
|
447
|
+
|
448
|
+
unless results
|
449
|
+
raise ZendeskAPI::Error::ClientError, "Expected #{@resource_class.model_key} or 'results' in response keys: #{body.keys.inspect}"
|
450
|
+
end
|
451
|
+
|
452
|
+
@resources = results.map do |res|
|
453
|
+
wrap_resource(res)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
413
457
|
def handle_response(response_body)
|
414
458
|
unless response_body.is_a?(Hash)
|
415
459
|
raise ZendeskAPI::Error::NetworkError, @response.env
|
@@ -16,6 +16,9 @@ module ZendeskAPI
|
|
16
16
|
# @return [Boolean] Whether to attempt to retry when rate-limited (http status: 429).
|
17
17
|
attr_accessor :retry
|
18
18
|
|
19
|
+
# @return [Boolean] Whether to raise error when rate-limited (http status: 429).
|
20
|
+
attr_accessor :raise_error_when_rate_limited
|
21
|
+
|
19
22
|
# @return [Logger] Logger to use when logging requests.
|
20
23
|
attr_accessor :logger
|
21
24
|
|
@@ -39,8 +42,18 @@ module ZendeskAPI
|
|
39
42
|
# @return [ZendeskAPI::LRUCache]
|
40
43
|
attr_accessor :cache
|
41
44
|
|
45
|
+
# @return [Boolean] Whether to use resource_cache or not
|
46
|
+
attr_accessor :use_resource_cache
|
47
|
+
|
48
|
+
# specify the server error codes in which you want a retry to be attempted
|
49
|
+
attr_accessor :retry_codes
|
50
|
+
|
51
|
+
# specify if you want a (network layer) exception to elicit a retry
|
52
|
+
attr_accessor :retry_on_exception
|
53
|
+
|
42
54
|
def initialize
|
43
55
|
@client_options = {}
|
56
|
+
@use_resource_cache = true
|
44
57
|
|
45
58
|
self.cache = ZendeskAPI::LRUCache.new(1000)
|
46
59
|
end
|
@@ -56,7 +69,8 @@ module ZendeskAPI
|
|
56
69
|
:user_agent => "ZendeskAPI Ruby #{ZendeskAPI::VERSION}"
|
57
70
|
},
|
58
71
|
:request => {
|
59
|
-
:open_timeout => 10
|
72
|
+
:open_timeout => 10,
|
73
|
+
:timeout => 60
|
60
74
|
},
|
61
75
|
:url => @url
|
62
76
|
}.merge(client_options)
|
data/lib/zendesk_api/error.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# tested via spec/core/middleware/response/raise_error_spec.rb
|
2
2
|
module ZendeskAPI
|
3
3
|
module Error
|
4
|
-
class ClientError < Faraday::
|
4
|
+
class ClientError < Faraday::ClientError
|
5
5
|
attr_reader :wrapped_exception
|
6
6
|
|
7
7
|
def to_s
|
@@ -20,7 +20,7 @@ module ZendeskAPI
|
|
20
20
|
super
|
21
21
|
|
22
22
|
if response[:body].is_a?(Hash)
|
23
|
-
@errors = response[:body]["details"] || response[:body]
|
23
|
+
@errors = response[:body]["details"] || generate_error_msg(response[:body])
|
24
24
|
end
|
25
25
|
|
26
26
|
@errors ||= {}
|
@@ -29,9 +29,21 @@ module ZendeskAPI
|
|
29
29
|
def to_s
|
30
30
|
"#{self.class.name}: #{@errors}"
|
31
31
|
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def generate_error_msg(response_body)
|
36
|
+
return unless response_body["description"] || response_body["message"]
|
37
|
+
|
38
|
+
[
|
39
|
+
response_body["description"],
|
40
|
+
response_body["message"]
|
41
|
+
].compact.join(" - ")
|
42
|
+
end
|
32
43
|
end
|
33
44
|
|
34
45
|
class NetworkError < ClientError; end
|
35
46
|
class RecordNotFound < ClientError; end
|
47
|
+
class RateLimited < ClientError; end
|
36
48
|
end
|
37
49
|
end
|
data/lib/zendesk_api/helpers.rb
CHANGED
@@ -2,6 +2,19 @@ module ZendeskAPI
|
|
2
2
|
# @private
|
3
3
|
module Helpers
|
4
4
|
# From https://github.com/rubyworks/facets/blob/master/lib/core/facets/string/modulize.rb
|
5
|
+
# Converts a string to module name representation.
|
6
|
+
#
|
7
|
+
# This is essentially #camelcase, but it also converts
|
8
|
+
# '/' to '::' which is useful for converting paths to
|
9
|
+
# namespaces.
|
10
|
+
#
|
11
|
+
# Examples
|
12
|
+
#
|
13
|
+
# "method_name".modulize #=> "MethodName"
|
14
|
+
# "method/name".modulize #=> "Method::Name"
|
15
|
+
#
|
16
|
+
# @param string [string] input, `module/class_name`
|
17
|
+
# @return [string] a string that can become a class, `Module::ClassName`
|
5
18
|
def self.modulize_string(string)
|
6
19
|
# gsub('__','/'). # why was this ever here?
|
7
20
|
string.gsub(/__(.?)/) { "::#{$1.upcase}" }.
|
@@ -11,6 +24,14 @@ module ZendeskAPI
|
|
11
24
|
end
|
12
25
|
|
13
26
|
# From https://github.com/rubyworks/facets/blob/master/lib/core/facets/string/snakecase.rb
|
27
|
+
# Underscore a string such that camelcase, dashes and spaces are
|
28
|
+
# replaced by underscores. This is the reverse of {#camelcase},
|
29
|
+
# albeit not an exact inverse.
|
30
|
+
#
|
31
|
+
# "SnakeCase".snakecase #=> "snake_case"
|
32
|
+
# "Snake-Case".snakecase #=> "snake_case"
|
33
|
+
# "Snake Case".snakecase #=> "snake_case"
|
34
|
+
# "Snake - Case".snakecase #=> "snake_case"
|
14
35
|
def self.snakecase_string(string)
|
15
36
|
# gsub(/::/, '/').
|
16
37
|
string.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
|
@@ -30,7 +30,11 @@ module ZendeskAPI
|
|
30
30
|
|
31
31
|
@app.call(environment).on_complete do |env|
|
32
32
|
if cached && env[:status] == 304 # not modified
|
33
|
+
# Handle differences in serialized env keys in Faraday < 1.0 and 1.0
|
34
|
+
# See https://github.com/lostisland/faraday/pull/847
|
33
35
|
env[:body] = cached[:body]
|
36
|
+
env[:response_body] = cached[:response_body]
|
37
|
+
|
34
38
|
env[:response_headers].merge!(
|
35
39
|
:etag => cached[:response_headers][:etag],
|
36
40
|
:content_type => cached[:response_headers][:content_type],
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'faraday/middleware'
|
2
|
+
require 'zendesk_api/error'
|
3
|
+
|
4
|
+
module ZendeskAPI
|
5
|
+
module Middleware
|
6
|
+
# @private
|
7
|
+
module Request
|
8
|
+
# Faraday middleware to handle HTTP Status 429 (rate limiting) / 503 (maintenance)
|
9
|
+
# @private
|
10
|
+
class RaiseRateLimited < Faraday::Middleware
|
11
|
+
ERROR_CODES = [429, 503].freeze
|
12
|
+
|
13
|
+
def initialize(app, options = {})
|
14
|
+
super(app)
|
15
|
+
@logger = options[:logger]
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
response = @app.call(env)
|
20
|
+
|
21
|
+
if ERROR_CODES.include?(response.env[:status])
|
22
|
+
@logger&.warn 'You have been rate limited. Raising error...'
|
23
|
+
raise Error::RateLimited, env
|
24
|
+
else
|
25
|
+
response
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|