koala 2.5.0 → 3.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -3
  3. data/Gemfile +1 -1
  4. data/ISSUE_TEMPLATE +25 -0
  5. data/PULL_REQUEST_TEMPLATE +11 -0
  6. data/changelog.md +66 -4
  7. data/code_of_conduct.md +64 -12
  8. data/koala.gemspec +3 -0
  9. data/lib/koala/api/batch_operation.rb +3 -6
  10. data/lib/koala/api/{graph_api.rb → graph_api_methods.rb} +13 -102
  11. data/lib/koala/api/graph_batch_api.rb +112 -65
  12. data/lib/koala/api/graph_collection.rb +19 -12
  13. data/lib/koala/api/graph_error_checker.rb +1 -1
  14. data/lib/koala/api.rb +49 -25
  15. data/lib/koala/configuration.rb +49 -0
  16. data/lib/koala/errors.rb +1 -1
  17. data/lib/koala/http_service/multipart_request.rb +6 -10
  18. data/lib/koala/http_service/request.rb +135 -0
  19. data/lib/koala/http_service/response.rb +6 -4
  20. data/lib/koala/http_service/uploadable_io.rb +0 -4
  21. data/lib/koala/http_service.rb +18 -76
  22. data/lib/koala/oauth.rb +7 -7
  23. data/lib/koala/realtime_updates.rb +26 -21
  24. data/lib/koala/test_users.rb +9 -8
  25. data/lib/koala/version.rb +1 -1
  26. data/lib/koala.rb +6 -8
  27. data/readme.md +50 -109
  28. data/spec/cases/api_spec.rb +99 -69
  29. data/spec/cases/configuration_spec.rb +11 -0
  30. data/spec/cases/graph_api_batch_spec.rb +73 -42
  31. data/spec/cases/graph_api_spec.rb +15 -29
  32. data/spec/cases/graph_collection_spec.rb +47 -34
  33. data/spec/cases/graph_error_checker_spec.rb +6 -1
  34. data/spec/cases/http_service/request_spec.rb +242 -0
  35. data/spec/cases/http_service/response_spec.rb +24 -0
  36. data/spec/cases/http_service_spec.rb +102 -296
  37. data/spec/cases/koala_spec.rb +7 -5
  38. data/spec/cases/oauth_spec.rb +40 -1
  39. data/spec/cases/realtime_updates_spec.rb +51 -13
  40. data/spec/cases/test_users_spec.rb +56 -2
  41. data/spec/cases/uploadable_io_spec.rb +31 -31
  42. data/spec/fixtures/cat.m4v +0 -0
  43. data/spec/fixtures/facebook_data.yml +4 -6
  44. data/spec/fixtures/mock_facebook_responses.yml +29 -69
  45. data/spec/fixtures/vcr_cassettes/app_test_accounts.yml +97 -0
  46. data/spec/integration/graph_collection_spec.rb +8 -5
  47. data/spec/spec_helper.rb +2 -2
  48. data/spec/support/graph_api_shared_examples.rb +143 -336
  49. data/spec/support/koala_test.rb +8 -10
  50. data/spec/support/mock_http_service.rb +9 -9
  51. data/spec/support/uploadable_io_shared_examples.rb +4 -4
  52. metadata +31 -11
  53. data/.autotest +0 -12
  54. data/Guardfile +0 -6
  55. data/autotest/discover.rb +0 -1
  56. data/lib/koala/api/rest_api.rb +0 -135
  57. data/spec/support/rest_api_shared_examples.rb +0 -168
@@ -1,16 +1,18 @@
1
- require 'koala/api'
2
- require 'koala/api/batch_operation'
1
+ require "koala/api"
2
+ require "koala/api/batch_operation"
3
3
 
4
4
  module Koala
5
5
  module Facebook
6
6
  # @private
7
- class GraphBatchAPI < API
7
+ class GraphBatchAPI
8
8
  # inside a batch call we can do anything a regular Graph API can do
9
9
  include GraphAPIMethods
10
10
 
11
+ # Limits from @see https://developers.facebook.com/docs/marketing-api/batch-requests/v2.8
12
+ MAX_CALLS = 50
13
+
11
14
  attr_reader :original_api
12
15
  def initialize(api)
13
- super(api.access_token, api.app_secret)
14
16
  @original_api = api
