whatsapp_sdk 0.7.3 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,7 +3,6 @@
3
3
 
4
4
  require "faraday"
5
5
  require "faraday/multipart"
6
- require "oj"
7
6
 
8
7
  module WhatsappSdk
9
8
  module Api
@@ -31,21 +30,27 @@ module WhatsappSdk
31
30
 
32
31
  response = faraday_request.public_send(http_method, endpoint, request_params(params, headers), headers)
33
32
 
34
- Oj.load(response.body)
33
+ return nil if response.body == ""
34
+
35
+ JSON.parse(response.body)
35
36
  end
36
37
 
37
- sig { params(url: String, path_to_file_name: T.nilable(String)).returns(Net::HTTPResponse) }
38
- def download_file(url, path_to_file_name = nil)
38
+ sig do
39
+ params(url: String, content_header: String, file_path: T.nilable(String))
40
+ .returns(Net::HTTPResponse)
41
+ end
42
+ def download_file(url:, content_header:, file_path: nil)
39
43
  uri = URI.parse(url)
40
44
  request = Net::HTTP::Get.new(uri)
41
45
  request["Authorization"] = "Bearer #{@access_token}"
46
+ request.content_type = content_header
42
47
  req_options = { use_ssl: uri.scheme == "https" }
43
48
 
44
49
  response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
45
50
  http.request(request)
46
51
  end
47
52
 
48
- File.write(path_to_file_name, response.body) if response.code == "200" && path_to_file_name
53
+ File.write(file_path, response.body, mode: 'wb') if response.code == "200" && file_path
49
54
 
50
55
  response
51
56
  end
@@ -25,6 +25,21 @@ module WhatsappSdk
25
25
  end
26
26
  end
27
27
 
28
+ class InvalidMediaTypeError < StandardError
29
+ extend T::Sig
30
+
31
+ sig { returns(String) }
32
+ attr_reader :media_type
33
+
34
+ sig { params(media_type: String).void }
35
+ def initialize(_media_type)
36
+ @file_path = file_path
37
+ message = "Invalid Media Type. See the supported types" \
38
+ "see the official documentation https://developers.facebook.com/docs/whatsapp/cloud-api/reference/media#supported-media-types."
39
+ super(message)
40
+ end
41
+ end
42
+
28
43
  # Get Media by ID.
29
44
  #
30
45
  # @param media_id [String] Media Id.
@@ -46,11 +61,16 @@ module WhatsappSdk
46
61
  #
47
62
  # @param url URL.
48
63
  # @param file_path [String] The file_path to download the media e.g. "tmp/downloaded_image.png".
64
+ # @param media_type [String] The media type e.g. "audio/mp4". See the supported types in the official
65
+ # documentation https://developers.facebook.com/docs/whatsapp/cloud-api/reference/media#supported-media-types.
49
66
  # @return [WhatsappSdk::Api::Response] Response object.
50
- sig { params(url: String, file_path: String).returns(WhatsappSdk::Api::Response) }
51
- def download(url:, file_path:)
52
- response = download_file(url, file_path)
67
+ sig { params(url: String, file_path: String, media_type: String).returns(WhatsappSdk::Api::Response) }
68
+ def download(url:, file_path:, media_type:)
69
+ return InvalidMediaTypeError(media_type) if media_type && !valid_content_header?(media_type)
70
+
71
+ content_header = media_type
53
72
 
73
+ response = download_file(url: url, file_path: file_path, content_header: content_header)
54
74
  response = if response.code.to_i == 200
55
75
  { "success" => true }
56
76
  else
@@ -105,6 +125,14 @@ module WhatsappSdk
105
125
  data_class_type: WhatsappSdk::Api::Responses::SuccessResponse
106
126
  )
107
127
  end
128
+
129
+ private
130
+
131
+ def valid_content_header?(_media_type)
132
+ # TODO: Add validations for media types. See available types in the official documentation
133
+ # https://developers.facebook.com/docs/whatsapp/cloud-api/reference/media#supported-media-types.
134
+ true
135
+ end
108
136
  end
109
137
  end
110
138
  end
@@ -363,13 +363,57 @@ module WhatsappSdk
363
363
  # # TODO: https://developers.facebook.com/docs/whatsapp_sdk/cloud-api/reference/messages#contacts-object
