gandi_v5 0.1.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +14 -4
  4. data/.travis.yml +16 -1
  5. data/CHANGELOG.md +80 -2
  6. data/FUNDING.yml +10 -0
  7. data/Guardfile +5 -6
  8. data/LICENSE.md +2 -6
  9. data/README.md +17 -23
  10. data/gandi_v5.gemspec +4 -3
  11. data/lib/gandi_v5.rb +54 -15
  12. data/lib/gandi_v5/billing.rb +2 -4
  13. data/lib/gandi_v5/billing/info.rb +0 -2
  14. data/lib/gandi_v5/data.rb +1 -2
  15. data/lib/gandi_v5/data/converter.rb +0 -4
  16. data/lib/gandi_v5/data/converter/integer.rb +26 -0
  17. data/lib/gandi_v5/domain.rb +201 -101
  18. data/lib/gandi_v5/domain/auto_renew.rb +4 -4
  19. data/lib/gandi_v5/domain/availability.rb +46 -0
  20. data/lib/gandi_v5/domain/availability/product.rb +49 -0
  21. data/lib/gandi_v5/domain/availability/product/period.rb +24 -0
  22. data/lib/gandi_v5/domain/availability/product/price.rb +36 -0
  23. data/lib/gandi_v5/domain/availability/tax.rb +20 -0
  24. data/lib/gandi_v5/domain/live_dns.rb +42 -0
  25. data/lib/gandi_v5/domain/renewal_information.rb +0 -3
  26. data/lib/gandi_v5/domain/sharing_space.rb +10 -2
  27. data/lib/gandi_v5/domain/tld.rb +57 -0
  28. data/lib/gandi_v5/email.rb +0 -4
  29. data/lib/gandi_v5/email/forward.rb +108 -0
  30. data/lib/gandi_v5/email/mailbox.rb +85 -24
  31. data/lib/gandi_v5/email/mailbox/responder.rb +43 -2
  32. data/lib/gandi_v5/email/offer.rb +2 -2
  33. data/lib/gandi_v5/email/slot.rb +52 -18
  34. data/lib/gandi_v5/error.rb +0 -2
  35. data/lib/gandi_v5/error/gandi_error.rb +2 -2
  36. data/lib/gandi_v5/live_dns.rb +20 -4
  37. data/lib/gandi_v5/live_dns/domain.rb +21 -150
  38. data/lib/gandi_v5/live_dns/has_zone_records.rb +153 -0
  39. data/lib/gandi_v5/live_dns/record_set.rb +1 -1
  40. data/lib/gandi_v5/live_dns/zone.rb +24 -165
  41. data/lib/gandi_v5/live_dns/zone/snapshot.rb +27 -8
  42. data/lib/gandi_v5/organization.rb +39 -6
  43. data/lib/gandi_v5/organization/customer.rb +90 -0
  44. data/lib/gandi_v5/version.rb +1 -1
  45. data/spec/.rubocop.yml +9 -2
  46. data/spec/features/domain_spec.rb +2 -2
  47. data/spec/features/livedns_zone_spec.rb +12 -13
  48. data/spec/fixtures/bodies/GandiV5_Billing/{info.yaml → info.yml} +0 -0
  49. data/spec/fixtures/bodies/GandiV5_Domain/{get.yaml → fetch.yml} +8 -0
  50. data/spec/fixtures/bodies/GandiV5_Domain/{fetch_contacts.yaml → fetch_contacts.yml} +0 -0
  51. data/spec/fixtures/bodies/GandiV5_Domain/fetch_glue_records.yml +7 -0
  52. data/spec/fixtures/bodies/GandiV5_Domain/fetch_livedns.yml +6 -0
  53. data/spec/fixtures/bodies/GandiV5_Domain/fetch_name_servers.yml +2 -0
  54. data/spec/fixtures/bodies/GandiV5_Domain/{renewal_info.yaml → fetch_renewal_info.yml} +0 -3
  55. data/spec/fixtures/bodies/GandiV5_Domain/{restore_info.yaml → fetch_restore_info.yml} +0 -0
  56. data/spec/fixtures/bodies/GandiV5_Domain/{list.yaml → list.yml} +1 -0
  57. data/spec/fixtures/bodies/{GandiV5_Domain/availability.yaml → GandiV5_Domain_Availability/fetch.yml} +0 -0
  58. data/spec/fixtures/bodies/{GandiV5_Domain/tld.yaml → GandiV5_Domain_TLD/fetch.yml} +0 -0
  59. data/spec/fixtures/bodies/{GandiV5_Domain/tlds.yaml → GandiV5_Domain_TLD/list.yml} +0 -0
  60. data/spec/fixtures/bodies/GandiV5_Email_Forward/list.yml +6 -0
  61. data/spec/fixtures/bodies/GandiV5_Email_Mailbox/{get.yaml → fetch.yml} +0 -0
  62. data/spec/fixtures/bodies/GandiV5_Email_Mailbox/{list.yaml → list.yml} +0 -0
  63. data/spec/fixtures/bodies/GandiV5_Email_Slot/{get.yaml → fetch.yml} +0 -0
  64. data/spec/fixtures/bodies/GandiV5_Email_Slot/{list.yaml → list.yml} +0 -0
  65. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain/{get.yaml → fetch.yml} +0 -0
  66. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain/{list.yaml → list.yml} +0 -0
  67. data/spec/fixtures/bodies/GandiV5_LiveDNS_Zone/{get.yaml → fetch.yml} +0 -0
  68. data/spec/fixtures/bodies/GandiV5_LiveDNS_Zone/{list.yaml → list.yml} +0 -0
  69. data/spec/fixtures/bodies/GandiV5_LiveDNS_Zone_Snapshot/{get.yaml → fetch.yml} +0 -0
  70. data/spec/fixtures/bodies/GandiV5_LiveDNS_Zone_Snapshot/list.yml +3 -0
  71. data/spec/fixtures/bodies/GandiV5_Organization/{get.yaml → fetch.yml} +0 -0
  72. data/spec/fixtures/bodies/GandiV5_Organization/list.yml +7 -0
  73. data/spec/fixtures/bodies/GandiV5_Organization_Customer/list.yml +8 -0
  74. data/spec/fixtures/vcr/Domain_features/List_domains.yml +2 -1
  75. data/spec/spec_helper.rb +2 -2
  76. data/spec/units/gandi_v5/billing_spec.rb +4 -4
  77. data/spec/units/gandi_v5/data/converter/integer_spec.rb +16 -0
  78. data/spec/units/gandi_v5/domain/auto_renew_spec.rb +5 -5
  79. data/spec/units/gandi_v5/domain/availability/product/period_spec.rb +4 -0
  80. data/spec/units/gandi_v5/domain/availability/product/price_spec.rb +4 -0
  81. data/spec/units/gandi_v5/domain/availability/product_spec.rb +4 -0
  82. data/spec/units/gandi_v5/domain/availability/tax_spec.rb +4 -0
  83. data/spec/units/gandi_v5/domain/availability_spec.rb +43 -0
  84. data/spec/units/gandi_v5/domain/live_dns_spec.rb +45 -0
  85. data/spec/units/gandi_v5/domain/tld_spec.rb +29 -0
  86. data/spec/units/gandi_v5/domain_spec.rb +359 -91
  87. data/spec/units/gandi_v5/email/forward_spec.rb +121 -0
  88. data/spec/units/gandi_v5/email/mailbox/responder_spec.rb +52 -0
  89. data/spec/units/gandi_v5/email/mailbox_spec.rb +174 -33
  90. data/spec/units/gandi_v5/email/offer_spec.rb +1 -1
  91. data/spec/units/gandi_v5/email/slot_spec.rb +113 -17
  92. data/spec/units/gandi_v5/live_dns/domain_spec.rb +73 -43
  93. data/spec/units/gandi_v5/live_dns/zone/snapshot_spec.rb +32 -3
  94. data/spec/units/gandi_v5/live_dns/zone_spec.rb +70 -52
  95. data/spec/units/gandi_v5/live_dns_spec.rb +24 -0
  96. data/spec/units/gandi_v5/organization/customer_spec.rb +81 -0
  97. data/spec/units/gandi_v5/organization_spec.rb +52 -1
  98. data/spec/units/gandi_v5_spec.rb +56 -19
  99. metadata +71 -29
  100. data/TODO.md +0 -29
