active_campaign 0.0.12 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,135 @@
1
+ require 'active_campaign/version'
2
+ require 'active_campaign/response/raise_error'
3
+ require 'active_campaign/response/debugger'
4
+ require 'active_campaign/response/json_normalizer'
5
+ require 'active_campaign/response/mashify'
6
+ require 'active_campaign/response/instrumentation'
7
+ require 'active_campaign/response/parse_json'
8
+
9
+ module ActiveCampaign
10
+
11
+ # Default configuration options for {Client}
12
+ module Default
13
+
14
+ # Default API endpoint
15
+ API_ENDPOINT = "https://subdomain.activehosted.com".freeze
16
+
17
+ API_PATH = "admin/api.php".freeze
18
+
19
+ # Default User Agent header string
20
+ USER_AGENT = "ActiveCampaign Ruby Gem #{ActiveCampaign::VERSION}".freeze
21
+
22
+ # Default media type
23
+ MEDIA_TYPE = "text/html"
24
+
25
+ # Default media type
26
+ API_OUTPUT = "json"
27
+
28
+ # Default WEB endpoint
29
+ WEB_ENDPOINT = "http://www.activecampaign.com/".freeze
30
+
31
+ # Default Faraday middleware stack
32
+ MIDDLEWARE = Faraday::Builder.new do |builder|
33
+ builder.request :url_encoded
34
+ builder.response :mashify
35
+ builder.response :json_normalizer
36
+ builder.response :parse_json
37
+ builder.use :debugger
38
+ builder.use :instrumentation
39
+ builder.adapter Faraday.default_adapter
40
+ end
41
+
42
+ class << self
43
+
44
+ # Configuration options
45
+ # @return [Hash]
46
+ def options
47
+ Hash[ActiveCampaign::Configurable.keys.map{|key| [key, send(key)]}]
48
+ end
49
+
50
+ # Default API endpoint from ENV or {API_ENDPOINT}
51
+ # @return [String]
52
+ def api_endpoint
53
+ ENV['ACTIVE_CAMPAIGN_API_ENDPOINT'] || "#{API_ENDPOINT}/#{api_path}"
54
+ end
55
+
56
+ # Default API endpoint from ENV or {API_ENDPOINT}
57
+ # @return [String]
58
+ def api_path
59
+ API_PATH
60
+ end
61
+
62
+ # Default pagination preference from ENV
63
+ # @return [String]
64
+ def auto_paginate
65
+ ENV['ACTIVE_CAMPAIGN_AUTO_PAGINATE']
66
+ end
67
+
68
+ # Default options for Faraday::Connection
69
+ # @return [Hash]
70
+ def connection_options
71
+ {
72
+ :headers => {
73
+ :accept => default_media_type,
74
+ :user_agent => user_agent
75
+ }
76
+ }
77
+ end
78
+
79
+ # Default media type from ENV or {MEDIA_TYPE}
80
+ # @return [String]
81
+ def default_media_type
82
+ ENV['ACTIVE_CAMPAIGN_DEFAULT_MEDIA_TYPE'] || MEDIA_TYPE
83
+ end
84
+
85
+ # Default GitHub username for Basic Auth from ENV
86
+ # @return [String]
87
+ def api_key
88
+ ENV['ACTIVE_CAMPAIGN_API_KEY']
89
+ end
90
+
91
+ def debug
92
+ false
93
+ end
94
+
95
+ # Default middleware stack for Faraday::Connection
96
+ # from {MIDDLEWARE}
97
+ # @return [String]
98
+ def middleware
99
+ MIDDLEWARE
100
+ end
101
+
102
+ # Default media api_output from ENV or {API_OUTPUT}
103
+ # @return [String]
104
+ def api_output
105
+ ENV['ACTIVE_CAMPAIGN_API_OUTPUT'] || API_OUTPUT
106
+ end
107
+
108
+ # Default pagination page size from ENV
109
+ # @return [Fixnum] Page size
110
+ def per_page
111
+ page_size = ENV['ACTIVE_CAMPAIGN_PER_PAGE']
112
+
113
+ page_size.to_i if page_size
114
+ end
115
+
116
+ # Default proxy server URI for Faraday connection from ENV
117
+ # @return [String]
118
+ def proxy
119
+ ENV['ACTIVE_CAMPAIGN_PROXY']
120
+ end
121
+
122
+ # Default User-Agent header string from ENV or {USER_AGENT}
123
+ # @return [String]
124
+ def user_agent
125
+ ENV['ACTIVE_CAMPAIGN_USER_AGENT'] || USER_AGENT
126
+ end
127
+
128
+ # Default web endpoint from ENV or {WEB_ENDPOINT}
129
+ # @return [String]
130
+ def web_endpoint
131
+ ENV['ACTIVE_CAMPAIGN_WEB_ENDPOINT'] || WEB_ENDPOINT
132
+ end
133
+ end
134
+ end
135
+ end
@@ -1,16 +1,54 @@
1
1
  module ActiveCampaign
