gibbon 3.5.0 → 4.0.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 +4 -4
- data/.document +3 -3
- data/.github/workflows/ci.yml +18 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +11 -1
- data/Gemfile +0 -8
- data/README.markdown +121 -73
- data/gibbon.gemspec +15 -5
- data/lib/gibbon/api_request.rb +30 -1
- data/lib/gibbon/gibbon_error.rb +3 -0
- data/lib/gibbon/gibbon_helpers.rb +9 -2
- data/lib/gibbon/mailchimp_error.rb +20 -1
- data/lib/gibbon/request.rb +94 -3
- data/lib/gibbon/response.rb +15 -2
- data/lib/gibbon/version.rb +2 -1
- data/lib/gibbon.rb +1 -0
- data/spec/gibbon/api_request_spec.rb +173 -0
- data/spec/gibbon/gibbon_helpers_spec.rb +27 -0
- data/spec/gibbon/gibbon_spec.rb +16 -0
- data/spec/gibbon/mailchimp_error_spec.rb +18 -0
- data/spec/gibbon/request_spec.rb +141 -0
- data/spec/gibbon/response_spec.rb +17 -0
- metadata +51 -21
- data/.travis.yml +0 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 421fe35f5c093158bac5d59d0a684325e8b77888257b6758fd805d9c86608520
|
|
4
|
+
data.tar.gz: cd15c1fa018171363d7fe32c6dddbe14e14985ed02ca1cbdcb313240e39c1640
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0da6bfbdd8f9bff5ffbeac3943f3c3f571204b9a38869dfa2e75cba7bc1ba2863aa7dcdcb82e3167b54ed9dd0222d118a7b74606b2d7d8f92ec8fc8681529d24
|
|
7
|
+
data.tar.gz: 0ff34fa92007f022b7faf7368cf1285ed995efa43e964b0328928e8c466bc1661d0cdc0a32f0fed612e4cb5d54deaba74dfdfecae5dc264e4215ddb4bfc39ddf
|
data/.document
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
strategy:
|
|
9
|
+
fail-fast: false
|
|
10
|
+
matrix:
|
|
11
|
+
ruby: ['3.1', '3.2', '3.3', '3.4', '4.0']
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v5
|
|
14
|
+
- uses: ruby/setup-ruby@v1
|
|
15
|
+
with:
|
|
16
|
+
ruby-version: ${{ matrix.ruby }}
|
|
17
|
+
bundler-cache: true
|
|
18
|
+
- run: bundle exec rake
|
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [4.0.1] - 2026-10-23
|
|
2
|
+
- Honor explicit `false` instance overrides for `symbolize_keys` and `debug`, while preserving class defaults for omitted options and `nil`.
|
|
3
|
+
- Constrain `json` to versions below 3.0 to preserve compatibility with the `multi_json` adapter available on Ruby 3.1.
|
|
4
|
+
- Expand API documentation, update usage examples, and add gem metadata links.
|
|
5
|
+
|
|
6
|
+
## [4.0.0] - 2026-07-23
|
|
7
|
+
- Require Ruby 3.1+ (drops support for EOL Rubies 2.4 through 3.0)
|
|
8
|
+
- Require TLS 1.2 as a minimum instead of pinning it exactly, allowing TLS 1.3
|
|
9
|
+
- Modernize development setup: loosen rspec pin, remove Rubinius leftovers, update CI matrix to Ruby 3.1 through 4.0
|
|
10
|
+
|
|
11
|
+
## [3.5.0] - 2023-06-07
|
|
2
12
|
- Remove support for retired Export API
|
|
3
13
|
|
|
4
14
|
## [3.4.4] - 2022-02-24
|
data/Gemfile
CHANGED
data/README.markdown
CHANGED
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
# gibbon
|
|
2
2
|
|
|
3
|
-
Gibbon is an API wrapper for MailChimp's [API](
|
|
3
|
+
Gibbon is an API wrapper for MailChimp's [API](https://mailchimp.com/developer/marketing/api/).
|
|
4
4
|
|
|
5
|
-
[](https://github.com/amro/gibbon/actions/workflows/ci.yml)
|
|
6
6
|
|
|
7
7
|
## Important Notes
|
|
8
8
|
|
|
9
|
-
Please read MailChimp's [Getting Started Guide](
|
|
9
|
+
Please read MailChimp's [Getting Started Guide](https://mailchimp.com/developer/marketing/guides/quick-start/).
|
|
10
10
|
|
|
11
|
-
Gibbon
|
|
11
|
+
Gibbon returns a `Gibbon::Response`, which exposes the parsed response `body` and the response `headers`. See [Responses](#responses) below.
|
|
12
|
+
|
|
13
|
+
See the [CHANGELOG](CHANGELOG.md) for release history and [Upgrading](#upgrading) for breaking changes.
|
|
12
14
|
|
|
13
15
|
## Installation
|
|
14
16
|
|
|
15
17
|
$ gem install gibbon
|
|
16
18
|
|
|
19
|
+
Or add it to your `Gemfile`:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
gem "gibbon"
|
|
23
|
+
```
|
|
24
|
+
|
|
17
25
|
## Requirements
|
|
18
26
|
|
|
19
|
-
|
|
27
|
+
* **Ruby 3.1 or newer.** Gibbon 4.0 dropped support for Ruby 2.4 through 3.0.
|
|
28
|
+
* A MailChimp account and API key. You can see your API keys [here](https://admin.mailchimp.com/account/api).
|
|
29
|
+
|
|
30
|
+
Gibbon depends on [Faraday](https://github.com/lostisland/faraday) (both 1.x and 2.x are supported) and [MultiJson](https://github.com/intridea/multi_json). Connections to MailChimp negotiate TLS 1.2 or newer.
|
|
20
31
|
|
|
21
32
|
## Usage
|
|
22
33
|
|
|
@@ -26,7 +37,7 @@ First, create a *one-time use instance* of `Gibbon::Request`:
|
|
|
26
37
|
gibbon = Gibbon::Request.new(api_key: "your_api_key")
|
|
27
38
|
```
|
|
28
39
|
|
|
29
|
-
***Note*** Only reuse instances of Gibbon after terminating a call with a verb, which makes a request. Requests are light weight objects that update an internal path based on your call chain. When you terminate a call chain with a verb, a request instance makes a request and resets the path.
|
|
40
|
+
***Note*** Only reuse instances of Gibbon after terminating a call with a verb, which makes a request. Requests are light weight objects that update an internal path based on your call chain. When you terminate a call chain with a verb, a request instance makes a request and resets the path. See [How call chains build paths](#how-call-chains-build-paths).
|
|
30
41
|
|
|
31
42
|
You can set an individual request's `timeout` and `open_timeout` like this:
|
|
32
43
|
|
|
@@ -35,12 +46,20 @@ gibbon.timeout = 30
|
|
|
35
46
|
gibbon.open_timeout = 30
|
|
36
47
|
```
|
|
37
48
|
|
|
38
|
-
You can read about `timeout` and `open_timeout` in the [Net::HTTP](https://ruby-
|
|
49
|
+
You can read about `timeout` and `open_timeout` in the [Net::HTTP](https://docs.ruby-lang.org/en/master/Net/HTTP.html) doc.
|
|
39
50
|
|
|
40
|
-
Now you can make requests using the resources defined in [MailChimp's docs](
|
|
51
|
+
Now you can make requests using the resources defined in [MailChimp's docs](https://mailchimp.com/developer/marketing/api/). Resource IDs
|
|
41
52
|
are specified inline and a `CRUD` (`create`, `retrieve` (or `get`), `update`, `upsert`, or `delete`) verb initiates the request. `upsert` lets you update a record, if it exists, or insert it otherwise where supported by MailChimp's API.
|
|
42
53
|
|
|
43
|
-
|
|
54
|
+
Each verb maps to an HTTP method:
|
|
55
|
+
|
|
56
|
+
| Gibbon verb | HTTP method | Accepts `body` |
|
|
57
|
+
| --------------------- | ----------- | -------------- |
|
|
58
|
+
| `create` | `POST` | yes |
|
|
59
|
+
| `retrieve` (or `get`) | `GET` | no |
|
|
60
|
+
| `update` | `PATCH` | yes |
|
|
61
|
+
| `upsert` | `PUT` | yes |
|
|
62
|
+
| `delete` | `DELETE` | no |
|
|
44
63
|
|
|
45
64
|
You can specify `headers`, `params`, and `body` when calling a `CRUD` method. For example:
|
|
46
65
|
|
|
@@ -48,8 +67,6 @@ You can specify `headers`, `params`, and `body` when calling a `CRUD` method. Fo
|
|
|
48
67
|
gibbon.lists.retrieve(headers: {"SomeHeader": "SomeHeaderValue"}, params: {"query_param": "query_param_value"})
|
|
49
68
|
```
|
|
50
69
|
|
|
51
|
-
***Note*** `get` can be substituted for `retrieve` as of Gibbon version 3.4.1 or newer!
|
|
52
|
-
|
|
53
70
|
Of course, `body` is only supported on `create`, `update`, and `upsert` calls. Those map to HTTP `POST`, `PATCH`, and `PUT` verbs respectively.
|
|
54
71
|
|
|
55
72
|
You can set `api_key`, `timeout`, `open_timeout`, `faraday_adapter`, `proxy`, `symbolize_keys`, `logger`, and `debug` globally:
|
|
@@ -76,15 +93,64 @@ You can also set the environment variable `MAILCHIMP_API_KEY` and Gibbon will us
|
|
|
76
93
|
gibbon = Gibbon::Request.new
|
|
77
94
|
```
|
|
78
95
|
|
|
79
|
-
***Note*** Substitute an underscore if a resource name contains a hyphen.
|
|
80
|
-
|
|
81
96
|
Pass `symbolize_keys: true` to use symbols (instead of strings) as hash keys in API responses.
|
|
82
97
|
|
|
83
98
|
```ruby
|
|
84
99
|
gibbon = Gibbon::Request.new(api_key: "your_api_key", symbolize_keys: true)
|
|
85
100
|
```
|
|
86
101
|
|
|
87
|
-
MailChimp's [resource documentation](
|
|
102
|
+
MailChimp's [resource documentation](https://mailchimp.com/developer/marketing/api/) is a list of available resources.
|
|
103
|
+
|
|
104
|
+
## How call chains build paths
|
|
105
|
+
|
|
106
|
+
`Gibbon::Request` defines no methods for MailChimp's resources. Any unknown method appends a segment to an internal path and returns the same object; a CRUD verb turns the accumulated path into a request. So `gibbon.lists(list_id).members.retrieve` builds `lists/<list_id>/members` and issues a `GET`.
|
|
107
|
+
|
|
108
|
+
Three consequences are worth knowing.
|
|
109
|
+
|
|
110
|
+
**Underscores become hyphens.** Segments containing a hyphen aren't valid Ruby method names, so write them with underscores and Gibbon converts:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
gibbon.lists(list_id).members(subscriber_hash).actions.delete_permanent.create
|
|
114
|
+
# POST lists/<list_id>/members/<subscriber_hash>/actions/delete-permanent
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**`send` is special cased.** `Object#send` would otherwise shadow the path segment, so `Gibbon::Request#send` falls through to path building when called with no arguments. That's what makes sending a campaign work:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
gibbon.campaigns(campaign_id).actions.send.create
|
|
121
|
+
# POST campaigns/<campaign_id>/actions/send
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Called *with* arguments, `send` keeps its usual Ruby meaning and invokes the named method.
|
|
125
|
+
|
|
126
|
+
**An instance is reusable only after a verb.** The path resets when a verb runs, not when a chain goes out of scope. Abandoning a chain leaves its segments in place and corrupts the next call:
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
gibbon = Gibbon::Request.new(api_key: "your_api_key")
|
|
130
|
+
|
|
131
|
+
gibbon.lists # no verb, so nothing is sent and nothing is reset
|
|
132
|
+
gibbon.campaigns.retrieve # GET lists/campaigns -- not what you wanted
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Use a fresh instance per chain unless you know the previous one ended in a verb.
|
|
136
|
+
|
|
137
|
+
## Responses
|
|
138
|
+
|
|
139
|
+
Every CRUD verb returns a `Gibbon::Response` with two attributes:
|
|
140
|
+
|
|
141
|
+
* `body` — the parsed JSON response, as a `Hash` (or `Array`, depending on the endpoint)
|
|
142
|
+
* `headers` — the response headers, keyed by lower case string
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
response = gibbon.lists.retrieve
|
|
146
|
+
|
|
147
|
+
response.body["lists"] # => [{"id" => "...", "name" => "...", ...}, ...]
|
|
148
|
+
response.headers["content-type"] # => "application/json; charset=utf-8"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`body` respects `symbolize_keys`; `headers` keys are always strings.
|
|
152
|
+
|
|
153
|
+
A response with an empty body — as with a successful `delete`, which MailChimp answers with `204 No Content` — returns `nil` rather than a `Gibbon::Response`.
|
|
88
154
|
|
|
89
155
|
## Debug Logging
|
|
90
156
|
|
|
@@ -96,7 +162,7 @@ gibbon = Gibbon::Request.new(api_key: "your_api_key", debug: true)
|
|
|
96
162
|
|
|
97
163
|
### Custom logger
|
|
98
164
|
|
|
99
|
-
Ruby `Logger.new` is used by default, but it can be
|
|
165
|
+
Ruby `Logger.new` is used by default, but it can be overridden using:
|
|
100
166
|
|
|
101
167
|
```ruby
|
|
102
168
|
gibbon = Gibbon::Request.new(api_key: "your_api_key", debug: true, logger: MyLogger.new)
|
|
@@ -241,7 +307,7 @@ gibbon.batches(batch_id).retrieve
|
|
|
241
307
|
```
|
|
242
308
|
|
|
243
309
|
***Note*** This response truncated for brevity. Reference the MailChimp
|
|
244
|
-
[API documentation for Batch Operations](
|
|
310
|
+
[API documentation for Batch Operations](https://mailchimp.com/developer/marketing/api/batches/) for more details.
|
|
245
311
|
|
|
246
312
|
### Fields
|
|
247
313
|
|
|
@@ -268,7 +334,7 @@ campaigns = gibbon.campaigns.retrieve
|
|
|
268
334
|
Fetch the number of opens for a campaign
|
|
269
335
|
|
|
270
336
|
```ruby
|
|
271
|
-
email_stats = gibbon.reports(campaign_id).retrieve["opens"]
|
|
337
|
+
email_stats = gibbon.reports(campaign_id).retrieve.body["opens"]
|
|
272
338
|
```
|
|
273
339
|
|
|
274
340
|
Create a new campaign:
|
|
@@ -337,7 +403,7 @@ gibbon.campaigns(campaign_id).actions.schedule.create(body: body)
|
|
|
337
403
|
|
|
338
404
|
### Interests
|
|
339
405
|
|
|
340
|
-
Interests are a little more complicated than other parts of the API, so here's an example of how you would set interests
|
|
406
|
+
Interests are a little more complicated than other parts of the API, so here's an example of how you would set interests at subscription time or update them later. The ID of the interests you want to opt in or out of must be known ahead of time so an example of how to find interest IDs is also included.
|
|
341
407
|
|
|
342
408
|
Subscribing a member to a list with specific interests up front:
|
|
343
409
|
|
|
@@ -357,13 +423,13 @@ So how do we get the interest IDs? When you query the API for a specific list me
|
|
|
357
423
|
gibbon.lists(list_id).members(member_id).retrieve
|
|
358
424
|
```
|
|
359
425
|
|
|
360
|
-
The response body (i.e. `response.body`) looks
|
|
426
|
+
The response body (i.e. `response.body`) looks something like this (unrelated things removed):
|
|
361
427
|
|
|
362
428
|
```ruby
|
|
363
429
|
{"id"=>"...", "email_address"=>"...", ..., "interests"=>{"3def637141"=>true, "f7cc4ee841"=>false, "fcdc951b9f"=>false, "3daf3cf27d"=>true, "293a3703ed"=>false, "72370e0d1f"=>false, "d434d21a1c"=>false, "bdb1ff199f"=>false, "a54e78f203"=>false, "c4527fd018"=>false} ...}
|
|
364
430
|
```
|
|
365
431
|
|
|
366
|
-
The API returns a map of interest ID to boolean value. Now we to get interest details so we know what these interest IDs map to. Looking at [this doc page](
|
|
432
|
+
The API returns a map of interest ID to boolean value. Now we need to get interest details so we know what these interest IDs map to. Looking at [this doc page](https://mailchimp.com/developer/marketing/api/interests/), we need to do this:
|
|
367
433
|
|
|
368
434
|
```ruby
|
|
369
435
|
gibbon.lists(list_id).interest_categories.retrieve
|
|
@@ -383,24 +449,9 @@ gibbon.lists(list_id).interest_categories("0ace7aa498").interests.retrieve
|
|
|
383
449
|
|
|
384
450
|
That response gives the interest data, including the ID for the interests themselves, which we can use to update a list member's interests or set them when we call the API to subscribe her or him to a list.
|
|
385
451
|
|
|
386
|
-
### Error handling
|
|
387
|
-
|
|
388
|
-
Gibbon raises an error when the API returns an error.
|
|
389
|
-
|
|
390
|
-
`Gibbon::MailChimpError` has the following attributes: `title`, `detail`, `body`, `raw_body`, `status_code`. Some or all of these may not be
|
|
391
|
-
available depending on the nature of the error. For example:
|
|
392
|
-
|
|
393
|
-
```ruby
|
|
394
|
-
begin
|
|
395
|
-
gibbon.lists(list_id).members.create(body: body)
|
|
396
|
-
rescue Gibbon::MailChimpError => e
|
|
397
|
-
puts "Houston, we have a problem: #{e.message} - #{e.raw_body}"
|
|
398
|
-
end
|
|
399
|
-
```
|
|
400
|
-
|
|
401
452
|
### Other
|
|
402
453
|
|
|
403
|
-
Overriding Gibbon's API endpoint (i.e. if using an access token from OAuth and have the `api_endpoint` from the [metadata](
|
|
454
|
+
Overriding Gibbon's API endpoint (i.e. if using an access token from OAuth and have the `api_endpoint` from the [metadata](https://mailchimp.com/developer/marketing/guides/access-user-data-oauth-2/)):
|
|
404
455
|
|
|
405
456
|
```ruby
|
|
406
457
|
Gibbon::Request.api_endpoint = "https://us1.api.mailchimp.com"
|
|
@@ -419,73 +470,70 @@ You can set a different [Faraday adapter](https://github.com/lostisland/faraday)
|
|
|
419
470
|
gibbon = Gibbon::Request.new(api_key: "your_api_key", faraday_adapter: :net_http)
|
|
420
471
|
```
|
|
421
472
|
|
|
422
|
-
|
|
473
|
+
## Error handling
|
|
423
474
|
|
|
424
|
-
Gibbon
|
|
475
|
+
Gibbon raises two error types, both subclasses of `StandardError`.
|
|
425
476
|
|
|
426
|
-
|
|
477
|
+
### Gibbon::GibbonError
|
|
427
478
|
|
|
428
|
-
Gibbon
|
|
479
|
+
Raised *before* a request goes out, when Gibbon can't tell where to send it. In practice that means the API key is missing, or it carries no data center suffix and no `api_endpoint` was set:
|
|
429
480
|
|
|
430
481
|
```ruby
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
Gibbon 2.x+:
|
|
482
|
+
Gibbon::Request.new.lists.retrieve
|
|
483
|
+
# => Gibbon::GibbonError: You must set an api_key prior to making a call
|
|
435
484
|
|
|
436
|
-
|
|
437
|
-
|
|
485
|
+
Gibbon::Request.new(api_key: "your_api_key").lists.retrieve
|
|
486
|
+
# => Gibbon::GibbonError: You must set an api_key prior to making a call
|
|
438
487
|
```
|
|
439
488
|
|
|
440
|
-
|
|
489
|
+
A key like `your_api_key-us1` carries its data center (`us1`) and works on its own. Access tokens obtained through OAuth do not, so pass `api_endpoint` alongside them — see [Other](#other) above.
|
|
441
490
|
|
|
442
|
-
|
|
491
|
+
### Gibbon::MailChimpError
|
|
443
492
|
|
|
444
|
-
|
|
493
|
+
Raised when a request fails. It exposes `title`, `detail`, `body`, `raw_body`, and `status_code`:
|
|
445
494
|
|
|
446
495
|
```ruby
|
|
447
|
-
|
|
496
|
+
begin
|
|
497
|
+
gibbon.lists(list_id).members.create(body: body)
|
|
498
|
+
rescue Gibbon::MailChimpError => e
|
|
499
|
+
puts "Houston, we have a problem: #{e.message} - #{e.raw_body}"
|
|
500
|
+
end
|
|
448
501
|
```
|
|
449
502
|
|
|
450
|
-
|
|
503
|
+
Some or all of those attributes may be `nil` depending on the nature of the failure. MailChimp returning a `4xx` or `5xx` populates all of them from the error document. A transport level failure — a timeout, a refused connection — has no response to read, so only `message` is set.
|
|
451
504
|
|
|
452
|
-
|
|
453
|
-
gibbon.lists.retrieve
|
|
454
|
-
```
|
|
505
|
+
Gibbon also raises `Gibbon::MailChimpError` when a *successful* response body isn't valid JSON. That error has a `title` of `"UNPARSEABLE_RESPONSE"` and a `status_code` of `500`, and its message includes the raw body.
|
|
455
506
|
|
|
456
|
-
|
|
507
|
+
## Upgrading
|
|
457
508
|
|
|
458
|
-
|
|
509
|
+
Full release history lives in the [CHANGELOG](CHANGELOG.md). The breaking changes are summarized here.
|
|
459
510
|
|
|
460
|
-
|
|
461
|
-
gibbon.lists.members({:id => list_id})
|
|
462
|
-
```
|
|
511
|
+
### Upgrading to 4.0
|
|
463
512
|
|
|
464
|
-
Gibbon
|
|
513
|
+
Gibbon 4.0 contains no API changes. It changes what it runs on:
|
|
465
514
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
```
|
|
515
|
+
* **Ruby 3.1 or newer is required.** Rubies 2.4 through 3.0 are EOL and are no longer supported. If you are on an older Ruby, stay on Gibbon 3.5.0.
|
|
516
|
+
* **TLS 1.2 is now a minimum rather than an exact pin.** Connections previously negotiated exactly TLS 1.2; they now accept TLS 1.3 as well. No code change is needed, but if you inspect the negotiated protocol in tests or middleware, expect 1.3.
|
|
469
517
|
|
|
470
|
-
|
|
518
|
+
### Upgrading to 3.0
|
|
471
519
|
|
|
472
|
-
Gibbon
|
|
520
|
+
`create`, `retrieve`/`get`, `update`, `upsert`, and `delete` return a `Gibbon::Response` rather than the parsed body. Add `.body`:
|
|
473
521
|
|
|
474
522
|
```ruby
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
Gibbon 2.x+:
|
|
523
|
+
# Gibbon 2.x
|
|
524
|
+
gibbon.lists.retrieve["lists"]
|
|
479
525
|
|
|
480
|
-
|
|
481
|
-
gibbon.lists
|
|
526
|
+
# Gibbon 3.x and newer
|
|
527
|
+
gibbon.lists.retrieve.body["lists"]
|
|
482
528
|
```
|
|
483
529
|
|
|
530
|
+
In exchange you also get `.headers`, which carries MailChimp's rate limit and content metadata. See [Responses](#responses).
|
|
531
|
+
|
|
484
532
|
## Thanks
|
|
485
533
|
|
|
486
534
|
Thanks to everyone who has [contributed](https://github.com/amro/gibbon/contributors) to Gibbon's development.
|
|
487
535
|
|
|
488
536
|
## Copyright
|
|
489
537
|
|
|
490
|
-
* Copyright (c) 2010-
|
|
491
|
-
* MailChimp (c) 2001-
|
|
538
|
+
* Copyright (c) 2010-2026 Amro Mousa. See LICENSE.txt for details.
|
|
539
|
+
* MailChimp (c) 2001-2026 The Rocket Science Group.
|
data/gibbon.gemspec
CHANGED
|
@@ -7,23 +7,33 @@ Gem::Specification.new do |s|
|
|
|
7
7
|
s.version = Gibbon::VERSION
|
|
8
8
|
s.authors = ["Amro Mousa"]
|
|
9
9
|
s.email = ["amromousa@gmail.com"]
|
|
10
|
-
s.homepage = "
|
|
10
|
+
s.homepage = "https://github.com/amro/gibbon"
|
|
11
11
|
|
|
12
12
|
s.summary = %q{A wrapper for MailChimp API 3.0}
|
|
13
|
-
s.description = %q{
|
|
13
|
+
s.description = %q{Gibbon is a Ruby wrapper for MailChimp's Marketing API (version 3.0). Ruby method chains map onto API resource paths, so every documented resource is reachable without waiting on a gem update.}
|
|
14
14
|
s.license = "MIT"
|
|
15
15
|
|
|
16
|
+
s.metadata = {
|
|
17
|
+
"source_code_uri" => "https://github.com/amro/gibbon",
|
|
18
|
+
"changelog_uri" => "https://github.com/amro/gibbon/blob/master/CHANGELOG.md",
|
|
19
|
+
"bug_tracker_uri" => "https://github.com/amro/gibbon/issues",
|
|
20
|
+
"documentation_uri" => "https://rubydoc.info/gems/gibbon"
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
s.files = `git ls-files`.split("\n")
|
|
17
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
18
24
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
19
25
|
s.require_paths = ["lib"]
|
|
20
|
-
s.required_ruby_version = '>=
|
|
26
|
+
s.required_ruby_version = '>= 3.1.0'
|
|
21
27
|
|
|
22
28
|
s.add_dependency('faraday', '>= 1.0')
|
|
23
29
|
s.add_dependency('multi_json', '>= 1.11.0')
|
|
30
|
+
# Ruby 3.1 uses multi_json <= 1.19.1, whose JSON adapter passes options
|
|
31
|
+
# positionally. JSON 3 requires keyword arguments.
|
|
32
|
+
s.add_dependency('json', '< 3.0')
|
|
24
33
|
|
|
25
34
|
s.add_development_dependency 'rake'
|
|
26
|
-
s.add_development_dependency "rspec", "3.
|
|
35
|
+
s.add_development_dependency "rspec", "~> 3.13"
|
|
27
36
|
s.add_development_dependency 'webmock', '~> 3.8'
|
|
37
|
+
s.add_development_dependency 'yard', '~> 0.9'
|
|
28
38
|
|
|
29
39
|
end
|
data/lib/gibbon/api_request.rb
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
module Gibbon
|
|
2
|
+
# Carries out the HTTP request described by a {Request}.
|
|
3
|
+
#
|
|
4
|
+
# This is an implementation detail of {Request}, which constructs one per
|
|
5
|
+
# call and reads its own configuration back out of it. Use {Request}'s CRUD
|
|
6
|
+
# verbs rather than instantiating this directly.
|
|
2
7
|
class APIRequest
|
|
3
8
|
include Helpers
|
|
4
9
|
|
|
10
|
+
# @param builder [Request] the request whose path and configuration to use
|
|
5
11
|
def initialize(builder: nil)
|
|
6
12
|
@request_builder = builder
|
|
7
13
|
end
|
|
8
14
|
|
|
15
|
+
# @param params [Hash, nil] query string parameters
|
|
16
|
+
# @param headers [Hash, nil] additional request headers
|
|
17
|
+
# @param body [Hash, nil] request body, serialized to JSON
|
|
18
|
+
# @return [Response, nil] the response, or nil when the body is empty
|
|
19
|
+
# @raise [GibbonError] if no usable API key is configured
|
|
20
|
+
# @raise [MailChimpError] if the request fails or the response cannot be parsed
|
|
9
21
|
def post(params: nil, headers: nil, body: nil)
|
|
10
22
|
validate_api_key
|
|
11
23
|
|
|
@@ -19,6 +31,9 @@ module Gibbon
|
|
|
19
31
|
end
|
|
20
32
|
end
|
|
21
33
|
|
|
34
|
+
# @param (see #post)
|
|
35
|
+
# @return (see #post)
|
|
36
|
+
# @raise (see #post)
|
|
22
37
|
def patch(params: nil, headers: nil, body: nil)
|
|
23
38
|
validate_api_key
|
|
24
39
|
|
|
@@ -32,6 +47,9 @@ module Gibbon
|
|
|
32
47
|
end
|
|
33
48
|
end
|
|
34
49
|
|
|
50
|
+
# @param (see #post)
|
|
51
|
+
# @return (see #post)
|
|
52
|
+
# @raise (see #post)
|
|
35
53
|
def put(params: nil, headers: nil, body: nil)
|
|
36
54
|
validate_api_key
|
|
37
55
|
|
|
@@ -45,6 +63,10 @@ module Gibbon
|
|
|
45
63
|
end
|
|
46
64
|
end
|
|
47
65
|
|
|
66
|
+
# @param params [Hash, nil] query string parameters
|
|
67
|
+
# @param headers [Hash, nil] additional request headers
|
|
68
|
+
# @return (see #post)
|
|
69
|
+
# @raise (see #post)
|
|
48
70
|
def get(params: nil, headers: nil)
|
|
49
71
|
validate_api_key
|
|
50
72
|
|
|
@@ -58,6 +80,9 @@ module Gibbon
|
|
|
58
80
|
end
|
|
59
81
|
end
|
|
60
82
|
|
|
83
|
+
# @param (see #get)
|
|
84
|
+
# @return (see #post)
|
|
85
|
+
# @raise (see #post)
|
|
61
86
|
def delete(params: nil, headers: nil)
|
|
62
87
|
validate_api_key
|
|
63
88
|
|
|
@@ -106,6 +131,10 @@ module Gibbon
|
|
|
106
131
|
# Helpers
|
|
107
132
|
|
|
108
133
|
def handle_error(error)
|
|
134
|
+
# Errors we raised ourselves (e.g. unparseable response bodies) already
|
|
135
|
+
# carry their details; re-wrapping them here would strip those attributes
|
|
136
|
+
raise error if error.is_a?(MailChimpError)
|
|
137
|
+
|
|
109
138
|
error_params = {}
|
|
110
139
|
|
|
111
140
|
begin
|
|
@@ -146,7 +175,7 @@ module Gibbon
|
|
|
146
175
|
end
|
|
147
176
|
|
|
148
177
|
def rest_client
|
|
149
|
-
client = Faraday.new(self.api_url, proxy: self.proxy, ssl: {
|
|
178
|
+
client = Faraday.new(self.api_url, proxy: self.proxy, ssl: { min_version: :TLS1_2 }) do |faraday|
|
|
150
179
|
faraday.response :raise_error
|
|
151
180
|
faraday.adapter adapter
|
|
152
181
|
if @request_builder.debug
|
data/lib/gibbon/gibbon_error.rb
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
module Gibbon
|
|
2
|
+
# Shared helpers mixed into {APIRequest}.
|
|
2
3
|
module Helpers
|
|
4
|
+
# Extracts the data center prefix from an API key, e.g. `"us1."` from
|
|
5
|
+
# `"TESTKEY-us1"`.
|
|
6
|
+
#
|
|
7
|
+
# @param api_key [String, nil] the API key
|
|
8
|
+
# @return [String] the prefix including its trailing dot, or an empty string
|
|
9
|
+
# when the key carries no data center suffix
|
|
3
10
|
def get_data_center_from_api_key(api_key)
|
|
4
11
|
# Return an empty string for invalid API keys so Gibbon hits the main endpoint
|
|
5
12
|
data_center = ""
|
|
6
13
|
|
|
7
14
|
if api_key && api_key["-"]
|
|
8
|
-
# Remove all non-
|
|
15
|
+
# Remove all non-alphanumeric characters in case someone attempts to inject
|
|
9
16
|
# a different domain into the API key (e.g. when consuming user form-provided keys)
|
|
10
|
-
# This approach avoids assuming a 3 letter prefix (e.g.
|
|
17
|
+
# This approach avoids assuming a 3 letter prefix (e.g. if MC were to create
|
|
11
18
|
# a us10 DC, this would continue to work), and will continue to hit MC's server
|
|
12
19
|
# rather than a would-be attacker's servers.
|
|
13
20
|
data_center = "#{api_key.split('-').last.gsub(/[^0-9a-z ]/i, '')}."
|
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
module Gibbon
|
|
2
|
+
# Raised when a request fails.
|
|
3
|
+
#
|
|
4
|
+
# A `4xx` or `5xx` from MailChimp populates every attribute from the error
|
|
5
|
+
# document it returns. A transport level failure, such as a timeout, has no
|
|
6
|
+
# response to read, so only the message is set. Gibbon also raises this when a
|
|
7
|
+
# successful response body is not valid JSON, with a {#title} of
|
|
8
|
+
# `"UNPARSEABLE_RESPONSE"` and a {#status_code} of `500`.
|
|
2
9
|
class MailChimpError < StandardError
|
|
3
|
-
|
|
10
|
+
# @return [String, nil] MailChimp's short name for the error, e.g. `"Resource Not Found"`
|
|
11
|
+
attr_reader :title
|
|
12
|
+
# @return [String, nil] MailChimp's human readable explanation of the error
|
|
13
|
+
attr_reader :detail
|
|
14
|
+
# @return [Hash, nil] the parsed error document
|
|
15
|
+
attr_reader :body
|
|
16
|
+
# @return [String, nil] the unparsed error response body
|
|
17
|
+
attr_reader :raw_body
|
|
18
|
+
# @return [Integer, nil] the HTTP status code
|
|
19
|
+
attr_reader :status_code
|
|
4
20
|
|
|
21
|
+
# @param message [String] the error message
|
|
22
|
+
# @param params [Hash] any of `:title`, `:detail`, `:body`, `:raw_body`, `:status_code`
|
|
5
23
|
def initialize(message = "", params = {})
|
|
6
24
|
@title = params[:title]
|
|
7
25
|
@detail = params[:detail]
|
|
@@ -12,6 +30,7 @@ module Gibbon
|
|
|
12
30
|
super(message)
|
|
13
31
|
end
|
|
14
32
|
|
|
33
|
+
# @return [String] the message followed by every populated attribute
|
|
15
34
|
def to_s
|
|
16
35
|
super + " " + instance_variables_to_s
|
|
17
36
|
end
|