@@ -21,10 +21,10 @@ class GandiV5
21
21
 
22
22
  # Disable auto renewal for the associated domain.
23
23
  # @return [String] The confirmation message from Gandi.
24
- # @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
24
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
25
25
  def disable
26
26
  body = { enabled: false }.to_json
27
- data = GandiV5.patch url, body
27
+ _response, data = GandiV5.patch url, body
28
28
  self.enabled = false
29
29
  data['message']
30
30
  end
@@ -35,7 +35,7 @@ class GandiV5
35
35
  # @return [String] The confirmation message from Gandi.
36
36
  # @raise [ArgumentError] if duration is invalid (not 1 to 9 (inclusive)).
37
37
  # @raise [ArgumentError] if org_id is not passed and not set for this domain.
38
- # @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
38
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
39
39
  def enable(duration: self.duration || 1, org_id: self.org_id)
40
40
  fail ArgumentError, 'duration can not be less than 1' if duration < 1
41
41
  fail ArgumentError, 'duration can not be more than 9' if duration > 9
@@ -47,7 +47,7 @@ class GandiV5
47
47
  org_id: org_id
48
48
  }.to_json
49
49
 
50
- data = GandiV5.patch url, body
50
+ _response, data = GandiV5.patch url, body
51
51
  self.enabled = true