2
- # Custom error class for rescuing from all ActiveCampaign errors
2
+ # Custom error class for rescuing from all GitHub errors
3
3
  class Error < StandardError
4
+
5
+ # Returns the appropriate ActiveCampaign::Error sublcass based
6
+ # on status and response message
7
+ #
8
+ # @param [Hash]
9
+ # @returns [ActiveCampaign::Error]
10
+ def self.from_response(response)
11
+ status = response[:status].to_i
12
+ body = response[:body].to_s
13
+
14
+ if klass = case status
15
+ when 400 then ActiveCampaign::BadRequest
16
+ when 401 then ActiveCampaign::Unauthorized
17
+ when 403
18
+ if body =~ /rate limit exceeded/i
19
+ ActiveCampaign::TooManyRequests
20
+ elsif body =~ /login attempts exceeded/i
21
+ ActiveCampaign::TooManyLoginAttempts
22
+ else
23
+ ActiveCampaign::Forbidden
24
+ end
25
+ when 404 then ActiveCampaign::NotFound
26
+ when 406 then ActiveCampaign::NotAcceptable
27
+ when 422 then ActiveCampaign::UnprocessableEntity
28
+ when 500 then ActiveCampaign::InternalServerError
29
+ when 501 then ActiveCampaign::NotImplemented
30
+ when 502 then ActiveCampaign::BadGateway
31
+ when 503 then ActiveCampaign::ServiceUnavailable
32
+ end
33
+ klass.new(response)
34
+ end
35
+ end
36
+
4
37
  def initialize(response=nil)
5
38
  @response = response
6
39
  super(build_error_message)
7
40
  end
8
41
 
9
- def response_body
10
- @response_body ||=
42
+ private
43
+
44
+ def data
45
+ @data ||=
11
46
  if (body = @response[:body]) && !body.empty?
12
- if body.is_a?(String)
13
- MultiJson.load(body, :symbolize_keys => true)
47
+ if body.is_a?(String) &&
48
+ @response[:response_headers] &&
49
+ @response[:response_headers][:content_type] =~ /json/
50
+
51
+ Sawyer::Agent.serializer.decode(body)
14
52
  else
15
53
  body
16
54
  end
@@ -19,50 +57,78 @@ module ActiveCampaign
19
57
  end
20
58
  end
21
59
 
22
- private
60
+ def response_message
61
+ case data
62
+ when Hash
63
+ data[:message]
64
+ when String
65
+ data
66
+ end
67
+ end
68
+
69
+ def response_error
70
+ "Error: #{data[:error]}" if data.is_a?(Hash) && data[:error]
71
+ end
72
+
73
+ def response_error_summary
74
+ return nil unless data.is_a?(Hash) && !Array(data[:errors]).empty?
75
+
76
+ summary = "\nError summary:\n"
77
+ summary << data[:errors].map do |hash|
78
+ hash.map { |k,v| " #{k}: #{v}" }
79
+ end.join("\n")
80
+
81
+ summary
82
+ end
23
83
 
24
84
  def build_error_message
25
- return nil if @response.nil?
85
+ return nil if @response.nil?
26
86
 
