recurly 3.4.0 → 3.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.bumpversion.cfg +5 -1
  3. data/.github/workflows/docs.yml +28 -0
  4. data/.travis.yml +1 -0
  5. data/CHANGELOG.md +107 -13
  6. data/GETTING_STARTED.md +61 -1
  7. data/README.md +1 -1
  8. data/lib/recurly/client.rb +126 -52
  9. data/lib/recurly/client/operations.rb +314 -1
  10. data/lib/recurly/http.rb +3 -2
  11. data/lib/recurly/pager.rb +31 -12
  12. data/lib/recurly/requests/add_on_create.rb +15 -3
  13. data/lib/recurly/requests/add_on_update.rb +9 -1
  14. data/lib/recurly/requests/billing_info_create.rb +26 -2
  15. data/lib/recurly/requests/external_transaction.rb +26 -0
  16. data/lib/recurly/requests/plan_create.rb +8 -0
  17. data/lib/recurly/requests/plan_update.rb +8 -0
  18. data/lib/recurly/requests/shipping_method_create.rb +26 -0
  19. data/lib/recurly/requests/shipping_method_update.rb +26 -0
  20. data/lib/recurly/requests/subscription_add_on_create.rb +9 -1
  21. data/lib/recurly/requests/subscription_add_on_tier.rb +18 -0
  22. data/lib/recurly/requests/subscription_add_on_update.rb +6 -2
  23. data/lib/recurly/requests/subscription_change_create.rb +1 -1
  24. data/lib/recurly/requests/tier.rb +18 -0
  25. data/lib/recurly/resources/add_on.rb +8 -0
  26. data/lib/recurly/resources/line_item.rb +1 -1
  27. data/lib/recurly/resources/payment_method.rb +4 -0
  28. data/lib/recurly/resources/plan.rb +8 -0
  29. data/lib/recurly/resources/shipping_method.rb +4 -0
  30. data/lib/recurly/resources/subscription_add_on.rb +16 -0
  31. data/lib/recurly/resources/subscription_add_on_tier.rb +18 -0
  32. data/lib/recurly/resources/subscription_change_preview.rb +74 -0
  33. data/lib/recurly/resources/tier.rb +18 -0
  34. data/lib/recurly/resources/transaction.rb +4 -0
  35. data/lib/recurly/version.rb +1 -1
  36. data/openapi/api.yaml +5407 -2794
  37. data/recurly.gemspec +8 -0
  38. data/scripts/changelog +2 -0
  39. data/scripts/format +5 -1
  40. data/scripts/release +5 -3
  41. metadata +18 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f39df94147fc041414ea7a4fd71254c61014cdfec4732e7325e327d219069d92
4
- data.tar.gz: 48c09a233544b57c4b2948d7d84cdb9eb9c489b3d957da2a2f91aaba3332cb51
3
+ metadata.gz: 5e07115aaa76b090899e7a8b3fbd473aa4c3d9b53f1a0c2e6610bb48e3cfd2c0
4
+ data.tar.gz: 0e9dd8516ed793916175fb8e500894925cfe030f8fa6a3b1457420ab3b23a12f
5
5
  SHA512:
6
- metadata.gz: bf7b86f0522eb6b30a056f3ab85a50589b467098cec88ad52751608cc6d903ec4148fd7d228fb861ada427b985eb6004ffc6fb976790bb2ae390f41013ac8075
7
- data.tar.gz: b92224a280cef663942667721dfb5eb95336f5b9c110237c4467f2304e4aa5e8901ebb51c9705406c69eeac2036a04a62a1df08eb60bdfd12f463126c6d55a82
6
+ metadata.gz: 1b159f6322d33ebdac54a9645c9f791c2bbbc22f42b1b398c4f478b657322f001bfc3275870918e2bca88d599d2409fdf3863a485afd0ee5e2d17c67db012362
7
+ data.tar.gz: 06ed96e99e5e910accf623edd708d36bfa38d439ee4ec7b7e59cdfb8b13cba09e4061ce827e03e66144823c5cd95df4c2741e9e5dd9ee746611ba4a8ef55e817
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 3.4.0
2
+ current_version = 3.8.0
3
3
  parse = (?P<major>\d+)
4
4
  \.(?P<minor>\d+)
5
5
  \.(?P<patch>\d+)
@@ -10,3 +10,7 @@ serialize =
10
10
 
11
11
  [bumpversion:file:lib/recurly/version.rb]
12
12
 
13
+ [bumpversion:file:GETTING_STARTED.md]
14
+ parse = (?P<major>\d+)\.(?P<minor>\d+)
15
+ serialize = {major}.{minor}
16
+
@@ -0,0 +1,28 @@
1
+ on:
2
+ release:
3
+ types:
4
+ - published
5
+ name: Documentation
6
+ jobs:
7
+ build:
8
+ name: Publish
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v2
12
+
13
+ - uses: ruby/setup-ruby@v1
14
+ with:
15
+ ruby-version: '2.6'
16
+
17
+ - name: Build Library and Docs
18
+ run: ./scripts/build
19
+
20
+ - name: Deploy
21
+ if: success()
22
+ uses: crazy-max/ghaction-github-pages@v1
23
+ with:
24
+ target_branch: gh-pages
25
+ build_dir: ./doc
26
+ env:
27
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28
+
@@ -4,6 +4,7 @@ rvm:
4
4
  - 2.4
