asana 0.0.6 → 0.1.1
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.
- checksums.yaml +9 -9
- data/.codeclimate.yml +4 -0
- data/.gitignore +12 -20
- data/.rspec +4 -0
- data/.rubocop.yml +18 -0
- data/.travis.yml +12 -0
- data/.yardopts +5 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +17 -0
- data/Guardfile +85 -4
- data/LICENSE.txt +21 -0
- data/README.md +264 -135
- data/Rakefile +62 -7
- data/asana.gemspec +27 -21
- data/examples/Gemfile +6 -0
- data/examples/Gemfile.lock +56 -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 +8 -11
- 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 +90 -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/resources.rb +11 -0
- data/lib/asana/resources/attachment.rb +44 -0
- data/lib/asana/resources/attachment_uploading.rb +33 -0
- data/lib/asana/resources/collection.rb +68 -0
- data/lib/asana/resources/event.rb +49 -0
- data/lib/asana/resources/event_subscription.rb +12 -0
- data/lib/asana/resources/events.rb +101 -0
- data/lib/asana/resources/project.rb +145 -19
- data/lib/asana/resources/registry.rb +62 -0
- data/lib/asana/resources/resource.rb +103 -0
- data/lib/asana/resources/response_helper.rb +14 -0
- data/lib/asana/resources/story.rb +58 -7
- data/lib/asana/resources/tag.rb +111 -19
- data/lib/asana/resources/task.rb +284 -57
- data/lib/asana/resources/team.rb +55 -0
- data/lib/asana/resources/user.rb +65 -10
- data/lib/asana/resources/workspace.rb +79 -34
- data/lib/asana/ruby2_0_0_compatibility.rb +3 -0
- data/lib/asana/version.rb +3 -1
- data/lib/templates/index.js +8 -0
- data/lib/templates/resource.ejs +225 -0
- data/package.json +7 -0
- metadata +91 -51
- data/LICENSE +0 -22
- data/lib/asana/config.rb +0 -23
- data/lib/asana/resource.rb +0 -52
- data/spec/asana/resources/project_spec.rb +0 -63
- data/spec/asana/resources/story_spec.rb +0 -39
- data/spec/asana/resources/tag_spec.rb +0 -63
- data/spec/asana/resources/task_spec.rb +0 -95
- data/spec/asana/resources/user_spec.rb +0 -64
- data/spec/asana/resources/workspace_spec.rb +0 -108
- data/spec/spec_helper.rb +0 -9
@@ -0,0 +1,32 @@
|
|
1
|
+
module Asana
|
2
|
+
module Authentication
|
3
|
+
module OAuth2
|
4
|
+
# Public: A mechanism to authenticate with an OAuth2 bearer token obtained
|
5
|
+
# somewhere, for instance through omniauth-asana.
|
6
|
+
#
|
7
|
+
# Note: This authentication mechanism doesn't support token refreshing. If
|
8
|
+
# you'd like refreshing and you have a refresh token as well as a bearer
|
9
|
+
# token, you can generate a proper access token with
|
10
|
+
# {AccessTokenAuthentication.from_refresh_token}.
|
11
|
+
class BearerTokenAuthentication
|
12
|
+
# Public: Initializes a new BearerTokenAuthentication with a plain
|
13
|
+
# bearer token.
|
14
|
+
#
|
15
|
+
# bearer_token - [String] a plain bearer token.
|
16
|
+
def initialize(bearer_token)
|
17
|
+
@token = bearer_token
|
18
|
+
end
|
19
|
+
|
20
|
+
# Public: Configures a Faraday connection injecting its token as an
|
21
|
+
# OAuth2 bearer token.
|
22
|
+
#
|
23
|
+
# connection - [Faraday::Connection] the Faraday connection instance.
|
24
|
+
#
|
25
|
+
# Returns nothing.
|
26
|
+
def configure(connection)
|
27
|
+
connection.request :oauth2, @token
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'oauth2'
|
2
|
+
|
3
|
+
module Asana
|
4
|
+
module Authentication
|
5
|
+
module OAuth2
|
6
|
+
# Public: Deals with the details of obtaining an OAuth2 authorization URL
|
7
|
+
# and obtaining access tokens from either authorization codes or refresh
|
8
|
+
# tokens.
|
9
|
+
class Client
|
10
|
+
# Public: Initializes a new client with client credentials associated
|
11
|
+
# with a registered Asana API application.
|
12
|
+
#
|
13
|
+
# client_id - [String] a client id from the registered application
|
14
|
+
# client_secret - [String] a client secret from the registered
|
15
|
+
# application
|
16
|
+
# redirect_uri - [String] a redirect uri from the registered
|
17
|
+
# application
|
18
|
+
def initialize(client_id: required('client_id'),
|
19
|
+
client_secret: required('client_secret'),
|
20
|
+
redirect_uri: required('redirect_uri'))
|
21
|
+
@client = ::OAuth2::Client.new(client_id, client_secret,
|
22
|
+
site: 'https://app.asana.com',
|
23
|
+
authorize_url: '/-/oauth_authorize',
|
24
|
+
token_url: '/-/oauth_token')
|
25
|
+
@redirect_uri = redirect_uri
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public:
|
29
|
+
# Returns the [String] OAuth2 authorize URL.
|
30
|
+
def authorize_url
|
31
|
+
@client.auth_code.authorize_url(redirect_uri: @redirect_uri)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Public: Retrieves a token from an authorization code.
|
35
|
+
#
|
36
|
+
# Returns the [::OAuth2::AccessToken] token.
|
37
|
+
def token_from_auth_code(auth_code)
|
38
|
+
@client.auth_code.get_token(auth_code, redirect_uri: @redirect_uri)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Public: Retrieves a token from a refresh token.
|
42
|
+
#
|
43
|
+
# Returns the refreshed [::OAuth2::AccessToken] token.
|
44
|
+
def token_from_refresh_token(token)
|
45
|
+
::OAuth2::AccessToken.new(@client, '', refresh_token: token).refresh!
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -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,90 @@
|
|
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
|
+
def initialize(errors)
|
68
|
+
@errors = errors
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_s
|
72
|
+
errors.join(', ')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Public: A 429 error. Raised when the Asana API enforces rate-limiting on
|
77
|
+
# the client to avoid overload. It contains the number of seconds to wait
|
78
|
+
# before retrying the operation.
|
79
|
+
RateLimitEnforced = Class.new(APIError) do
|
80
|
+
attr_accessor :retry_after_seconds
|
81
|
+
def initialize(retry_after_seconds)
|
82
|
+
@retry_after_seconds = retry_after_seconds
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_s
|
86
|
+
"Retry your request after #{@retry_after_seconds} seconds."
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|