dnsimple 2.0.0.alpha2 → 2.0.0.alpha3

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.
Files changed (158) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +6 -2
  3. data/README.markdown +32 -30
  4. data/dnsimple.gemspec +1 -2
  5. data/lib/dnsimple.rb +2 -13
  6. data/lib/dnsimple/client.rb +164 -53
  7. data/lib/dnsimple/client/certificates_service.rb +98 -0
  8. data/lib/dnsimple/client/client_service.rb +8 -0
  9. data/lib/dnsimple/client/contacts_service.rb +82 -0
  10. data/lib/dnsimple/client/domains_service.rb +333 -0
  11. data/lib/dnsimple/client/name_servers_service.rb +69 -0
  12. data/lib/dnsimple/client/registrars_service.rb +105 -0
  13. data/lib/dnsimple/client/services_service.rb +95 -0
  14. data/lib/dnsimple/client/templates_service.rb +180 -0
  15. data/lib/dnsimple/client/users_service.rb +37 -0
  16. data/lib/dnsimple/compatibility.rb +46 -0
  17. data/lib/dnsimple/default.rb +86 -0
  18. data/lib/dnsimple/error.rb +7 -11
  19. data/lib/dnsimple/extra.rb +54 -0
  20. data/lib/dnsimple/struct.rb +29 -0
  21. data/lib/dnsimple/struct/certificate.rb +56 -0
  22. data/lib/dnsimple/struct/contact.rb +61 -0
  23. data/lib/dnsimple/struct/domain.rb +40 -0
  24. data/lib/dnsimple/struct/email_forward.rb +14 -0
  25. data/lib/dnsimple/struct/extended_attribute.rb +39 -0
  26. data/lib/dnsimple/struct/membership.rb +22 -0
  27. data/lib/dnsimple/struct/price.rb +16 -0
  28. data/lib/dnsimple/struct/record.rb +22 -0
  29. data/lib/dnsimple/struct/service.rb +19 -0
  30. data/lib/dnsimple/struct/template.rb +19 -0
  31. data/lib/dnsimple/struct/template_record.rb +24 -0
  32. data/lib/dnsimple/struct/transfer_order.rb +10 -0
  33. data/lib/dnsimple/struct/user.rb +17 -0
  34. data/lib/dnsimple/struct/whois_privacy.rb +19 -0
  35. data/lib/dnsimple/version.rb +1 -1
  36. data/spec/dnsimple/client/certificates_service_spec.rb +196 -0
  37. data/spec/dnsimple/client/contacts_service_spec.rb +179 -0
  38. data/spec/dnsimple/client/domains_service_spec.rb +662 -0
  39. data/spec/dnsimple/client/name_servers_service_spec.rb +131 -0
  40. data/spec/dnsimple/client/registrars_service_spec.rb +160 -0
  41. data/spec/dnsimple/client/services_service_spec.rb +162 -0
  42. data/spec/dnsimple/client/templates_service_spec.rb +371 -0
  43. data/spec/dnsimple/client/users_service_spec.rb +70 -0
  44. data/spec/dnsimple/client_spec.rb +108 -99
  45. data/spec/dnsimple/compatibility_spec.rb +57 -0
  46. data/spec/files/2fa/exchange-token.http +8 -11
  47. data/spec/files/badgateway.http +14 -0
  48. data/spec/files/certificates/configure/success.http +19 -0
  49. data/spec/files/certificates/index/success.http +1 -1
  50. data/spec/files/certificates/notfound.http +19 -0
  51. data/spec/files/certificates/purchase/success.http +19 -0
  52. data/spec/files/certificates/show/success.http +1 -1
  53. data/spec/files/certificates/submit/success.http +19 -0
  54. data/spec/files/contacts/create/badrequest-missingcontact.http +19 -0
  55. data/spec/files/contacts/create/badrequest-validationerror.http +19 -0
  56. data/spec/files/contacts/create/created.http +22 -0
  57. data/spec/files/contacts/delete/success-204.http +18 -0
  58. data/spec/files/{extended_attributes/com.http → contacts/delete/success.http} +5 -5
  59. data/spec/files/{domains/auto_renewal_enable → contacts/index}/success.http +6 -6
  60. data/spec/files/contacts/notfound.http +19 -0
  61. data/spec/files/contacts/update/success.http +21 -0
  62. data/spec/files/domains/create/created.http +21 -0
  63. data/spec/files/domains/index/success.http +11 -9
  64. data/spec/files/domains/notfound.http +11 -9
  65. data/spec/files/domains/show/success.http +12 -10
  66. data/spec/files/domains_autorenewal/disable/success.http +21 -0
  67. data/spec/files/domains_autorenewal/enable/success.http +21 -0
  68. data/spec/files/domains_autorenewal/notfound-domain.http +19 -0
  69. data/spec/files/domains_forwards/create/created.http +22 -0
  70. data/spec/files/domains_forwards/delete/success.http +17 -0
  71. data/spec/files/domains_forwards/get/success.http +21 -0
  72. data/spec/files/domains_forwards/list/success.http +21 -0
  73. data/spec/files/domains_forwards/notfound-domain.http +19 -0
  74. data/spec/files/domains_forwards/notfound.http +19 -0
  75. data/spec/files/domains_records/create/created.http +21 -0
  76. data/spec/files/domains_records/delete/success-204.http +18 -0
  77. data/spec/files/{contacts/show/notfound.http → domains_records/delete/success.http} +9 -7
  78. data/spec/files/{records → domains_records}/index/success.http +0 -0
  79. data/spec/files/{records/show → domains_records}/notfound.http +1 -1
  80. data/spec/files/{records → domains_records}/show/success.http +0 -0
  81. data/spec/files/domains_records/update/success.http +21 -0
  82. data/spec/files/domains_sharing/create/success.http +21 -0
  83. data/spec/files/domains_sharing/delete/success.http +17 -0
  84. data/spec/files/domains_sharing/list/success.http +21 -0
  85. data/spec/files/domains_sharing/notfound-domain.http +19 -0
  86. data/spec/files/domains_sharing/notfound.http +19 -0
  87. data/spec/files/domains_whois_privacy/disable/success.http +21 -0
  88. data/spec/files/domains_whois_privacy/enable/success.http +22 -0
  89. data/spec/files/domains_zones/get/success.http +21 -0
  90. data/spec/files/domains_zones/notfound-domain.http +19 -0
  91. data/spec/files/{domains/auto_renewal_disable/notfound.http → nameservers/change/success.http} +23 -21
  92. data/spec/files/nameservers/deregister/success.http +17 -0
  93. data/spec/files/{domains/auto_renewal_enable/notfound.http → nameservers/list/success.http} +23 -21
  94. data/spec/files/nameservers/notfound-domain.http +19 -0
  95. data/spec/files/nameservers/register/badrequest-valueerror.http +19 -0
  96. data/spec/files/nameservers/register/success.http +21 -0
  97. data/spec/files/registrars/check/available.http +19 -0
  98. data/spec/files/registrars/check/registered.http +21 -0
  99. data/spec/files/registrars/register/badrequest-missingdomain.http +19 -0
  100. data/spec/files/registrars/register/badrequest-missingregistrant.http +19 -0
  101. data/spec/files/registrars/register/success.http +21 -0
  102. data/spec/files/registrars/renew/badrequest-missingrenewal.http +19 -0
  103. data/spec/files/registrars/renew/badrequest-unable.http +19 -0
  104. data/spec/files/registrars/renew/success.http +21 -0
  105. data/spec/files/registrars/transfer/success.http +21 -0
  106. data/spec/files/registrars_extended_attributes/list/success.http +21 -0
  107. data/spec/files/registrars_prices/list/success.http +21 -0
  108. data/spec/files/services/applied/success.http +21 -0
  109. data/spec/files/services/apply/success.http +21 -0
  110. data/spec/files/services/available/success.http +21 -0
  111. data/spec/files/services/index/success.http +21 -0
  112. data/spec/files/services/notfound.http +19 -0
  113. data/spec/files/services/show/success.http +21 -0
  114. data/spec/files/services/unapply/success.http +21 -0
  115. data/spec/files/subscription/show/success.http +21 -0
  116. data/spec/files/templates/apply/success.http +21 -0
  117. data/spec/files/templates/create/created.http +22 -0
  118. data/spec/files/templates/delete/success-204.http +21 -0
  119. data/spec/files/templates/delete/success.http +21 -0
  120. data/spec/files/templates/index/success.http +21 -0
  121. data/spec/files/templates/notfound.http +19 -0
  122. data/spec/files/templates/show/success.http +12 -10
  123. data/spec/files/templates/update/success.http +21 -0
  124. data/spec/files/templates_records/create/created.http +22 -0
  125. data/spec/files/templates_records/delete/success-204.http +19 -0
  126. data/spec/files/templates_records/delete/success.http +21 -0
  127. data/spec/files/templates_records/index/success.http +21 -0
  128. data/spec/files/templates_records/notfound.http +19 -0
  129. data/spec/files/templates_records/show/success.http +21 -0
  130. data/spec/files/templates_records/update/success.http +21 -0
  131. data/spec/files/users/user/success.http +21 -0
  132. data/spec/spec_helper.rb +0 -2
  133. metadata +206 -73
  134. data/lib/dnsimple/base.rb +0 -10
  135. data/lib/dnsimple/certificate.rb +0 -143
  136. data/lib/dnsimple/contact.rb +0 -157
  137. data/lib/dnsimple/domain.rb +0 -252
  138. data/lib/dnsimple/extended_attribute.rb +0 -52
  139. data/lib/dnsimple/record.rb +0 -94
  140. data/lib/dnsimple/service.rb +0 -42
  141. data/lib/dnsimple/template.rb +0 -65
  142. data/lib/dnsimple/template_record.rb +0 -80
  143. data/lib/dnsimple/transfer_order.rb +0 -34
  144. data/lib/dnsimple/user.rb +0 -50
  145. data/spec/dnsimple/certificate_spec.rb +0 -59
  146. data/spec/dnsimple/contact_spec.rb +0 -45
  147. data/spec/dnsimple/domain_spec.rb +0 -241
  148. data/spec/dnsimple/extended_attributes_spec.rb +0 -54
  149. data/spec/dnsimple/record_spec.rb +0 -51
  150. data/spec/dnsimple/template_spec.rb +0 -31
  151. data/spec/dnsimple/user_spec.rb +0 -70
  152. data/spec/files/account/user/success.http +0 -19
  153. data/spec/files/certificates/show/notfound.http +0 -17
  154. data/spec/files/domains/auto_renewal_disable/success.http +0 -23
  155. data/spec/files/domains/create/success.http +0 -19
  156. data/spec/files/extended_attributes/ca.http +0 -19
  157. data/spec/files/extended_attributes/success.http +0 -19
  158. data/spec/files/templates/show/notfound.http +0 -17