52
52
  self.duration = duration
53
53
  self.org_id = org_id
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GandiV5
4
+ class Domain
5
+ # Information about the availabillity of processes on a domain.
6
+ # @!attribute [r] currency
7
+ # @return [String]
8
+ # @!attribute [r] grid
9
+ # @return [String]
10
+ # @!attribute [r] products
11
+ # @return [Arrav<GandiV5::Domain::Availability::Product>]
12
+ # @!attribute [r] taxes
13
+ # @return [Array<GandiV5::Domain::Availability::Tax>]
14
+ class Availability
15
+ include GandiV5::Data
16
+
17
+ members :currency, :grid
18
+ member :products, converter: GandiV5::Domain::Availability::Product, array: true
19
+ member :taxes, converter: GandiV5::Domain::Availability::Tax, array: true
20
+
21
+ # Check domain availability and pricing.
22
+ # @see https://api.gandi.net/docs/domains#get-v5-domain-check
23
+ # @param fqdn [String, #to_s] the fully qualified domain name to check.
24
+ # @param country [String, #to_s] (optional)
25
+ # ISO country code for which taxes are to be applied.
26
+ # @param currency [String, #to_s] (optional) request price for a specific ISO currency code.
27
+ # @param duration_unit [String, #to_s] (optional) define the unit for max_duration.
28
+ # @param extension [String, #to_s] (optional) query a specific extension for product options.
29
+ # @param grid [String, #to_s] (optional) request price for a specific rate.
30
+ # @param lang [String, #to_s] (optional) language code.
31
+ # @param max_duration [Integer, #to_s] (optional)
32
+ # set a limit on the duration range for returned prices.
33
+ # @param period [String, #to_s] (optional) specific registration period to query.
34
+ # @param processes [Array<:create, :renew, :transfer etc.>] (optional default [:create])
35
+ # list of at least 1 process for which pricing is to be made.
36
+ # @param sharing_id [String, #to_s] (optional)
37
+ # organization for which the pricing is to be made.
38
+ # @return [GandiV5::Domain::Availability]
39
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
40
+ def self.fetch(fqdn, **options)
41
+ _response, data = GandiV5.get("#{BASE}domain/check", params: { name: fqdn }.merge(options))
42
+ GandiV5::Domain::Availability.from_gandi data
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GandiV5
4
+ class Domain
5
+ class Availability
6
+ # Information about an available product.
7
+ # @!attribute [r] name
8
+ # @return [String]
9
+ # @!attribute [r] prices
10
+ # @return [Array<GandiV5::Domain::Availability::Product::Price>]
11
+ # @!attribute [r] periods
12
+ # @return [Array<GandiV5::Domain::Availability::Product::Period>]
13
+ # @!attribute [r] taxes
14
+ # @return [Array<GandiV5::Domain::Availability::Tax>]
15
+ # @!attribute [r] process
16
+ # @return [Symbol]
17
+ # @!attribute [r] status
18
+ # @return [Symbol]
19
+ class Product
20
+ include GandiV5::Data
21
+
22
+ STATUSES = {
23
+ available: 'Domain name is available',
24
+ available_reserved: 'Domain name reserved under special conditions',
25
+ available_preorder: 'Domain name can be pre-ordered',
26
+ unavailable: 'Domain name is not available',
27
+ unavailable_premium: 'Domain name is not available',
28
+ unavailable_restricted: 'Domain name is not available (forbidden)',
29
+ error_invalid: 'Provided value is not a valid domain name',
30
+ error_refused: 'Service is temporarily down',
31
+ error_timeout: 'Service timed out, try the method again later',
32
+ error_unknown: 'Internal server error',
33
+ reserved_corporate: 'The TLD for the given domain name is reserved for ' \
34
+ 'Gandi Corporate Services customers',
35
+ pending: 'Result is not yet ready, try the method again later',
36
+ error_eoi: 'The TLD for the given domain name is in an ' \
37
+ 'Expression of Interest (EOI) period'
38
+ }.freeze
39
+
40
+ members :name
41
+ member :prices, converter: GandiV5::Domain::Availability::Product::Price, array: true
42
+ member :periods, converter: GandiV5::Domain::Availability::Product::Period, array: true
43
+ member :process, converter: GandiV5::Data::Converter::Symbol
44
+ member :status, converter: GandiV5::Data::Converter::Symbol
45
+ member :taxes, converter: GandiV5::Domain::Availability::Tax, array: true
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GandiV5
4
+ class Domain
5
+ class Availability
6
+ class Product
7
+ # Information about an available product.
8
+ # @!attribute [r] name
9
+ # @return [String]
10
+ # @!attribute [r]
11
+ # @return [Time] starts_at
12
+ # @!attribute [r]
13
+ # @return [Time, nil] ends_at
14
+ class Period
15
+ include GandiV5::Data
16
+
17
+ members :name
18
+ member :starts_at, converter: GandiV5::Data::Converter::Time
19
+ member :ends_at, converter: GandiV5::Data::Converter::Time
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GandiV5
4
+ class Domain
5
+ class Availability
6
+ class Product
7
+ # Information about an available product.
8
+ # @!attribute [r] duration_unit
9
+ # @return [String] time unit for the duration (e.g. y)
10
+ # @!attribute [r] max_duration
11
+ # @return [Integer] maximum duration for which this price unit applies
12
+ # @!attribute [r] min_duration
13
+ # @return [Integer] minimum duration for which this price unit applies.
14
+ # @!attribute [r] price_after_taxes
15
+ # @return [Numeric] pricing after tax is applied
16
+ # @!attribute [r] price_before_taxes
17
+ # @return [Numeric] pricing before tax is applied
18
+ # @!attribute [r] discount
19
+ # @return [Boolean, nil] whether a discount is active on this price unit
20
+ # @!attribute [r] normal_price_after_taxes
21
+ # @return [Numeric, nil] pricing after tax is applied, when no discount applies
22
+ # @!attribute [r] normal_price_before_taxes
23
+ # @return [Numeric, nil] pricing before tax is applied, when no discount applies
24
+ # @!attribute [r] type
25
+ # @return [String, nil]
26
+ class Price
27
+ include GandiV5::Data
28
+
29
+ members :duration_unit, :max_duration, :min_duration,
30
+ :price_after_taxes, :price_before_taxes,
31
+ :discount, :normal_price_after_taxes, :normal_price_after_taxes, :type
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GandiV5
4
+ class Domain
5
+ class Availability
6
+ # Information about tax due on a process/product.
7
+ # @!attribute [r] name
8
+ # @return [String] name of the tax (e.g. VAT)
9
+ # @!attribute [r] rate
10
+ # @return [Numeric] percentage rate of the tax (e.g. 20)
11
+ # @!attribute [r] type
12
+ # @return [String] type of the tax (e.g. service)
13
+ class Tax
14
+ include GandiV5::Data
15
+
16
+ members :name, :rate, :type
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GandiV5
4
+ class Domain
5
+ # LiveDNS information for a domain.
6
+ # @!attribute [r] current
7
+ # @return [:classic, :livedns, :other]
8
+ # type of nameservers currently set. classic corresponds to Gandi's classic nameservers,
9
+ # livedns is for the new, default, Gandi nameservers and other is for custom nameservers.
10
+ # @!attribute [r] name_servers
11
+ # @return [Array<String>] list of current nameservers.
12
+ # @!attribute [r] dnssec_available
13
+ # @return [nil, Boolean] whether DNSSEC may be applied to the domain.
14
+ # @!attribute [r] livednssec_available
15
+ # @return [nil, Boolean] whether DNSSEC with liveDNS may be applied to this domain.
16
+ class LiveDNS
17
+ include GandiV5::Data
18
+
19
+ members :dnssec_available, :livednssec_available
20
+ member :name_servers, gandi_key: 'nameservers'
21
+ member :current, converter: GandiV5::Data::Converter::Symbol
22
+
23
+ # Check if classic DNS is being used.
24
+ # @return [Boolean]
25
+ def classic?
26
+ current == :classic
27
+ end
28
+
29
+ # Check if custom DNS is being used.
30
+ # @return [Boolean]
31
+ def custom?
32
+ current == :custom
33
+ end
34
+
35
+ # Check if LiveDNS is being used.
36
+ # @return [Boolean]
37
+ def livedns?
38
+ current == :livedns
39
+ end
40
+ end
41
+ end
42
+ end
@@ -17,8 +17,6 @@ class GandiV5
17
17
  # @return [Integer]