364
364
  # end
365
365
 
366
- # def send_interactive_reply_buttons
367
- # # TODO: https://developers.facebook.com/docs/whatsapp_sdk/cloud-api/reference/messages#contacts-object
368
- # end
366
+ # Send interactive reply buttons.
367
+ # https://developers.facebook.com/docs/whatsapp/guides/interactive-messages#reply-buttons
368
+ # You can either send interactive object or as JSON.
369
+ #
370
+ # @param sender_id [Integer] Sender' phone number.
371
+ # @param recipient_number [Integer] Recipient' Phone number.
372
+ # @param interactive [Interactive] Interactive.
373
+ # @param interactive_json [Json] The interactive object as a Json.
374
+ # If you pass interactive_json, you can't pass interactive.
375
+ # @param message_id [String] The id of the message to reply to.
376
+ # @return [WhatsappSdk::Api::Response] Response object.
377
+ sig do
378
+ params(
379
+ sender_id: Integer, recipient_number: Integer,
380
+ interactive: T.nilable(WhatsappSdk::Resource::Interactive),
381
+ interactive_json: T.nilable(T::Hash[T.untyped, T.untyped]), message_id: T.nilable(String)
382
+ ).returns(WhatsappSdk::Api::Response)
383
+ end
384
+ def send_interactive_message(
385
+ sender_id:, recipient_number:, interactive: nil, interactive_json: nil, message_id: nil
386
+ )
387
+ raise MissingArgumentError, "interactive or interactive_json is required" if !interactive && !interactive_json
369
388
 
370
- # def send_interactive_section
371
- # # TODO: https://developers.facebook.com/docs/whatsapp_sdk/cloud-api/reference/messages#contacts-object
372
- # end
389
+ params = {
390
+ messaging_product: "whatsapp",
391
+ to: recipient_number,
392
+ recipient_type: "individual",
393
+ type: "interactive"
394
+ }
395
+
396
+ params[:interactive] = if interactive.nil?
397
+ interactive_json
398
+ else
399
+ interactive.to_json
400
+ end
401
+ params[:context] = { message_id: message_id } if message_id
402
+
403
+ response = send_request(
404
+ endpoint: endpoint(sender_id),
405
+ params: params,
406
+ headers: DEFAULT_HEADERS
407
+ )
408
+
409
+ WhatsappSdk::Api::Response.new(
410
+ response: response,
411
+ data_class_type: WhatsappSdk::Api::Responses::MessageDataResponse
412
+ )
413
+ end
414
+
415
+ alias send_interactive_reply_buttons send_interactive_message
416
+ alias send_interactive_list_messages send_interactive_message
373
417
 
374
418
  # Mark a message as read.
375
419
  #
@@ -10,8 +10,8 @@ module WhatsappSdk
10
10
  @client = client
11
11
  end
12
12
 
13
- def download_file(url, path_to_file_name = nil)
14
- @client.download_file(url, path_to_file_name)
13
+ def download_file(url:, content_header:, file_path: nil)
14
+ @client.download_file(url: url, content_header: content_header, file_path: file_path)
15
15
  end
16
16
 
17
17
  def send_request(endpoint: nil, full_url: nil, http_method: "post", params: {}, headers: {})