@@ -0,0 +1,86 @@
1
+ module Dnsimple
2
+
3
+ # Default configuration options for {Client}
4
+ module Default
5
+
6
+ # Default API endpoint
7
+ API_ENDPOINT = "https://api.dnsimple.com/".freeze
8
+
9
+ # Default User Agent header
10
+ USER_AGENT = "dnsimple-ruby/#{VERSION}".freeze
11
+
12
+ class << self
13
+
14
+ # List of configurable keys for {Client}
15
+ # @return [Array] of option keys
16
+ def keys
17
+ @keys ||= [
18
+ :api_endpoint,
19
+ :username,
20
+ :password,
21
+ :exchange_token,
22
+ :api_token,
23
+ :domain_api_token,
24
+ :user_agent,
25
+ :proxy,
26
+ ]
27
+ end
28
+
29
+ # Configuration options
30
+ # @return [Hash]
31
+ def options
32
+ Hash[keys.map { |key| [key, send(key)]}]
33
+ end
34
+
35
+ # Default API endpoint from ENV or {API_ENDPOINT}
36
+ # @return [String]
37
+ def api_endpoint
38
+ ENV['DNSIMPLE_API_ENDPOINT'] || API_ENDPOINT
39
+ end
40
+
41
+ # Default DNSimple username for Basic Auth from ENV
42
+ # @return [String]
43
+ def username
44
+ ENV['DNSIMPLE_USERNAME']
45
+ end
46
+
47
+ # Default DNSimple password for Basic Auth from ENV
48
+ # @return [String]
49
+ def password
50
+ ENV['DNSIMPLE_PASSWORD']
51
+ end
52
+
53
+ # Default DNSimple Exchange Token for Basic Auth from ENV
54
+ # @return [String]
55
+ def exchange_token
56
+ ENV['DNSIMPLE_API_EXCHANGE_TOKEN']
57
+ end
58
+
59
+ # Default DNSimple API Token for Token Auth from ENV
60
+ # @return [String]
61
+ def domain_api_token
62
+ ENV['DNSIMPLE_API_TOKEN']
63
+ end
64
+
65
+ # Default DNSimple Domain API Token for Token Auth from ENV
66
+ # @return [String]
67
+ def api_token
68
+ ENV['DNSIMPLE_API_DOMAIN_TOKEN']
69
+ end
70
+
71
+ # Default User-Agent header string from ENV or {USER_AGENT}
72
+ # @return [String]
73
+ def user_agent
74
+ ENV['DNSIMPLE_USER_AGENT'] || USER_AGENT
75
+ end
76
+
77
+ # Default Proxy address:port from ENV
78
+ # @return [String]
79
+ def proxy
80
+ ENV['DNSIMPLE_PROXY']
81
+ end
82
+
83
+ end
84
+ end
85
+
86
+ end
@@ -3,20 +3,16 @@ module Dnsimple
3
3
  class Error < StandardError