27
- message = if response_body
28
- ": #{response_body[:error] || response_body[:message] || ''}"
29
- else
30
- ''
31
- end
32
- errors = unless message.empty?
33
- response_body[:errors] ? ": #{response_body[:errors].map{|e|e[:message]}.join(', ')}" : ''
34
- end
35
- "#{@response[:method].to_s.upcase} #{@response[:url].to_s}: #{@response[:status]}#{message}#{errors}"
87
+ message = "#{@response[:method].to_s.upcase} "
88
+ message << "#{@response[:url].to_s}: "
89
+ message << "#{@response[:status]} - "
90
+ message << "#{response_message}" unless response_message.nil?
91
+ message << "#{response_error}" unless response_error.nil?
92
+ message << "#{response_error_summary}" unless response_error_summary.nil?
93
+ message
36
94
  end
37
95
  end
38
96
 
39
- # Raised when ActiveCampaign returns a 400 HTTP status code
97
+ # Raised when GitHub returns a 400 HTTP status code
40
98
  class BadRequest < Error; end
41
99
 
42
- # Raised when ActiveCampaign returns a 401 HTTP status code
100
+ # Raised when GitHub returns a 401 HTTP status code
43
101
  class Unauthorized < Error; end
44
102
 
45
- # Raised when ActiveCampaign returns a 403 HTTP status code
103
+ # Raised when GitHub returns a 403 HTTP status code
46
104
  class Forbidden < Error; end
47
105
 
48
- # Raised when ActiveCampaign returns a 404 HTTP status code
106
+ # Raised when GitHub returns a 403 HTTP status code
107
+ # and body matches 'rate limit exceeded'
108
+ class TooManyRequests < Forbidden; end
109
+
110
+ # Raised when GitHub returns a 403 HTTP status code
111
+ # and body matches 'login attempts exceeded'
112
+ class TooManyLoginAttempts < Forbidden; end
113
+
114
+ # Raised when GitHub returns a 404 HTTP status code
49
115
  class NotFound < Error; end
50
116
 
51
- # Raised when ActiveCampaign returns a 406 HTTP status code
117
+ # Raised when GitHub returns a 406 HTTP status code
52
118
  class NotAcceptable < Error; end
53
119
 
54
- # Raised when ActiveCampaign returns a 422 HTTP status code
120
+ # Raised when GitHub returns a 422 HTTP status code
55
121
  class UnprocessableEntity < Error; end
56
122
 
57
- # Raised when ActiveCampaign returns a 500 HTTP status code
123
+ # Raised when GitHub returns a 500 HTTP status code
58
124
  class InternalServerError < Error; end
59
125
 
60
- # Raised when ActiveCampaign returns a 501 HTTP status code
126
+ # Raised when GitHub returns a 501 HTTP status code
61
127
  class NotImplemented < Error; end
62
128
 
63
- # Raised when ActiveCampaign returns a 502 HTTP status code
129
+ # Raised when GitHub returns a 502 HTTP status code
64
130
  class BadGateway < Error; end
65
131
 
66
- # Raised when ActiveCampaign returns a 503 HTTP status code
132
+ # Raised when GitHub returns a 503 HTTP status code
67
133
  class ServiceUnavailable < Error; end