15
17
  end
16
18
 
@@ -18,7 +20,9 @@ module Koala
18
20
  @batch_calls ||= []
19
21
  end
20
22
 
21
- def graph_call_in_batch(path, args = {}, verb = "get", options = {}, &post_processing)
23
+ # Enqueue a call into the batch for later processing.
24
+ # See API#graph_call
25
+ def graph_call(path, args = {}, verb = "get", options = {}, &post_processing)
22
26
  # normalize options for consistency
23
27
  options = Koala::Utils.symbolize_hash(options)
24
28
 
@@ -34,74 +38,117 @@ module Koala
34
38
  nil # batch operations return nothing immediately
35
39
  end
36
40
 
37
- # redefine the graph_call method so we can use this API inside the batch block
38
- # just like any regular Graph API
39
- alias_method :graph_call_outside_batch, :graph_call
40
- alias_method :graph_call, :graph_call_in_batch
41
-
42
- # execute the queued batch calls
41
+ # execute the queued batch calls. limits it to 50 requests per call.
42
+ # NOTE: if you use `name` and JsonPath references, you should ensure to call `execute` for each
43
+ # co-reference group and that the group size is not greater than the above limits.
43
44
  def execute(http_options = {})
44
- return [] unless batch_calls.length > 0
45
- # Turn the call args collected into what facebook expects
46
- args = {}
47
- args["batch"] = JSON.dump(batch_calls.map { |batch_op|
48
- args.merge!(batch_op.files) if batch_op.files
49
- batch_op.to_batch_params(access_token, app_secret)
50
- })
45
+ return [] if batch_calls.empty?
46
+
47
+ batch_results = []
48
+ batch_calls.each_slice(MAX_CALLS) do |batch|
49
+ # Turn the call args collected into what facebook expects
50
+ args = {"batch" => batch_args(batch)}
51
+ batch.each do |call|
52
+ args.merge!(call.files || {})
53
+ end
51
54
 
52
- batch_result = graph_call_outside_batch('/', args, 'post', http_options) do |response|
53
- unless response
54
- # Facebook sometimes reportedly returns an empty body at times
55
- # see https://github.com/arsduo/koala/issues/184
56
- raise BadFacebookResponse.new(200, '', "Facebook returned an empty body")
55
+ original_api.graph_call("/", args, "post", http_options) do |response|
56
+ raise bad_response if response.nil?
57
+
58
+ batch_results += generate_results(response, batch)
57
59
  end
60
+ end
61
+
62
+ batch_results
63
+ end
64
+
65
+ def generate_results(response, batch)
66
+ index = 0
67
+ response.map do |call_result|
68
+ batch_op = batch[index]
69
+ index += 1
70
+ post_process = batch_op.post_processing
58
71
 
59
- # map the results with post-processing included
60
- index = 0 # keep compat with ruby 1.8 - no with_index for map
61
- response.map do |call_result|
62
- # Get the options hash
63
- batch_op = batch_calls[index]
64
- index += 1
65
-
66
- raw_result = nil
67
- if call_result
68
- parsed_headers = if call_result.has_key?('headers')
69
- call_result['headers'].inject({}) { |headers, h| headers[h['name']] = h['value']; headers}
70
- else
71
- {}
72
- end
73
-
74
- if (error = check_response(call_result['code'], call_result['body'].to_s, parsed_headers))
75
- raw_result = error
76
- else
77
- # (see note in regular api method about JSON parsing)
78
- body = JSON.load("[#{call_result['body'].to_s}]")[0]
79
-
80
- # Get the HTTP component they want
81
- raw_result = case batch_op.http_options[:http_component]
82
- when :status
83
- call_result["code"].to_i
84
- when :headers
85
- # facebook returns the headers as an array of k/v pairs, but we want a regular hash
86
- parsed_headers
87
- else
88
- body
89
- end
90
- end
91
- end
92
-
93
- # turn any results that are pageable into GraphCollections
94
- # and pass to post-processing callback if given
95
- result = GraphCollection.evaluate(raw_result, @original_api)
96
- if batch_op.post_processing
97
- batch_op.post_processing.call(result)
98
- else
99
- result
100
- end
72
+ # turn any results that are pageable into GraphCollections
73
+ result = result_from_response(call_result, batch_op)
74
+
75
+ # and pass to post-processing callback if given
76
+ if post_process
77
+ post_process.call(result)
78
+ else
79
+ result
101
80
  end
