discourse_zendesk_api 1.0.0
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 +7 -0
- data/LICENSE +176 -0
- data/lib/zendesk_api/actions.rb +334 -0
- data/lib/zendesk_api/association.rb +195 -0
- data/lib/zendesk_api/associations.rb +212 -0
- data/lib/zendesk_api/client.rb +243 -0
- data/lib/zendesk_api/collection.rb +474 -0
- data/lib/zendesk_api/configuration.rb +79 -0
- data/lib/zendesk_api/core_ext/inflection.rb +3 -0
- data/lib/zendesk_api/delegator.rb +5 -0
- data/lib/zendesk_api/error.rb +49 -0
- data/lib/zendesk_api/helpers.rb +24 -0
- data/lib/zendesk_api/lru_cache.rb +39 -0
- data/lib/zendesk_api/middleware/request/encode_json.rb +26 -0
- data/lib/zendesk_api/middleware/request/etag_cache.rb +52 -0
- data/lib/zendesk_api/middleware/request/raise_rate_limited.rb +31 -0
- data/lib/zendesk_api/middleware/request/retry.rb +59 -0
- data/lib/zendesk_api/middleware/request/upload.rb +86 -0
- data/lib/zendesk_api/middleware/request/url_based_access_token.rb +26 -0
- data/lib/zendesk_api/middleware/response/callback.rb +21 -0
- data/lib/zendesk_api/middleware/response/deflate.rb +17 -0
- data/lib/zendesk_api/middleware/response/gzip.rb +19 -0
- data/lib/zendesk_api/middleware/response/logger.rb +44 -0
- data/lib/zendesk_api/middleware/response/parse_iso_dates.rb +30 -0
- data/lib/zendesk_api/middleware/response/parse_json.rb +23 -0
- data/lib/zendesk_api/middleware/response/raise_error.rb +26 -0
- data/lib/zendesk_api/middleware/response/sanitize_response.rb +11 -0
- data/lib/zendesk_api/resource.rb +208 -0
- data/lib/zendesk_api/resources.rb +971 -0
- data/lib/zendesk_api/sideloading.rb +58 -0
- data/lib/zendesk_api/silent_mash.rb +8 -0
- data/lib/zendesk_api/track_changes.rb +85 -0
- data/lib/zendesk_api/trackie.rb +13 -0
- data/lib/zendesk_api/verbs.rb +65 -0
- data/lib/zendesk_api/version.rb +3 -0
- data/lib/zendesk_api.rb +4 -0
- data/util/resource_handler.rb +74 -0
- data/util/verb_handler.rb +16 -0
- metadata +166 -0
@@ -0,0 +1,474 @@
|
|
1
|
+
require 'zendesk_api/resource'
|
2
|
+
require 'zendesk_api/resources'
|
3
|
+
|
4
|
+
module ZendeskAPI
|
5
|
+
# Represents a collection of resources. Lazily loaded, resources aren't
|
6
|
+
# actually fetched until explicitly needed (e.g. #each, {#fetch}).
|
7
|
+
class Collection
|
8
|
+
include ZendeskAPI::Sideloading
|
9
|
+
|
10
|
+
# Options passed in that are automatically converted from an array to a comma-separated list.
|
11
|
+
SPECIALLY_JOINED_PARAMS = [:ids, :only]
|
12
|
+
|
13
|
+
# @return [ZendeskAPI::Association] The class association
|
14
|
+
attr_reader :association
|
15
|
+
|
16
|
+
# @return [Faraday::Response] The last response
|
17
|
+
attr_reader :response
|
18
|
+
|
19
|
+
# @return [Hash] query options
|
20
|
+
attr_reader :options
|
21
|
+
|
22
|
+
# @return [ZendeskAPI::ClientError] The last response error
|
23
|
+
attr_reader :error
|
24
|
+
|
25
|
+
# Creates a new Collection instance. Does not fetch resources.
|
26
|
+
# Additional options are: verb (default: GET), path (default: resource param), page, per_page.
|
27
|
+
# @param [Client] client The {Client} to use.
|
28
|
+
# @param [String] resource The resource being collected.
|
29
|
+
# @param [Hash] options Any additional options to be passed in.
|
30
|
+
def initialize(client, resource, options = {})
|
31
|
+
@client, @resource_class, @resource = client, resource, resource.resource_name
|
32
|
+
@options = SilentMash.new(options)
|
33
|
+
|
34
|
+
set_association_from_options
|
35
|
+
join_special_params
|
36
|
+
|
37
|
+
@verb = @options.delete(:verb)
|
38
|
+
@includes = Array(@options.delete(:include))
|
39
|
+
|
40
|
+
# Used for Attachments, TicketComment
|
41
|
+
if @resource_class.is_a?(Class) && @resource_class.superclass == ZendeskAPI::Data
|
42
|
+
@resources = []
|
43
|
+
@fetchable = false
|
44
|
+
else
|
45
|
+
@fetchable = true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Methods that take a Hash argument
|
50
|
+
methods = %w{create find update update_many destroy}
|
51
|
+
methods += methods.map { |method| method + "!" }
|
52
|
+
methods.each do |deferrable|
|
53
|
+
# Passes arguments and the proper path to the resource class method.
|
54
|
+
# @param [Hash] options Options or attributes to pass
|
55
|
+
define_method deferrable do |*args|
|
56
|
+
unless @resource_class.respond_to?(deferrable)
|
57
|
+
raise NoMethodError.new("undefined method \"#{deferrable}\" for #{@resource_class}", deferrable, args)
|
58
|
+
end
|
59
|
+
|
60
|
+
args << {} unless args.last.is_a?(Hash)
|
61
|
+
args.last.merge!(:association => @association)
|
62
|
+
|
63
|
+
@resource_class.send(deferrable, @client, *args)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Methods that take an Array argument
|
68
|
+
methods = %w{create_many! destroy_many!}
|
69
|
+
methods.each do |deferrable|
|
70
|
+
# Passes arguments and the proper path to the resource class method.
|
71
|
+
# @param [Array] array arguments
|
72
|
+
define_method deferrable do |*args|
|
73
|
+
unless @resource_class.respond_to?(deferrable)
|
74
|
+
raise NoMethodError.new("undefined method \"#{deferrable}\" for #{@resource_class}", deferrable, args)
|
75
|
+
end
|
76
|
+
|
77
|
+
array = args.last.is_a?(Array) ? args.pop : []
|
78
|
+
|
79
|
+
@resource_class.send(deferrable, @client, array, @association)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Convenience method to build a new resource and
|
84
|
+
# add it to the collection. Fetches the collection as well.
|
85
|
+
# @param [Hash] opts Options or attributes to pass
|
86
|
+
def build(opts = {})
|
87
|
+
wrap_resource(opts, true).tap do |res|
|
88
|
+
self << res
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Convenience method to build a new resource and
|
93
|
+
# add it to the collection. Fetches the collection as well.
|
94
|
+
# @param [Hash] opts Options or attributes to pass
|
95
|
+
def build!(opts = {})
|
96
|
+
wrap_resource(opts, true).tap do |res|
|
97
|
+
fetch!
|
98
|
+
|
99
|
+
# << does a fetch too
|
100
|
+
self << res
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# @return [Number] The total number of resources server-side (disregarding pagination).
|
105
|
+
def count
|
106
|
+
fetch
|
107
|
+
@count || -1
|
108
|
+
end
|
109
|
+
|
110
|
+
# @return [Number] The total number of resources server-side (disregarding pagination).
|
111
|
+
def count!
|
112
|
+
fetch!
|
113
|
+
@count || -1
|
114
|
+
end
|
115
|
+
|
116
|
+
# Changes the per_page option. Returns self, so it can be chained. No execution.
|
117
|
+
# @return [Collection] self
|
118
|
+
def per_page(count)
|
119
|
+
clear_cache if count
|
120
|
+
@options["per_page"] = count
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
# Changes the page option. Returns self, so it can be chained. No execution.
|
125
|
+
# @return [Collection] self
|
126
|
+
def page(number)
|
127
|
+
clear_cache if number
|
128
|
+
@options["page"] = number
|
129
|
+
self
|
130
|
+
end
|
131
|
+
|
132
|
+
def first_page?
|
133
|
+
!@prev_page
|
134
|
+
end
|
135
|
+
|
136
|
+
def last_page?
|
137
|
+
!@next_page || @next_page == @query
|
138
|
+
end
|
139
|
+
|
140
|
+
# Saves all newly created resources stored in this collection.
|
141
|
+
# @return [Collection] self
|
142
|
+
def save
|
143
|
+
_save
|
144
|
+
end
|
145
|
+
|
146
|
+
# Saves all newly created resources stored in this collection.
|
147
|
+
# @return [Collection] self
|
148
|
+
def save!
|
149
|
+
_save(:save!)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Adds an item (or items) to the list of side-loaded resources to request
|
153
|
+
# @option sideloads [Symbol or String] The item(s) to sideload
|
154
|
+
def include(*sideloads)
|
155
|
+
tap { @includes.concat(sideloads.map(&:to_s)) }
|
156
|
+
end
|
157
|
+
|
158
|
+
# Adds an item to this collection
|
159
|
+
# @option item [ZendeskAPI::Data] the resource to add
|
160
|
+
# @raise [ArgumentError] if the resource doesn't belong in this collection
|
161
|
+
def <<(item)
|
162
|
+
fetch
|
163
|
+
|
164
|
+
if item.is_a?(Resource)
|
165
|
+
if item.is_a?(@resource_class)
|
166
|
+
@resources << item
|
167
|
+
else
|
168
|
+
raise "this collection is for #{@resource_class}"
|
169
|
+
end
|
170
|
+
else
|
171
|
+
@resources << wrap_resource(item, true)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# The API path to this collection
|
176
|
+
def path
|
177
|
+
@association.generate_path(:with_parent => true)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Executes actual GET from API and loads resources into proper class.
|
181
|
+
# @param [Boolean] reload Whether to disregard cache
|
182
|
+
def fetch!(reload = false)
|
183
|
+
if @resources && (!@fetchable || !reload)
|
184
|
+
return @resources
|
185
|
+
elsif association && association.options.parent && association.options.parent.new_record?
|
186
|
+
return (@resources = [])
|
187
|
+
end
|
188
|
+
|
189
|
+
@response = get_response(@query || path)
|
190
|
+
handle_response(@response.body)
|
191
|
+
|
192
|
+
@resources
|
193
|
+
end
|
194
|
+
|
195
|
+
def fetch(*args)
|
196
|
+
fetch!(*args)
|
197
|
+
rescue Faraday::ClientError => e
|
198
|
+
@error = e
|
199
|
+
|
200
|
+
[]
|
201
|
+
end
|
202
|
+
|
203
|
+
# Alias for fetch(false)
|
204
|
+
def to_a
|
205
|
+
fetch
|
206
|
+
end
|
207
|
+
|
208
|
+
# Alias for fetch!(false)
|
209
|
+
def to_a!
|
210
|
+
fetch!
|
211
|
+
end
|
212
|
+
|
213
|
+
# Calls #each on every page with the passed in block
|
214
|
+
# @param [Block] block Passed to #each
|
215
|
+
def all!(start_page = @options["page"], &block)
|
216
|
+
_all(start_page, :bang, &block)
|
217
|
+
end
|
218
|
+
|
219
|
+
# Calls #each on every page with the passed in block
|
220
|
+
# @param [Block] block Passed to #each
|
221
|
+
def all(start_page = @options["page"], &block)
|
222
|
+
_all(start_page, &block)
|
223
|
+
end
|
224
|
+
|
225
|
+
def each_page!(*args, &block)
|
226
|
+
warn "ZendeskAPI::Collection#each_page! is deprecated, please use ZendeskAPI::Collection#all!"
|
227
|
+
all!(*args, &block)
|
228
|
+
end
|
229
|
+
|
230
|
+
def each_page(*args, &block)
|
231
|
+
warn "ZendeskAPI::Collection#each_page is deprecated, please use ZendeskAPI::Collection#all"
|
232
|
+
all(*args, &block)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Replaces the current (loaded or not) resources with the passed in collection
|
236
|
+
# @option collection [Array] The collection to replace this one with
|
237
|
+
# @raise [ArgumentError] if any resources passed in don't belong in this collection
|
238
|
+
def replace(collection)
|
239
|
+
raise "this collection is for #{@resource_class}" if collection.any? { |r| !r.is_a?(@resource_class) }
|
240
|
+
@resources = collection
|
241
|
+
end
|
242
|
+
|
243
|
+
# Find the next page. Does one of three things:
|
244
|
+
# * If there is already a page number in the options hash, it increases it and invalidates the cache, returning the new page number.
|
245
|
+
# * If there is a next_page url cached, it executes a fetch on that url and returns the results.
|
246
|
+
# * Otherwise, returns an empty array.
|
247
|
+
def next
|
248
|
+
if @options["page"]
|
249
|
+
clear_cache
|
250
|
+
@options["page"] += 1
|
251
|
+
elsif @query = @next_page
|
252
|
+
fetch(true)
|
253
|
+
else
|
254
|
+
clear_cache
|
255
|
+
@resources = []
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Find the previous page. Does one of three things:
|
260
|
+
# * If there is already a page number in the options hash, it increases it and invalidates the cache, returning the new page number.
|
261
|
+
# * If there is a prev_page url cached, it executes a fetch on that url and returns the results.
|
262
|
+
# * Otherwise, returns an empty array.
|
263
|
+
def prev
|
264
|
+
if @options["page"] && @options["page"] > 1
|
265
|
+
clear_cache
|
266
|
+
@options["page"] -= 1
|
267
|
+
elsif @query = @prev_page
|
268
|
+
fetch(true)
|
269
|
+
else
|
270
|
+
clear_cache
|
271
|
+
@resources = []
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# Clears all cached resources and associated values.
|
276
|
+
def clear_cache
|
277
|
+
@resources = nil
|
278
|
+
@count = nil
|
279
|
+
@next_page = nil
|
280
|
+
@prev_page = nil
|
281
|
+
@query = nil
|
282
|
+
end
|
283
|
+
|
284
|
+
# @private
|
285
|
+
def to_ary
|
286
|
+
nil
|
287
|
+
end
|
288
|
+
|
289
|
+
def respond_to_missing?(name, include_all)
|
290
|
+
[].respond_to?(name, include_all)
|
291
|
+
end
|
292
|
+
|
293
|
+
# Sends methods to underlying array of resources.
|
294
|
+
def method_missing(name, *args, &block)
|
295
|
+
if resource_methods.include?(name)
|
296
|
+
collection_method(name, *args, &block)
|
297
|
+
elsif [].respond_to?(name, false)
|
298
|
+
array_method(name, *args, &block)
|
299
|
+
else
|
300
|
+
next_collection(name, *args, &block)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# @private
|
305
|
+
def to_s
|
306
|
+
if @resources
|
307
|
+
@resources.inspect
|
308
|
+
else
|
309
|
+
inspect = []
|
310
|
+
inspect << "options=#{@options.inspect}" if @options.any?
|
311
|
+
inspect << "path=#{path}"
|
312
|
+
"#{Inflection.singular(@resource)} collection [#{inspect.join(',')}]"
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
alias :to_str :to_s
|
317
|
+
|
318
|
+
def to_param
|
319
|
+
map(&:to_param)
|
320
|
+
end
|
321
|
+
|
322
|
+
private
|
323
|
+
|
324
|
+
def set_page_and_count(body)
|
325
|
+
@count = (body["count"] || @resources.size).to_i
|
326
|
+
@next_page, @prev_page = body["next_page"], body["previous_page"]
|
327
|
+
|
328
|
+
if @next_page =~ /page=(\d+)/
|
329
|
+
@options["page"] = $1.to_i - 1
|
330
|
+
elsif @prev_page =~ /page=(\d+)/
|
331
|
+
@options["page"] = $1.to_i + 1
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def _all(start_page = @options["page"], bang = false, &block)
|
336
|
+
raise(ArgumentError, "must pass a block") unless block
|
337
|
+
|
338
|
+
page(start_page)
|
339
|
+
clear_cache
|
340
|
+
|
341
|
+
while (bang ? fetch! : fetch)
|
342
|
+
each do |resource|
|
343
|
+
arguments = [resource, @options["page"] || 1]
|
344
|
+
|
345
|
+
if block.arity >= 0
|
346
|
+
arguments = arguments.take(block.arity)
|
347
|
+
end
|
348
|
+
|
349
|
+
block.call(*arguments)
|
350
|
+
end
|
351
|
+
|
352
|
+
last_page? ? break : self.next
|
353
|
+
end
|
354
|
+
|
355
|
+
page(nil)
|
356
|
+
clear_cache
|
357
|
+
end
|
358
|
+
|
359
|
+
def _save(method = :save)
|
360
|
+
return self unless @resources
|
361
|
+
|
362
|
+
result = true
|
363
|
+
|
364
|
+
@resources.map! do |item|
|
365
|
+
if item.respond_to?(method) && !item.destroyed? && item.changed?
|
366
|
+
result &&= item.send(method)
|
367
|
+
end
|
368
|
+
|
369
|
+
item
|
370
|
+
end
|
371
|
+
|
372
|
+
result
|
373
|
+
end
|
374
|
+
|
375
|
+
## Initialize
|
376
|
+
|
377
|
+
def join_special_params
|
378
|
+
# some params use comma-joined strings instead of query-based arrays for multiple values
|
379
|
+
@options.each do |k, v|
|
380
|
+
if SPECIALLY_JOINED_PARAMS.include?(k.to_sym) && v.is_a?(Array)
|
381
|
+
@options[k] = v.join(',')
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
def set_association_from_options
|
387
|
+
@collection_path = @options.delete(:collection_path)
|
388
|
+
|
389
|
+
association_options = { :path => @options.delete(:path) }
|
390
|
+
association_options[:path] ||= @collection_path.join("/") if @collection_path
|
391
|
+
@association = @options.delete(:association) || Association.new(association_options.merge(:class => @resource_class))
|
392
|
+
|
393
|
+
@collection_path ||= [@resource]
|
394
|
+
end
|
395
|
+
|
396
|
+
## Fetch
|
397
|
+
|
398
|
+
def get_response(path)
|
399
|
+
@error = nil
|
400
|
+
@response = @client.connection.send(@verb || "get", path) do |req|
|
401
|
+
opts = @options.delete_if { |_, v| v.nil? }
|
402
|
+
|
403
|
+
req.params.merge!(:include => @includes.join(",")) if @includes.any?
|
404
|
+
|
405
|
+
if %w{put post}.include?(@verb.to_s)
|
406
|
+
req.body = opts
|
407
|
+
else
|
408
|
+
req.params.merge!(opts)
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
def handle_response(response_body)
|
414
|
+
unless response_body.is_a?(Hash)
|
415
|
+
raise ZendeskAPI::Error::NetworkError, @response.env
|
416
|
+
end
|
417
|
+
|
418
|
+
body = response_body.dup
|
419
|
+
results = body.delete(@resource_class.model_key) || body.delete("results")
|
420
|
+
|
421
|
+
unless results
|
422
|
+
raise ZendeskAPI::Error::ClientError, "Expected #{@resource_class.model_key} or 'results' in response keys: #{body.keys.inspect}"
|
423
|
+
end
|
424
|
+
|
425
|
+
@resources = results.map do |res|
|
426
|
+
wrap_resource(res)
|
427
|
+
end
|
428
|
+
|
429
|
+
set_page_and_count(body)
|
430
|
+
set_includes(@resources, @includes, body)
|
431
|
+
end
|
432
|
+
|
433
|
+
# Simplified Associations#wrap_resource
|
434
|
+
def wrap_resource(res, with_association = with_association?)
|
435
|
+
case res
|
436
|
+
when Array
|
437
|
+
wrap_resource(Hash[*res], with_association)
|
438
|
+
when Hash
|
439
|
+
res = res.merge(:association => @association) if with_association
|
440
|
+
@resource_class.new(@client, res)
|
441
|
+
else
|
442
|
+
res = { :id => res }
|
443
|
+
res.merge!(:association => @association) if with_association
|
444
|
+
@resource_class.new(@client, res)
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
# Two special cases, and all namespaced classes
|
449
|
+
def with_association?
|
450
|
+
[Tag, Setting].include?(@resource_class) ||
|
451
|
+
@resource_class.to_s.split("::").size > 2
|
452
|
+
end
|
453
|
+
|
454
|
+
## Method missing
|
455
|
+
|
456
|
+
def array_method(name, *args, &block)
|
457
|
+
to_a.public_send(name, *args, &block)
|
458
|
+
end
|
459
|
+
|
460
|
+
def next_collection(name, *args, &block)
|
461
|
+
opts = args.last.is_a?(Hash) ? args.last : {}
|
462
|
+
opts.merge!(:collection_path => @collection_path.dup.push(name))
|
463
|
+
self.class.new(@client, @resource_class, @options.merge(opts))
|
464
|
+
end
|
465
|
+
|
466
|
+
def collection_method(name, *args, &block)
|
467
|
+
@resource_class.send(name, @client, *args, &block)
|
468
|
+
end
|
469
|
+
|
470
|
+
def resource_methods
|
471
|
+
@resource_methods ||= @resource_class.singleton_methods(false).map(&:to_sym)
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module ZendeskAPI
|
2
|
+
# Holds the configuration options for the client and connection
|
3
|
+
class Configuration
|
4
|
+
# @return [String] The basic auth username.
|
5
|
+
attr_accessor :username
|
6
|
+
|
7
|
+
# @return [String] The basic auth password.
|
8
|
+
attr_accessor :password
|
9
|
+
|
10
|
+
# @return [String] The basic auth token.
|
11
|
+
attr_accessor :token
|
12
|
+
|
13
|
+
# @return [String] The API url. Must be https unless {#allow_http} is set.
|
14
|
+
attr_accessor :url
|
15
|
+
|
16
|
+
# @return [Boolean] Whether to attempt to retry when rate-limited (http status: 429).
|
17
|
+
attr_accessor :retry
|
18
|
+
|
19
|
+
# @return [Boolean] Whether to raise error when rate-limited (http status: 429).
|
20
|
+
attr_accessor :raise_error_when_rate_limited
|
21
|
+
|
22
|
+
# @return [Logger] Logger to use when logging requests.
|
23
|
+
attr_accessor :logger
|
24
|
+
|
25
|
+
# @return [Hash] Client configurations (eg ssh config) to pass to Faraday
|
26
|
+
attr_accessor :client_options
|
27
|
+
|
28
|
+
# @return [Symbol] Faraday adapter
|
29
|
+
attr_accessor :adapter
|
30
|
+
|
31
|
+
# @return [Boolean] Whether to allow non-HTTPS connections for development purposes.
|
32
|
+
attr_accessor :allow_http
|
33
|
+
|
34
|
+
# @return [String] OAuth2 access_token
|
35
|
+
attr_accessor :access_token
|
36
|
+
|
37
|
+
attr_accessor :url_based_access_token
|
38
|
+
|
39
|
+
# Use this cache instead of default ZendeskAPI::LRUCache.new
|
40
|
+
# - must respond to read/write/fetch e.g. ActiveSupport::Cache::MemoryStore.new)
|
41
|
+
# - pass false to disable caching
|
42
|
+
# @return [ZendeskAPI::LRUCache]
|
43
|
+
attr_accessor :cache
|
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
|
+
|
54
|
+
def initialize
|
55
|
+
@client_options = {}
|
56
|
+
@use_resource_cache = true
|
57
|
+
|
58
|
+
self.cache = ZendeskAPI::LRUCache.new(1000)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Sets accept and user_agent headers, and url.
|
62
|
+
#
|
63
|
+
# @return [Hash] Faraday-formatted hash of options.
|
64
|
+
def options
|
65
|
+
{
|
66
|
+
:headers => {
|
67
|
+
:accept => 'application/json',
|
68
|
+
:accept_encoding => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
|
69
|
+
:user_agent => "ZendeskAPI Ruby #{ZendeskAPI::VERSION}"
|
70
|
+
},
|
71
|
+
:request => {
|
72
|
+
:open_timeout => 10,
|
73
|
+
:timeout => 60
|
74
|
+
},
|
75
|
+
:url => @url
|
76
|
+
}.merge(client_options)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# tested via spec/core/middleware/response/raise_error_spec.rb
|
2
|
+
module ZendeskAPI
|
3
|
+
module Error
|
4
|
+
class ClientError < Faraday::ClientError
|
5
|
+
attr_reader :wrapped_exception
|
6
|
+
|
7
|
+
def to_s
|
8
|
+
if response
|
9
|
+
"#{super} -- #{response.method} #{response.url}"
|
10
|
+
else
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class RecordInvalid < ClientError
|
17
|
+
attr_accessor :errors
|
18
|
+
|
19
|
+
def initialize(*)
|
20
|
+
super
|
21
|
+
|
22
|
+
if response[:body].is_a?(Hash)
|
23
|
+
@errors = response[:body]["details"] || generate_error_msg(response[:body])
|
24
|
+
end
|
25
|
+
|
26
|
+
@errors ||= {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"#{self.class.name}: #{@errors}"
|
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
|
43
|
+
end
|
44
|
+
|
45
|
+
class NetworkError < ClientError; end
|
46
|
+
class RecordNotFound < ClientError; end
|
47
|
+
class RateLimited < ClientError; end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ZendeskAPI
|
2
|
+
# @private
|
3
|
+
module Helpers
|
4
|
+
# From https://github.com/rubyworks/facets/blob/master/lib/core/facets/string/modulize.rb
|
5
|
+
def self.modulize_string(string)
|
6
|
+
# gsub('__','/'). # why was this ever here?
|
7
|
+
string.gsub(/__(.?)/) { "::#{$1.upcase}" }.
|
8
|
+
gsub(/\/(.?)/) { "::#{$1.upcase}" }.
|
9
|
+
gsub(/(?:_+|-+)([a-z])/) { $1.upcase }.
|
10
|
+
gsub(/(\A|\s)([a-z])/) { $1 + $2.upcase }
|
11
|
+
end
|
12
|
+
|
13
|
+
# From https://github.com/rubyworks/facets/blob/master/lib/core/facets/string/snakecase.rb
|
14
|
+
def self.snakecase_string(string)
|
15
|
+
# gsub(/::/, '/').
|
16
|
+
string.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
|
17
|
+
gsub(/([a-z\d])([A-Z])/, '\1_\2').
|
18
|
+
tr('-', '_').
|
19
|
+
gsub(/\s/, '_').
|
20
|
+
gsub(/__+/, '_').
|
21
|
+
downcase
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ZendeskAPI
|
2
|
+
# http://codesnippets.joyent.com/posts/show/12329
|
3
|
+
# @private
|
4
|
+
class ZendeskAPI::LRUCache
|
5
|
+
attr_accessor :size
|
6
|
+
|
7
|
+
def initialize(size = 10)
|
8
|
+
@size = size
|
9
|
+
@store = {}
|
10
|
+
@lru = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def write(key, value)
|
14
|
+
@store[key] = value
|
15
|
+
set_lru(key)
|
16
|
+
@store.delete(@lru.pop) if @lru.size > @size
|
17
|
+
value
|
18
|
+
end
|
19
|
+
|
20
|
+
def read(key)
|
21
|
+
set_lru(key)
|
22
|
+
@store[key]
|
23
|
+
end
|
24
|
+
|
25
|
+
def fetch(key)
|
26
|
+
if @store.has_key? key
|
27
|
+
read key
|
28
|
+
else
|
29
|
+
write key, yield
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def set_lru(key)
|
36
|
+
@lru.unshift(@lru.delete(key) || key)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ZendeskAPI
|
2
|
+
# @private
|
3
|
+
module Middleware
|
4
|
+
# @private
|
5
|
+
module Request
|
6
|
+
class EncodeJson < Faraday::Middleware
|
7
|
+
CONTENT_TYPE = 'Content-Type'.freeze
|
8
|
+
MIME_TYPE = 'application/json'.freeze
|
9
|
+
dependency 'json'
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
type = env[:request_headers][CONTENT_TYPE].to_s
|
13
|
+
type = type.split(';', 2).first if type.index(';')
|
14
|
+
type
|
15
|
+
|
16
|
+
if env[:body] && !(env[:body].respond_to?(:to_str) && env[:body].empty?) && (type.empty? || type == MIME_TYPE)
|
17
|
+
env[:body] = JSON.dump(env[:body])
|
18
|
+
env[:request_headers][CONTENT_TYPE] ||= MIME_TYPE
|
19
|
+
end
|
20
|
+
|
21
|
+
@app.call(env)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|