18
18
  # @!attribute [r] minimum
19
19
  # @return [Integer]
20
- # @!attribute [r] contracts
21
- # @return [Array<GandiV5::Domain::Contract>]
22
20
  class RenewalInformation
23
21
  include GandiV5::Data
24
22
 
@@ -26,7 +24,6 @@ class GandiV5
26
24
 
27
25
  member :begins_at, converter: GandiV5::Data::Converter::Time
28
26
  member :ends_at, converter: GandiV5::Data::Converter::Time
29
- member :contracts, converter: GandiV5::Domain::Contract, array: true
30
27
 
31
28
  # Check if the domain is currently renewable.
32
29
  # @return [Boolean]
@@ -7,14 +7,22 @@ class GandiV5
7
7
  # @return [String]
8
8
  # @!attribute [r] name
9
9
  # @return [String]
10
+ # @!attribute [r] type
11
+ # @return [String]
10
12
  # @!attribute [r] reseller
11
13
  # @return [nil, Boolean]
14
+ # @!attribute [r] reseller_details
15
+ # @return [nil, GandiV5::Domain::SharingSpace]
12
16
  class SharingSpace
13
17
  include GandiV5::Data
14
18
 
15
- members :name, :reseller
19
+ members :name, :type, :reseller
16
20
  member :uuid, gandi_key: 'id'