102
81
  end
103
82
  end
104
83
 
84
+ def bad_response
85
+ # Facebook sometimes reportedly returns an empty body at times
86
+ BadFacebookResponse.new(200, "", "Facebook returned an empty body")
87
+ end
88
+
89
+ def result_from_response(response, options)
90
+ return nil if response.nil?
91
+
92
+ headers = headers_from_response(response)
93
+ error = error_from_response(response, headers)
94
+ component = options.http_options[:http_component]
95
+
96
+ error || desired_component(
97
+ component: component,
98
+ response: response,
99
+ headers: headers
100
+ )
101
+ end
102
+
103
+ def headers_from_response(response)
104
+ headers = response.fetch("headers", [])
105
+
106
+ headers.inject({}) do |compiled_headers, header|
107
+ compiled_headers.merge(header.fetch("name") => header.fetch("value"))
108
+ end
109
+ end
110
+
111
+ def error_from_response(response, headers)
112
+ code = response["code"]
113
+ body = response["body"].to_s
114
+
115
+ GraphErrorChecker.new(code, body, headers).error_if_appropriate
116
+ end
117
+
118
+ def batch_args(calls_for_batch)
119
+ calls = calls_for_batch.map do |batch_op|
120
+ batch_op.to_batch_params(access_token, app_secret)
121
+ end
122
+
123
+ JSON.dump calls
124
+ end
125
+
126
+ def json_body(response)
127
+ # quirks_mode is needed because Facebook sometimes returns a raw true or false value --
128
+ # in Ruby 2.4 we can drop that.
129
+ JSON.parse(response.fetch("body"), quirks_mode: true)
130
+ end
131
+
132
+ def desired_component(component:, response:, headers:)
133
+ result = Koala::HTTPService::Response.new(response['status'], response['body'], headers)
134
+
135
+ # Get the HTTP component they want
136
+ case component
137
+ when :status then response["code"].to_i
138
+ # facebook returns the headers as an array of k/v pairs, but we want a regular hash
139
+ when :headers then headers
140
+ # (see note in regular api method about JSON parsing)
141
+ else GraphCollection.evaluate(result, original_api)
142
+ end
143
+ end
144
+
145
+ def access_token
146
+ original_api.access_token
147
+ end
148
+
149
+ def app_secret
150
+ original_api.app_secret
151
+ end
105
152
  end
106
153
  end
107
154
  end
@@ -16,30 +16,41 @@ module Koala
16
16
  attr_reader :api
17
17
  # The entire raw response from Facebook.
18
18
  attr_reader :raw_response
19
+ # The headers from the Facebook response
20
+ attr_reader :headers
19
21
 
20
22
  # Initialize the array of results and store various additional paging-related information.
21
23
  #
22
- # @param response the response from Facebook (a hash whose "data" key is an array)
24
+ # @param [Koala::HTTPService::Response] response object wrapping the raw Facebook response
23
25
  # @param api the Graph {Koala::Facebook::API API} instance to use to make calls
24
26
  # (usually the API that made the original call).
25
27
  #
26
- # @return [Koala::Facebook::GraphCollection] an initialized GraphCollection
28
+ # @return [Koala::Facebook::API::GraphCollection] an initialized GraphCollection
27
29
  # whose paging, summary, raw_response, and api attributes are populated.
28
30
  def initialize(response, api)
29
- super response["data"]
30
- @paging = response["paging"]
31
- @summary = response["summary"]
32
- @raw_response = response
31
+ super response.data["data"]
32
+ @paging = response.data["paging"]
33
+ @summary = response.data["summary"]
34
+ @raw_response = response.data
33
35
  @api = api
36
+ @headers = response.headers
34
37
  end
35
38
 
36
39
  # @private
37
40
  # Turn the response into a GraphCollection if they're pageable;
38
- # if not, return the original response.
41
+ # if not, return the data of the original response.
39
42
  # The Ads API (uniquely so far) returns a hash rather than an array when queried
40
43
  # with get_connections.
41
44
  def self.evaluate(response, api)
