signal_api 0.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.
Files changed (43) hide show
  1. data/.gitignore +19 -0
  2. data/.travis.yml +5 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +22 -0
  5. data/README.md +43 -0
  6. data/Rakefile +49 -0
  7. data/lib/signal_api/carrier.rb +43 -0
  8. data/lib/signal_api/contact.rb +60 -0
  9. data/lib/signal_api/core_ext/array.rb +7 -0
  10. data/lib/signal_api/core_ext/hash.rb +7 -0
  11. data/lib/signal_api/core_ext/nil_class.rb +7 -0
  12. data/lib/signal_api/core_ext/string.rb +7 -0
  13. data/lib/signal_api/coupon_group.rb +47 -0
  14. data/lib/signal_api/deliver_sms.rb +54 -0
  15. data/lib/signal_api/exceptions.rb +19 -0
  16. data/lib/signal_api/list.rb +224 -0
  17. data/lib/signal_api/mocks/api_mock.rb +70 -0
  18. data/lib/signal_api/mocks/contact.rb +13 -0
  19. data/lib/signal_api/mocks/deliver_sms.rb +14 -0
  20. data/lib/signal_api/mocks/list.rb +19 -0
  21. data/lib/signal_api/mocks/short_url.rb +9 -0
  22. data/lib/signal_api/segment.rb +161 -0
  23. data/lib/signal_api/short_url.rb +56 -0
  24. data/lib/signal_api/signal_http_api.rb +49 -0
  25. data/lib/signal_api/util/email_address.rb +10 -0
  26. data/lib/signal_api/util/phone.rb +37 -0
  27. data/lib/signal_api/version.rb +3 -0
  28. data/lib/signal_api.rb +114 -0
  29. data/signal_api.gemspec +27 -0
  30. data/test/api/carrier_test.rb +43 -0
  31. data/test/api/contact_test.rb +93 -0
  32. data/test/api/coupon_group_test.rb +36 -0
  33. data/test/api/deliver_sms_test.rb +66 -0
  34. data/test/api/general_test.rb +26 -0
  35. data/test/api/list_test.rb +261 -0
  36. data/test/api/segment_test.rb +144 -0
  37. data/test/api/short_url_test.rb +50 -0
  38. data/test/mocks/contact_mock_test.rb +24 -0
  39. data/test/mocks/deliver_sms_mock_test.rb +21 -0
  40. data/test/mocks/list_mock_test.rb +33 -0
  41. data/test/mocks/short_url_mock_test.rb +17 -0
  42. data/test/test_helper.rb +20 -0
  43. metadata +248 -0
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ *.swp
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ log/test.log
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Signal
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # SignalApi
2
+
3
+ A simple library for working with the Signal API (see [http://dev.signalhq.com](http://dev.signalhq.com))
4
+
5
+ [![Build Status](https://secure.travis-ci.org/signal/signal-ruby.png?branch=master)](http://travis-ci.org/signal/signal-ruby)
6
+
7
+ ## Installation
8
+ ------------
9
+
10
+ ### RubyGems ###
11
+ Signal can be installed using RubyGems
12
+
13
+ gem install signal_api
14
+
15
+ Inside your script, be sure to
16
+
17
+ require "rubygems"
18
+ require "signal_api"
19
+
20
+ ### Bundler ###
21
+ If you're using Bundler, add the following to your Gemfile
22
+
23
+ gem "signal_api"
24
+
25
+ and then run
26
+
27
+ bundle install
28
+
29
+ Usage
30
+ ------------
31
+
32
+ Before using any of the APIs, you will need to set your API key:
33
+
34
+ SignalApi.api_key = 'foobar123456abcxyz77'
35
+
36
+ You can find your Signal API key while logged into Signal and looking at your account settings.
37
+
38
+ You may also specify where Signal should log messages (optional):
39
+
40
+ SignalApi.logger = Rails.logger
41
+ SignalApi.logger = Logger.new(STDERR)
42
+
43
+ After SignalApi has been configured, you may use any of the API classes to interact with the Signal platform.
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ begin
7
+ Bundler.setup(:default, :development)
8
+ rescue Bundler::BundlerError => e
9
+ $stderr.puts e.message
10
+ $stderr.puts "Run `bundle install` to install missing gems"
11
+ exit e.status_code
12
+ end
13
+
14
+ require 'rake'
15
+ require 'rake/testtask'
16
+ require 'yard'
17
+
18
+ task :default => :test
19
+
20
+ namespace :test do
21
+ Rake::TestTask.new(:api) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = ENV['TEST'] || "test/api/**/*_test.rb"
24
+ test.verbose = true
25
+ end
26
+
27
+ Rake::TestTask.new(:mocks) do |test|
28
+ test.libs << 'lib' << 'test'
29
+ test.pattern = ENV['TEST'] || "test/mocks/**/*_test.rb"
30
+ test.verbose = true
31
+ end
32
+ end
33
+
34
+ desc 'Run all of the tests'
35
+ task :test => ["test:api", "test:mocks"]
36
+
37
+ YARD::Rake::YardocTask.new do |t|
38
+ t.files = ['lib/**/*.rb']
39
+ end
40
+
41
+ desc 'Delete yard, and other generated files'
42
+ task :clobber => [:clobber_yard]
43
+
44
+ desc 'Delete yard generated files'
45
+ task :clobber_yard do
46
+ puts 'rm -rf doc .yardoc'
47
+ FileUtils.rm_rf ['doc', '.yardoc']
48
+ end
49
+
@@ -0,0 +1,43 @@
1
+ module SignalApi
2
+
3
+ # Managed carrier from signal api
4
+ class Carrier < SignalHttpApi
5
+
6
+ # The Carrier id from Signal
7
+ attr_reader :id
8
+
9
+ # The Carrier name from Signal
10
+ attr_reader :name
11
+
12
+ def initialize(id, name)
13
+ @id = id
14
+ @name = name
15
+ end
16
+
17
+ # Lookup a carrier on textme
18
+ #
19
+ # @param [String] mobile_phone The mobile phone to lookup
20
+ #
21
+ # @return [carrier] A Carrier object representing the Carrier on the Signal platform
22
+ def self.lookup(mobile_phone)
23
+ raise InvalidParameterException.new("mobile_phone cannot be blank") if mobile_phone.blank?
24
+
25
+ SignalApi.logger.info "Attempting to lookup carrier for mobile phone #{mobile_phone}"
26
+
27
+ with_retries do
28
+ response = get("/app/carriers/lookup/#{mobile_phone}.xml",
29
+ :format => :xml,
30
+ :headers => common_headers)
31
+
32
+ if response.code == 200 && response.parsed_response['carrier']
33
+ Carrier.new(response.parsed_response['carrier']['id'], response.parsed_response['carrier']['name'])
34
+ elsif response.code == 404
35
+ raise InvalidMobilePhoneException.new("carrier for mobile phone #{mobile_phone} could not be found")
36
+ else
37
+ handle_api_failure(response)
38
+ end
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,60 @@
1
+ module SignalApi
2
+
3
+ # A Contact (person, subscriber, etc) on the Signal platform
4
+ class Contact
5
+
6
+ # The user attributes associated with the contact
7
+ attr_accessor :attributes
8
+
9
+ def initialize(attributes={})
10
+ @attributes = attributes
11
+ end
12
+
13
+ # Convenience accessor for the contact's mobile phone
14
+ def mobile_phone
15
+ @attributes['mobile-phone']
16
+ end
17
+
18
+ # Convenience accessor for the contact's email address
19
+ def email_address
20
+ @attributes['email-address']
21
+ end
22
+
23
+ # Update the contact's data on the Signal platform.
24
+ #
25
+ # @return true If the contact's data was saved successfully.
26
+ def save
27
+ validate_contact_update
28
+
29
+ xml = Builder::XmlMarkup.new
30
+ xml.user_attributes do
31
+ attributes.each do |key, value|
32
+ xml.tag!(key, value)
33
+ end
34
+ end
35
+
36
+ contact_identifier = mobile_phone.blank? ? email_address : mobile_phone
37
+
38
+ with_retries do
39
+ response = put("/api/contacts/#{contact_identifier}.xml",
40
+ :body => xml.target!,
41
+ :format => :xml,
42
+ :headers => common_headers)
43
+
44
+ if response.code == 200
45
+ true
46
+ else
47
+ handle_api_failure(response)
48
+ end
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def validate_contact_update
55
+ raise InvalidParameterException.new("mobile_phone or email is required") if mobile_phone.blank? && email_address.blank?
56
+ raise InvalidParameterException.new("nothing to update, only identifier provided") if attributes.count < 2
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,7 @@
1
+ class Array
2
+
3
+ def blank?
4
+ empty?
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ class Hash
2
+
3
+ def blank?
4
+ empty?
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ class NilClass
2
+
3
+ def blank?
4
+ true
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ class String
2
+
3
+ def blank?
4
+ strip.empty?
5
+ end
6
+
7
+ end
@@ -0,0 +1,47 @@
1
+ module SignalApi
2
+
3
+ # manage a copuon group using signals api
4
+ class CouponGroup < SignalHttpApi
5
+
6
+ # Consume a coupon
7
+ #
8
+ # @param [String] coupon_group_tag The tag for the coupon group in Textme to consume this coupon from.]
9
+ # @param [String] mobile_phone The mobile phone to consume this coupon for
10
+ #
11
+ # @return a coupon code
12
+ def self.consume_coupon(coupon_group_tag, mobile_phone)
13
+ validate_consume_coupon_parameters(coupon_group_tag, mobile_phone)
14
+
15
+ SignalApi.logger.info "Attempting to consume coupon from group #{coupon_group_tag} #{mobile_phone}"
16
+
17
+ xml = Builder::XmlMarkup.new
18
+ xml.request do
19
+ xml.user do
20
+ xml.tag!('mobile_phone',mobile_phone)
21
+ end
22
+ xml.tag!('coupon_group', coupon_group_tag)
23
+ end
24
+
25
+ with_retries do
26
+ response = get('/api/coupon_groups/consume_coupon.xml',
27
+ :body => xml.target!,
28
+ :format => :xml,
29
+ :headers => common_headers)
30
+
31
+ if response.code == 200
32
+ response.parsed_response['coupon_code']
33
+ else
34
+ handle_api_failure(response)
35
+ end
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def self.validate_consume_coupon_parameters(coupon_group_tag, mobile_phone)
42
+ raise InvalidParameterException.new("Coupon group tag cannot be blank") if coupon_group_tag.blank?
43
+ raise InvalidParameterException.new("Mobile_phone cannot be blank") if mobile_phone.blank?
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,54 @@
1
+ module SignalApi
2
+
3
+ # Deliver a SMS message using Signal's messaging API
4
+ class DeliverSms < SignalHttpApi
5
+
6
+ base_uri "https://api.imws.us"
7
+
8
+ # Create an instance of this class, with your messaging API credentials.
9
+ # These credentials are separate from the api_key that is used by the
10
+ # other APIs, and can be found in the API campaign configuration.
11
+ def initialize(username, password)
12
+ @username = username
13
+ @password = password
14
+
15
+ if @username.nil? || @password.nil?
16
+ raise InvalidParameterException.new("username and password must be provided")
17
+ end
18
+ end
19
+
20
+ # Deliver a SMS message to a mobile phone. Messages exceeding the 160 character
21
+ # limit will be split into multiple messages.
22
+ #
23
+ # @param [String] mobile_phone The mobile phone to send the message to
24
+ # @param [String] message The message to send
25
+ #
26
+ # @return [String] The unique message ID
27
+ def deliver(mobile_phone, message)
28
+ sanitized_mobile_phone = Phone.sanitize(mobile_phone)
29
+ unless Phone.valid?(sanitized_mobile_phone)
30
+ raise InvalidParameterException.new("An invalid mobile phone was specified: #{mobile_phone}")
31
+ end
32
+
33
+ if message.nil? || message.strip.empty?
34
+ raise InvalidParameterException.new("A message must be provided")
35
+ end
36
+
37
+ SignalApi.logger.info "Delivering the following message to #{sanitized_mobile_phone}: #{message}"
38
+ self.class.with_retries do
39
+ response = self.class.post('/messages/send',
40
+ :basic_auth => { :username => @username, :password => @password },
41
+ :query => { :mobile_phone => sanitized_mobile_phone, :message => message })
42
+
43
+ if response.code == 200
44
+ response.parsed_response =~ /^Message ID: (.*)$/
45
+ $1
46
+ else
47
+ self.class.handle_api_failure(response)
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ end
54
+
@@ -0,0 +1,19 @@
1
+ module SignalApi
2
+ # Base class for exceptions that should not be retried
3
+ class NonRetryableException < StandardError; end
4
+
5
+ # Exception raised when a request to the Signal API fails
6
+ class ApiException < StandardError; end
7
+
8
+ # Exception raised when the api key is not properly set
9
+ class InvalidApiKeyException < StandardError; end
10
+
11
+ # Authentication to the Signal platform failed. Make sure your API key is correct.
12
+ class AuthFailedException < StandardError; end
13
+
14
+ # An invalid parameter was passed to the given method
15
+ class InvalidParameterException < StandardError; end
16
+
17
+ # An invalid mobile phone number was passed
18
+ class InvalidMobilePhoneException < NonRetryableException; end
19
+ end
@@ -0,0 +1,224 @@
1
+ module SignalApi
2
+
3
+ # The type of subscription
4
+ class SubscriptionType
5
+ SMS = "SMS"
6
+ EMAIL = "EMAIL"
7
+ end
8
+
9
+ # Represents a message to be sent to users on a particular carrier
10
+ class CarrierOverrideMessage
11
+ attr_accessor :carrier_id, :text
12
+
13
+ def initialize(carrier_id, text)
14
+ @carrier_id = carrier_id
15
+ @text = text
16
+ end
17
+ end
18
+
19
+ # Manage subscriptions to, and send messages to subscribers of a List.
20
+ class List < SignalHttpApi
21
+
22
+ # Create a new List object
23
+ #
24
+ # @param [Fixnum] list_id The ID of the list in the Signal platform
25
+ def initialize(list_id)
26
+ @list_id = list_id
27
+ raise InvalidParameterException.new("list_id cannot be nil") if @list_id.nil?
28
+ end
29
+
30
+ # Create a new subscription to the list.
31
+ #
32
+ # @param [SubscriptionType] subscription_type The type of subscription to create
33
+ # @param [Contact] contact The contact to create the subscription for. The contact must contain a valid
34
+ # mobile phone number for SMS subscriptions, and a valid email address for
35
+ # EMAIL subscriptions. Any other attributes stored with the contact will also
36
+ # be stored on the Signal platform.
37
+ # @param [Hash] options <b>Optional</b> The options used to create the subscription
38
+ # @option options [String] :source_keyword The source keyword to use when creating the subscription (for SMS subscriptions)
39
+ #
40
+ # @return [Bool] True if a subscription was created, false if the subscription already existed.
41
+ def create_subscription(subscription_type, contact, options={})
42
+ validate_create_subscription_request(subscription_type, contact, options)
43
+
44
+ builder = Builder::XmlMarkup.new
45
+ body = builder.subscription do |subscription|
46
+ subscription.tag!('subscription-type', subscription_type)
47
+ subscription.tag!('source-keyword', options[:source_keyword]) if options[:source_keyword]
48
+ subscription.user do |user|
49
+ contact.attributes.each do |attribute_name, attribute_value|
50
+ user.__send__(attribute_name, attribute_value)
51
+ end
52
+ end
53
+ end
54
+
55
+ SignalApi.logger.info "Attempting to create a subscription to list #{@list_id}"
56
+ SignalApi.logger.debug "Subscription data: #{body}"
57
+ self.class.with_retries do
58
+ response = self.class.post("/api/subscription_campaigns/#{@list_id}/subscriptions.xml",
59
+ :body => body,
60
+ :format => :xml,
61
+ :headers => self.class.common_headers)
62
+
63
+ if response.code == 200
64
+ return true
65
+ else
66
+ if response.body.include?("Could not find the carrier for mobile phone")
67
+ raise SignalApi::InvalidMobilePhoneException.new(response.body)
68
+ elsif response.body.include?("already signed up")
69
+ SignalApi.logger.info response.body
70
+ return false
71
+ elsif response.body.include?("Subscriber cannot be re-added since they have unsubscribed within the past")
72
+ SignalApi.logger.info response.body
73
+ return false
74
+ elsif response.body.include?("User already subscribed, resending confirmation message")
75
+ SignalApi.logger.info response.body
76
+ return false
77
+ else
78
+ self.class.handle_api_failure(response)
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ # Destroy a subscription which exists in this list.
85
+ #
86
+ # @param [SubscriptionType] subscription_type The type of subscription to destroy
87
+ # @param [Contact] contact The contact to destroy the subscription for. The contact must contain a valid
88
+ # mobile phone number for SMS subscriptions, and a valid email address for
89
+ # EMAIL subscriptions.
90
+ #
91
+ # @return [Bool] True if a subscription was desctroyed, false if the subscription did not exist.
92
+ def destroy_subscription(subscription_type, contact)
93
+ validate_destroy_subscription_request(subscription_type, contact)
94
+
95
+ SignalApi.logger.info "Attempting to destroy a subscription to list #{@list_id}"
96
+ SignalApi.logger.debug "Contact data: #{contact.inspect}"
97
+
98
+ if subscription_type == SubscriptionType::SMS
99
+ contact_id = contact.mobile_phone
100
+ else
101
+ contact_id = contact.email_address
102
+ end
103
+
104
+ self.class.with_retries do
105
+ response = self.class.delete("/api/subscription_campaigns/#{@list_id}/subscriptions/#{contact_id}.xml",
106
+ :headers => self.class.common_headers)
107
+
108
+ if response.code == 200
109
+ return true
110
+ else
111
+ if response.body.include?("is not subscribed to campaign")
112
+ SignalApi.logger.info response.body
113
+ return false
114
+ elsif response.body.include?("Invalid user ID")
115
+ SignalApi.logger.info response.body
116
+ return false
117
+ else
118
+ self.class.handle_api_failure(response)
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ # Sends an SMS message to the subscribers of the subscription list.
125
+ #
126
+ # @param [String] description A description of the message.
127
+ # @param [String] text The message to send. Must not be greater than 160 characters.
128
+ # @param [Hash] options <b>Optional</b> The options used when sending the message.
129
+ # @option options [Time] :send_at The date and time to send the message. The message will be
130
+ # sent immediately if not provided.
131
+ # @option options [Fixnum] :segment_id The id of the segment to send the message to. If not
132
+ # specified, the message will be sent to all subscribers in the list.
133
+ # @option options [Array<Fixnum>] :tags An array of tag ids to tag the scheduled message with.
134
+ # @option options [Array<CarrierOverrideMessage>] :carrier_overrides An alternate text message to send to
135
+ # users on a particular carrier.
136
+ # @return [Fixnum] The ID of the scheduled message on the Signal platform.
137
+ def send_message(description, text, options={})
138
+ raise InvalidParameterException.new("A description must be provided") if description.blank?
139
+ raise InvalidParameterException.new("A text message must be provided") if text.blank?
140
+ raise InvalidParameterException.new("The text message must not be greater than 160 characters") if text.size > 160
141
+
142
+ builder = Builder::XmlMarkup.new
143
+ body = builder.message do |message|
144
+ message.description(description)
145
+ message.text(text)
146
+ message.send_at(options[:send_at].strftime("%Y-%m-%d %H:%M:%S")) if options[:send_at]
147
+ message.segment_id(options[:segment_id]) if options[:segment_id]
148
+
149
+ if options[:tags]
150
+ message.tags(:type => :array) do |tags|
151
+ options[:tags].each { |tag_id| tags.tag(tag_id) }
152
+ end
153
+ end
154
+
155
+ if options[:carrier_overrides]
156
+ message.carrier_overrides(:type => :array) do |carrier_overrides|
157
+ options[:carrier_overrides].each do |carrier_override_message|
158
+ carrier_overrides.carrier_override do |carrier_override|
159
+ carrier_override.carrier_id(carrier_override_message.carrier_id)
160
+ carrier_override.text(carrier_override_message.text)
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+ SignalApi.logger.info "Attempting to send a message to list #{@list_id}"
168
+ SignalApi.logger.debug "Message data: #{body}"
169
+ self.class.with_retries do
170
+ response = self.class.post("/api/subscription_campaigns/#{@list_id}/send_message.xml",
171
+ :body => body,
172
+ :format => :xml,
173
+ :headers => self.class.common_headers)
174
+
175
+ if response.code == 200
176
+ data = response.parsed_response['scheduled_message']
177
+ data['id']
178
+ else
179
+ self.class.handle_api_failure(response)
180
+ end
181
+ end
182
+ end
183
+
184
+ private
185
+
186
+ def validate_create_subscription_request(subscription_type, contact, options)
187
+ unless [SubscriptionType::SMS, SubscriptionType::EMAIL].include?(subscription_type)
188
+ raise InvalidParameterException.new("Invalid subscription type")
189
+ end
190
+
191
+ if contact.nil?
192
+ raise InvalidParameterException.new("A contact must be provided")
193
+ end
194
+
195
+ if subscription_type == SubscriptionType::SMS && !Phone.valid?(contact.mobile_phone)
196
+ raise InvalidMobilePhoneException.new("A valid mobile phone number required for SMS subscriptions")
197
+ end
198
+
199
+ if subscription_type == SubscriptionType::EMAIL && !EmailAddress.valid?(contact.email_address)
200
+ raise InvalidParameterException.new("A valid email address required for EMAIL subscriptions")
201
+ end
202
+ end
203
+
204
+ def validate_destroy_subscription_request(subscription_type, contact)
205
+ unless [SubscriptionType::SMS, SubscriptionType::EMAIL].include?(subscription_type)
206
+ raise InvalidParameterException.new("Invalid subscription type")
207
+ end
208
+
209
+ if contact.nil?
210
+ raise InvalidParameterException.new("A contact must be provided")
211
+ end
212
+
213
+ if subscription_type == SubscriptionType::SMS && !Phone.valid?(contact.mobile_phone)
214
+ raise InvalidMobilePhoneException.new("A valid mobile phone number required for SMS subscriptions")
215
+ end
216
+
217
+ if subscription_type == SubscriptionType::EMAIL && !EmailAddress.valid?(contact.email_address)
218
+ raise InvalidParameterException.new("A valid email address required for EMAIL subscriptions")
219
+ end
220
+ end
221
+
222
+ end
223
+ end
224
+