@@ -0,0 +1,39 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module WhatsappSdk
5
+ module Resource
6
+ module Error
7
+ class MissingValue < WhatsappSdk::Error
8
+ extend T::Sig
9
+
10
+ sig { returns(String) }
11
+ attr_reader :field
12
+
13
+ sig { returns(String) }
14
+ attr_reader :message
15
+
16
+ sig { params(field: String, message: String).void }
17
+ def initialize(field, message)
18
+ @field = field
19
+ @message = message
20
+ super(message)
21
+ end
22
+ end
23
+
24
+ class InvalidField < MissingValue; end
25
+
26
+ class InvalidInteractiveBody < WhatsappSdk::Error; end
27
+
28
+ class InvalidInteractiveActionReplyButton < WhatsappSdk::Error; end
29
+
30
+ class InvalidInteractiveActionButton < WhatsappSdk::Error; end
31
+
32
+ class InvalidInteractiveActionSection < WhatsappSdk::Error; end
33
+
34
+ class InvalidInteractiveActionSectionRow < WhatsappSdk::Error; end
35
+
36
+ class InvalidInteractiveFooter < WhatsappSdk::Error; end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,89 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module WhatsappSdk
5
+ module Resource
6
+ class Interactive
7
+ extend T::Sig
8
+
9
+ class Type < T::Enum
10
+ extend T::Sig
11
+
12
+ enums do
13
+ ListMessage = new("list")
14
+ ReplyButton = new("button")
15
+ SingleProductMessage = new("product")
16
+ MultiProductMessage = new("product_list")
17
+ end
18
+ end
19
+
20
+ # Returns the Interactive type of message you want to send.
21
+ #
22
+ # @returns type [Type]. Supported Options are list, button, product and product_list.
23
+ sig { returns(Type) }
24
+ attr_accessor :type
25
+
26
+ # Returns the interactive header if present. Required for type product_list.
27
+ #
28
+ # @returns type [InteractiveHeader] It can be nil.
29
+ sig { returns(T.nilable(InteractiveHeader)) }
30
+ attr_accessor :header
31
+
32
+ # Returns the interactive body.
33
+ #
34
+ # @returns type [InteractiveBody] Valid option is of type text only.
35
+ sig { returns(InteractiveBody) }
36
+ attr_accessor :body
37
+
38
+ # Returns the interactive footer if present.
39
+ #
40
+ # @returns type [InteractiveFooter] Valid option is of type text only. It can be nil.
41
+ sig { returns(T.nilable(InteractiveFooter)) }
42
+ attr_accessor :footer
43
+
44
+ # Returns the interactive action.
45
+ #
46
+ # @returns type [InteractiveBody] Valid condition is buttons of length of 1, 2 or 3 if type is button.
47
+ sig { returns(InteractiveAction) }
48
+ attr_accessor :action
49
+
50
+ sig do
51
+ params(
52
+ type: Type, body: InteractiveBody, action: InteractiveAction,
53
+ header: T.nilable(InteractiveHeader), footer: T.nilable(InteractiveFooter)
54
+ ).void
55
+ end
56
+ def initialize(type:, body:, action:, header: nil, footer: nil)
57
+ @type = type
58
+ @body = body
59
+ @action = action
60
+ @header = header
61
+ @footer = footer
62
+ validate
63
+ end
64
+
65
+ sig { returns(T::Hash[T.untyped, T.untyped]) }
66
+ def to_json
67
+ json = { type: type.serialize }
68
+ json[:header] = header.to_json if header
69
+ json[:body] = body.to_json
70
+ json[:footer] = footer.to_json if footer
71
+ json[:action] = action.to_json
72
+
73
+ json
74
+ end
75
+
76
+ private
77
+
78
+ sig { void }
79
+ def validate
80
+ validate_action
81
+ end
82
+
83
+ sig { void }
84
+ def validate_action
85
+ action.validate
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,141 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module WhatsappSdk
5
+ module Resource
6
+ class InteractiveAction
7
+ extend T::Sig
8
+
9
+ class Type < T::Enum
10
+ extend T::Sig
11
+
12
+ enums do
13
+ ListMessage = new("list_message")
14
+ ReplyButton = new("reply_button")
15
+ end
16
+ end
17
+
18
+ # Returns the type of interactive action you want to send.
19
+ #
20
+ # @returns type [Type]. Supported Options are list_message and reply_button.
21
+ sig { returns(Type) }
22
+ attr_accessor :type
23
+
24
+ # Returns the buttons of the Action. For reply_button type, it's required.
25
+ #
26
+ # @returns buttons [Array<InteractiveActionReplyButton>] .
27
+ sig { returns(T::Array[InteractiveActionReplyButton]) }
28
+ attr_accessor :buttons
29
+
30
+ # Returns the button of the Action. For list_message type, it's required.
31
+ #
32
+ # @returns button [String] .
33
+ sig { returns(String) }
34
+ attr_accessor :button
35
+
36
+ # Returns the sections of the Action. For list_message type, it's required.
37
+ #
38
+ # @returns sections [Array<InteractiveActionSection>] .
39
+ sig { returns(T::Array[InteractiveActionSection]) }
40
+ attr_accessor :sections
41
+
42
+ # TODO: attr_accessor :catalog_id
43
+ # TODO: attr_accessor :product_retailer_id
44
+
45
+ sig { params(reply_button: InteractiveActionReplyButton).void }
46
+ def add_reply_button(reply_button)
47
+ @buttons << reply_button
48
+ end
49
+
50
+ sig { params(section: InteractiveActionSection).void }
51
+ def add_section(section)
52
+ @sections << section
53
+ end
54
+
55
+ REPLY_BUTTONS_MINIMUM = 1
56
+ REPLY_BUTTONS_MAXIMUM = 3
57
+ LIST_BUTTON_TITLE_MAXIMUM = 20
58
+ LIST_SECTIONS_MINIMUM = 1
59
+ LIST_SECTIONS_MAXIMUM = 10
60
+
61
+ sig do
62
+ params(
63
+ type: Type, buttons: T::Array[InteractiveActionReplyButton],
64
+ button: String, sections: T::Array[InteractiveActionSection]
65
+ ).void
66
+ end
67
+ def initialize(type:, buttons: [], button: "", sections: [])
68
+ @type = type
69
+ @buttons = buttons
70
+ @button = button
71
+ @sections = sections
72
+ end
73
+
74
+ sig { returns(T::Hash[T.untyped, T.untyped]) }
75
+ def to_json
76
+ json = {}
77
+ case type.serialize
78
+ when "list_message"
79
+ json = { button: button, sections: sections.map(&:to_json) }
80
+ when "reply_button"
81
+ json = { buttons: buttons.map(&:to_json) }
82
+ end
83
+
84
+ json
85
+ end
86
+
87
+ def validate
88
+ validate_fields
89
+ end
90
+
91
+ private
92
+
93
+ def validate_fields
94
+ case type.serialize
95
+ when "list_message"
96
+ validate_list_message
97
+ when "reply_button"
98
+ validate_reply_button
99
+ end
100
+ end
101
+
102
+ def validate_list_message
103
+ button_length = button.length
104
+ sections_count = sections.length
105
+ unless button_length.positive?
106
+ raise WhatsappSdk::Resource::Error::InvalidInteractiveActionButton,
107
+ "Invalid button in action. Button label is required."
108
+ end
109
+
110
+ unless button_length <= LIST_BUTTON_TITLE_MAXIMUM
111
+ raise WhatsappSdk::Resource::Error::InvalidInteractiveActionButton,
112
+ "Invalid length #{button_length} for button. Maximum length: " \
113
+ "#{LIST_BUTTON_TITLE_MAXIMUM} characters."
114
+ end
115
+
116
+ unless (LIST_SECTIONS_MINIMUM..LIST_SECTIONS_MAXIMUM).cover?(sections_count)
117
+ raise WhatsappSdk::Resource::Error::InvalidInteractiveActionSection,
118
+ "Invalid length #{sections_count} for sections in action. It should be between " \
119
+ "#{LIST_SECTIONS_MINIMUM} and #{LIST_SECTIONS_MAXIMUM}."
120
+ end
121
+
122
+ sections.each(&:validate)
123
+ end
124
+
125
+ def validate_reply_button
126
+ buttons_count = buttons.length
127
+ unless (REPLY_BUTTONS_MINIMUM..REPLY_BUTTONS_MAXIMUM).cover?(buttons_count)
128
+ raise WhatsappSdk::Resource::Error::InvalidInteractiveActionReplyButton,
129
+ "Invalid length #{buttons_count} for buttons in action. It should be between " \
130
+ "#{REPLY_BUTTONS_MINIMUM} and #{REPLY_BUTTONS_MAXIMUM}."
131
+ end
132
+
133
+ button_ids = buttons.map(&:id)
134
+ return if button_ids.length.eql?(button_ids.uniq.length)
135
+
136
+ raise WhatsappSdk::Resource::Error::InvalidInteractiveActionReplyButton,
137
+ "Duplicate ids #{button_ids} for buttons in action. They should be unique."
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,88 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module WhatsappSdk
5
+ module Resource
6
+ class InteractiveActionReplyButton
7
+ extend T::Sig
8
+
9
+ class Type < T::Enum
10
+ extend T::Sig
11
+
12
+ enums do
13
+ Reply = new("reply")
14
+ end
15
+ end
16
+
17
+ # Returns the ActionButton type of message you want to send.
18
+ #
19
+ # @returns type [String]. Supported Options are reply only.
20
+ sig { returns(Type) }
21
+ attr_accessor :type
22
+
23
+ # Returns the ActionButton title you want to send.
24
+ #
25
+ # @returns title [String]. The character limit is 20 characters.
26
+ sig { returns(String) }
27
+ attr_accessor :title
28
+
29
+ # Returns the ActionButton unique identifier you want to send.
30
+ # This ID is returned in the webhook when the button is clicked by the user.
31
+ #
32
+ # @returns id [String]. The character limit is 256 characters.
33
+ sig { returns(String) }
34
+ attr_accessor :id
35
+
36
+ ACTION_BUTTON_TITLE_MAXIMUM = 20
37
+ ACTION_BUTTON_ID_MAXIMUM = 256
38
+
39
+ sig { params(title: String, id: String).void }
40
+ def initialize(title:, id:)
41
+ @type = Type::Reply
42
+ @title = title
43
+ @id = id
44
+ validate
45
+ end
46
+
47
+ def to_json
48
+ json = { type: type.serialize }
49
+ json[type.serialize.to_sym] = {
50
+ id: id,
51
+ title: title
52
+ }
53
+
54
+ json
55
+ end
56
+
57
+ private
58
+
59
+ sig { void }
60
+ def validate
61
+ validate_title
62
+ validate_id
63
+ end
64
+
65
+ sig { void }
66
+ def validate_title
67
+ title_length = title.length
68
+ return if title_length <= ACTION_BUTTON_TITLE_MAXIMUM
69
+
70
+ raise WhatsappSdk::Resource::Error::InvalidInteractiveActionReplyButton,
71
+ "Invalid length #{title_length} for title in button. " \
72
+ "Maximum length: #{ACTION_BUTTON_TITLE_MAXIMUM} characters."
73
+ end
74
+
75
+ sig { void }
76
+ def validate_id
77
+ id_unfrozen = id.dup
78
+ id_unfrozen.strip! # You cannot have leading or trailing spaces when setting the ID.
79
+ id = id_unfrozen.freeze
80
+ id_length = id.length
81
+ return if id_length <= ACTION_BUTTON_ID_MAXIMUM
82
+
83
+ raise WhatsappSdk::Resource::Error::InvalidInteractiveActionReplyButton,
84
+ "Invalid length #{id_length} for id in button. Maximum length: #{ACTION_BUTTON_ID_MAXIMUM} characters."
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,72 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module WhatsappSdk
5
+ module Resource
6
+ class InteractiveActionSection
7
+ extend T::Sig
8
+
9
+ # Returns the ActionSection title you want to send.
10
+ #
11
+ # @returns title [String]. The character limit is 24 characters.
12
+ sig { returns(String) }
13
+ attr_accessor :title
14
+
15
+ # Returns the ActionSection rows you want to send.
16
+ #
17
+ # @returns id [T::Array[InteractiveActionSectionRow]]. There must be at least one rows object.
18
+ sig { returns(T::Array[InteractiveActionSectionRow]) }
19
+ attr_accessor :rows
20
+
21
+ sig { params(row: InteractiveActionSectionRow).void }
22
+ def add_row(row)
23
+ @rows << row
24
+ end
25
+
26
+ ACTION_SECTION_TITLE_MAXIMUM = 24
27
+ ACTION_SECTION_ROWS_MAXIMUM = 10
28
+
29
+ sig { params(title: String, rows: T::Array[InteractiveActionSectionRow]).void }
30
+ def initialize(title:, rows: [])
31
+ @title = title
32
+ @rows = rows
33
+ validate(skip_rows: true)
34
+ end
35
+
36
+ def to_json
37
+ {
38
+ title: title,
39
+ rows: rows.map(&:to_json)
40
+ }
41
+ end
42
+
43
+ sig { params(skip_rows: T.nilable(T::Boolean)).void }
44
+ def validate(skip_rows: false)
45
+ validate_title
46
+ validate_rows unless skip_rows
47
+ end
48
+
49
+ private
50
+
51
+ sig { void }
52
+ def validate_title
53
+ title_length = title.length
54
+ return if title_length <= ACTION_SECTION_TITLE_MAXIMUM
55
+
56
+ raise WhatsappSdk::Resource::Error::InvalidInteractiveActionSection,
57
+ "Invalid length #{title_length} for title in section. Maximum length: " \
58
+ "#{ACTION_SECTION_TITLE_MAXIMUM} characters."
59
+ end
60
+
61
+ sig { void }
62
+ def validate_rows
63
+ rows_length = rows.length
64
+ return if rows_length <= ACTION_SECTION_ROWS_MAXIMUM
65
+
66
+ raise WhatsappSdk::Resource::Error::InvalidInteractiveActionSection,
67
+ "Invalid number of rows #{rows_length} in section. Maximum count: " \
68
+ "#{ACTION_SECTION_ROWS_MAXIMUM}."
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,93 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module WhatsappSdk
5
+ module Resource
6
+ class InteractiveActionSectionRow
7
+ extend T::Sig
8
+
9
+ # Returns the ActionSection title you want to send.
10
+ #
11
+ # @returns title [String]. The character limit is 24 characters.
12
+ sig { returns(String) }
13
+ attr_accessor :title
14
+
15
+ # Returns the ActionSection description you want to send.
16
+ #
17
+ # @returns description [String]. The character limit is 72 characters if present.
18
+ sig { returns(String) }
19
+ attr_accessor :description
20
+
21
+ # Returns the ActionSection unique identifier you want to send.
22
+ # This ID is returned in the webhook when the section is selected by the user.
23
+ #
24
+ # @returns id [String]. The character limit is 256 characters.
25
+ sig { returns(String) }
26
+ attr_accessor :id
27
+
28
+ ACTION_SECTION_TITLE_MAXIMUM = 24
29
+ ACTION_SECTION_DESCRIPTION_MAXIMUM = 72
30
+ ACTION_SECTION_ID_MAXIMUM = 256
31
+
32
+ sig { params(title: String, id: String, description: T.nilable(String)).void }
33
+ def initialize(title:, id:, description: "")
34
+ @title = title
35
+ @id = id
36
+ @description = description
37
+ validate
38
+ end
39
+
40
+ def to_json
41
+ json = {
42
+ id: id,
43
+ title: title
44
+ }
45
+ json[:description] = description if description.length.positive?
46
+
47
+ json
48
+ end
49
+
50
+ private
51
+
52
+ sig { void }
53
+ def validate
54
+ validate_title
55
+ validate_id
56
+ validate_description
57
+ end
58
+
59
+ sig { void }
60
+ def validate_title
61
+ title_length = title.length
62
+ return if title_length <= ACTION_SECTION_TITLE_MAXIMUM
63
+
64
+ raise WhatsappSdk::Resource::Error::InvalidInteractiveActionSectionRow,
65
+ "Invalid length #{title_length} for title in section row. "\
66
+ "Maximum length: #{ACTION_SECTION_TITLE_MAXIMUM} characters."
67
+ end
68
+
69
+ sig { void }
70
+ def validate_id
71
+ id_unfrozen = id.dup
72
+ id_unfrozen.strip! # You cannot have leading or trailing spaces when setting the ID.
73
+ id = id_unfrozen.freeze
74
+ id_length = id.length
75
+ return if id_length <= ACTION_SECTION_ID_MAXIMUM
76
+
77
+ raise WhatsappSdk::Resource::Error::InvalidInteractiveActionSectionRow,
78
+ "Invalid length #{id_length} for id in section row. Maximum length: "\
79
+ "#{ACTION_SECTION_ID_MAXIMUM} characters."
80
+ end
81
+
82
+ sig { void }
83
+ def validate_description
84
+ description_length = description.length
85
+ return if description_length <= ACTION_SECTION_DESCRIPTION_MAXIMUM
86
+
87
+ raise WhatsappSdk::Resource::Error::InvalidInteractiveActionSectionRow,
88
+ "Invalid length #{description_length} for description in section " \
89
+ "row. Maximum length: #{ACTION_SECTION_DESCRIPTION_MAXIMUM} characters."
90
+ end
91
+ end
92
+ end
93
+ end