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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +176 -0
  3. data/lib/zendesk_api/actions.rb +334 -0
  4. data/lib/zendesk_api/association.rb +195 -0
  5. data/lib/zendesk_api/associations.rb +212 -0
  6. data/lib/zendesk_api/client.rb +243 -0
  7. data/lib/zendesk_api/collection.rb +474 -0
  8. data/lib/zendesk_api/configuration.rb +79 -0
  9. data/lib/zendesk_api/core_ext/inflection.rb +3 -0
  10. data/lib/zendesk_api/delegator.rb +5 -0
  11. data/lib/zendesk_api/error.rb +49 -0
  12. data/lib/zendesk_api/helpers.rb +24 -0
  13. data/lib/zendesk_api/lru_cache.rb +39 -0
  14. data/lib/zendesk_api/middleware/request/encode_json.rb +26 -0
  15. data/lib/zendesk_api/middleware/request/etag_cache.rb +52 -0
  16. data/lib/zendesk_api/middleware/request/raise_rate_limited.rb +31 -0
  17. data/lib/zendesk_api/middleware/request/retry.rb +59 -0
  18. data/lib/zendesk_api/middleware/request/upload.rb +86 -0
  19. data/lib/zendesk_api/middleware/request/url_based_access_token.rb +26 -0
  20. data/lib/zendesk_api/middleware/response/callback.rb +21 -0
  21. data/lib/zendesk_api/middleware/response/deflate.rb +17 -0
  22. data/lib/zendesk_api/middleware/response/gzip.rb +19 -0
  23. data/lib/zendesk_api/middleware/response/logger.rb +44 -0
  24. data/lib/zendesk_api/middleware/response/parse_iso_dates.rb +30 -0
  25. data/lib/zendesk_api/middleware/response/parse_json.rb +23 -0
  26. data/lib/zendesk_api/middleware/response/raise_error.rb +26 -0
  27. data/lib/zendesk_api/middleware/response/sanitize_response.rb +11 -0
  28. data/lib/zendesk_api/resource.rb +208 -0
  29. data/lib/zendesk_api/resources.rb +971 -0
  30. data/lib/zendesk_api/sideloading.rb +58 -0
  31. data/lib/zendesk_api/silent_mash.rb +8 -0
  32. data/lib/zendesk_api/track_changes.rb +85 -0
  33. data/lib/zendesk_api/trackie.rb +13 -0
  34. data/lib/zendesk_api/verbs.rb +65 -0
  35. data/lib/zendesk_api/version.rb +3 -0
  36. data/lib/zendesk_api.rb +4 -0
  37. data/util/resource_handler.rb +74 -0
  38. data/util/verb_handler.rb +16 -0
  39. 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,3 @@
1
+ require 'inflection'
2
+
3
+ Inflection.plural_rule 'forum', 'forums'
@@ -0,0 +1,5 @@
1
+ require 'delegate'
2
+
3
+ module ZendeskAPI
4
+ class Delegator < SimpleDelegator; end
5
+ 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