17
-
21
+ member(
22
+ :reseller_details,
23
+ gandi_key: 'sharing_space',
24
+ converter: GandiV5::Domain::SharingSpace
25
+ )
18
26
  alias sharing_space_uuid uuid
19
27
  end
20
28
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GandiV5
4
+ class Domain
5
+ # Information about a specific TLD (Top Level Domain).
6
+ # @!attribute [r] name
7
+ # @return [String]
8
+ # @!attribute [r] full_tld
9
+ # @return [String]
10
+ # @!attribute [r] authinfo_for_transfer
11
+ # @return [Boolean] whether authinfo is required for a transfer.
12
+ # @!attribute [r] category
13
+ # @return [String]
14
+ # @!attribute [r] change_owner
15
+ # @return [Boolean] whther changing owner is pemritted.
16
+ # @!attribute [r] corporate
17
+ # @return [Boolean] whether this is a corporate TLD.
18
+ # @!attribute [r] ext_trade
19
+ # @return [Boolean]
20
+ # @!attribute [r] lock
21
+ # @return [Boolean]
22
+ class TLD
23
+ include GandiV5::Data
24
+
25
+ members :name, :full_tld, :authinfo_for_transfer, :change_owner, :corporate, :ext_trade, :lock
26
+ member :category, converter: GandiV5::Data::Converter::Symbol
27
+
28
+ # List of available TLDs.
29
+ # @see https://api.gandi.net/docs/domains#get-v5-domain-tlds
30
+ # @return Array<GandiV5::Domain::TLD>
31
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
32
+ def self.list
33
+ GandiV5.get(url)
34
+ .last
35
+ .map { |tld| GandiV5::Domain::TLD.from_gandi tld }
36
+ end
37
+
38
+ # Get TLD information.
39
+ # @see https://api.gandi.net/docs/domains#get-v5-domain-tlds-name
40
+ # @param name [String, #to_s] the top level domain to get information for.
41
+ # @return [GandiV5::Domain::TLD]
42
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
43
+ def self.fetch(name)
44
+ _response, data = GandiV5.get url(name)
45
+ GandiV5::Domain::TLD.from_gandi data
46
+ end
47
+
48
+ private
49
+
50
+ def self.url(name = nil)
51
+ "#{BASE}domain/tlds" +
52
+ (name ? "/#{CGI.escape name}" : '')
53
+ end
54
+ private_class_method :url
55
+ end
56
+ end
57
+ end
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'email/mailbox'
4
- require_relative 'email/offer'
5
- require_relative 'email/slot'
6
-
7
3
  class GandiV5
8
4
  # Gandi Email Mailbox Management API.