42
- response.is_a?(Hash) && response["data"].is_a?(Array) ? self.new(response, api) : response
45
+ return nil if response.nil?
46
+
47
+ is_pageable?(response) ? self.new(response, api) : response.data
48
+ end
49
+
50
+ # response will always be an instance of Koala::HTTPService::Response
51
+ # since that is what we get from Koala::Facebook::API#api
52
+ def self.is_pageable?(response)
53
+ response.data.is_a?(Hash) && response.data["data"].is_a?(Array)
43
54
  end
44
55
 
45
56
  # Retrieve the next page of results.
@@ -113,9 +124,5 @@ module Koala
113
124
  end
114
125
  end
115
126
  end
116
-
117
- # @private
118
- # legacy support for when GraphCollection lived directly under Koala::Facebook
119
- GraphCollection = API::GraphCollection
120
127
  end
121
128
  end
@@ -61,7 +61,7 @@ module Koala
61
61
  # Normally, we start with the response body. If it isn't valid JSON, we start with an empty
62
62
  # hash and fill it with error data.
63
63
  @response_hash ||= begin
64
- JSON.load(body)
64
+ JSON.parse(body)
65
65
  rescue JSON::ParserError
66
66
  {}
67
67
  end
data/lib/koala/api.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # graph_batch_api and legacy are required at the bottom, since they depend on API being defined
2
- require 'koala/api/graph_api'
3
- require 'koala/api/rest_api'
2
+ require 'koala/api/graph_api_methods'
3
+ require 'koala/api/graph_collection'
4
4
  require 'openssl'
5
5
 
6
6
  module Koala
@@ -15,7 +15,7 @@ module Koala
15
15
  # https://developers.facebook.com/docs/graph-api/securing-requests/)
16
16
  # @note If no access token is provided, you can only access some public information.
17
17
  # @return [Koala::Facebook::API] the API client
18
- def initialize(access_token = nil, app_secret = nil)
18
+ def initialize(access_token = Koala.config.access_token, app_secret = Koala.config.app_secret)
19
19
  @access_token = access_token
20
20
  @app_secret = app_secret
21
21
  end
@@ -23,13 +23,50 @@ module Koala
23
23
  attr_reader :access_token, :app_secret
24
24
 
25
25
  include GraphAPIMethods
26
- include RestAPIMethods
26
+
27
+ # Make a call directly to the Graph API.
28
+ # (See any of the other methods for example invocations.)
29
+ #
30
+ # @param path the Graph API path to query (no leading / needed)
31
+ # @param args (see #get_object)
32
+ # @param verb the type of HTTP request to make (get, post, delete, etc.)
33
+ # @options (see #get_object)
34
+ #
35
+ # @yield response when making a batch API call, you can pass in a block
36
+ # that parses the results, allowing for cleaner code.
37
+ # The block's return value is returned in the batch results.
38
+ # See the code for {#get_picture} for examples.
39
+ # (Not needed in regular calls; you'll probably rarely use this.)
40
+ #
41
+ # @raise [Koala::Facebook::APIError] if Facebook returns an error
42
+ #
43
+ # @return the result from Facebook
44
+ def graph_call(path, args = {}, verb = "get", options = {}, &post_processing)
45
+ # enable appsecret_proof by default
46
+ options = {:appsecret_proof => true}.merge(options) if @app_secret
47
+ response = api(path, args, verb, options)
48
+
49
+ error = GraphErrorChecker.new(response.status, response.body, response.headers).error_if_appropriate
50
+ raise error if error
51
+
52
+ # if we want a component other than the body (e.g. redirect header for images), provide that
53
+ http_component = options[:http_component]
54
+ desired_data = if options[:http_component]
55
+ http_component == :response ? response : response.send(http_component)
56
+ else
57
+ # turn this into a GraphCollection if it's pageable
58
+ API::GraphCollection.evaluate(response, self)
59
+ end
60
+
61
+ # now process as appropriate for the given call (get picture header, etc.)
62
+ post_processing ? post_processing.call(desired_data) : desired_data
63
+ end
64
+
27
65
 
28
66
  # Makes a request to the appropriate Facebook API.
29
67
  # @note You'll rarely need to call this method directly.
30
68
  #
31
69
  # @see GraphAPIMethods#graph_call
32
- # @see RestAPIMethods#rest_call
33
70
  #
