discourse_zendesk_api 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,195 @@
1
+ require 'zendesk_api/helpers'
2
+
3
+ module ZendeskAPI
4
+ # Represents an association between two resources
5
+ # @private
6
+ class Association
7
+ class << self
8
+ def namespaces
9
+ [ZendeskAPI] + ZendeskAPI::DataNamespace.descendants
10
+ end
11
+
12
+ def class_from_namespace(klass_as_string)
13
+ namespaces.each do |ns|
14
+ if module_defines_class?(ns, klass_as_string)
15
+ return ns.const_get(klass_as_string)
16
+ end
17
+ end
18
+
19
+ nil
20
+ end
21
+
22
+ # 1.9+ changed default to search ancestors, added flag to disable behavior.
23
+ def module_defines_class?(mod, klass_as_string)
24
+ if RUBY_VERSION < '1.9'
25
+ mod.const_defined?(klass_as_string)
26
+ else
27
+ mod.const_defined?(klass_as_string, false)
28
+ end
29
+ end
30
+ end
31
+
32
+ # @return [Hash] Options passed into the association
33
+ attr_reader :options
34
+
35
+ # Options to pass in
36
+ # * class - Required
37
+ # * parent - Parent instance
38
+ # * path - Optional path instead of resource name
39
+ def initialize(options = {})
40
+ @options = SilentMash.new(options)
41
+ end
42
+
43
+ # Generate a path to the resource.
44
+ # id and <parent>_id attributes will be deleted from passed in options hash if they are used in the built path.
45
+ # Arguments that can be passed in:
46
+ # An instance, any resource instance
47
+ # Hash Options:
48
+ # * with_parent - Include the parent path (false by default)
49
+ # * with_id - Include the instance id, if possible (true)
50
+ def generate_path(*args)
51
+ options = SilentMash.new(:with_id => true)
52
+ if args.last.is_a?(Hash)
53
+ original_options = args.pop
54
+ options.merge!(original_options)
55
+ end
56
+
57
+ instance = args.first
58
+
59
+ namespace = @options[:class].to_s.split("::")
60
+ namespace[-1] = @options[:class].resource_path
61
+
62
+ # Remove components without path information
63
+ ignorable_namespace_strings.each { |ns| namespace.delete(ns) }
64
+ has_parent = namespace.size > 1 || (options[:with_parent] && @options.parent)
65
+
66
+ if has_parent
67
+ parent_class = @options.parent ? @options.parent.class : Association.class_from_namespace(ZendeskAPI::Helpers.modulize_string(namespace[0]))
68
+ parent_namespace = build_parent_namespace(parent_class, instance, options, original_options)
69
+ namespace[1..1] = parent_namespace if parent_namespace
70
+ namespace[0] = parent_class.resource_path
71
+ else
72
+ namespace[0] = @options.path || @options[:class].resource_path
73
+ end
74
+
75
+ if id = extract_id(instance, options, original_options)
76
+ namespace << id
77
+ end
78
+
79
+ namespace.join("/")
80
+ end
81
+
82
+ # Tries to place side loads onto given resources.
83
+ def side_load(resources, side_loads)
84
+ key = "#{options.name}_id"
85
+ plural_key = "#{Inflection.singular options.name.to_s}_ids"
86
+
87
+ resources.each do |resource|
88
+ if resource.key?(plural_key) # Grab associations from child_ids field on resource
89
+ side_load_from_child_ids(resource, side_loads, plural_key)
90
+ elsif resource.key?(key) || options.singular
91
+ side_load_from_child_or_parent_id(resource, side_loads, key)
92
+ else # Grab associations from parent_id field from multiple child resources
93
+ side_load_from_parent_id(resource, side_loads, key)
94
+ end
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ # @return [Array<String>] ['ZendeskAPI', 'Voice', etc.. ]
101
+ def ignorable_namespace_strings
102
+ ZendeskAPI::DataNamespace.descendants.map { |klass| klass.to_s.split('::') }.flatten.uniq
103
+ end
104
+
105
+ def _side_load(resource, side_loads)
106
+ side_loads.map! do |side_load|
107
+ resource.send(:wrap_resource, side_load, options)
108
+ end
109
+
110
+ ZendeskAPI::Collection.new(resource.client, options[:class]).tap do |collection|
111
+ collection.replace(side_loads)
112
+ end
113
+ end
114
+
115
+ def side_load_from_parent_id(resource, side_loads, key)
116
+ key = "#{resource.class.singular_resource_name}_id"
117
+
118
+ resource.send("#{options.name}=", _side_load(resource, side_loads.select {|side_load|
119
+ side_load[key] == resource.id
120
+ }))
121
+ end
122
+
123
+ def side_load_from_child_ids(resource, side_loads, plural_key)
124
+ ids = resource.send(plural_key)
125
+
126
+ resource.send("#{options.name}=", _side_load(resource, side_loads.select {|side_load|
127
+ ids.include?(side_load[options.include_key])
128
+ }))
129
+ end
130
+
131
+ def side_load_from_child_or_parent_id(resource, side_loads, key)
132
+ # Either grab association from child_id field on resource or parent_id on child resource
133
+ if resource.key?(key)
134
+ id = resource.send(key)
135
+ include_key = options.include_key
136
+ else
137
+ id = resource.id
138
+ include_key = "#{resource.class.singular_resource_name}_id"
139
+ end
140
+
141
+ return unless id
142
+
143
+ side_load = side_loads.detect do |side_load|
144
+ id == side_load[include_key]
145
+ end
146
+
147
+ resource.send("#{options.name}=", side_load) if side_load
148
+ end
149
+
150
+ def build_parent_namespace(parent_class, instance, options, original_options)
151
+ path = @options.path
152
+
153
+ association_on_parent = parent_class.associations.detect { |a| a[:name] == @options[:name] }
154
+ association_on_parent ||= parent_class.associations.detect do |a|
155
+ !a[:inline] && a[:class] == @options[:class]
156
+ end
157
+
158
+ if association_on_parent
159
+ path ||= association_on_parent[:path]
160
+ path ||= association_on_parent[:name].to_s
161
+ end
162
+
163
+ path ||= @options[:class].resource_path
164
+
165
+ [
166
+ extract_parent_id(parent_class, instance, options, original_options),
167
+ path
168
+ ]
169
+ end
170
+
171
+ def extract_parent_id(parent_class, instance, options, original_options)
172
+ parent_id_column = "#{parent_class.singular_resource_name}_id"
173
+
174
+ if @options.parent
175
+ @options.parent.id
176
+ elsif instance
177
+ instance.send(parent_id_column)
178
+ elsif options[parent_id_column]
179
+ original_options.delete(parent_id_column) || original_options.delete(parent_id_column.to_sym)
180
+ else
181
+ raise ArgumentError.new("#{@options[:class].resource_name} requires #{parent_id_column} or parent")
182
+ end
183
+ end
184
+
185
+ def extract_id(instance, options, original_options)
186
+ if options[:with_id] && !@options[:class].ancestors.include?(SingularResource)
187
+ if instance && instance.id
188
+ instance.id
189
+ elsif options[:id]
190
+ original_options.delete(:id) || original_options.delete("id")
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,212 @@
1
+ require 'zendesk_api/helpers'
2
+
3
+ module ZendeskAPI
4
+ # This module holds association method for resources.
5
+ # Associations can be loaded in three ways:
6
+ # * Commonly used resources are automatically side-loaded server side and sent along with their parent object.
7
+ # * Associated resource ids are sent and are then loaded one-by-one into the parent collection.
8
+ # * The association is represented with Rails' nested association urls (such as tickets/:id/groups) and are loaded that way.
9
+ #
10
+ # @private
11
+ module Associations
12
+ def self.included(base)
13
+ base.extend ClassMethods
14
+ end
15
+
16
+ def wrap_resource(resource, class_level_association, options = {})
17
+ instance_association = Association.new(class_level_association.merge(:parent => self))
18
+ klass = class_level_association[:class]
19
+
20
+ case resource
21
+ when Hash
22
+ klass.new(@client, resource.merge(:association => instance_association))
23
+ when String, Integer
24
+ klass.new(@client, (options[:include_key] || :id) => resource, :association => instance_association)
25
+ else
26
+ resource.association = instance_association
27
+ resource
28
+ end
29
+ end
30
+
31
+ # @private
32
+ module ClassMethods
33
+ def self.extended(klass)
34
+ klass.extend Has
35
+ klass.extend HasMany
36
+ end
37
+
38
+ def associations
39
+ @associations ||= []
40
+ end
41
+
42
+ def associated_with(name)
43
+ associations.inject([]) do |associated_with, association|
44
+ if association[:include] == name.to_s
45
+ associated_with.push(Association.new(association))
46
+ end
47
+
48
+ associated_with
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def build_association(klass, resource_name, options)
55
+ {
56
+ :class => klass,
57
+ :name => resource_name,
58
+ :inline => options.delete(:inline),
59
+ :path => options.delete(:path),
60
+ :include => (options.delete(:include) || klass.resource_name).to_s,
61
+ :include_key => (options.delete(:include_key) || :id).to_s,
62
+ :singular => options.delete(:singular),
63
+ :extensions => Array(options.delete(:extend))
64
+ }
65
+ end
66
+
67
+ def define_used(association)
68
+ define_method "#{association[:name]}_used?" do
69
+ !!instance_variable_get("@#{association[:name]}")
70
+ end
71
+ end
72
+
73
+ module Has
74
+ # Represents a parent-to-child association between resources. Options to pass in are: class, path.
75
+ # @param [Symbol] resource_name_or_class The underlying resource name or a class to get it from
76
+ # @param [Hash] class_level_options The options to pass to the method definition.
77
+ def has(resource_name_or_class, class_level_options = {})
78
+ if klass = class_level_options.delete(:class)
79
+ resource_name = resource_name_or_class
80
+ else
81
+ klass = resource_name_or_class
82
+ resource_name = klass.singular_resource_name
83
+ end
84
+
85
+ class_level_association = build_association(klass, resource_name, class_level_options)
86
+ class_level_association.merge!(:singular => true, :id_column => "#{resource_name}_id")
87
+
88
+ associations << class_level_association
89
+
90
+ define_used(class_level_association)
91
+ define_has_getter(class_level_association)
92
+ define_has_setter(class_level_association)
93
+ end
94
+
95
+ private
96
+
97
+ def define_has_getter(association)
98
+ klass = association[:class] # shorthand
99
+
100
+ define_method association[:name] do |*args|
101
+ instance_options = args.last.is_a?(Hash) ? args.pop : {}
102
+
103
+ # return if cached
104
+ cached = instance_variable_get("@#{association[:name]}")
105
+ return cached if cached && !instance_options[:reload]
106
+
107
+ # find and cache association
108
+ instance_association = Association.new(association.merge(:parent => self))
109
+ resource = if klass.respond_to?(:find) && resource_id = method_missing(association[:id_column])
110
+ klass.find(@client, :id => resource_id, :association => instance_association)
111
+ elsif found = method_missing(association[:name].to_sym)
112
+ wrap_resource(found, association, :include_key => association[:include_key])
113
+ elsif klass.superclass == DataResource && !association[:inline]
114
+ response = @client.connection.get(instance_association.generate_path(:with_parent => true))
115
+ klass.new(@client, response.body[klass.singular_resource_name].merge(:association => instance_association))
116
+ end
117
+
118
+ send("#{association[:id_column]}=", resource.id) if resource && has_key?(association[:id_column])
119
+ instance_variable_set("@#{association[:name]}", resource)
120
+ end
121
+ end
122
+
123
+ def define_has_setter(association)
124
+ define_method "#{association[:name]}=" do |resource|
125
+ resource = wrap_resource(resource, association)
126
+ send("#{association[:id_column]}=", resource.id) if has_key?(association[:id_column])
127
+ instance_variable_set("@#{association[:name]}", resource)
128
+ end
129
+ end
130
+ end
131
+
132
+ module HasMany
133
+ # Represents a parent-to-children association between resources. Options to pass in are: class, path.
134
+ # @param [Symbol] resource_name_or_class The underlying resource name or class to get it from
135
+ # @param [Hash] class_level_options The options to pass to the method definition.
136
+ def has_many(resource_name_or_class, class_level_options = {})
137
+ if klass = class_level_options.delete(:class)
138
+ resource_name = resource_name_or_class
139
+ else
140
+ klass = resource_name_or_class
141
+ resource_name = klass.resource_name
142
+ end
143
+
144
+ class_level_association = build_association(klass, resource_name, class_level_options)
145
+ class_level_association.merge!(:singular => false, :id_column => "#{resource_name}_ids")
146
+
147
+ associations << class_level_association
148
+
149
+ define_used(class_level_association)
150
+ define_has_many_getter(class_level_association)
151
+ define_has_many_setter(class_level_association)
152
+ end
153
+
154
+ private
155
+
156
+ def define_has_many_getter(association)
157
+ klass = association[:class]
158
+
159
+ define_method association[:name] do |*args|
160
+ instance_opts = args.last.is_a?(Hash) ? args.pop : {}
161
+
162
+ # return if cached
163
+ cached = instance_variable_get("@#{association[:name]}")
164
+ return cached if cached && !instance_opts[:reload]
165
+
166
+ # find and cache association
167
+ instance_association = Association.new(association.merge(:parent => self))
168
+ singular_resource_name = Inflection.singular(association[:name].to_s)
169
+
170
+ resources = if (ids = method_missing("#{singular_resource_name}_ids")) && ids.any?
171
+ ids.map do |id|
172
+ klass.find(@client, :id => id, :association => instance_association)
173
+ end.compact
174
+ elsif (resources = method_missing(association[:name].to_sym)) && resources.any?
175
+ resources.map { |res| wrap_resource(res, association) }
176
+ else
177
+ []
178
+ end
179
+
180
+ collection = ZendeskAPI::Collection.new(@client, klass, instance_opts.merge(:association => instance_association))
181
+
182
+ if association[:extensions].any?
183
+ collection.extend(*association[:extensions])
184
+ end
185
+
186
+ if resources.any?
187
+ collection.replace(resources)
188
+ end
189
+
190
+ send("#{association[:id_column]}=", resources.map(&:id)) if has_key?(association[:id_column])
191
+ instance_variable_set("@#{association[:name]}", collection)
192
+ end
193
+ end
194
+
195
+ def define_has_many_setter(association)
196
+ define_method "#{association[:name]}=" do |resources|
197
+ if resources.is_a?(Array)
198
+ wrapped = resources.map { |attr| wrap_resource(attr, association) }
199
+ send(association[:name]).replace(wrapped)
200
+ else
201
+ resources.association = Association.new(association.merge(:parent => self))
202
+ instance_variable_set("@#{association[:name]}", resources)
203
+ end
204
+
205
+ send("#{association[:id_column]}=", resources.map(&:id)) if resources && has_key?(association[:id_column])
206
+ resource
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,243 @@
1
+ require 'faraday'
2
+
3
+ require 'zendesk_api/version'
4
+ require 'zendesk_api/sideloading'
5
+ require 'zendesk_api/configuration'
6
+ require 'zendesk_api/collection'
7
+ require 'zendesk_api/lru_cache'
8
+ require 'zendesk_api/silent_mash'
9
+ require 'zendesk_api/middleware/request/etag_cache'
10
+ require 'zendesk_api/middleware/request/retry'
11
+ require 'zendesk_api/middleware/request/raise_rate_limited'
12
+ require 'zendesk_api/middleware/request/upload'
13
+ require 'zendesk_api/middleware/request/encode_json'
14
+ require 'zendesk_api/middleware/request/url_based_access_token'
15
+ require 'zendesk_api/middleware/response/callback'
16
+ require 'zendesk_api/middleware/response/deflate'
17
+ require 'zendesk_api/middleware/response/gzip'
18
+ require 'zendesk_api/middleware/response/sanitize_response'
19
+ require 'zendesk_api/middleware/response/parse_iso_dates'
20
+ require 'zendesk_api/middleware/response/parse_json'
21
+ require 'zendesk_api/middleware/response/raise_error'
22
+ require 'zendesk_api/middleware/response/logger'
23
+ require 'zendesk_api/delegator'
24
+
25
+ module ZendeskAPI
26
+ # The top-level class that handles configuration and connection to the Zendesk API.
27
+ # Can also be used as an accessor to resource collections.
28
+ class Client
29
+ GZIP_EXCEPTIONS = [:em_http, :httpclient]
30
+
31
+ # @return [Configuration] Config instance
32
+ attr_reader :config
33
+ # @return [Array] Custom response callbacks
34
+ attr_reader :callbacks
35
+
36
+ # Handles resources such as 'tickets'. Any options are passed to the underlying collection, except reload which disregards
37
+ # memoization and creates a new Collection instance.
38
+ # @return [Collection] Collection instance for resource
39
+ def method_missing(method, *args, &block)
40
+ method = method.to_s
41
+ options = args.last.is_a?(Hash) ? args.pop : {}
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
+
48
+ @resource_cache[method] ||= { :class => nil, :cache => ZendeskAPI::LRUCache.new }
49
+ if !options.delete(:reload) && (cached = @resource_cache[method][:cache].read(options.hash))
50
+ cached
51
+ else
52
+ @resource_cache[method][:class] ||= method_as_class(method)
53
+ raise "Resource for #{method} does not exist" unless @resource_cache[method][:class]
54
+ @resource_cache[method][:cache].write(options.hash, ZendeskAPI::Collection.new(self, @resource_cache[method][:class], options))
55
+ end
56
+ end
57
+
58
+ def respond_to?(method, *args)
59
+ ((cache = @resource_cache[method]) && cache[:class]) || !method_as_class(method).nil? || super
60
+ end
61
+
62
+ # Returns the current user (aka me)
63
+ # @return [ZendeskAPI::User] Current user or nil
64
+ def current_user(reload = false)
65
+ return @current_user if @current_user && !reload
66
+ @current_user = users.find(:id => 'me')
67
+ end
68
+
69
+ # Returns the current account
70
+ # @return [Hash] The attributes of the current account or nil
71
+ def current_account(reload = false)
72
+ return @current_account if @current_account && !reload
73
+ @current_account = SilentMash.new(connection.get('account/resolve').body)
74
+ end
75
+
76
+ # Returns the current locale
77
+ # @return [ZendeskAPI::Locale] Current locale or nil
78
+ def current_locale(reload = false)
79
+ return @locale if @locale && !reload
80
+ @locale = locales.find(:id => 'current')
81
+ end
82
+
83
+ # Creates a new {Client} instance and yields {#config}.
84
+ #
85
+ # Requires a block to be given.
86
+ #
87
+ # Does basic configuration constraints:
88
+ # * {Configuration#url} must be https unless {Configuration#allow_http} is set.
89
+ def initialize
90
+ raise ArgumentError, "block not given" unless block_given?
91
+
92
+ @config = ZendeskAPI::Configuration.new
93
+ yield config
94
+
95
+ @callbacks = []
96
+ @resource_cache = {}
97
+
98
+ check_url
99
+
100
+ config.retry = !!config.retry # nil -> false
101
+
102
+ set_raise_error_when_rated_limited
103
+
104
+ set_token_auth
105
+
106
+ set_default_logger
107
+ add_warning_callback
108
+ end
109
+
110
+ # Creates a connection if there is none, otherwise returns the existing connection.
111
+ #
112
+ # @return [Faraday::Connection] Faraday connection for the client
113
+ def connection
114
+ @connection ||= build_connection
115
+ return @connection
116
+ end
117
+
118
+ # Pushes a callback onto the stack. Callbacks are executed on responses, last in the Faraday middleware stack.
119
+ # @param [Proc] block The block to execute. Takes one parameter, env.
120
+ def insert_callback(&block)
121
+ @callbacks << block
122
+ end
123
+
124
+ # show a nice warning for people using the old style api
125
+ def self.check_deprecated_namespace_usage(attributes, name)
126
+ if attributes[name].is_a?(Hash)
127
+ raise "un-nest '#{name}' from the attributes"
128
+ end
129
+ end
130
+
131
+ ZendeskAPI::DataNamespace.descendants.each do |namespace|
132
+ delegator = ZendeskAPI::Helpers.snakecase_string(namespace.to_s.split("::").last)
133
+ define_method delegator do |*| # takes arguments, but doesn't do anything with them
134
+ Delegator.new(self)
135
+ end
136
+ end
137
+
138
+ protected
139
+
140
+ # Called by {#connection} to build a connection. Can be overwritten in a
141
+ # subclass to add additional middleware and make other configuration
142
+ # changes.
143
+ #
144
+ # Uses middleware according to configuration options.
145
+ #
146
+ # Request logger if logger is not nil
147
+ #
148
+ # Retry middleware if retry is true
149
+ def build_connection
150
+ Faraday.new(config.options) do |builder|
151
+ # response
152
+ builder.use ZendeskAPI::Middleware::Response::RaiseError
153
+ builder.use ZendeskAPI::Middleware::Response::Callback, self
154
+ builder.use ZendeskAPI::Middleware::Response::Logger, config.logger if config.logger
155
+ builder.use ZendeskAPI::Middleware::Response::ParseIsoDates
156
+ builder.use ZendeskAPI::Middleware::Response::ParseJson
157
+ builder.use ZendeskAPI::Middleware::Response::SanitizeResponse
158
+
159
+ adapter = config.adapter || Faraday.default_adapter
160
+
161
+ unless GZIP_EXCEPTIONS.include?(adapter)
162
+ builder.use ZendeskAPI::Middleware::Response::Gzip
163
+ builder.use ZendeskAPI::Middleware::Response::Deflate
164
+ end
165
+
166
+ # request
167
+ if config.access_token && !config.url_based_access_token
168
+ builder.authorization("Bearer", config.access_token)
169
+ elsif config.access_token
170
+ builder.use ZendeskAPI::Middleware::Request::UrlBasedAccessToken, config.access_token
171
+ else
172
+ builder.use Faraday::Request::BasicAuthentication, config.username, config.password
173
+ end
174
+
175
+ if config.cache
176
+ builder.use ZendeskAPI::Middleware::Request::EtagCache, :cache => config.cache
177
+ end
178
+
179
+ builder.use ZendeskAPI::Middleware::Request::Upload
180
+ builder.request :multipart
181
+ builder.use ZendeskAPI::Middleware::Request::EncodeJson
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
188
+
189
+ builder.adapter(*adapter)
190
+ end
191
+ end
192
+
193
+ private
194
+
195
+ def method_as_class(method)
196
+ klass_as_string = ZendeskAPI::Helpers.modulize_string(Inflection.singular(method.to_s.gsub(/\W/, '')))
197
+ ZendeskAPI::Association.class_from_namespace(klass_as_string)
198
+ end
199
+
200
+ def check_url
201
+ if !config.allow_http && config.url !~ /^https/
202
+ raise ArgumentError, "zendesk_api is ssl only; url must begin with https://"
203
+ end
204
+ end
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
+
214
+ def set_token_auth
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
220
+ config.password = config.token
221
+ config.username += "/token" unless config.username.end_with?("/token")
222
+ end
223
+ end
224
+
225
+ def set_default_logger
226
+ if config.logger.nil? || config.logger == true
227
+ require 'logger'
228
+ config.logger = Logger.new($stderr)
229
+ config.logger.level = Logger::WARN
230
+ end
231
+ end
232
+
233
+ def add_warning_callback
234
+ return unless logger = config.logger
235
+
236
+ insert_callback do |env|
237
+ if warning = env[:response_headers]["X-Zendesk-API-Warn"]
238
+ logger.warn "WARNING: #{warning}"
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end