9
5
  class Email
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GandiV5
4
+ class Email
5
+ # A forwarding address that lives within a domain.
6
+ # @see https://docs.gandi.net/en/gandimail/forwarding_and_aliases/
7
+ # @!attribute [r] source
8
+ # @return [String] the source email address ("alice" rather than "alice@example.com").
9
+ # @!attribute [r] destinations
10
+ # @return [Array<String>] list of destination email addresses.
11
+ # @!attribute [r] fqdn
12
+ # @return [String] domain name.
13
+ class Forward
14
+ include GandiV5::Data
15
+
16
+ members :source, :fqdn
17
+ member :destinations, array: true
18
+
19
+ # Delete the forwarding.
20
+ # @see https://api.gandi.net/docs/email/#delete-v5-email-forwards-domain-source
21
+ # @return [String] The confirmation message from Gandi.
22
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
23
+ def delete
24
+ _response, data = GandiV5.delete url
25
+ data['message']
26
+ end
27
+
28
+ # Update the forwarding.
29
+ # @see https://api.gandi.net/docs/email/#put-v5-email-forwards-domain-source
30
+ # @param destinations [Array<String, #to_s>] new list of destination email addresses.
31
+ # @return [String] The confirmation message from Gandi.
32
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
33
+ def update(*destinations)
34
+ fail ArgumentError, 'destinations can\'t be empty' if destinations.none?
35
+
36
+ _response, data = GandiV5.put url, { destinations: destinations }.to_json
37
+ @destinations = destinations.map(&:to_s)
38
+ data['message']
39
+ end
40
+
41
+ # Returns the string representation of the forwarding.
42
+ # @return [String]
43
+ def to_s
44
+ "#{source}@#{fqdn} -> #{destinations.join(', ')}"
45
+ end
46
+
47
+ # Create a new forward.
48
+ # @see https://api.gandi.net/docs/email/#post-v5-email-forwards-domain
49
+ # @param fqdn [String, #to_s] the fully qualified domain name for the forward.
50
+ # @param source [String, #to_s]
51
+ # the source email address ("alice" rather than "alice@example.com").
52
+ # @param destinations [Array<String, #to_s>] list of destination email addresses.
53
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
54
+ def self.create(fqdn, source, *destinations)
55
+ fail ArgumentError, 'destinations can\'t be empty' if destinations.none?
56
+
57
+ body = {
58
+ source: source,
59
+ destinations: destinations
60
+ }.to_json
61
+ _response, _data = GandiV5.post url(fqdn), body
62
+
63
+ new fqdn: fqdn, source: source, destinations: destinations
64
+ end
65
+
66
+ # List forwards for a domain.
67
+ # @see https://api.gandi.net/docs/email/#get-v5-email-forwards-domain
68
+ # @param fqdn [String, #to_s] the fully qualified domain name for the forwards.
69
+ # @param page [Integer, #each<Integer>] which page(s) of results to get.
70
+ # If page is not provided keep querying until an empty list is returned.
71
+ # If page responds to .each then iterate until an empty list is returned.
72
+ # @param per_page [Integer, #to_s] (optional default 100) how many results ot get per page.
73
+ # @param sort_by [#to_s] (optional default "login")
74
+ # how to sort the results ("login", "-login").
75
+ # @param source [String] (optional) filter the source (pattern)
76
+ # e.g. ("alice" "*lice", "alic*").
77
+ # @return [Array<GandiV5::Email::Forward>]
78
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
79
+ def self.list(fqdn, page: (1..), **params)
80
+ page = [page.to_i] unless page.respond_to?(:each)
81
+
82
+ params.reject! { |_k, v| v.nil? }
83
+
84
+ mailboxes = []
85
+ page.each do |page_number|
86
+ _response, data = GandiV5.get url(fqdn), params: params.merge(page: page_number)
87
+ break if data.empty?
88
+
89
+ mailboxes += data.map { |mailbox| from_gandi mailbox.merge(fqdn: fqdn) }
90
+ break if data.count < params.fetch(:per_page, 100)
91
+ end
92
+ mailboxes
93
+ end
94
+
95
+ private
96
+
97
+ def url
98
+ "#{BASE}email/forwards/#{CGI.escape fqdn}/#{CGI.escape source}"
99
+ end
100
+
101
+ def self.url(fqdn, source = nil)
102
+ "#{BASE}email/forwards/#{CGI.escape fqdn}" +
103
+ (source ? "/#{CGI.escape source}" : '')
104
+ end
105
+ private_class_method :url
106
+ end
107
+ end
108
+ end