34
71
  # @param path the server path for this request (leading / is prepended if not present)
35
72
  # @param args arguments to be sent to Facebook
@@ -44,14 +81,10 @@ module Koala
44
81
  # @option options [Boolean] :beta use Facebook's beta tier
45
82
  # @option options [Boolean] :use_ssl force SSL for this request, even if it's tokenless.
46
83
  # (All API requests with access tokens use SSL.)
47
- # @param error_checking_block a block to evaluate the response status for additional JSON-encoded errors
48
- #
49
- # @yield The response for evaluation
50
- #
51
84
  # @raise [Koala::Facebook::ServerError] if Facebook returns an error (response status >= 500)
52
85
  #
53
- # @return the body of the response from Facebook (unless another http_component is requested)
54
- def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
86
+ # @return a Koala::HTTPService::Response object representing the returned Facebook data
87
+ def api(path, args = {}, verb = "get", options = {})
55
88
  # we make a copy of args so the modifications (added access_token & appsecret_proof)
56
89
  # do not affect the received argument
57
90
  args = args.dup
@@ -60,6 +93,7 @@ module Koala
60
93
  # This is explicitly needed in batch requests so GraphCollection
61
94
  # results preserve any specific access tokens provided
62
95
  args["access_token"] ||= @access_token || @app_access_token if @access_token || @app_access_token
96
+
63
97
  if options.delete(:appsecret_proof) && args["access_token"] && @app_secret
64
98
  args["appsecret_proof"] = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), @app_secret, args["access_token"])
65
99
  end
@@ -77,18 +111,7 @@ module Koala
77
111
  raise Koala::Facebook::ServerError.new(result.status.to_i, result.body)
78
112
  end
79
113
 
80
- yield result if error_checking_block
81
-
82
- # if we want a component other than the body (e.g. redirect header for images), return that
83
- if component = options[:http_component]
84
- component == :response ? result : result.send(options[:http_component])
85
- else
86
- # parse the body as JSON and run it through the error checker (if provided)
87
- # Note: Facebook sometimes sends results like "true" and "false", which are valid[RFC7159]
88
- # but unsupported by Ruby's stdlib[RFC4627] and cause JSON.load to fail -- so we account for
89
- # that by wrapping the result in []
90
- JSON.load("[#{result.body.to_s}]")[0]
91
- end
114
+ result
92
115
  end
93
116
 
94
117
  private
@@ -114,8 +137,9 @@ module Koala
114
137
  def preserve_form_arguments?(options)
115
138
  options[:format] == :json || options[:preserve_form_arguments] || Koala.config.preserve_form_arguments
116
139
  end
140
+
141
+ def check_response(http_status, body, headers)
142
+ end
117
143
  end
118
144
  end
119
145
  end
120
-
121
- require 'koala/api/graph_batch_api'
@@ -0,0 +1,49 @@
1
+ # Global configuration for Koala.
2
+ class Koala::Configuration
3
+ # The default access token to be used if none is otherwise supplied.
4
+ attr_accessor :access_token
5
+
6
+ # The default app secret value to be used if none is otherwise supplied.
7
+ attr_accessor :app_secret
8
+
9
+ # The default application ID to use if none is otherwise supplied.
10
+ attr_accessor :app_id
11
+
12
+ # The default app access token to be used if none is otherwise supplied.
13
+ attr_accessor :app_access_token
14
+
15
+ # The default API version to use if none is otherwise specified.
16
+ attr_accessor :api_version
17
+
18
+ # The default value to use for the oauth_callback_url if no other is provided.
19
+ attr_accessor :oauth_callback_url
20
+
21
+ # Whether to preserve arrays in arguments, which are expected by certain FB APIs (see the ads API
22
+ # in particular, https://developers.facebook.com/docs/marketing-api/adgroup/v2.4)
23
+ attr_accessor :preserve_form_arguments
24
+
25
+ # The server to use for Graph API requests
26
+ attr_accessor :graph_server
27
+
28
+ # The server to use when constructing dialog URLs.
29
+ attr_accessor :dialog_host
30
+
31
+ # Certain Facebook services (beta, video) require you to access different
32
+ # servers. If you're using your own servers, for instance, for a proxy,
33
+ # you can change both the matcher (what value to change when updating the URL) and the
34
+ # replacement values (what to add).
35
+ #
36
+ # So, for instance, to use the beta stack, we match on .facebook and change it to .beta.facebook.
37
+ # If you're talking to fbproxy.mycompany.com, you could set up beta.fbproxy.mycompany.com for
38
+ # FB's beta tier, and set the matcher to /\.fbproxy/ and the beta_replace to '.beta.fbproxy'.
39
+ attr_accessor :host_path_matcher
40
+ attr_accessor :video_replace
41
+ attr_accessor :beta_replace
42
+
43
+ def initialize
44
+ # Default to our default values.
45
+ Koala::HTTPService::DEFAULT_SERVERS.each_pair do |key, value|
46
+ self.public_send("#{key}=", value)
47
+ end
48
+ end
49
+ end
data/lib/koala/errors.rb CHANGED
@@ -50,7 +50,7 @@ module Koala
50
50
  else
