dwolla_v2 0.4.0 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 347bf32bfbb4db49d6341c3db975cf696a97f492
4
- data.tar.gz: 38d265059cb4ddac927d141096bd145a76cf2c60
3
+ metadata.gz: 2db53d14d332c6a4e13f74f7a9986e361b8509e7
4
+ data.tar.gz: 2c61640b59008a32254807efcc050f6d4256b277
5
5
  SHA512:
6
- metadata.gz: 053be0c20b5da0da5c421dcb8979ca1aefcc4a6620c8383e2251b0e458042b222c5c76682051d08706fe2a9f9d8e043d8702bcd8781fc6a89397b9982bc532db
7
- data.tar.gz: 43695d6c3e63d61003cac1ba3b72735ebc9f7e53b7926b4cec186f01d4e5b0d34e466091a0405cb340a846511f788493d9bf7cbd779d8b9c18cfe6900e89c9be
6
+ metadata.gz: 47f74c7ce9469f1b9f46422ac6339c3091784da493892e2e2a68ea9a46c6cb25082513eb7455d0d404c22934d62c8c5b967929394c1588762d23fabf3eb6007b
7
+ data.tar.gz: e0117f0296365e9abec6b5caaa1ceaa3f716fb5e75c6824c35b49f851490abe48dc14fa5aa362e2490af89cbf7ca90d1c267db0d83e552859c3237ea01f851ce
data/README.md CHANGED
@@ -11,7 +11,7 @@ Dwolla V2 Ruby client. For the V1 Ruby client see [Dwolla/dwolla-ruby](https://g
11
11
  Add this line to your application's Gemfile:
12
12
 
13
13
  ```ruby
14
- gem 'dwolla_v2', '~> 0.4'
14
+ gem 'dwolla_v2', '~> 1.0'
15
15
  ```
16
16
 
17
17
  And then execute:
@@ -22,41 +22,122 @@ Or install it yourself as:
22
22
 
23
23
  $ gem install dwolla_v2
24
24
 
25
- ## Usage
25
+ ## `DwollaV2::Client`
26
26
 
27
- #### Creating a client
27
+ ### Basic usage
28
28
 
29
29
  ```ruby
30
- $dwolla = DwollaV2::Client.new(id: "CLIENT_ID", secret: "CLIENT_SECRET") do |optional_config|
31
- optional_config.environment = :sandbox
32
- optional_config.on_grant do |token|
33
- YourTokenData.create! token
34
- end
35
- optional_config.faraday do |faraday|
30
+ # config/initializers/dwolla.rb
31
+ $dwolla = DwollaV2::Client.new(id: ENV["DWOLLA_ID"], secret: ENV["DWOLLA_SECRET"])
32
+ ```
33
+
34
+ ### Using the sandbox environment (optional)
35
+
36
+ ```ruby
37
+ # config/initializers/dwolla.rb
38
+ $dwolla = DwollaV2::Client.new(id: ENV["DWOLLA_ID"], secret: ENV["DWOLLA_SECRET"]) do |config|
39
+ config.environment = :sandbox
40
+ end
41
+ ```
42
+
43
+ `environment=` defaults to `:production` and accepts either a String or a Symbol.
44
+
45
+ ### Configure an `on_grant` callback (optional)
46
+
47
+ An `on_grant` callback is useful for storing new tokens when they are granted. For example, you
48
+ may have a `YourTokenData` ActiveRecord model that you use to store your tokens. The `on_grant`
49
+ callback is called with the `DwollaV2::Token` that was just granted by the server. You can pass
50
+ this to the `create!` method of an ActiveRecord model to create a record using the token's data.
51
+
52
+ ```ruby
53
+ # config/initializers/dwolla.rb
54
+ $dwolla = DwollaV2::Client.new(id: ENV["DWOLLA_ID"], secret: ENV["DWOLLA_SECRET"]) do |config|
55
+ config.on_grant {|t| YourTokenData.create!(t) }
56
+ end
57
+ ```
58
+
59
+ It is highly recommended that you encrypt any token data you store using
60
+ [attr_encrypted][attr_encrypted] or [vault-rails][vault-rails] (if you use [Vault][vault]). These
61
+ are both configured in your ActiveRecord models.
62
+
63
+ [attr_encrypted]: https://github.com/attr-encrypted/attr_encrypted
64
+ [vault-rails]: https://github.com/hashicorp/vault-rails
65
+ [vault]: https://www.vaultproject.io/
66
+
67
+ ### Configure Faraday (optional)
68
+
69
+ DwollaV2 uses [Faraday][faraday] to make HTTP requests. You can configure your own
70
+ [Faraday middleware][faraday-middleware] and adapter when configuring your client. Remember to
71
+ always include an adapter last, even if you want to use the default adapter.
72
+
73
+ [faraday]: https://github.com/lostisland/faraday
74
+ [faraday-middleware]: https://github.com/lostisland/faraday_middleware
75
+
76
+ ```ruby
77
+ # config/initializers/dwolla.rb
78
+ $dwolla = DwollaV2::Client.new(id: ENV["DWOLLA_ID"], secret: ENV["DWOLLA_SECRET"]) do |config|
79
+ config.faraday do |faraday|
36
80
  faraday.response :logger
37
81
  faraday.adapter Faraday.default_adapter
38
82
  end
39
83
  end
40
84
  ```
41
85
 
42
- #### Authorization
86
+ ## `DwollaV2::Token`
43
87
 
44
- Get an application token:
88
+ Tokens can be used to make requests to the Dwolla V2 API. There are two types of tokens:
89
+
90
+ ### Application tokens
91
+
92
+ Application tokens are used to access the API on behalf of a consumer application. API resources that
93
+ belong to an application include: `webhook-subscriptions`, `events`, and `webhooks`. Application
94
+ tokens can be created using the [`client_credentials`][client_credentials] OAuth grant type:
95
+
96
+ [client_credentials]: https://tools.ietf.org/html/rfc6749#section-4.4
45
97
 
46
98
  ```ruby
47
- $dwolla.auths.client scope: "ManageCustomers|Funding"
99
+ application_token = $dwolla.auths.client
100
+ # => #<DwollaV2::Token client=#<DwollaV2::Client id="..." secret="..." environment=:sandbox> access_token="..." expires_in=3600 scope="...">
48
101
  ```
49
102
 
50
- Get an account token:
103
+ *Application tokens do not include a `refresh_token`. When an application token expires, generate
104
+ a new one using `$dwolla.auths.client`.*
105
+
106
+ ### Account tokens
107
+
108
+ Account tokens are used to access the API on behalf of a Dwolla account. API resources that belong
109
+ to an account include `customers`, `funding-sources`, `documents`, `mass-payments`, `mass-payment-items`,
110
+ `transfers`, and `on-demand-authorizations`.
111
+
112
+ There are two ways to get an account token. One is by generating a token at
113
+ https://uat.dwolla.com/applications (sandbox) or https://www.dwolla.com/applications (production).
114
+
115
+ You can instantiate a generated token by doing the following:
116
+
117
+ ```ruby
118
+ account_token = $dwolla.tokens.new access_token: "...", refresh_token: "..."
119
+ # => #<DwollaV2::Token client=#<DwollaV2::Client id="..." secret="..." environment=:sandbox> access_token="..." refresh_token="...">
120
+ ```
121
+
122
+ The other way to get an account token is using the [`authorization_code`][authorization_code]
123
+ OAuth grant type. This flow works by redirecting a user to dwolla.com in order to get authorization
124
+ and sending them back to your website with an authorization code which can be exchanged for a token.
125
+ For example:
126
+
127
+ [authorization_code]: https://tools.ietf.org/html/rfc6749#section-4.1
51
128
 
52
129
  ```ruby
53
130
  class YourAuthController < ApplicationController
131
+ # redirect the user to dwolla.com for authorization
54
132
  def authorize
55
133
  redirect_to auth.url
56
134
  end
57
135
 
136
+ # https://yoursite.com/callback
58
137
  def callback
59
- token = auth.callback params
138
+ # exchange the code for a token
139
+ token = auth.callback(params)
140
+ # => #<DwollaV2::Token client=#<DwollaV2::Client id="..." secret="..." environment=:sandbox> access_token="..." refresh_token="..." expires_in=3600 scope="ManageCustomers|Funding" account_id="...">
60
141
  session[:account_id] = token.account_id
61
142
  end
62
143
 
@@ -70,31 +151,51 @@ class YourAuthController < ApplicationController
70
151
  end
71
152
  ```
72
153
 
73
- Refresh a token:
154
+ ### Refreshing tokens
155
+
156
+ Tokens with `refresh_token`s can be refreshed using `$dwolla.auths.refresh`, which takes a
157
+ `DwollaV2::Token` as its first argument and returns a new token.
74
158
 
75
159
  ```ruby
76
- token = $dwolla.auths.refresh expired_token
160
+ refreshed_token = $dwolla.auths.refresh(expired_token)
161
+ # => #<DwollaV2::Token client=#<DwollaV2::Client id="..." secret="..." environment=:sandbox> access_token="..." refresh_token="..." expires_in=3600 scope="ManageCustomers|Funding" account_id="...">
77
162
  ```
78
163
 
79
- Initialize a token:
164
+ ### Initializing tokens:
165
+
166
+ `DwollaV2::Token`s can be initialized with the following attributes:
80
167
 
81
168
  ```ruby
82
- token_data = YourTokenData.find_by account_id: "ACCOUNT_ID"
83
- token = $dwolla.tokens.new token_data
169
+ $dwolla.tokens.new access_token: "...",
170
+ refresh_token: "...",
171
+ expires_in: 123,
172
+ scope: "...",
173
+ account_id: "..."
174
+ #<DwollaV2::Token client=#<DwollaV2::Client id="..." secret="..." environment=:sandbox> access_token="..." refresh_token="..." expires_in=123 scope="..." account_id="...">
84
175
  ```
85
176
 
86
- #### Making requests
177
+ ## Requests
178
+
179
+ `DwollaV2::Token`s can make requests using the `#get`, `#post`, `#put`, and `#delete` methods.
87
180
 
88
181
  ```ruby
89
- token.get "/resource", foo: "bar"
90
- token.post "/resource", foo: "bar"
91
- token.post "/resource", foo: Faraday::UploadIO.new("/path/to/bar.png", "image/png")
92
- token.put "/resource", foo: "bar"
93
- token.put "/resource", foo: Faraday::UploadIO.new("/path/to/bar.png", "image/png")
94
- token.delete "/resource"
182
+ # GET api.dwolla.com/resource?foo=bar
183
+ token.get "resource", foo: "bar"
184
+
185
+ # POST api.dwolla.com/resource {"foo":"bar"}
186
+ token.post "resource", foo: "bar"
187
+
188
+ # POST api.dwolla.com/resource multipart/form-data foo=...
189
+ token.post "resource", foo: Faraday::UploadIO.new("/path/to/bar.png", "image/png")
190
+
191
+ # PUT api.dwolla.com/resource {"foo":"bar"}
192
+ token.put "resource", foo: "bar"
193
+
194
+ # DELETE api.dwolla.com/resource
195
+ token.delete "resource"
95
196
  ```
96
197
 
97
- In parallel:
198
+ Requests can also be made in parallel:
98
199
 
99
200
  ```ruby
100
201
  foo, bar = nil
@@ -106,50 +207,52 @@ puts foo # only ready after `in_parallel` block has executed
106
207
  puts bar # only ready after `in_parallel` block has executed
107
208
  ```
108
209
 
109
- #### Responses
210
+ ## Responses
110
211
 
111
212
  Requests return a `DwollaV2::Response`.
112
213
 
113
- **Response status**:
114
-
115
214
  ```ruby
116
- res = token.post "/customers", customer_params
117
- res.status # => 201
118
- ```
215
+ res = token.get "/"
216
+ # => #<DwollaV2::Response status=200 headers={"server"=>"cloudflare-nginx", "date"=>"Mon, 28 Mar 2016 15:30:23 GMT", "content-type"=>"application/vnd.dwolla.v1.hal+json; charset=UTF-8", "content-length"=>"150", "connection"=>"close", "set-cookie"=>"__cfduid=d9dcd0f586c166d36cbd45b992bdaa11b1459179023; expires=Tue, 28-Mar-17 15:30:23 GMT; path=/; domain=.dwolla.com; HttpOnly", "x-request-id"=>"69a4e612-5dae-4c52-a6a0-2f921e34a88a", "cf-ray"=>"28ac1f81875941e3-MSP"} {"_links"=>{"events"=>{"href"=>"https://api-uat.dwolla.com/events"}, "webhook-subscriptions"=>{"href"=>"https://api-uat.dwolla.com/webhook-subscriptions"}}}>
119
217
 
120
- **Response headers**:
218
+ res.status
219
+ # => 200
121
220
 
122
- ```ruby
123
- customer = token.post "/customers", customer_params
124
- customer.headers[:location] # => "https://api.dwolla.com/customers/aa76ca27-1920-4a0a-9bbe-a22085c0010e"
221
+ res.headers
222
+ # => {"server"=>"cloudflare-nginx", "date"=>"Mon, 28 Mar 2016 15:30:23 GMT", "content-type"=>"application/vnd.dwolla.v1.hal+json; charset=UTF-8", "content-length"=>"150", "connection"=>"close", "set-cookie"=>"__cfduid=d9dcd0f586c166d36cbd45b992bdaa11b1459179023; expires=Tue, 28-Mar-17 15:30:23 GMT; path=/; domain=.dwolla.com; HttpOnly", "x-request-id"=>"69a4e612-5dae-4c52-a6a0-2f921e34a88a", "cf-ray"=>"28ac1f81875941e3-MSP"}
223
+
224
+ res._links.events.href
225
+ # => "https://api-uat.dwolla.com/events"
125
226
  ```
126
227
 
127
- **Response body**:
228
+ ## Errors
128
229
 
129
- [Enumerable methods](http://ruby-doc.org/core-2.3.0/Enumerable.html), `#==`, and `#[]` are
130
- forwarded to a response's body. For example:
230
+ If the server returns an error, a `DwollaV2::Error` (or one of its subclasses) will be raised.
231
+ `DwollaV2::Error`s are similar to `DwollaV2::Response`s.
131
232
 
132
233
  ```ruby
133
- customer = token.get "/customers/aa76ca27-1920-4a0a-9bbe-a22085c0010e"
134
-
135
- customer.collect {|k,v| k } # => [:_links, :id, ...]
136
- customer.body.collect {|k,v| k } # => [:_links, :id, ...]
137
-
138
- customer == customer.body # => true
139
-
140
- customer[:name] # => John Doe
141
- customer.body[:name] # => John Doe
234
+ begin
235
+ token.get "/not-found"
236
+ rescue DwollaV2::NotFoundError => e
237
+ e
238
+ # => #<DwollaV2::NotFoundError status=404 headers={"server"=>"cloudflare-nginx", "date"=>"Mon, 28 Mar 2016 15:35:32 GMT", "content-type"=>"application/vnd.dwolla.v1.hal+json; profile=\"http://nocarrier.co.uk/profiles/vnd.error/\"; charset=UTF-8", "content-length"=>"69", "connection"=>"close", "set-cookie"=>"__cfduid=da1478bfdf3e56275cd8a6a741866ccce1459179332; expires=Tue, 28-Mar-17 15:35:32 GMT; path=/; domain=.dwolla.com; HttpOnly", "access-control-allow-origin"=>"*", "x-request-id"=>"667fca74-b53d-43db-bddd-50426a011881", "cf-ray"=>"28ac270abca64207-MSP"} {"code"=>"NotFound", "message"=>"The requested resource was not found."}>
239
+
240
+ e.status
241
+ # => 404
242
+
243
+ e.headers
244
+ # => {"server"=>"cloudflare-nginx", "date"=>"Mon, 28 Mar 2016 15:35:32 GMT", "content-type"=>"application/vnd.dwolla.v1.hal+json; profile=\"http://nocarrier.co.uk/profiles/vnd.error/\"; charset=UTF-8", "content-length"=>"69", "connection"=>"close", "set-cookie"=>"__cfduid=da1478bfdf3e56275cd8a6a741866ccce1459179332; expires=Tue, 28-Mar-17 15:35:32 GMT; path=/; domain=.dwolla.com; HttpOnly", "access-control-allow-origin"=>"*", "x-request-id"=>"667fca74-b53d-43db-bddd-50426a011881", "cf-ray"=>"28ac270abca64207-MSP"}
245
+
246
+ e.code
247
+ # => "NotFound"
248
+ rescue DwollaV2::Error => e
249
+ # ...
250
+ end
142
251
  ```
143
252
 
144
- #### Errors
145
-
146
- All errors inherit from `DwollaV2::Error`
253
+ ### `DwollaV2::Error` subclasses:
147
254
 
148
- OAuth errors have `error`, `error_description`, and `error_uri` attributes.
149
-
150
- Dwolla API errors have `code`, `message`, `_links`, and `_embedded` attributes.
151
-
152
- **Error list:**
255
+ *See https://docsv2.dwolla.com/#errors for more info.*
153
256
 
154
257
  - `DwollaV2::AccessDeniedError`
155
258
  - `DwollaV2::InvalidCredentialsError`
@@ -176,6 +279,15 @@ Dwolla API errors have `code`, `message`, `_links`, and `_embedded` attributes.
176
279
  - `DwollaV2::MethodNotAllowedError`
177
280
  - `DwollaV2::ValidationError`
178
281
 
282
+ ## Sample code
283
+
284
+ The following gist contains some sample bootstrapping code:
285
+
286
+ https://gist.github.com/sausman/df58a196b3bc0381b0e8
287
+
288
+ If you have any questions regarding your specific implementation we'll do our best to help
289
+ at https://discuss.dwolla.com/.
290
+
179
291
  ## Development
180
292
 
181
293
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -192,6 +304,7 @@ The gem is available as open source under the terms of the [MIT License](https:/
192
304
 
193
305
  ## Changelog
194
306
 
307
+ - **1.0.0** - Refactor `Error` class to be more like response, add ability to access keys using methods.
195
308
  - **0.4.0** - Refactor and document how `DwollaV2::Response` works
196
309
  - **0.3.1** - better `DwollaV2::Error` error messages
197
310
  - **0.3.0** - ISO8601 values in response body are converted to `Time` objects
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency "rspec", "~> 3.0"
25
25
  spec.add_development_dependency "webmock", "~> 1.22"
26
26
 
27
+ spec.add_dependency "hashie", "~> 3.4"
27
28
  spec.add_dependency "faraday", "~> 0.9"
28
29
  spec.add_dependency "faraday_middleware", "~> 0.10"
29
30
  end
@@ -2,6 +2,9 @@ require "base64"
2
2
  require "uri"
3
3
  require "json"
4
4
  require "forwardable"
5
+ require "ostruct"
6
+
7
+ require "hashie"
5
8
 
6
9
  require "faraday"
7
10
  require "faraday_middleware"
@@ -14,9 +17,10 @@ require "dwolla_v2/token"
14
17
  require "dwolla_v2/response"
15
18
  require "dwolla_v2/error"
16
19
  require "dwolla_v2/util"
20
+ require "dwolla_v2/super_hash"
17
21
 
18
- require "dwolla_v2/middleware/symbolize_response_body"
19
- require "dwolla_v2/middleware/parse_iso8601_response_body"
22
+ require "dwolla_v2/middleware/deep_parse_iso8601_response_body"
23
+ require "dwolla_v2/middleware/deep_super_hasherize_response_body"
20
24
  require "dwolla_v2/middleware/handle_errors"
21
25
 
22
26
  # OAuth errors https://tools.ietf.org/html/rfc6749
@@ -46,11 +46,10 @@ module DwollaV2
46
46
 
47
47
  def self.request_token client, params
48
48
  res = client.conn.post client.token_url, params
49
- res_body = Util.deep_symbolize_keys res.body
50
- if res.status >= 400 || (res_body.is_a?(Hash) && res_body.has_key?(:error))
51
- Error.raise! res_body
49
+ if !res.body.is_a?(Hash) || res.body.has_key?(:error)
50
+ Error.raise! res
52
51
  else
53
- token = Token.new client, res_body
52
+ token = Token.new client, res.body
54
53
  client.on_grant.call token unless client.on_grant.nil?
55
54
  token
56
55
  end
@@ -1,7 +1,7 @@
1
1
  module DwollaV2
2
2
  class Client
3
3
  ENVIRONMENTS = {
4
- :default => {
4
+ :production => {
5
5
  :auth_url => "https://www.dwolla.com/oauth/v2/authenticate",
6
6
  :token_url => "https://www.dwolla.com/oauth/v2/token",
7
7
  :api_url => "https://api.dwolla.com"
@@ -28,13 +28,14 @@ module DwollaV2
28
28
  end
29
29
 
30
30
  def environment= env
31
+ env = :"#{env}"
31
32
  raise ArgumentError.new "invalid environment" unless ENVIRONMENTS.has_key? env
32
33
  @environment = env
33
34
  end
34
35
 
35
36
  def environment env = nil
36
37
  self.environment = env unless env.nil?
37
- @environment || :default
38
+ @environment || :production
38
39
  end
39
40
 
40
41
  def on_grant &callback
@@ -51,6 +52,9 @@ module DwollaV2
51
52
  @conn ||= Faraday.new do |f|
52
53
  f.request :basic_auth, id, secret
53
54
  f.request :url_encoded
55
+ f.use HandleErrors
56
+ f.use DeepSuperHasherizeResponseBody
57
+ f.use DeepParseIso8601ResponseBody
54
58
  f.response :json, :content_type => /\bjson$/
55
59
  faraday.call(f) if faraday
56
60
  f.adapter Faraday.default_adapter unless faraday
@@ -68,5 +72,9 @@ module DwollaV2
68
72
  def api_url
69
73
  ENVIRONMENTS[environment][:api_url]
70
74
  end
75
+
76
+ def inspect
77
+ Util.pretty_inspect self.class.name, id: id, secret: secret, environment: environment
78
+ end
71
79
  end
72
80
  end
@@ -1,38 +1,83 @@
1
1
  module DwollaV2
2
2
  class Error < StandardError
3
- def self.raise! error
4
- raise self.new unless error.is_a? Hash
5
- if klass = error_class(error[:error] || error[:code])
6
- raise klass.new error
3
+ extend Forwardable
4
+
5
+ delegate [:status] => :@response
6
+ delegate [:to_s, :to_json] => :response_body
7
+
8
+ def self.raise! response
9
+ unless response.respond_to? :body
10
+ response = turn_into_response(response)
11
+ end
12
+ if response.body.is_a?(Hash) && klass = error_class(response.body[:error] || response.body[:code])
13
+ raise klass, response, caller
7
14
  else
8
- raise self.new error
15
+ raise self, response, caller
9
16
  end
10
17
  end
11
18
 
12
- def initialize error = nil
13
- @error = error
19
+ def initialize response
20
+ @response = response
14
21
  end
15
22
 
16
- def message
17
- @error[:message]
23
+ def headers
24
+ if @response.respond_to? :response_headers
25
+ @response.response_headers
26
+ elsif @response.respond_to? :headers
27
+ @response.headers
28
+ end
29
+ end
30
+
31
+ def respond_to? method, include_private = false
32
+ super || response_body.respond_to?(method)
33
+ end
34
+
35
+ def is_a? klass
36
+ super || response_body.is_a?(klass)
37
+ end
38
+
39
+ def kind_of? klass
40
+ super || response_body.kind_of?(klass)
18
41
  end
19
42
 
20
- def [] key
21
- @error[key] rescue nil
43
+ def == other
44
+ super || response_body == other
22
45
  end
23
46
 
24
- def method_missing method
25
- @error[method] rescue nil
47
+ def method_missing method, *args, &block
48
+ if response_body.respond_to? method
49
+ response_body.public_send method, *args, &block
50
+ else
51
+ super
52
+ end
53
+ end
54
+
55
+ def message
56
+ to_s
26
57
  end
27
58
 
28
- def to_s
29
- @error.to_s
59
+ def to_str
60
+ nil
61
+ end
62
+
63
+ def inspect
64
+ Util.pretty_inspect self.class.name, { status: status, headers: headers }, response_body
30
65
  end
31
66
 
32
67
  private
33
68
 
69
+ def self.turn_into_response response
70
+ OpenStruct.new status: nil,
71
+ headers: nil,
72
+ body: Util.deep_super_hasherize(Util.deep_parse_iso8601_values response)
73
+ end
74
+
34
75
  def self.error_class error_code
35
76
  DwollaV2.const_get "#{Util.classify(error_code).chomp("Error")}Error" rescue false
36
77
  end
78
+
79
+ def response_body
80
+ @response.body
81
+ end
37
82
  end
38
83
  end
@@ -1,5 +1,5 @@
1
1
  module DwollaV2
2
- class ParseIso8601ResponseBody
2
+ class DeepParseIso8601ResponseBody
3
3
  def initialize app
4
4
  @app = app
5
5
  end
@@ -1,12 +1,12 @@
1
1
  module DwollaV2
2
- class SymbolizeResponseBody
2
+ class DeepSuperHasherizeResponseBody
3
3
  def initialize app
4
4
  @app = app
5
5
  end
6
6
 
7
7
  def call request_env
8
8
  @app.call(request_env).on_complete do |response_env|
9
- response_env.body = Util.deep_symbolize_keys(response_env.body)
9
+ response_env.body = Util.deep_super_hasherize(response_env.body)
10
10
  end
11
11
  end
12
12
  end
@@ -6,7 +6,7 @@ module DwollaV2
6
6
 
7
7
  def call request_env
8
8
  @app.call(request_env).on_complete do |response_env|
9
- Error.raise!(response_env.body) if response_env.status >= 400
9
+ Error.raise!(response_env) if response_env.status >= 400
10
10
  end
11
11
  end
12
12
  end
@@ -2,23 +2,49 @@ module DwollaV2
2
2
  class Response
3
3
  extend Forwardable
4
4
 
5
- # http://ruby-doc.org/core-2.3.0/Enumerable.html
6
- ENUMERABLE = \
7
- [:all?, :any?, :chunk, :chunk_while, :collect, :collect_concat, :count, :cycle,
8
- :detect, :drop, :drop_while, :each_cons, :each_entry, :each_slice, :each_with_index,
9
- :each_with_object, :entries, :find, :find_all, :find_index, :first, :flat_map, :grep,
10
- :grep_v, :group_by, :include?, :inject, :lazy, :map, :max, :max_by, :member?, :min,
11
- :min_by, :minmax, :minmax_by, :none?, :one?, :partition, :reduce, :reject,
12
- :reverse_each, :select, :slice_after, :slice_before, :slice_when, :sort, :sort_by,
13
- :take, :take_while, :to_a, :to_h, :zip]
14
-
15
- delegate [:status, :headers, :body] => :@response
16
- delegate [*ENUMERABLE, :==, :[]] => :response_body
5
+ delegate [:status] => :@response
6
+ delegate [:to_s, :to_json] => :response_body
17
7
 
18
8
  def initialize response
19
9
  @response = response
20
10
  end
21
11
 
12
+ def headers
13
+ if @response.respond_to? :response_headers
14
+ @response.response_headers
15
+ elsif @response.respond_to? :headers
16
+ @response.headers
17
+ end
18
+ end
19
+
20
+ def respond_to? method, include_private = false
21
+ super || response_body.respond_to?(method)
22
+ end
23
+
24
+ def is_a? klass
25
+ super || response_body.is_a?(klass)
26
+ end
27
+
28
+ def kind_of? klass
29
+ super || response_body.kind_of?(klass)
30
+ end
31
+
32
+ def == other
33
+ super || response_body == other
34
+ end
35
+
36
+ def method_missing method, *args, &block
37
+ if response_body.respond_to? method
38
+ response_body.public_send method, *args, &block
39
+ else
40
+ super
41
+ end
42
+ end
43
+
44
+ def inspect
45
+ Util.pretty_inspect self.class.name, { status: status, headers: headers }, response_body
46
+ end
47
+
22
48
  private
23
49
 
24
50
  def response_body
@@ -0,0 +1,12 @@
1
+ module DwollaV2
2
+ class SuperHash < Hash
3
+ include Hashie::Extensions::KeyConversion
4
+ include Hashie::Extensions::MethodAccess
5
+ include Hashie::Extensions::IndifferentAccess
6
+ include Hashie::Extensions::DeepFetch
7
+
8
+ def == other
9
+ super(other) || super(self.class[other])
10
+ end
11
+ end
12
+ end
@@ -43,6 +43,12 @@ module DwollaV2
43
43
  end
44
44
  end
45
45
 
46
+ def inspect
47
+ Util.pretty_inspect self.class.name,
48
+ client: client, access_token: access_token, refresh_token: refresh_token,
49
+ expires_in: expires_in, scope: scope, app_id: app_id, account_id: account_id
50
+ end
51
+
46
52
  private
47
53
 
48
54
  def conn
@@ -52,8 +58,8 @@ module DwollaV2
52
58
  f.request :multipart
53
59
  f.request :json
54
60
  f.use HandleErrors
55
- f.use ParseIso8601ResponseBody
56
- f.use SymbolizeResponseBody
61
+ f.use DeepSuperHasherizeResponseBody
62
+ f.use DeepParseIso8601ResponseBody
57
63
  f.response :json, :content_type => /\bjson$/
58
64
  client.faraday.call(f) if client.faraday
59
65
  f.adapter Faraday.default_adapter unless client.faraday
@@ -1,10 +1,10 @@
1
1
  module DwollaV2
2
2
  module Util
3
- def self.deep_symbolize_keys obj
3
+ def self.deep_super_hasherize obj
4
4
  if obj.is_a? Hash
5
- Hash[obj.map{|k,v| [k.to_sym, deep_symbolize_keys(v)] }]
5
+ SuperHash[obj.map{|k,v| [k, deep_super_hasherize(v)] }]
6
6
  elsif obj.is_a? Array
7
- obj.map {|i| deep_symbolize_keys(i) }
7
+ obj.map {|i| deep_super_hasherize(i) }
8
8
  else
9
9
  obj
10
10
  end
@@ -27,5 +27,14 @@ module DwollaV2
27
27
  i.sub(/^(.)/) { $1.capitalize }
28
28
  end.join
29
29
  end
30
+
31
+ def self.pretty_inspect klass_name, attrs, append = nil
32
+ [
33
+ "#<#{klass_name}",
34
+ attrs.map {|k,v| " #{k}=#{v.inspect}" unless v.nil? },
35
+ (" #{append.is_a?(String) ? append.inspect : append}" unless append.nil?),
36
+ ">"
37
+ ].flatten.join
38
+ end
30
39
  end
31
40
  end
@@ -1,3 +1,3 @@
1
1
  module DwollaV2
2
- VERSION = "0.4.0"
2
+ VERSION = "1.0.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dwolla_v2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Ausman
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-03-08 00:00:00.000000000 Z
11
+ date: 2016-03-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.22'
69
+ - !ruby/object:Gem::Dependency
70
+ name: hashie
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.4'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.4'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: faraday
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -139,11 +153,12 @@ files:
139
153
  - lib/dwolla_v2/errors/unsupported_grant_type_error.rb
140
154
  - lib/dwolla_v2/errors/unsupported_response_type_error.rb
141
155
  - lib/dwolla_v2/errors/validation_error.rb
156
+ - lib/dwolla_v2/middleware/deep_parse_iso8601_response_body.rb
157
+ - lib/dwolla_v2/middleware/deep_super_hasherize_response_body.rb
142
158
  - lib/dwolla_v2/middleware/handle_errors.rb
143
- - lib/dwolla_v2/middleware/parse_iso8601_response_body.rb
144
- - lib/dwolla_v2/middleware/symbolize_response_body.rb
145
159
  - lib/dwolla_v2/portal.rb
146
160
  - lib/dwolla_v2/response.rb
161
+ - lib/dwolla_v2/super_hash.rb
147
162
  - lib/dwolla_v2/token.rb
148
163
  - lib/dwolla_v2/util.rb
149
164
  - lib/dwolla_v2/version.rb