openc-asana 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +4 -0
- data/.gitignore +13 -0
- data/.rspec +4 -0
- data/.rubocop.yml +11 -0
- data/.travis.yml +12 -0
- data/.yardopts +5 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +21 -0
- data/Guardfile +86 -0
- data/LICENSE.txt +21 -0
- data/README.md +355 -0
- data/Rakefile +65 -0
- data/examples/Gemfile +6 -0
- data/examples/Gemfile.lock +59 -0
- data/examples/api_token.rb +21 -0
- data/examples/cli_app.rb +25 -0
- data/examples/events.rb +38 -0
- data/examples/omniauth_integration.rb +54 -0
- data/lib/asana.rb +12 -0
- data/lib/asana/authentication.rb +8 -0
- data/lib/asana/authentication/oauth2.rb +42 -0
- data/lib/asana/authentication/oauth2/access_token_authentication.rb +51 -0
- data/lib/asana/authentication/oauth2/bearer_token_authentication.rb +32 -0
- data/lib/asana/authentication/oauth2/client.rb +50 -0
- data/lib/asana/authentication/token_authentication.rb +20 -0
- data/lib/asana/client.rb +124 -0
- data/lib/asana/client/configuration.rb +165 -0
- data/lib/asana/errors.rb +92 -0
- data/lib/asana/http_client.rb +155 -0
- data/lib/asana/http_client/environment_info.rb +53 -0
- data/lib/asana/http_client/error_handling.rb +103 -0
- data/lib/asana/http_client/response.rb +32 -0
- data/lib/asana/resource_includes/attachment_uploading.rb +33 -0
- data/lib/asana/resource_includes/collection.rb +68 -0
- data/lib/asana/resource_includes/event.rb +51 -0
- data/lib/asana/resource_includes/event_subscription.rb +14 -0
- data/lib/asana/resource_includes/events.rb +103 -0
- data/lib/asana/resource_includes/registry.rb +63 -0
- data/lib/asana/resource_includes/resource.rb +103 -0
- data/lib/asana/resource_includes/response_helper.rb +14 -0
- data/lib/asana/resources.rb +14 -0
- data/lib/asana/resources/attachment.rb +44 -0
- data/lib/asana/resources/project.rb +154 -0
- data/lib/asana/resources/story.rb +64 -0
- data/lib/asana/resources/tag.rb +120 -0
- data/lib/asana/resources/task.rb +300 -0
- data/lib/asana/resources/team.rb +55 -0
- data/lib/asana/resources/user.rb +72 -0
- data/lib/asana/resources/workspace.rb +91 -0
- data/lib/asana/ruby2_0_0_compatibility.rb +3 -0
- data/lib/asana/version.rb +5 -0
- data/lib/templates/index.js +8 -0
- data/lib/templates/resource.ejs +225 -0
- data/openc-asana.gemspec +32 -0
- data/package.json +7 -0
- metadata +200 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
module Asana
|
2
|
+
module Authentication
|
3
|
+
# Public: Represents an API token authentication mechanism.
|
4
|
+
class TokenAuthentication
|
5
|
+
def initialize(token)
|
6
|
+
@token = token
|
7
|
+
end
|
8
|
+
|
9
|
+
# Public: Configures a Faraday connection injecting its token as
|
10
|
+
# basic auth.
|
11
|
+
#
|
12
|
+
# builder - [Faraday::Connection] the Faraday connection instance.
|
13
|
+
#
|
14
|
+
# Returns nothing.
|
15
|
+
def configure(connection)
|
16
|
+
connection.basic_auth(@token, '')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/asana/client.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
require_relative 'authentication'
|
2
|
+
require_relative 'client/configuration'
|
3
|
+
require_relative 'resources'
|
4
|
+
|
5
|
+
module Asana
|
6
|
+
# Public: A client to interact with the Asana API. It exposes all the
|
7
|
+
# available resources of the Asana API in idiomatic Ruby.
|
8
|
+
#
|
9
|
+
# Examples
|
10
|
+
#
|
11
|
+
# # Authentication with an API token
|
12
|
+
# Asana::Client.new do |client|
|
13
|
+
# client.authentication :api_token, '...'
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # OAuth2 with a plain bearer token (doesn't support auto-refresh)
|
17
|
+
# Asana::Client.new do |client|
|
18
|
+
# client.authentication :oauth2, bearer_token: '...'
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # OAuth2 with a plain refresh token and client credentials
|
22
|
+
# Asana::Client.new do |client|
|
23
|
+
# client.authentication :oauth2,
|
24
|
+
# refresh_token: '...',
|
25
|
+
# client_id: '...',
|
26
|
+
# client_secret: '...',
|
27
|
+
# redirect_uri: '...'
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# # OAuth2 with an ::OAuth2::AccessToken object
|
31
|
+
# Asana::Client.new do |client|
|
32
|
+
# client.authentication :oauth2, my_oauth2_access_token_object
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# # Use a custom Faraday network adapter
|
36
|
+
# Asana::Client.new do |client|
|
37
|
+
# client.authentication ...
|
38
|
+
# client.adapter :typhoeus
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# # Use a custom user agent string
|
42
|
+
# Asana::Client.new do |client|
|
43
|
+
# client.authentication ...
|
44
|
+
# client.user_agent '...'
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# # Pass in custom configuration to the Faraday connection
|
48
|
+
# Asana::Client.new do |client|
|
49
|
+
# client.authentication ...
|
50
|
+
# client.configure_faraday { |conn| conn.use MyMiddleware }
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
class Client
|
54
|
+
# Internal: Proxies Resource classes to implement a fluent API on the Client
|
55
|
+
# instances.
|
56
|
+
class ResourceProxy
|
57
|
+
def initialize(client: required('client'), resource: required('resource'))
|
58
|
+
@client = client
|
59
|
+
@resource = resource
|
60
|
+
end
|
61
|
+
|
62
|
+
def method_missing(m, *args, &block)
|
63
|
+
@resource.public_send(m, *([@client] + args), &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
def respond_to_missing?(m, *)
|
67
|
+
@resource.respond_to?(m)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Public: Initializes a new client.
|
72
|
+
#
|
73
|
+
# Yields a {Asana::Client::Configuration} object as a configuration
|
74
|
+
# DSL. See {Asana::Client} for usage examples.
|
75
|
+
def initialize
|
76
|
+
config = Configuration.new.tap { |c| yield c }.to_h
|
77
|
+
@http_client =
|
78
|
+
HttpClient.new(authentication: config.fetch(:authentication),
|
79
|
+
adapter: config[:faraday_adapter],
|
80
|
+
user_agent: config[:user_agent],
|
81
|
+
debug_mode: config[:debug_mode],
|
82
|
+
&config[:faraday_config])
|
83
|
+
end
|
84
|
+
|
85
|
+
# Public: Performs a GET request against an arbitrary Asana URL. Allows for
|
86
|
+
# the user to interact with the API in ways that haven't been
|
87
|
+
# reflected/foreseen in this library.
|
88
|
+
def get(url, *args)
|
89
|
+
@http_client.get(url, *args)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Public: Performs a POST request against an arbitrary Asana URL. Allows for
|
93
|
+
# the user to interact with the API in ways that haven't been
|
94
|
+
# reflected/foreseen in this library.
|
95
|
+
def post(url, *args)
|
96
|
+
@http_client.post(url, *args)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Public: Performs a PUT request against an arbitrary Asana URL. Allows for
|
100
|
+
# the user to interact with the API in ways that haven't been
|
101
|
+
# reflected/foreseen in this library.
|
102
|
+
def put(url, *args)
|
103
|
+
@http_client.put(url, *args)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Public: Performs a DELETE request against an arbitrary Asana URL. Allows
|
107
|
+
# for the user to interact with the API in ways that haven't been
|
108
|
+
# reflected/foreseen in this library.
|
109
|
+
def delete(url, *args)
|
110
|
+
@http_client.delete(url, *args)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Public: Exposes queries for all top-evel endpoints.
|
114
|
+
#
|
115
|
+
# E.g. #users will query /users and return a
|
116
|
+
# Asana::Resources::Collection<User>.
|
117
|
+
Resources::Registry.resources.each do |resource_class|
|
118
|
+
define_method(resource_class.plural_name) do
|
119
|
+
ResourceProxy.new(client: @http_client,
|
120
|
+
resource: resource_class)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module Asana
|
2
|
+
class Client
|
3
|
+
# Internal: Represents a configuration DSL for an Asana::Client.
|
4
|
+
#
|
5
|
+
# Examples
|
6
|
+
#
|
7
|
+
# config = Configuration.new
|
8
|
+
# config.authentication :api_token, 'my_api_token'
|
9
|
+
# config.adapter :typhoeus
|
10
|
+
# config.configure_faraday { |conn| conn.use MyMiddleware }
|
11
|
+
# config.to_h
|
12
|
+
# # => { authentication: #<Authentication::TokenAuthentication>,
|
13
|
+
# faraday_adapter: :typhoeus,
|
14
|
+
# faraday_configuration: #<Proc> }
|
15
|
+
#
|
16
|
+
class Configuration
|
17
|
+
# Public: Initializes an empty configuration object.
|
18
|
+
def initialize
|
19
|
+
@configuration = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Public: Sets an authentication strategy.
|
23
|
+
#
|
24
|
+
# type - [:oauth2, :api_token] the kind of authentication strategy to use
|
25
|
+
# value - [::OAuth2::AccessToken, String, Hash] the configuration for the
|
26
|
+
# chosen authentication strategy.
|
27
|
+
#
|
28
|
+
# Returns nothing.
|
29
|
+
#
|
30
|
+
# Raises ArgumentError if the arguments are invalid.
|
31
|
+
def authentication(type, value)
|
32
|
+
auth = case type
|
33
|
+
when :oauth2 then oauth2(value)
|
34
|
+
when :api_token then api_token(value)
|
35
|
+
else error "unsupported authentication type #{type}"
|
36
|
+
end
|
37
|
+
@configuration[:authentication] = auth
|
38
|
+
end
|
39
|
+
|
40
|
+
# Public: Sets a custom network adapter for Faraday.
|
41
|
+
#
|
42
|
+
# adapter - [Symbol, Proc] the adapter.
|
43
|
+
#
|
44
|
+
# Returns nothing.
|
45
|
+
def faraday_adapter(adapter)
|
46
|
+
@configuration[:faraday_adapter] = adapter
|
47
|
+
end
|
48
|
+
|
49
|
+
# Public: Sets a custom configuration block for the Faraday connection.
|
50
|
+
#
|
51
|
+
# config - [Proc] the configuration block.
|
52
|
+
#
|
53
|
+
# Returns nothing.
|
54
|
+
def configure_faraday(&config)
|
55
|
+
@configuration[:faraday_configuration] = config
|
56
|
+
end
|
57
|
+
|
58
|
+
# Public: Configures the client in debug mode, which will print verbose
|
59
|
+
# information on STDERR.
|
60
|
+
#
|
61
|
+
# Returns nothing.
|
62
|
+
def debug_mode
|
63
|
+
@configuration[:debug_mode] = true
|
64
|
+
end
|
65
|
+
|
66
|
+
# Public:
|
67
|
+
# Returns the configuration [Hash].
|
68
|
+
def to_h
|
69
|
+
@configuration
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# Internal: Configures an OAuth2 authentication strategy from either an
|
75
|
+
# OAuth2 access token object, or a plain refresh token, or a plain bearer
|
76
|
+
# token.
|
77
|
+
#
|
78
|
+
# value - [::OAuth::AccessToken, String] the value to configure the
|
79
|
+
# strategy from.
|
80
|
+
#
|
81
|
+
# Returns [Asana::Authentication::OAuth2::AccessTokenAuthentication,
|
82
|
+
# Asana::Authentication::OAuth2::BearerTokenAuthentication]
|
83
|
+
# the OAuth2 authentication strategy.
|
84
|
+
#
|
85
|
+
# Raises ArgumentError if the OAuth2 configuration arguments are invalid.
|
86
|
+
#
|
87
|
+
# rubocop:disable Metrics/MethodLength
|
88
|
+
def oauth2(value)
|
89
|
+
case value
|
90
|
+
when ::OAuth2::AccessToken
|
91
|
+
from_access_token(value)
|
92
|
+
when -> v { v.is_a?(Hash) && v[:refresh_token] }
|
93
|
+
from_refresh_token(value)
|
94
|
+
when -> v { v.is_a?(Hash) && v[:bearer_token] }
|
95
|
+
from_bearer_token(value[:bearer_token])
|
96
|
+
else
|
97
|
+
error 'Invalid OAuth2 configuration: pass in either an ' \
|
98
|
+
'::OAuth2::AccessToken object of your own or a hash ' \
|
99
|
+
'containing :refresh_token or :bearer_token.'
|
100
|
+
end
|
101
|
+
end
|
102
|
+
# rubocop:enable Metrics/MethodLength
|
103
|
+
|
104
|
+
# Internal: Configures a TokenAuthentication strategy.
|
105
|
+
#
|
106
|
+
# token - [String] the API token
|
107
|
+
#
|
108
|
+
# Returns a [Authentication::TokenAuthentication] strategy.
|
109
|
+
def api_token(token)
|
110
|
+
Authentication::TokenAuthentication.new(token)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Internal: Configures an OAuth2 AccessTokenAuthentication strategy.
|
114
|
+
#
|
115
|
+
# access_token - [::OAuth2::AccessToken] the OAuth2 access token object
|
116
|
+
#
|
117
|
+
# Returns a [Authentication::OAuth2::AccessTokenAuthentication] strategy.
|
118
|
+
def from_access_token(access_token)
|
119
|
+
Authentication::OAuth2::AccessTokenAuthentication
|
120
|
+
.new(access_token)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Internal: Configures an OAuth2 AccessTokenAuthentication strategy.
|
124
|
+
#
|
125
|
+
# hash - The configuration hash:
|
126
|
+
# :refresh_token - [String] the OAuth2 refresh token
|
127
|
+
# :client_id - [String] the OAuth2 client id
|
128
|
+
# :client_secret - [String] the OAuth2 client secret
|
129
|
+
# :redirect_uri - [String] the OAuth2 redirect URI
|
130
|
+
#
|
131
|
+
# Returns a [Authentication::OAuth2::AccessTokenAuthentication] strategy.
|
132
|
+
def from_refresh_token(hash)
|
133
|
+
refresh_token, client_id, client_secret, redirect_uri =
|
134
|
+
requiring(hash, :refresh_token, :client_id,
|
135
|
+
:client_secret, :redirect_uri)
|
136
|
+
|
137
|
+
Authentication::OAuth2::AccessTokenAuthentication
|
138
|
+
.from_refresh_token(refresh_token,
|
139
|
+
client_id: client_id,
|
140
|
+
client_secret: client_secret,
|
141
|
+
redirect_uri: redirect_uri)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Internal: Configures an OAuth2 BearerTokenAuthentication strategy.
|
145
|
+
#
|
146
|
+
# bearer_token - [String] the plain OAuth2 bearer token
|
147
|
+
#
|
148
|
+
# Returns a [Authentication::OAuth2::BearerTokenAuthentication] strategy.
|
149
|
+
def from_bearer_token(bearer_token)
|
150
|
+
Authentication::OAuth2::BearerTokenAuthentication
|
151
|
+
.new(bearer_token)
|
152
|
+
end
|
153
|
+
|
154
|
+
def requiring(hash, *keys)
|
155
|
+
missing_keys = keys.select { |k| !hash.key?(k) }
|
156
|
+
missing_keys.any? && error("Missing keys: #{missing_keys.join(', ')}")
|
157
|
+
keys.map { |k| hash[k] }
|
158
|
+
end
|
159
|
+
|
160
|
+
def error(msg)
|
161
|
+
fail ArgumentError, msg
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
data/lib/asana/errors.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
module Asana
|
2
|
+
# Public: Defines the different errors that the Asana API may throw, which the
|
3
|
+
# client code may want to catch.
|
4
|
+
module Errors
|
5
|
+
# Public: A generic, catch-all API error. It contains the whole response
|
6
|
+
# object for debugging purposes.
|
7
|
+
#
|
8
|
+
# Note: This exception should never be raised when there exists a more
|
9
|
+
# specific subclass.
|
10
|
+
APIError = Class.new(StandardError) do
|
11
|
+
attr_accessor :response
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
'An unknown API error ocurred.'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Public: A 401 error. Raised when the credentials used are invalid and the
|
19
|
+
# user could not be authenticated.
|
20
|
+
NotAuthorized = Class.new(APIError) do
|
21
|
+
def to_s
|
22
|
+
'A valid API key was not provided with the request, so the API could '\
|
23
|
+
'not associate a user with the request.'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Public: A 403 error. Raised when the user doesn't have permission to
|
28
|
+
# access the requested resource or to perform the requested action on it.
|
29
|
+
Forbidden = Class.new(APIError) do
|
30
|
+
def to_s
|
31
|
+
'The API key and request syntax was valid but the server is refusing '\
|
32
|
+
'to complete the request. This can happen if you try to read or write '\
|
33
|
+
'to objects or properties that the user does not have access to.'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: A 404 error. Raised when the requested resource doesn't exist.
|
38
|
+
NotFound = Class.new(APIError) do
|
39
|
+
def to_s
|
40
|
+
'Either the request method and path supplied do not specify a known '\
|
41
|
+
'action in the API, or the object specified by the request does not '\
|
42
|
+
'exist.'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Public: A 500 error. Raised when there is a problem in the Asana API
|
47
|
+
# server. It contains a unique phrase that can be used to identify the
|
48
|
+
# problem when contacting developer support.
|
49
|
+
ServerError = Class.new(APIError) do
|
50
|
+
attr_accessor :phrase
|
51
|
+
|
52
|
+
def initialize(phrase)
|
53
|
+
@phrase = phrase
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_s
|
57
|
+
"There has been an error on Asana's end. Use this unique phrase to "\
|
58
|
+
'identify the problem when contacting developer support: ' +
|
59
|
+
%("#{@phrase}")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Public: A 400 error. Raised when the request was malformed or missing some
|
64
|
+
# parameters. It contains a list of errors indicating the specific problems.
|
65
|
+
InvalidRequest = Class.new(APIError) do
|
66
|
+
attr_accessor :errors
|
67
|
+
|
68
|
+
def initialize(errors)
|
69
|
+
@errors = errors
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_s
|
73
|
+
errors.join(', ')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Public: A 429 error. Raised when the Asana API enforces rate-limiting on
|
78
|
+
# the client to avoid overload. It contains the number of seconds to wait
|
79
|
+
# before retrying the operation.
|
80
|
+
RateLimitEnforced = Class.new(APIError) do
|
81
|
+
attr_accessor :retry_after_seconds
|
82
|
+
|
83
|
+
def initialize(retry_after_seconds)
|
84
|
+
@retry_after_seconds = retry_after_seconds
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_s
|
88
|
+
"Retry your request after #{@retry_after_seconds} seconds."
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
require 'faraday_middleware/multi_json'
|
4
|
+
|
5
|
+
require_relative 'http_client/error_handling'
|
6
|
+
require_relative 'http_client/environment_info'
|
7
|
+
require_relative 'http_client/response'
|
8
|
+
|
9
|
+
module Asana
|
10
|
+
# Internal: Wrapper over Faraday that abstracts authentication, request
|
11
|
+
# parsing and common options.
|
12
|
+
class HttpClient
|
13
|
+
# Internal: The API base URI.
|
14
|
+
BASE_URI = 'https://app.asana.com/api/1.0'
|
15
|
+
|
16
|
+
# Public: Initializes an HttpClient to make requests to the Asana API.
|
17
|
+
#
|
18
|
+
# authentication - [Asana::Authentication] An authentication strategy.
|
19
|
+
# adapter - [Symbol, Proc] A Faraday adapter, eiter a Symbol for
|
20
|
+
# registered adapters or a Proc taking a builder for a
|
21
|
+
# custom one. Defaults to Faraday.default_adapter.
|
22
|
+
# user_agent - [String] The user agent. Defaults to "ruby-asana vX.Y.Z".
|
23
|
+
# config - [Proc] An optional block that yields the Faraday builder
|
24
|
+
# object for customization.
|
25
|
+
def initialize(authentication: required('authentication'),
|
26
|
+
adapter: nil,
|
27
|
+
user_agent: nil,
|
28
|
+
debug_mode: false,
|
29
|
+
&config)
|
30
|
+
@authentication = authentication
|
31
|
+
@adapter = adapter || Faraday.default_adapter
|
32
|
+
@environment_info = EnvironmentInfo.new(user_agent)
|
33
|
+
@debug_mode = debug_mode
|
34
|
+
@config = config
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: Performs a GET request against the API.
|
38
|
+
#
|
39
|
+
# resource_uri - [String] the resource URI relative to the base Asana API
|
40
|
+
# URL, e.g "/users/me".
|
41
|
+
# params - [Hash] the request parameters
|
42
|
+
# options - [Hash] the request I/O options
|
43
|
+
#
|
44
|
+
# Returns an [Asana::HttpClient::Response] if everything went well.
|
45
|
+
# Raises [Asana::Errors::APIError] if anything went wrong.
|
46
|
+
def get(resource_uri, params: {}, options: {})
|
47
|
+
opts = options.reduce({}) do |acc, (k, v)|
|
48
|
+
acc.tap do |hash|
|
49
|
+
hash[:"opt_#{k}"] = v.is_a?(Array) ? v.join(',') : v
|
50
|
+
end
|
51
|
+
end
|
52
|
+
perform_request(:get, resource_uri, params.merge(opts))
|
53
|
+
end
|
54
|
+
|
55
|
+
# Public: Performs a PUT request against the API.
|
56
|
+
#
|
57
|
+
# resource_uri - [String] the resource URI relative to the base Asana API
|
58
|
+
# URL, e.g "/users/me".
|
59
|
+
# body - [Hash] the body to PUT.
|
60
|
+
# options - [Hash] the request I/O options
|
61
|
+
#
|
62
|
+
# Returns an [Asana::HttpClient::Response] if everything went well.
|
63
|
+
# Raises [Asana::Errors::APIError] if anything went wrong.
|
64
|
+
def put(resource_uri, body: {}, options: {})
|
65
|
+
params = { data: body }.merge(options.empty? ? {} : { options: options })
|
66
|
+
perform_request(:put, resource_uri, params)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Public: Performs a POST request against the API.
|
70
|
+
#
|
71
|
+
# resource_uri - [String] the resource URI relative to the base Asana API
|
72
|
+
# URL, e.g "/tags".
|
73
|
+
# body - [Hash] the body to POST.
|
74
|
+
# upload - [Faraday::UploadIO] an upload object to post as multipart.
|
75
|
+
# Defaults to nil.
|
76
|
+
# options - [Hash] the request I/O options
|
77
|
+
#
|
78
|
+
# Returns an [Asana::HttpClient::Response] if everything went well.
|
79
|
+
# Raises [Asana::Errors::APIError] if anything went wrong.
|
80
|
+
def post(resource_uri, body: {}, upload: nil, options: {})
|
81
|
+
params = { data: body }.merge(options.empty? ? {} : { options: options })
|
82
|
+
if upload
|
83
|
+
perform_request(:post, resource_uri, params.merge(file: upload)) do |c|
|
84
|
+
c.request :multipart
|
85
|
+
end
|
86
|
+
else
|
87
|
+
perform_request(:post, resource_uri, params)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Public: Performs a DELETE request against the API.
|
92
|
+
#
|
93
|
+
# resource_uri - [String] the resource URI relative to the base Asana API
|
94
|
+
# URL, e.g "/tags".
|
95
|
+
#
|
96
|
+
# Returns an [Asana::HttpClient::Response] if everything went well.
|
97
|
+
# Raises [Asana::Errors::APIError] if anything went wrong.
|
98
|
+
def delete(resource_uri)
|
99
|
+
perform_request(:delete, resource_uri)
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def connection(&request_config)
|
105
|
+
Faraday.new do |builder|
|
106
|
+
@authentication.configure(builder)
|
107
|
+
@environment_info.configure(builder)
|
108
|
+
request_config.call(builder) if request_config
|
109
|
+
configure_format(builder)
|
110
|
+
add_middleware(builder)
|
111
|
+
@config.call(builder) if @config
|
112
|
+
use_adapter(builder, @adapter)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def perform_request(method, resource_uri, body = {}, &request_config)
|
117
|
+
handling_errors do
|
118
|
+
url = BASE_URI + resource_uri
|
119
|
+
log_request(method, url, body) if @debug_mode
|
120
|
+
Response.new(connection(&request_config).public_send(method, url, body))
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def configure_format(builder)
|
125
|
+
builder.request :multi_json
|
126
|
+
builder.response :multi_json
|
127
|
+
end
|
128
|
+
|
129
|
+
def add_middleware(builder)
|
130
|
+
builder.use Faraday::Response::RaiseError
|
131
|
+
builder.use FaradayMiddleware::FollowRedirects
|
132
|
+
end
|
133
|
+
|
134
|
+
def use_adapter(builder, adapter)
|
135
|
+
case adapter
|
136
|
+
when Symbol
|
137
|
+
builder.adapter(adapter)
|
138
|
+
when Proc
|
139
|
+
adapter.call(builder)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def handling_errors(&request)
|
144
|
+
ErrorHandling.handle(&request)
|
145
|
+
end
|
146
|
+
|
147
|
+
def log_request(method, url, body)
|
148
|
+
STDERR.puts format('[%s] %s %s (%s)',
|
149
|
+
self.class,
|
150
|
+
method.to_s.upcase,
|
151
|
+
url,
|
152
|
+
body.inspect)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|