51
51
  unless error_info
52
52
  begin
53
- error_info = JSON.load(response_body)['error'] if response_body
53
+ error_info = JSON.parse(response_body)['error'] if response_body
54
54
  rescue
55
55
  end
56
56
  error_info ||= {}
@@ -1,6 +1,6 @@
1
1
  require 'faraday'
2
2
 
3
- module Koala
3
+ module Koala
4
4
  module HTTPService
5
5
  class MultipartRequest < Faraday::Request::Multipart
6
6
  # Facebook expects nested parameters to be passed in a certain way
@@ -8,15 +8,15 @@ module Koala
8
8
  # Faraday needs two changes to make that work:
9
9
  # 1) [] need to be escaped (e.g. params[foo]=bar ==> params%5Bfoo%5D=bar)
10
10
  # 2) such messages need to be multipart-encoded
11
-
11
+
12
12
  self.mime_type = 'multipart/form-data'.freeze
13
-
13
+
14
14
  def process_request?(env)
15
15
  # if the request values contain any hashes or arrays, multipart it
16
16
  super || !!(env[:body].respond_to?(:values) && env[:body].values.find {|v| v.is_a?(Hash) || v.is_a?(Array)})
17
- end
18
-
19
-
17
+ end
18
+
19
+
20
20
  def process_params(params, prefix = nil, pieces = nil, &block)
21
21
  params.inject(pieces || []) do |all, (key, value)|
22
22
  key = "#{prefix}%5B#{key}%5D" if prefix
@@ -34,8 +34,4 @@ module Koala
34
34
  end
35
35
  end
36
36
  end
37
-
38
- # @private
39
- # legacy support for when MultipartRequest lived directly under Koala
40
- MultipartRequest = HTTPService::MultipartRequest
41
37
  end
