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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 61103c56f7d3ff60ca48fab8a2ca7c71949c1c01c784abd7ddb062dbd74d4560
4
- data.tar.gz: 93f18505844eb71b72d189622547c61c0bda3c416bdb2a2caa4ec0358899e52b
3
+ metadata.gz: 421fe35f5c093158bac5d59d0a684325e8b77888257b6758fd805d9c86608520
4
+ data.tar.gz: cd15c1fa018171363d7fe32c6dddbe14e14985ed02ca1cbdcb313240e39c1640
5
5
  SHA512:
6
- metadata.gz: 7e4203bc006c80cc20c5fff0217673543faa842a28b57eb3620a373a63409c0e1b5f503335d44ff05d8ba8d124db1f5b984dcf5f98632aaa8ee077d525d0a413
7
- data.tar.gz: ecc7c39379e218a5a10d92c4597fdb9e5f28c90c8297ef5a19d04189044a0b86a0f84120151369ecca4c989c3d88bb359fcc651de9527497cba368b608225d9d
6
+ metadata.gz: 0da6bfbdd8f9bff5ffbeac3943f3c3f571204b9a38869dfa2e75cba7bc1ba2863aa7dcdcb82e3167b54ed9dd0222d118a7b74606b2d7d8f92ec8fc8681529d24
7
+ data.tar.gz: 0ff34fa92007f022b7faf7368cf1285ed995efa43e964b0328928e8c466bc1661d0cdc0a32f0fed612e4cb5d54deaba74dfdfecae5dc264e4215ddb4bfc39ddf
data/.document CHANGED
@@ -1,5 +1,5 @@
1
1
  lib/**/*.rb
2
- bin/*
3
- -
4
- features/**/*.feature
2
+ -
3
+ README.markdown
4
+ CHANGELOG.md
5
5
  LICENSE.txt
@@ -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
@@ -0,0 +1,7 @@
1
+ --markup markdown
2
+ --readme README.markdown
3
+ --no-private
4
+ lib/**/*.rb
5
+ -
6
+ CHANGELOG.md
7
+ LICENSE.txt
data/CHANGELOG.md CHANGED
@@ -1,4 +1,14 @@
1
- ## [3.5.0, unreleased] - 2022-06-05
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
@@ -1,11 +1,3 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- platforms :rbx do
4
- gem 'rubinius-developer_tools'
5
- end
6
-
7
- group :development, :test do
8
- gem 'webmock', '~> 3.8'
9
- end
10
-
11
3
  gemspec
data/README.markdown CHANGED
@@ -1,22 +1,33 @@
1
1
  # gibbon
2
2
 
3
- Gibbon is an API wrapper for MailChimp's [API](http://kb.mailchimp.com/api/).
3
+ Gibbon is an API wrapper for MailChimp's [API](https://mailchimp.com/developer/marketing/api/).
4
4
 
5
- [![Build Status](https://travis-ci.com/amro/gibbon.svg?branch=master)](https://app.travis-ci.com/github/amro/gibbon)
5
+ [![Build Status](https://github.com/amro/gibbon/actions/workflows/ci.yml/badge.svg?branch=master)](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](http://kb.mailchimp.com/api/article/api-3-overview).
9
+ Please read MailChimp's [Getting Started Guide](https://mailchimp.com/developer/marketing/guides/quick-start/).
10
10
 
11
- Gibbon 3.0.0+ returns a `Gibbon::Response` instead of the response body directly. `Gibbon::Response` exposes the parsed response `body` and `headers`.
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
- A MailChimp account and API key. You can see your API keys [here](http://admin.mailchimp.com/account/api).
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-doc.org/stdlib-2.3.3/libdoc/net/http/rdoc/Net/HTTP.html) doc.
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](http://kb.mailchimp.com/api/resources). Resource IDs
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
- ***Note*** `upsert` requires Gibbon version 2.1.0 or newer!
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](http://kb.mailchimp.com/api/resources) is a list of available resources.
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 overrided using:
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](http://developer.mailchimp.com/documentation/mailchimp/reference/batches/) for more details.
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 during 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.
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 someting like this (unrelated things removed):
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](http://kb.mailchimp.com/api/resources/lists/interest-categories/interests/lists-interests-collection), we need to do this:
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](http://apidocs.mailchimp.com/oauth2/)):
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
- ### Migrating from Gibbon 1.x
473
+ ## Error handling
423
474
 
424
- Gibbon 2.x+ exposes a different API from version 1.x. This is because Gibbon maps to MailChimp's API and because version 3 of the API is quite different from version 2. First, the name of the primary class has changed from `API` to `Request`. And the way you pass an API key during initialization is different. A few examples below.
475
+ Gibbon raises two error types, both subclasses of `StandardError`.
425
476
 
426
- #### Initialization
477
+ ### Gibbon::GibbonError
427
478
 
428
- Gibbon 1.x:
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
- gibbon = Gibbon::API.new("your_api_key")
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
- ```ruby
437
- gibbon = Gibbon::Request.new(api_key: "your_api_key")
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
- MailChimp API 3 is a RESTful API, so Gibbon's syntax now requires a trailing call to a verb, as described above.
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
- #### Fetching Lists
491
+ ### Gibbon::MailChimpError
443
492
 
444
- Gibbon 1.x:
493
+ Raised when a request fails. It exposes `title`, `detail`, `body`, `raw_body`, and `status_code`:
445
494
 
446
495
  ```ruby
447
- gibbon.lists.list
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
- Gibbon 2.x+:
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
- ```ruby
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
- #### Fetching List Members
507
+ ## Upgrading
457
508
 
458
- Gibbon 1.x:
509
+ Full release history lives in the [CHANGELOG](CHANGELOG.md). The breaking changes are summarized here.
459
510
 
460
- ```ruby
461
- gibbon.lists.members({:id => list_id})
462
- ```
511
+ ### Upgrading to 4.0
463
512
 
464
- Gibbon 2.x+:
513
+ Gibbon 4.0 contains no API changes. It changes what it runs on:
465
514
 
466
- ```ruby
467
- gibbon.lists(list_id).members.retrieve
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
- #### Subscribing a Member to a List
518
+ ### Upgrading to 3.0
471
519
 
472
- Gibbon 1.x:
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
- gibbon.lists.subscribe({:id => list_id, :email => {:email => "foo@bar.com"}, :merge_vars => {:FNAME => "Bob", :LNAME => "Smith"}})
476
- ```
477
-
478
- Gibbon 2.x+:
523
+ # Gibbon 2.x
524
+ gibbon.lists.retrieve["lists"]
479
525
 
480
- ```ruby
481
- gibbon.lists(list_id).members.create(body: {email_address: "foo@bar.com", status: "subscribed", merge_fields: {FNAME: "Bob", LNAME: "Smith"}})
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-2022 Amro Mousa. See LICENSE.txt for details.
491
- * MailChimp (c) 2001-2022 The Rocket Science Group.
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 = "http://github.com/amro/gibbon"
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{A wrapper for MailChimp API 3.0}
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 = '>= 2.4.0'
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.5.0"
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
@@ -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: { version: "TLSv1_2" }) do |faraday|
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
@@ -1,3 +1,6 @@
1
1
  module Gibbon
2
+ # Raised before a request is sent, when Gibbon cannot determine where to send
3
+ # it: the API key is missing, or it carries no data center suffix and no
4
+ # `api_endpoint` was configured.
2
5
  class GibbonError < StandardError; end
3
6
  end
@@ -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-alphanumberic characters in case someone attempts to inject
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. is MC were to create
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
- attr_reader :title, :detail, :body, :raw_body, :status_code
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