5
5
  - 2.5
6
6
  - 2.6
7
+ - 2.7
7
8
  bundler_args: --binstubs
8
9
  before_install:
9
10
  - gem update --system
@@ -1,6 +1,98 @@
1
- # Change Log
1
+ # Changelog
2
+
3
+ ## [3.8.0](https://github.com/recurly/recurly-client-ruby/tree/HEAD)
4
+
5
+ [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.7.0...HEAD)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Wed Jul 1 02:06:24 UTC 2020 Upgrade API version v2019-10-10 [\#605](https://github.com/recurly/recurly-client-ruby/pull/605) ([douglasmiller](https://github.com/douglasmiller))
10
+
11
+ **Merged pull requests:**
12
+
13
+ - Release 3.8.0 [\#606](https://github.com/recurly/recurly-client-ruby/pull/606) ([douglasmiller](https://github.com/douglasmiller))
14
+
15
+ ## [3.7.0](https://github.com/recurly/recurly-client-ruby/tree/3.7.0) (2020-06-30)
16
+
17
+ [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.6.0...3.7.0)
18
+
19
+ **Implemented enhancements:**
20
+
21
+ - Mon Jun 29 17:01:25 UTC 2020 Upgrade API version v2019-10-10 [\#601](https://github.com/recurly/recurly-client-ruby/pull/601) ([douglasmiller](https://github.com/douglasmiller))
22
+
23
+ **Fixed bugs:**
24
+
25
+ - Allow :headers to be included in operations [\#597](https://github.com/recurly/recurly-client-ruby/pull/597) ([douglasmiller](https://github.com/douglasmiller))
26
+
27
+ **Merged pull requests:**
28
+
29
+ - Release 3.7.0 [\#602](https://github.com/recurly/recurly-client-ruby/pull/602) ([douglasmiller](https://github.com/douglasmiller))
30
+ - Fix doc link [\#596](https://github.com/recurly/recurly-client-ruby/pull/596) ([bhelx](https://github.com/bhelx))
31
+
32
+ ## [3.6.0](https://github.com/recurly/recurly-client-ruby/tree/3.6.0) (2020-06-01)
33
+
34
+ [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.5.0...3.6.0)
35
+
36
+ **Implemented enhancements:**
37
+
38
+ - Latest Features [\#592](https://github.com/recurly/recurly-client-ruby/pull/592) ([bhelx](https://github.com/bhelx))
39
+ - Support the programmer passing their own logger [\#590](https://github.com/recurly/recurly-client-ruby/pull/590) ([bhelx](https://github.com/bhelx))
40
+
41
+ **Merged pull requests:**
42
+
43
+ - Release 3.6.0 [\#594](https://github.com/recurly/recurly-client-ruby/pull/594) ([bhelx](https://github.com/bhelx))
44
+ - Better format error message [\#593](https://github.com/recurly/recurly-client-ruby/pull/593) ([bhelx](https://github.com/bhelx))
45
+ - Let bump2version manage the getting started doc [\#591](https://github.com/recurly/recurly-client-ruby/pull/591) ([bhelx](https://github.com/bhelx))
46
+ - Document `Pager#first` and `Pager#count` [\#589](https://github.com/recurly/recurly-client-ruby/pull/589) ([bhelx](https://github.com/bhelx))
47
+ - Ensure that path parameters are not empty strings [\#587](https://github.com/recurly/recurly-client-ruby/pull/587) ([douglasmiller](https://github.com/douglasmiller))
48
+
49
+ ## [3.5.0](https://github.com/recurly/recurly-client-ruby/tree/3.5.0) (2020-04-20)
50
+
51
+ [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.4.1...3.5.0)
52
+
53
+ **Implemented enhancements:**
54
+
55
+ - Tue Apr 14 20:21:21 UTC 2020 Upgrade API version v2019-10-10 [\#585](https://github.com/recurly/recurly-client-ruby/pull/585) ([bhelx](https://github.com/bhelx))
56
+ - Set an Idempotency-Key header, retry GET requests after 5xx errors [\#579](https://github.com/recurly/recurly-client-ruby/pull/579) ([isaachall](https://github.com/isaachall))
57
+ - Adding \#first and \#count methods to Pager [\#560](https://github.com/recurly/recurly-client-ruby/pull/560) ([douglasmiller](https://github.com/douglasmiller))
58
+
59
+ **Fixed bugs:**
60
+
61
+ - Fixing the omission of query params [\#581](https://github.com/recurly/recurly-client-ruby/pull/581) ([douglasmiller](https://github.com/douglasmiller))
62
+
63
+ **Merged pull requests:**
64
+
65
+ - Release 3.5.0 [\#586](https://github.com/recurly/recurly-client-ruby/pull/586) ([douglasmiller](https://github.com/douglasmiller))
66
+ - Included the to-be released changes in the changelog [\#584](https://github.com/recurly/recurly-client-ruby/pull/584) ([douglasmiller](https://github.com/douglasmiller))
67
+ - Add 2.7 to test matrix [\#582](https://github.com/recurly/recurly-client-ruby/pull/582) ([bhelx](https://github.com/bhelx))
68
+ - Use github pages for docs [\#580](https://github.com/recurly/recurly-client-ruby/pull/580) ([bhelx](https://github.com/bhelx))
69
+ - Add project metadata to the gemspec [\#578](https://github.com/recurly/recurly-client-ruby/pull/578) ([orien](https://github.com/orien))
70
+ - Updating release script to be uniform across all clients [\#577](https://github.com/recurly/recurly-client-ruby/pull/577) ([douglasmiller](https://github.com/douglasmiller))
71
+ - Thu Mar 26 20:41:10 UTC 2020 Upgrade API version v2019-10-10 [\#573](https://github.com/recurly/recurly-client-ruby/pull/573) ([bhelx](https://github.com/bhelx))
72
+
73
+ ## [3.4.1](https://github.com/recurly/recurly-client-ruby/tree/3.4.1) (2020-03-26)
74
+
75
+ [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.4.0...3.4.1)
76
+
77
+ **Merged pull requests:**
78
+
79
+ - Release 3.4.1 [\#571](https://github.com/recurly/recurly-client-ruby/pull/571) ([bhelx](https://github.com/bhelx))
80
+ - Follow up bug fixes for \#568 [\#570](https://github.com/recurly/recurly-client-ruby/pull/570) ([bhelx](https://github.com/bhelx))
81
+
82
+ ## [3.4.0](https://github.com/recurly/recurly-client-ruby/tree/3.4.0) (2020-03-23)
83
+
84
+ [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.3.1...3.4.0)
85
+
86
+ **Implemented enhancements:**
87
+
88
+ - Replace Faraday gem with Net::HTTP, add connection pooling & keep-alive, update CA roots [\#568](https://github.com/recurly/recurly-client-ruby/pull/568) ([isaachall](https://github.com/isaachall))
89
+
90
+ **Merged pull requests:**
91
+
92
+ - Release 3.4.0 [\#569](https://github.com/recurly/recurly-client-ruby/pull/569) ([bhelx](https://github.com/bhelx))
2
93
 
3
94
  ## [3.3.1](https://github.com/recurly/recurly-client-ruby/tree/3.3.1) (2020-03-20)
95
+
4
96
  [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.3.0...3.3.1)
5
97
 
6
98
  **Merged pull requests:**
@@ -12,6 +104,7 @@
12
104
  - Add request for stack trace in issue report [\#558](https://github.com/recurly/recurly-client-ruby/pull/558) ([bhelx](https://github.com/bhelx))
13
105
 
14
106
  ## [3.3.0](https://github.com/recurly/recurly-client-ruby/tree/3.3.0) (2020-02-20)
107
+
15
108
  [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.2.2...3.3.0)
16
109
 
17
110
  **Merged pull requests:**
@@ -22,6 +115,7 @@
22
115
  - Latest v2019-10-10 Changes [\#552](https://github.com/recurly/recurly-client-ruby/pull/552) ([bhelx](https://github.com/bhelx))
23
116
 
24
117
  ## [3.2.2](https://github.com/recurly/recurly-client-ruby/tree/3.2.2) (2020-02-03)
118
+
25
119
  [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.2.1...3.2.2)
26
120
 
27
121
  **Merged pull requests:**
@@ -30,6 +124,7 @@
30
124
  - Loosen version restriction on faraday [\#549](https://github.com/recurly/recurly-client-ruby/pull/549) ([bhelx](https://github.com/bhelx))
31
125
 
32
126
  ## [3.2.1](https://github.com/recurly/recurly-client-ruby/tree/3.2.1) (2019-12-10)
127
+
33
128
  [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.2.0...3.2.1)
34
129
 
35
130
  **Fixed bugs:**
@@ -37,6 +132,7 @@
37
132
  - Convert Array params to CSV strings [\#545](https://github.com/recurly/recurly-client-ruby/pull/545) ([douglasmiller](https://github.com/douglasmiller))
38
133
 
39
134
  ## [3.2.0](https://github.com/recurly/recurly-client-ruby/tree/3.2.0) (2019-12-03)
135
+
40
136
  [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.1.3...3.2.0)
41
137
 
42
138
  **Fixed bugs:**
@@ -50,6 +146,7 @@
50
146
  - Allow object attributes through [\#542](https://github.com/recurly/recurly-client-ruby/pull/542) ([bhelx](https://github.com/bhelx))
51
147
 
52
148
  ## [3.1.3](https://github.com/recurly/recurly-client-ruby/tree/3.1.3) (2019-12-02)
149
+
53
150
  [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.1.2...3.1.3)
54
151
 
55
152
  **Fixed bugs:**
@@ -58,6 +155,7 @@
58
155
  - Issue 540 error may have transaction [\#541](https://github.com/recurly/recurly-client-ruby/pull/541) ([bhelx](https://github.com/bhelx))
59
156
 
60
157
  ## [3.1.2](https://github.com/recurly/recurly-client-ruby/tree/3.1.2) (2019-12-02)
158
+
61
159
  [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.1.1...3.1.2)
62
160
 
63
161
  **Fixed bugs:**
@@ -65,6 +163,7 @@
65
163
  - Skip request property type validation for nil values [\#539](https://github.com/recurly/recurly-client-ruby/pull/539) ([bhelx](https://github.com/bhelx))
66
164
 
67
165
  ## [3.1.1](https://github.com/recurly/recurly-client-ruby/tree/3.1.1) (2019-11-27)
166
+
68
167
  [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.1.0...3.1.1)
69
168
 
70
169
  **Fixed bugs:**
@@ -73,6 +172,7 @@
73
172
  - Disable searching ancestors when looking up constants [\#537](https://github.com/recurly/recurly-client-ruby/pull/537) ([douglasmiller](https://github.com/douglasmiller))
74
173
 
75
174
  ## [3.1.0](https://github.com/recurly/recurly-client-ruby/tree/3.1.0) (2019-11-18)
175
+
76
176
  [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.0.0...3.1.0)
77
177
 
78
178
  **Merged pull requests:**
@@ -82,6 +182,7 @@
82
182
  - Generated Updates for API version v2019-10-10 [\#528](https://github.com/recurly/recurly-client-ruby/pull/528) ([bhelx](https://github.com/bhelx))
83
183
 
84
184
  ## [3.0.0](https://github.com/recurly/recurly-client-ruby/tree/3.0.0) (2019-10-09)
185
+
85
186
  [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.0.0.beta.5...3.0.0)
86
187
 
87
188
  **Merged pull requests:**
@@ -103,13 +204,14 @@
103
204
  - Add CONTRIBUTING.md [\#486](https://github.com/recurly/recurly-client-ruby/pull/486) ([bhelx](https://github.com/bhelx))
104
205
  - Bump 3.0.0.beta.6 [\#485](https://github.com/recurly/recurly-client-ruby/pull/485) ([bhelx](https://github.com/bhelx))
105
206
  - Latest v2018-08-09 Changes [\#484](https://github.com/recurly/recurly-client-ruby/pull/484) ([bhelx](https://github.com/bhelx))
106
- - 3.0.0.beta.5 [\#483](https://github.com/recurly/recurly-client-ruby/pull/483) ([bhelx](https://github.com/bhelx))
107
207
 
108
208
  ## [3.0.0.beta.5](https://github.com/recurly/recurly-client-ruby/tree/3.0.0.beta.5) (2019-06-28)
209
+
109
210
  [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.0.0.beta.4...3.0.0.beta.5)
110
211
 
111
212
  **Merged pull requests:**
112
213
 
214
+ - 3.0.0.beta.5 [\#483](https://github.com/recurly/recurly-client-ruby/pull/483) ([bhelx](https://github.com/bhelx))
113
215
  - Latest v2018-08-09 Changes [\#482](https://github.com/recurly/recurly-client-ruby/pull/482) ([bhelx](https://github.com/bhelx))
114
216
  - no longer need dep scripts [\#476](https://github.com/recurly/recurly-client-ruby/pull/476) ([bhelx](https://github.com/bhelx))
115
217
  - Add format script and check in specs [\#474](https://github.com/recurly/recurly-client-ruby/pull/474) ([bhelx](https://github.com/bhelx))
@@ -117,28 +219,20 @@
117
219
  - Add strict mode for json deserializer [\#469](https://github.com/recurly/recurly-client-ruby/pull/469) ([bhelx](https://github.com/bhelx))
118
220
 
119
221
  ## [3.0.0.beta.4](https://github.com/recurly/recurly-client-ruby/tree/3.0.0.beta.4) (2019-04-04)
120
- [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.0.0.beta.3...3.0.0.beta.4)
222
+
223
+ [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.0.0.beta.1...3.0.0.beta.4)
121
224
 
122
225
  **Merged pull requests:**
123
226
 
124
227
  - V3 Update v2018-08-09 [\#460](https://github.com/recurly/recurly-client-ruby/pull/460) ([aaron-suarez](https://github.com/aaron-suarez))
125
228
  - Small fixes for private beta [\#458](https://github.com/recurly/recurly-client-ruby/pull/458) ([bhelx](https://github.com/bhelx))
126
229
  - Use Net-http-persistent for persistent connection [\#408](https://github.com/recurly/recurly-client-ruby/pull/408) ([bhelx](https://github.com/bhelx))
127
-
128
- ## [3.0.0.beta.3](https://github.com/recurly/recurly-client-ruby/tree/3.0.0.beta.3) (2018-08-27)
129
- [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.0.0.beta.2...3.0.0.beta.3)
130
-
131
- **Merged pull requests:**
132
-
133
230
  - Update to API 2018-06-06 [\#407](https://github.com/recurly/recurly-client-ruby/pull/407) ([bhelx](https://github.com/bhelx))
134
231
  - Regenerating the client [\#406](https://github.com/recurly/recurly-client-ruby/pull/406) ([drewish](https://github.com/drewish))
135
232
  - V3 Pager can error [\#401](https://github.com/recurly/recurly-client-ruby/pull/401) ([drewish](https://github.com/drewish))
136
233
  - \[V3\] Test more versions of ruby [\#397](https://github.com/recurly/recurly-client-ruby/pull/397) ([drewish](https://github.com/drewish))
137
234
  - Allow faraday 0.12 for compatibility with oauth2 gem [\#396](https://github.com/recurly/recurly-client-ruby/pull/396) ([drewish](https://github.com/drewish))
138
235
 
139
- ## [3.0.0.beta.2](https://github.com/recurly/recurly-client-ruby/tree/3.0.0.beta.2) (2018-07-17)
140
- [Full Changelog](https://github.com/recurly/recurly-client-ruby/compare/3.0.0.beta.1...3.0.0.beta.2)
141
-
142
236
 
143
237
 
144
- \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
238
+ \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
@@ -5,7 +5,7 @@ This repository houses the official ruby client for Recurly's V3 API.
5
5
  In your Gemfile, add `recurly` as a dependency.
6
6
 
7
7
  ```ruby
8
- gem 'recurly', '~> 3.2'
8
+ gem 'recurly', '~> 3.8'
9
9
  ```
10
10
 
11
11
  > *Note*: We try to follow [semantic versioning](https://semver.org/) and will only apply breaking changes to major versions.
@@ -41,6 +41,27 @@ client = Recurly::Client.new(api_key: API_KEY2)
41
41
  sub = client.get_subscription(subscription_id: 'abcd7890')
42
42
  ```
43
43
 
44
+ ## Logging
45
+
46
+ The client constructor optionally accepts a logger provided by the programmer. The logger you pass should be an instance of ruby stdlib's [Logger](https://ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger.html)
47
+ or follow the same interface. By default, the client creates a logger to `STDOUT` with level `WARN`.
48
+
49
+ ```ruby
50
+ require 'logger'
51
+
52
+ # Create a logger to STDOUT
53
+ logger = Logger.new(STDOUT)
54
+ logger.level = Logger::INFO
55
+
56
+ # You could also use an existing logger
57
+ # If you are using Rails you may want to use your application's logger
58
+ logger = Rails.logger
59
+
60
+ client = Recurly::Client.new(api_key: API_KEY, logger: logger)
61
+ ```
62
+
63
+ > *SECURITY WARNING*: The log level should never be set to DEBUG in production. This could potentially result in sensitive data in your logging system.
64
+
44
65
  # Operations
45
66
 
46
67
  The {Recurly::Client} contains every `operation` you can perform on the site as a list of methods. Each method is documented explaining
@@ -109,6 +130,45 @@ end
109
130
  `limit` defaults to 20 items per page and can be set from 1 to 200. Choosing a lower limit means more network requests but smaller payloads.
110
131
  We recommend keeping the default for most cases but increasing the limit if you are planning on iterating through many pages of items (e.g. all transactions in your site).
111
132
 
133
+ ## Efficiently Fetch the First or Last Resource
134
+
135
+ The Pager class implements a first method which allows you to fetch just the first or last resource from the server. On top of being a convenient abstraction, this is implemented efficiently by only asking the server for the 1 item you want.
136
+
137
+ ```ruby
138
+ accounts = client.list_accounts(
139
+ subscriber: true,
140
+ order: :desc
141
+ )
142
+
143
+ last_subscriber = accounts.first
144
+ ```
145
+
146
+ If you want to fetch the last account in this scenario, invert the order from descending `desc` to ascending `asc`:
147
+
148
+ ```ruby
149
+ accounts = client.list_accounts(
150
+ subscriber: true,
151
+ order: :asc
152
+ )
153
+
154
+ first_subscriber = accounts.first
155
+ ```
156
+
157
+ ## Counting Resources
158
+
159
+ The Pager class implements a `count` method which allows you to count the resources the pager would return. It does so by calling the endpoint with `HEAD` and parsing and returning the `Recurly-Total-Records` header. This method respects any filtering parameters you apply to the pager, but the sorting parameters will have no effect.
160
+
161
+ ```ruby
162
+ accounts = client.list_accounts(
163
+ subscriber: true,
164
+ begin_time: DateTime.new(2017,1,1)
165
+ )
166
+
167
+ # Calling count here will return an integer indicating
168
+ # the number of subscribers since 2017
169
+ count = accounts.count
170
+ # => 573
171
+ ```
112
172
 
113
173
  # Creating Resources
114
174
 
data/README.md CHANGED
@@ -7,7 +7,7 @@ This repository houses the official ruby client for Recurly's V3 API.
7
7
 
8
8
  ## Reference Documentation
9
9
 
10
- Getting Started Guide and reference documentation can be found on [rubydoc.info](https://www.rubydoc.info/github/recurly/recurly-client-ruby/).
10
+ Getting Started Guide and reference documentation can be found on [Github Pages](https://recurly.github.io/recurly-client-ruby/).
11
11
 
12
12
  ## Contributing
13
13
 
@@ -2,6 +2,7 @@ require "logger"
2
2
  require "erb"
3
3
  require "net/https"
4
4
  require "base64"
5
+ require "securerandom"
5
6
  require_relative "./schema/json_parser"
6
7
  require_relative "./schema/file_parser"
7
8
 
@@ -14,9 +15,12 @@ module Recurly
14
15
  CA_FILE = File.join(File.dirname(__FILE__), "../data/ca-certificates.crt")
15
16
  BINARY_TYPES = [
16
17
  "application/pdf",
17
- ]
18
+ ].freeze
18
19
  JSON_CONTENT_TYPE = "application/json"
19
20
  MAX_RETRIES = 3
21
+ LOG_LEVELS = %i(debug info warn error fatal).freeze
22
+ BASE36_ALPHABET = (("0".."9").to_a + ("a".."z").to_a).freeze
23
+ REQUEST_OPTIONS = [:headers].freeze
20
24
 
21
25
  # Initialize a client. It requires an API key.
22
26
  #
@@ -42,29 +46,42 @@ module Recurly
42
46
  # sub = client.get_subscription(subscription_id: 'uuid-abcd7890')
43
47
  #
44
48
  # @param api_key [String] The private API key
45
- # @param site_id [String] The site you wish to be scoped to.
46
- # @param subdomain [String] Optional subdomain for the site you wish to be scoped to. Providing this makes all the `site_id` parameters optional.
47
- def initialize(api_key:, site_id: nil, subdomain: nil, **options)
49
+ # @param logger [Logger] A logger to use. Defaults to creating a new STDOUT logger with level WARN.
50
+ def initialize(api_key:, site_id: nil, subdomain: nil, logger: nil)
48
51
  set_site_id(site_id, subdomain)
49
52
  set_api_key(api_key)
50
- set_options(options)
53
+
54
+ if logger.nil?
55
+ @logger = Logger.new(STDOUT).tap do |l|
56
+ l.level = Logger::WARN
57
+ end
58
+ else
59
+ unless LOG_LEVELS.all? { |lev| logger.respond_to?(lev) }
60
+ raise ArgumentError, "You must pass in a logger implementation that responds to the following messages: #{LOG_LEVELS}"
61
+ end
62
+ @logger = logger
63
+ end
64
+
65
+ if @logger.level < Logger::INFO
66
+ msg = <<-MSG
67
+ The Recurly logger should not be initialized
68
+ beyond the level INFO. It is currently configured to emit
69
+ headers and request / response bodies. This has the potential to leak
70
+ PII and other sensitive information and should never be used in production.
71
+ MSG
72
+ log_warn("SECURITY_WARNING", message: msg)
73
+ end
51
74
 
52
75
  # execute block with this client if given
53
76
  yield(self) if block_given?
54
77
  end
55
78
 
56
- def next_page(pager)
57
- path = extract_path(pager.next)
58
- request = Net::HTTP::Get.new path
59
- set_headers(request)
60
- http_response = run_request(request)
61
- handle_response! request, http_response
62
- end
63
-
64
79
  protected
65
80
 
81
+ # Used by the operations.rb file to interpolate paths
82
+ attr_reader :site_id
83
+
66
84
  def pager(path, **options)
67
- path = scope_by_site(path, **options)
68
85
  Pager.new(
69
86
  client: self,
70
87
  path: path,
@@ -72,9 +89,15 @@ module Recurly
72
89
  )
73
90
  end
74
91
 
92
+ def head(path, **options)
93
+ request = Net::HTTP::Head.new build_url(path, options)
94
+ set_headers(request, options[:headers])
95
+ http_response = run_request(request, options)
96
+ handle_response! request, http_response
97
+ end
98
+
75
99
  def get(path, **options)
76
- path = scope_by_site(path, **options)
77
- request = Net::HTTP::Get.new path
100
+ request = Net::HTTP::Get.new build_url(path, options)
78
101
  set_headers(request, options[:headers])
79
102
  http_response = run_request(request, options)
80
103
  handle_response! request, http_response
@@ -82,8 +105,7 @@ module Recurly
82
105
 
83
106
  def post(path, request_data, request_class, **options)
84
107
  request_class.new(request_data).validate!
85
- path = scope_by_site(path, **options)
86
- request = Net::HTTP::Post.new path
108
+ request = Net::HTTP::Post.new build_url(path, options)
87
109
  request.set_content_type(JSON_CONTENT_TYPE)
88
110
  set_headers(request, options[:headers])
89
111
  request.body = JSON.dump(request_data)
@@ -92,14 +114,12 @@ module Recurly
92
114
  end
93
115
 
94
116
  def put(path, request_data = nil, request_class = nil, **options)
95
- path = scope_by_site(path, **options)
96
- request = Net::HTTP::Put.new path
117
+ request = Net::HTTP::Put.new build_url(path, options)
97
118
  request.set_content_type(JSON_CONTENT_TYPE)
98
119
  set_headers(request, options[:headers])
99
120
  if request_data
100
121
  request_class.new(request_data).validate!
101
122
  json_body = JSON.dump(request_data)
102
- logger.info("PUT BODY #{json_body}")
103
123
  request.body = json_body
104
124
  end
105
125
  http_response = run_request(request, options)
@@ -107,23 +127,14 @@ module Recurly
107
127
  end
108
128
 
109
129
  def delete(path, **options)
110
- path = scope_by_site(path, **options)
111
- request = Net::HTTP::Delete.new path
130
+ request = Net::HTTP::Delete.new build_url(path, options)
112
131
  set_headers(request, options[:headers])
113
132
  http_response = run_request(request, options)
114
133
  handle_response! request, http_response
115
134
  end
116
135
 
117
- protected
118
-
119
- # Used by the operations.rb file to interpolate paths
120
- attr_reader :site_id
121
-
122
136
  private
123
137
 
124
- # @return [Logger]
125
- attr_reader :logger
126
-
127
138
  @connection_pool = Recurly::ConnectionPool.new
128
139
 
129
140
  class << self
@@ -139,7 +150,38 @@ module Recurly
139
150
 
140
151
  begin
141
152
  http.start unless http.started?
142
- http.request(request)
153
+ log_attrs = {
154
+ method: request.method,
155
+ path: request.path,
156
+ }
157
+ if @logger.level < Logger::INFO
158
+ log_attrs[:request_body] = request.body
159
+ # No need to log the authorization header
160
+ headers = request.to_hash.reject { |k, _| k&.downcase == "authorization" }
161
+ log_attrs[:request_headers] = headers
162
+ end
163
+
164
+ log_info("Request", **log_attrs)
165
+ start = Time.now
166
+ response = http.request(request)
167
+ elapsed = Time.now - start
168
+
169
+ # GETs are safe to retry after a server error, requests with an Idempotency-Key will return the prior response
170
+ if response.kind_of?(Net::HTTPServerError) && request.is_a?(Net::HTTP::Get)
171
+ retries += 1
172
+ log_info("Retrying", retries: retries, **log_attrs)
173
+ start = Time.now
174
+ response = http.request(request) if retries < MAX_RETRIES
175
+ elapsed = Time.now - start
176
+ end
177
+
178
+ if @logger.level < Logger::INFO
179
+ log_attrs[:response_body] = response.body
180
+ log_attrs[:response_headers] = response.to_hash
181
+ end
182
+ log_info("Response", time_ms: (elapsed * 1_000).floor, status: response.code, **log_attrs)
183
+
184
+ response
143
185
  rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EHOSTUNREACH, Errno::ECONNABORTED,
144
186
  Errno::EPIPE, Errno::ETIMEDOUT, Net::OpenTimeout, EOFError, SocketError => ex
145
187
  retries += 1
@@ -163,26 +205,37 @@ module Recurly
163
205
  end
164
206
 
165
207
  def set_headers(request, additional_headers = {})
208
+ # TODO this is undocumented until we finalize it
209
+ additional_headers.each { |header, v| request[header] = v } if additional_headers
210
+
166
211
  request["Accept"] = "application/vnd.recurly.#{api_version}".chomp # got this method from operations.rb
167
212
  request["Authorization"] = "Basic #{Base64.encode64(@api_key)}".chomp
168
213
  request["User-Agent"] = "Recurly/#{VERSION}; #{RUBY_DESCRIPTION}"
169
214
 
170
- # TODO this is undocumented until we finalize it
171
- additional_headers.each { |header, v| request[header] = v } if additional_headers
215
+ unless request.is_a?(Net::HTTP::Get) || request.is_a?(Net::HTTP::Head)
216
+ request["Idempotency-Key"] ||= generate_idempotency_key
217
+ end
218
+ end
219
+
220
+ # from https://github.com/rails/rails/blob/6-0-stable/activesupport/lib/active_support/core_ext/securerandom.rb
221
+ def generate_idempotency_key(n = 16)
222
+ SecureRandom.random_bytes(n).unpack("C*").map do |byte|
223
+ idx = byte % 64
224
+ idx = SecureRandom.random_number(36) if idx >= 36
225
+ BASE36_ALPHABET[idx]
226
+ end.join
172
227
  end
173
228
 
174
229
  def set_http_options(http, options)
175
230
  http.open_timeout = options[:open_timeout] || 20
176
231
  http.read_timeout = options[:read_timeout] || 60
177
-
178
- http.set_debug_output(logger) if @log_level <= Logger::INFO && !http.started?
179
232
  end
180
233
 
181
234
  def handle_response!(request, http_response)
182
235
  response = HTTP::Response.new(http_response, request)
183
236
  raise_api_error!(http_response, response) unless http_response.kind_of?(Net::HTTPSuccess)
184
237
  resource = if response.body
185
- if http_response.content_type.include?(JSON_CONTENT_TYPE)
238
+ if http_response.content_type&.include?(JSON_CONTENT_TYPE)
186
239
  JSONParser.parse(self, response.body)
187
240
  elsif BINARY_TYPES.include?(http_response.content_type)
188
241
  FileParser.parse(response.body)
@@ -216,15 +269,15 @@ module Recurly
216
269
 
217
270
  def read_headers(response)
218
271
  if !@_ignore_deprecation_warning && response.headers["Recurly-Deprecated"]&.upcase == "TRUE"
219
- puts "[recurly-client-ruby] WARNING: Your current API version \"#{api_version}\" is deprecated and will be sunset on #{response.headers["Recurly-Sunset-Date"]}"
272
+ log_warn("DEPRECTATION WARNING", message: "Your current API version \"#{api_version}\" is deprecated and will be sunset on #{response.headers["Recurly-Sunset-Date"]}")
220
273
  end
221
274
  response
222
275
  end
223
276
 
224
- def interpolate_path(path, **options)
277
+ def validate_path_parameters!(**options)
278
+ # Check to see that we are passing the correct data types
279
+ # This prevents a confusing error if the user passes in a non-primitive by mistake
225
280
  options.each do |k, v|
226
- # Check to see that we are passing the correct data types
227
- # This prevents a confusing error if the user passes in a non-primitive by mistake
228
281
  unless [String, Symbol, Integer, Float].include?(v.class)
229
282
  message = "We cannot build the url with the given argument #{k}=#{v.inspect}."
230
283
  if k =~ /_id$/
@@ -232,6 +285,17 @@ module Recurly
232
285
  end
233
286
  raise ArgumentError, message
234
287
  end
288
+ end
289
+ # Check to make sure that parameters are not empty string values
290
+ empty_strings = options.select { |_, v| v.is_a?(String) && v.strip.empty? }
291
+ if empty_strings.any?
292
+ raise ArgumentError, "#{empty_strings.keys.join(", ")} cannot be an empty string"
293
+ end
294
+ end
295
+
296
+ def interpolate_path(path, **options)
297
+ validate_path_parameters!(options)
298
+ options.each do |k, v|
235
299
  # We need to encode the values for the url
236
300
  options[k] = ERB::Util.url_encode(v.to_s)
237
301
  end
@@ -251,24 +315,34 @@ module Recurly
251
315
  @api_key = api_key
252
316
  end
253
317
 
254
- def scope_by_site(path, **options)
255
- if site = site_id || options[:site_id]
256
- "/sites/#{site}#{path}"
318
+ def build_url(path, options)
319
+ path = scope_by_site(path, options)
320
+ query_params = options.reject { |k, _| REQUEST_OPTIONS.include?(k.to_sym) }
321
+ if query_params.any?
322
+ "#{path}?#{URI.encode_www_form(query_params)}"
257
323
  else
258
324
  path
259
325
  end
260
326
  end
261
327
 
262
- # Returns just the path and parameters so we can safely reuse the connection
263
- def extract_path(uri_or_path)
264
- uri = URI(uri_or_path)
265
- uri.kind_of?(URI::HTTP) ? uri.request_uri : uri_or_path
328
+ def scope_by_site(path, **options)
329
+ if site = site_id || options[:site_id]
330
+ # Ensure that we are only including the site_id once because the Pager operations
331
+ # will use the cursor returned from the API which may already have these components
332
+ path.start_with?("/sites/#{site}") ? path : "/sites/#{site}#{path}"
333
+ else
334
+ path
335
+ end
266
336
  end
267
337
 
268
- def set_options(options)
269
- @log_level = options[:log_level] || Logger::WARN
270
- @logger = Logger.new(STDOUT)
271
- @logger.level = @log_level
338
+ # Define a private `log_<level>` method for each log level
339
+ LOG_LEVELS.each do |level|
340
+ define_method "log_#{level}" do |tag, **attrs|
341
+ @logger.send(level, "Recurly") do
342
+ msg = attrs.each_pair.map { |k, v| "#{k}=#{v.inspect}" }.join(" ")
343
+ "[#{tag}] #{msg}"
344
+ end
345
+ end
272
346
  end
273
347
  end
274
348
  end