4
4
  end
5
5
 
6
- class RecordExists < Error
7
- end
8
-
9
- class RecordNotFound < Error
10
- end
6
+ class RequestError < Error
7
+ attr_reader :response
11
8
 
12
- # An exception that is raised if a method is called with missing or invalid parameter values.
13
- class ValidationError < Error
9
+ def initialize(response)
10
+ @response = response
11
+ super("#{response.code}")
12
+ end
14
13
  end
15
14
 
16
- class RequestError < Error
17
- def initialize(description, response)
18
- super("#{description}: #{response["error"]}")
19
- end
15
+ class RecordNotFound < RequestError
20
16
  end
21
17
 
22
18
  class AuthenticationError < Error
@@ -0,0 +1,54 @@
1
+ module Dnsimple
2
+ module Extra
3
+
4
+ # Returns a new hash with +self+ and +other+ merged recursively.
5
+ #
6
+ # h1 = { a: true, b: { c: [1, 2, 3] } }
7
+ # h2 = { a: false, b: { x: [3, 4, 5] } }
8
+ #
9
+ # h1.deep_merge(h2) #=> { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }
10
+ #
11
+ # Like with Hash#merge in the standard library, a block can be provided
12
+ # to merge values:
13
+ #
14
+ # h1 = { a: 100, b: 200, c: { c1: 100 } }
15
+ # h2 = { b: 250, c: { c1: 200 } }
16
+ # h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
17
+ # # => { a: 100, b: 450, c: { c1: 300 } }
18
+ def self.deep_merge(this, other, &block)
19
+ deep_merge!(this.dup, other, &block)
20
+ end
21
+
22
+ # Same as +deep_merge+, but modifies +self+.
23
+ def self.deep_merge!(this, other, &block)
24
+ other.each_pair do |current_key, other_value|
25
+ this_value = this[current_key]
26
+
27
+ this[current_key] = if this_value.is_a?(Hash) && other_value.is_a?(Hash)
28
+ deep_merge(this_value, other_value, &block)
29
+ else
30
+ if block_given? && key?(current_key)
31
+ block.call(current_key, this_value, other_value)
32
+ else
33
+ other_value
34
+ end
35
+ end
36
+ end
37
+
38
+ this
39
+ end
40
+
41
+ # Validates the presence of mandatory attributes.
42
+ #
43
+ # @param [Hash] attributes
44
+ # @param [Array<Symbol>] required
45
+ # @return [void]
46
+ # @raise [ArgumentError]
47
+ def self.validate_mandatory_attributes(attributes, required)
48
+ required.each do |name|
49
+ attributes.key?(name) or raise(ArgumentError, ":#{name} is required")
50
+ end
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,29 @@
1
+ module Dnsimple
2
+ module Struct
3
+
4
+ class Base
5
+ def initialize(attributes = {})
6
+ attributes.each do |key, value|
7
+ m = "#{key}=".to_sym
8
+ self.send(m, value) if self.respond_to?(m)
9
+ end
10
+ end
11
+ end
12
+
13
+ end
14
+ end
15
+
16
+ require 'dnsimple/struct/certificate'
17
+ require 'dnsimple/struct/contact'
18
+ require 'dnsimple/struct/domain'
19
+ require 'dnsimple/struct/email_forward'
20
+ require 'dnsimple/struct/extended_attribute'
21
+ require 'dnsimple/struct/membership'
22
+ require 'dnsimple/struct/price'
23
+ require 'dnsimple/struct/record'
24
+ require 'dnsimple/struct/service'
25
+ require 'dnsimple/struct/template'
26
+ require 'dnsimple/struct/template_record'
27
+ require 'dnsimple/struct/transfer_order'
28
+ require 'dnsimple/struct/user'
29
+ require 'dnsimple/struct/whois_privacy'
@@ -0,0 +1,56 @@
1
+ module Dnsimple
2
+ module Struct
3
+
4
+ class Certificate < Base
5
+ # @return [Fixnum] The certificate ID in DNSimple.
6
+ attr_accessor :id
7
+
8
+ # @return [Fixnum] The associated domain ID.
9
+ attr_accessor :domain_id
10
+
11
+ # The Fixnum associated contact ID.
12
+ attr_accessor :contact_id
13
+
14
+ # The String subdomain on the certificate.
15
+ attr_accessor :name
16
+
17
+ # The String state.
18
+ attr_accessor :state
19
+
20
+ # The String Certificate Signing Request.
21
+ attr_accessor :csr
22
+
23
+ # The String SSL certificate.
24
+ # It is set only if the order issued by the Certificate Authority.
25
+ attr_accessor :ssl_certificate
26
+
27
+ # The String private key.
28
+ # It is set only if DNSimple generated the Certificate Signing Request.
29
+ attr_accessor :private_key
30
+
31
+ # The String approver email address
32
+ # It is set only if the state is submitted.
33
+ attr_accessor :approver_email
34
+
35
+ # The Array of all emails that can be used to approve the certificate.
36
+ # It is set only if the state is configured.
37
+ attr_accessor :approver_emails
38
+
39
+ # @return [String] When the certificate was created in DNSimple.
40
+ attr_accessor :created_at
41
+
42
+ # @return [String] When the certificate was last updated in DNSimple.
43
+ attr_accessor :updated_at
44
+
45
+ # The Date the certificate was configured.
46
+ attr_accessor :configured_at
47
+
48
+ # The Date the certificate will expire.
49
+ attr_accessor :expires_on
50
+
51
+ # The associated Domain.
52
+ attr_accessor :domain
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,61 @@
1
+ module Dnsimple
2
+ module Struct
3
+
4
+ class Contact < Base
5
+ # @return [Fixnum] The contact ID in DNSimple.
6
+ attr_accessor :id
7
+
8
+ # @return [String] The label to represent the contact.
9
+ attr_accessor :label
10
+
11
+ # @return [String] The contact first name.
12
+ attr_accessor :first_name
13
+
14
+ # @return [String] The contact last name.
15
+ attr_accessor :last_name
16
+
17
+ # @return [String] The contact's job title.
18
+ attr_accessor :job_title
19
+
20
+ # @return [String] The name of the organization in which the contact works.
21
+ attr_accessor :organization_name
22
+
23
+ # @return [String] The contact street address.
24
+ attr_accessor :address1
25
+
26
+ # @return [String] Apartment or suite number.
27
+ attr_accessor :address2
28
+
29
+ # @return [String] The city name.
30
+ attr_accessor :city
31
+
32
+ # @return [String] The state or province name.
33
+ attr_accessor :state_province
34
+
35
+ # @return [String] The contact postal code.
36
+ attr_accessor :postal_code
37
+
38
+ # @return [String] The contact country (as a 2-character country code).
39
+ attr_accessor :country
40
+
41
+ # @return [String] The contact phone number.
42
+ attr_accessor :phone
43
+
44
+ # @return [String] The contact fax number (may be omitted).
45
+ attr_accessor :fax
46
+
47
+ # @return [String] The contact email address.
48
+ attr_accessor :email_address
49
+
50
+ # @return [String] When the contact was created in DNSimple.
51
+ attr_accessor :created_at
52
+
53
+ # @return [String] When the contact was last updated in DNSimple.
54
+ attr_accessor :updated_at
55
+
56
+ alias :email :email_address
57
+ alias :email= :email_address=
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,40 @@
1
+ module Dnsimple
2
+ module Struct
3
+
4
+ class Domain < Base
5
+ # @return [Fixnum] The domain ID in DNSimple.
6
+ attr_accessor :id
7
+
8
+ # @return [Fixnum] The associated user ID.
9
+ attr_accessor :user_id
10
+
11
+ # @return [Fixnum] The associated registrant (contact) ID.
12
+ attr_accessor :registrant_id
13
+
14
+ # The String name.
15
+ attr_accessor :name
16
+
17
+ # The String state.
18
+ attr_accessor :state
19
+
20
+ # The String API token
21
+ attr_accessor :token
22
+
23
+ # Is the domain set to auto renew?
24
+ attr_accessor :auto_renew
25
+
26
+ # Is the whois information protected?
27
+ attr_accessor :whois_protected
28
+
29
+ # @return [String] The date the domain will expire.
30
+ attr_accessor :expires_on
31
+
32
+ # @return [String] When the domain was created in DNSimple.
33
+ attr_accessor :created_at
34
+
35
+ # @return [String] When the domain was last updated in DNSimple.
36
+ attr_accessor :updated_at
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,14 @@
1
+ module Dnsimple
2
+ module Struct
3
+
4
+ class EmailForward < Base
5
+ attr_accessor :id
6
+ attr_accessor :domain_id
7
+ attr_accessor :from
8
+ attr_accessor :to
9
+ attr_accessor :created_at
10
+ attr_accessor :updated_at
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,39 @@
1
+ module Dnsimple
2
+ module Struct
3
+
4
+ class ExtendedAttribute < Base
5
+
6
+ class Option < Base
7
+ # The option name
8
+ attr_accessor :title
9
+
10
+ # The option value
11
+ attr_accessor :value
12
+
13
+ # A long description of the option
14
+ attr_accessor :description
15
+ end
16
+
17
+ # The extended attribute name
18
+ attr_accessor :name
19
+
20
+ # A description of the extended attribute
21
+ attr_accessor :description
22
+
23
+ # Boolean indicating if the extended attribute is required
24
+ attr_accessor :required
25
+
26
+ def options
27
+ @options ||= []
28
+ end
29
+
30
+ def options=(opts)
31
+ @options = opts.map do |opt|
32
+ ExtendedAttribute::Option.new(opt)
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,22 @@
1
+ module Dnsimple
2
+ module Struct
3
+
4
+ class Membership < Base
5
+ # @return [Fixnum] The membership ID in DNSimple.
6
+ attr_accessor :id
7
+
8
+ # @return [Fixnum] The associated domain ID.
9
+ attr_accessor :domain_id
10
+
11
+ # @return [Fixnum] The associated user ID.
12
+ attr_accessor :user_id
13
+
14
+ # @return [String] When the membership was created in DNSimple.
15
+ attr_accessor :created_at
16
+
17
+ # @return [String] When the membership was last updated in DNSimple.
18
+ attr_accessor :updated_at
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ module Dnsimple
2
+ module Struct
3
+
4
+ class Price < Base
5
+ attr_accessor :tld
6
+ attr_accessor :minimum_registration
7
+ attr_accessor :registration_price
8
+ attr_accessor :registration_enabled
9
+ attr_accessor :transfer_price
10
+ attr_accessor :transfer_enabled
11
+ attr_accessor :renewal_price
12
+ attr_accessor :renewal_enabled
13
+ end
14
+
15
+ end
16
+ end