68
- end
134
+ end
@@ -0,0 +1,42 @@
1
+ require 'faraday'
2
+ require 'forwardable'
3
+
4
+ module ActiveCampaign
5
+ module Response
6
+ class Debugger < Faraday::Response::Middleware
7
+ extend Forwardable
8
+
9
+ def initialize(app, logger = nil)
10
+ super(app)
11
+ @logger = logger || begin
12
+ require 'logger'
13
+ ::Logger.new(STDOUT)
14
+ end
15
+ end
16
+
17
+ def_delegators :@logger, :debug, :info, :warn, :error, :fatal
18
+
19
+ def call(env)
20
+ info("#{env[:method]} #{env[:url].to_s}")
21
+ debug('request') { dump_headers(env[:request_headers]) + "\nbody: #{env[:body]}" }
22
+ super
23
+ end
24
+
25
+ def on_complete(env)
26
+ info('Status') { env[:status].to_s }
27
+ debug('response-head') { dump_headers env[:response_headers] }
28
+ debug('response-body') { env[:body] }
29
+ end
30
+
31
+ private
32
+
33
+ def dump_headers(headers)
34
+ headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ Faraday.register_middleware :middleware, debugger: lambda {
41
+ ActiveCampaign::Response::Debugger
42
+ }
@@ -0,0 +1,33 @@
1
+ module ActiveCampaign
2
+ module Response
3
+ # Public: Instruments requests using Active Support.
4
+ #
5
+ # Measures time spent only for synchronous requests.
6
+ #
7
+ # Examples
8
+ #
9
+ # ActiveSupport::Notifications.subscribe('request.faraday') do |name, starts, ends, _, env|
10
+ # url = env[:url]
11
+ # http_method = env[:method].to_s.upcase
12
+ # duration = ends - starts
13
+ # $stderr.puts '[%s] %s %s (%.3f s)' % [url.host, http_method, url.request_uri, duration]
14
+ # end
15
+ class Instrumentation < Faraday::Middleware
16
+ dependency 'active_support/notifications'
17
+
18
+ def initialize(app, options = {})
19
+ super(app)
20
+ @name = options.fetch(:name, 'request.faraday')
21
+ end
22
+
23
+ def call(env)
24
+ ActiveSupport::Notifications.instrument(@name, env) do
25
+ @app.call(env)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ Faraday.register_middleware :middleware, instrumentation: lambda {
32
+ ActiveCampaign::Response::Instrumentation
33
+ }
@@ -0,0 +1,49 @@
1
+ require 'faraday'
2
+ require 'faraday/response'
3
+
4
+ module ActiveCampaign
5
+ module Response
6
+ class JsonNormalizer < Faraday::Response::Middleware
7
+ def initialize(app, logger = nil)
8
+ super(app)
9
+ end
10
+
11
+ def call(environment)
12
+ @app.call(environment).on_complete do |env|
13
+ if env[:body].is_a?(Hash)
14
+ env[:body] = normalize(env[:body])
15
+ end
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def normalize(response)
22
+ keys, values = keys_values(response)
23
+
24
+ if keys.all?{|key| is_numeric?(key) }
25
+ response[:results] = values
26
+ keys.each do |key|
27
+ response.delete(key)
28
+ end
29
+ end
30
+
31
+ response
32
+ end
33
+
34
+ def is_numeric?(string)
35
+ string.to_s.match(/\A[+-]?\d+\Z/) == nil ? false : true
36
+ end
37
+
38
+ def keys_values(response)
39
+ results = results(response)
40
+ [results.keys, results.values]
41
+ end
42
+
43
+ def results(response)
44
+ response.reject{|k,v| %w(result_code result_message result_output).include?(k) }
45
+ end
46
+ end
47
+ end
48
+ end
49
+ Faraday.register_middleware :response, json_normalizer: lambda { ActiveCampaign::Response::JsonNormalizer }
@@ -0,0 +1,40 @@
1
+ require 'faraday'
2
+
3
+ module ActiveCampaign
4
+ module Response
5
+
6
+ # Public: Converts parsed response bodies to a Hashie::Mash if they were of
7
+ # Hash or Array type.
8
+ class Mashify < Faraday::Response::Middleware
9
+ attr_accessor :mash_class
10
+
11
+ class << self
12
+ attr_accessor :mash_class
13
+ end
14
+
15
+ dependency do
16
+ require 'hashie/mash'
17
+ self.mash_class = ::Hashie::Mash
18
+ end
19
+
20
+ def initialize(app = nil, options = {})
21
+ super(app)
22
+ self.mash_class = options[:mash_class] || self.class.mash_class
23
+ end
24
+
25
+ def parse(body)
26
+ case body
27
+ when Hash
28
+ mash_class.new(body)
29
+ when Array
30
+ body.map { |item| parse(item) }
31
+ else
32
+ body
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ Faraday.register_middleware :response, mashify: lambda {
39
+ ActiveCampaign::Response::Mashify
40
+ }