@@ -0,0 +1,135 @@
1
+ module Koala
2
+ module HTTPService
3
+ class Request
4
+ attr_reader :raw_path, :raw_args, :raw_verb, :raw_options
5
+
6
+ # @param path the server path for this request
7
+ # @param args (see Koala::Facebook::API#api)
8
+ # @param verb the HTTP method to use.
9
+ # If not get or post, this will be turned into a POST request with the appropriate :method
10
+ # specified in the arguments.
11
+ # @param options various flags to indicate which server to use. (see Koala::Facebook::API#api)
12
+ # @param options
13
+ # @option options :video use the server designated for video uploads
14
+ # @option options :beta use the beta tier
15
+ # @option options :use_ssl force https, even if not needed
16
+ # @option options :json whether or not to send JSON to Facebook
17
+ def initialize(path:, verb:, args: {}, options: {})
18
+ @raw_path = path
19
+ @raw_args = args
20
+ @raw_verb = verb
21
+ @raw_options = options
22
+ end
23
+
24
+ # Determines which type of request to send to Facebook. Facebook natively accepts GETs and POSTs, for others we have to include the method in the post body.
25
+ #
26
+ # @return one of get or post
27
+ def verb
28
+ ["get", "post"].include?(raw_verb) ? raw_verb : "post"
29
+ end
30
+
31
+ # Determines the path to be requested on Facebook, incorporating an API version if specified.
32
+ #
33
+ # @return the original path, with API version if appropriate.
34
+ def path
35
+ # if an api_version is specified and the path does not already contain
36
+ # one, prepend it to the path
37
+ api_version = raw_options[:api_version] || Koala.config.api_version
38
+ if api_version && !path_contains_api_version?
39
+ begins_with_slash = raw_path[0] == "/"
40
+ divider = begins_with_slash ? "" : "/"
41
+ "/#{api_version}#{divider}#{raw_path}"
42
+ else
43
+ raw_path
44
+ end
45
+ end
46
+
47
+ # Determines any arguments to be sent in a POST body.
48
+ #
49
+ # @return {} for GET; the provided args for POST; those args with the method parameter for
50
+ # other values
51
+ def post_args
52
+ if raw_verb == "get"
53
+ {}
54
+ elsif raw_verb == "post"
55
+ args
56
+ else
57
+ args.merge(method: raw_verb)
58
+ end
59
+ end
60
+
61
+ def get_args
62
+ raw_verb == "get" ? args : {}
63
+ end
64
+
65
+ # Calculates a set of request options to pass to Faraday.
66
+ #
67
+ # @return a hash combining GET parameters (if appropriate), default options, and
68
+ # any specified for the request.
69
+ def options
70
+ # figure out our options for this request
71
+ add_ssl_options(
72
+ # for GETs, we pass the params to Faraday to encode
73
+ {params: get_args}.merge(HTTPService.http_options).merge(raw_options)
74
+ )
75
+ end
76
+
77
+ # Whether or not this request should use JSON.
78
+ #
79
+ # @return true or false
80
+ def json?
81
+ raw_options[:format] == :json
82
+ end
83
+
84
+ # The address of the appropriate Facebook server.
85
+ #
86
+ # @return a complete server address with protocol
87
+ def server
88
+ uri = "#{options[:use_ssl] ? "https" : "http"}://#{Koala.config.graph_server}"
89
+ # if we want to use the beta tier or the video server, make those substitutions as
90
+ # appropriate
91
+ replace_server_component(
92
+ replace_server_component(uri, options[:video], Koala.config.video_replace),
93
+ options[:beta],
94
+ Koala.config.beta_replace
95
+ )
96
+ end
97
+
98
+ protected
99
+
100
+ # The arguments to include in the request.
101
+ def args
102
+ raw_args.inject({}) do |hash, (key, value)|
103
+ # Resolve UploadableIOs into data Facebook can work with
104
+ hash.merge(key => value.is_a?(UploadableIO) ? value.to_upload_io : value)
105
+ end
106
+ end
107
+
108
+ def add_ssl_options(opts)
109
+ # require https if there's a token
110
+ return opts unless raw_args["access_token"]
111
+
112
+ {
113
+ use_ssl: true,
114
+ ssl: {verify: true}.merge(opts[:ssl] || {})
115
+ }.merge(opts)
116
+ end
117
+
118
+ # Determines whether a given path already contains an API version.
119
+ #
120
+ # @param path the URL path.
121
+ #
122
+ # @return true or false accordingly.
123
+ def path_contains_api_version?
124
+ # looks for "/$MAJOR[.$MINOR]/" in the path
125
+ match = /^\/?(v\d+(?:\.\d+)?)\//.match(raw_path)
126
+ !!(match && match[1])
127
+ end
128
+
129
+ def replace_server_component(host, condition_met, replacement)
130
+ return host unless condition_met
131
+ host.gsub(Koala.config.host_path_matcher, replacement)
132
+ end
133
+ end
134
+ end
135
+ end
@@ -9,10 +9,12 @@ module Koala
9
9
  @body = body
10
10
  @headers = headers
11
11
  end
12
+
13
+ def data
14
+ # quirks_mode is needed because Facebook sometimes returns a raw true or false value --
15
+ # in Ruby 2.4 we can drop that.
16
+ @data ||= JSON.parse(body, quirks_mode: true) unless body.empty?
17
+ end
12
18
  end
13
19
  end
14
-
15
- # @private
16
- # legacy support for when Response lived directly under Koala
17
- Response = HTTPService::Response
18
20
  end
@@ -181,8 +181,4 @@ module Koala
181
181
  end
182
182
  end
183
183
  end
184
-
185
- # @private
186
- # legacy support for when UploadableIO lived directly under Koala
187
- UploadableIO = HTTPService::UploadableIO
188
184
  end