unisender-ruby 0.1.0

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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +25 -0
  3. data/CONTRIBUTING.md +43 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +140 -0
  6. data/docs/api.md +1063 -0
  7. data/examples/campaign.rb +13 -0
  8. data/examples/check_connection.rb +16 -0
  9. data/examples/events.rb +20 -0
  10. data/examples/export.rb +11 -0
  11. data/examples/import.rb +18 -0
  12. data/examples/offline.rb +26 -0
  13. data/examples/quickstart.rb +6 -0
  14. data/examples/subscribe.rb +12 -0
  15. data/lib/unisender/batches.rb +31 -0
  16. data/lib/unisender/client.rb +59 -0
  17. data/lib/unisender/errors.rb +129 -0
  18. data/lib/unisender/event_payload.rb +63 -0
  19. data/lib/unisender/events.rb +19 -0
  20. data/lib/unisender/form.rb +67 -0
  21. data/lib/unisender/helpers.rb +28 -0
  22. data/lib/unisender/operations/contacts_1.rb +51 -0
  23. data/lib/unisender/operations/contacts_2.rb +44 -0
  24. data/lib/unisender/operations/inputs_1.rb +48 -0
  25. data/lib/unisender/operations/messages_1.rb +58 -0
  26. data/lib/unisender/operations/messages_2.rb +54 -0
  27. data/lib/unisender/operations/messages_3.rb +12 -0
  28. data/lib/unisender/operations/notes_1.rb +38 -0
  29. data/lib/unisender/operations/statistics_1.rb +48 -0
  30. data/lib/unisender/operations/statistics_2.rb +18 -0
  31. data/lib/unisender/operations/templates_1.rb +45 -0
  32. data/lib/unisender/paginated_operations.rb +42 -0
  33. data/lib/unisender/pagination.rb +68 -0
  34. data/lib/unisender/protocol.rb +46 -0
  35. data/lib/unisender/response.rb +64 -0
  36. data/lib/unisender/retry_policy.rb +82 -0
  37. data/lib/unisender/task_waiter.rb +52 -0
  38. data/lib/unisender/transport.rb +53 -0
  39. data/lib/unisender/validation.rb +72 -0
  40. data/lib/unisender/version.rb +5 -0
  41. data/lib/unisender.rb +29 -0
  42. data/sig/operations.rbs +78 -0
  43. data/sig/unisender.rbs +218 -0
  44. metadata +87 -0
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unisender
4
+ # Polls an existing asynchronous job without starting or downloading an export.
5
+ class TaskWaiter
6
+ PENDING = %w[new processing].freeze
7
+
8
+ def initialize(client, clock:, sleeper:)
9
+ @client = client
10
+ @clock = clock
11
+ @sleeper = sleeper
12
+ end
13
+
14
+ def call(task_uuid, interval, timeout)
15
+ validate!(interval, timeout)
16
+ deadline = @clock.call + timeout
17
+ loop do
18
+ response = @client.get_task_result(task_uuid:)
19
+ return response if complete?(response)
20
+
21
+ wait(interval, deadline)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def validate!(interval, timeout)
28
+ [interval, timeout].each do |value|
29
+ unless value.is_a?(Numeric) && value.positive? && value.finite?
30
+ raise ArgumentError, "Polling interval and timeout must be positive and finite"
31
+ end
32
+ end
33
+ end
34
+
35
+ def complete?(response)
36
+ result = response.result
37
+ raise TaskError, response unless result.is_a?(Hash)
38
+ return true if result["status"] == "completed"
39
+ raise TaskError, response unless PENDING.include?(result["status"])
40
+
41
+ false
42
+ end
43
+
44
+ def wait(interval, deadline)
45
+ remaining = deadline - @clock.call
46
+ raise TimeoutError, "Timed out waiting for the UniSender task" unless remaining.positive?
47
+
48
+ @sleeper.call([interval, remaining].min)
49
+ raise TimeoutError, "Timed out waiting for the UniSender task" if @clock.call >= deadline
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "openssl"
5
+
6
+ module Unisender
7
+ # Verified HTTPS transport with explicit timeouts and no implicit retries.
8
+ class Transport
9
+ def initialize(open_timeout: 5, read_timeout: 60, write_timeout: 30, http_factory: nil)
10
+ [open_timeout, read_timeout, write_timeout].each do |timeout|
11
+ unless timeout.is_a?(Numeric) && timeout.positive? && timeout.finite?
12
+ raise ArgumentError, "Timeouts must be positive finite numbers"
13
+ end
14
+ end
15
+ @open_timeout = open_timeout
16
+ @read_timeout = read_timeout
17
+ @write_timeout = write_timeout
18
+ @http_factory = http_factory || ->(uri) { Net::HTTP.new(uri.hostname.to_s, uri.port) }
19
+ freeze
20
+ end
21
+
22
+ def post(uri, headers:, body:)
23
+ exchange(uri, headers:, body:)
24
+ rescue Timeout::Error
25
+ raise TimeoutError, "UniSender request timed out", cause: nil
26
+ rescue OpenSSL::SSL::SSLError
27
+ raise TransportError, "UniSender TLS connection failed", cause: nil
28
+ rescue IOError, SystemCallError, SocketError
29
+ raise TransportError.new("UniSender connection failed", retryable: true), cause: nil
30
+ end
31
+
32
+ private
33
+
34
+ def exchange(uri, headers:, body:)
35
+ request = Net::HTTP::Post.new(uri, headers)
36
+ request.body = body
37
+ response = configured_http(uri).request(request)
38
+ headers = response.to_hash.transform_values { |items| items.join(", ") }
39
+ HTTPResponse.new(status: response.code.to_i, headers:, body: response.body.to_s)
40
+ end
41
+
42
+ def configured_http(uri)
43
+ http = @http_factory.call(uri)
44
+ http.use_ssl = true
45
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
46
+ http.open_timeout = @open_timeout
47
+ http.read_timeout = @read_timeout
48
+ http.write_timeout = @write_timeout
49
+ http.max_retries = 0
50
+ http
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unisender
4
+ # Structural checks only. Account permissions and business rules belong to the API.
5
+ module Validation
6
+ def self.operation!(method, params)
7
+ case method
8
+ when "importContacts" then import!(params)
9
+ when "getTemplate" then template!(params)
10
+ when "createEmailMessage" then email_message!(params)
11
+ when "createCampaign" then campaign!(params)
12
+ end
13
+ end
14
+
15
+ def self.email_message!(params)
16
+ required!(params, %w[subject body]) if params[:template_id].nil? && params[:system_template_id].nil?
17
+ end
18
+
19
+ def self.campaign!(params)
20
+ return unless params[:contacts] && params[:contacts_url]
21
+
22
+ raise ArgumentError,
23
+ "contacts and contacts_url are mutually exclusive"
24
+ end
25
+
26
+ def self.template!(params)
27
+ return unless params[:template_id].nil? && params[:system_template_id].nil?
28
+
29
+ raise ArgumentError, "Provide template_id or system_template_id"
30
+ end
31
+
32
+ def self.import!(params)
33
+ fields = params[:field_names]
34
+ unless fields.is_a?(Array) && fields.all?(String) && fields.include?("email") && fields.uniq == fields
35
+ raise ArgumentError, "field_names must contain unique column names including email"
36
+ end
37
+
38
+ rows!(params[:data], fields.size)
39
+ end
40
+
41
+ def self.rows!(rows, width)
42
+ unless rows.is_a?(Array) && (1..500).cover?(rows.size) && rows.all? do |row|
43
+ row.is_a?(Array) && row.size == width
44
+ end
45
+ raise ArgumentError, "data must contain 1 to 500 rows matching field_names"
46
+ end
47
+ end
48
+
49
+ def self.required!(params, names)
50
+ names.each do |name|
51
+ value = params[name.to_sym]
52
+ raise ArgumentError, "Missing required parameter: #{name}" if missing?(value)
53
+ end
54
+ end
55
+
56
+ def self.missing?(value)
57
+ case value
58
+ when nil then true
59
+ when String, Array, Hash then value.empty?
60
+ else false
61
+ end
62
+ end
63
+
64
+ def self.comma_list(value)
65
+ unless value.all? { |item| item.is_a?(String) || item.is_a?(Integer) }
66
+ raise ArgumentError, "Comma-separated lists require strings or integers"
67
+ end
68
+
69
+ value.join(",")
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unisender
4
+ VERSION = "0.1.0"
5
+ end
data/lib/unisender.rb ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "unisender/version"
4
+ require "uri"
5
+ require_relative "unisender/response"
6
+ require_relative "unisender/errors"
7
+ require_relative "unisender/protocol"
8
+ require_relative "unisender/form"
9
+ require_relative "unisender/transport"
10
+ require_relative "unisender/validation"
11
+ require_relative "unisender/event_payload"
12
+ require_relative "unisender/retry_policy"
13
+ require_relative "unisender/client"
14
+ require_relative "unisender/events"
15
+ require_relative "unisender/paginated_operations"
16
+ require_relative "unisender/pagination"
17
+ require_relative "unisender/batches"
18
+ require_relative "unisender/task_waiter"
19
+ require_relative "unisender/helpers"
20
+ require_relative "unisender/operations/contacts_1"
21
+ require_relative "unisender/operations/contacts_2"
22
+ require_relative "unisender/operations/inputs_1"
23
+ require_relative "unisender/operations/messages_1"
24
+ require_relative "unisender/operations/messages_2"
25
+ require_relative "unisender/operations/messages_3"
26
+ require_relative "unisender/operations/notes_1"
27
+ require_relative "unisender/operations/statistics_1"
28
+ require_relative "unisender/operations/statistics_2"
29
+ require_relative "unisender/operations/templates_1"
@@ -0,0 +1,78 @@
1
+ module Unisender
2
+ type id = String | Integer
3
+ type flag = bool | 0 | 1
4
+ type list = { "id" => id, "title" => String }
5
+ type list_created = { "id" => id }
6
+ type subscription = { "person_id" => id }
7
+ type message_created = { "message_id" => id }
8
+ type campaign_created = { "campaign_id" => id, "status" => String }
9
+ type task_created = { "task_uuid" => String }
10
+ type task_completed = { "status" => String, "file_to_download" => String }
11
+
12
+ type object = Hash[String, json]
13
+ type objects = Array[object]
14
+ type contact_count = { "count" => String }
15
+ type total_contacts = { "total" => Integer }
16
+ type delivery_status = { "status" => String }
17
+ type message_version = { "message_id" => id, "actual_version_id" => id }
18
+ type note = { "id" => id, "content" => String }
19
+ type event_accepted = { "success" => true }
20
+
21
+ class Client
22
+ def create_list: (title: String, ?before_subscribe_url: String | nil, ?after_subscribe_url: String | nil, ?sub_user_rights: params | nil) -> Response[list_created]
23
+ def delete_list: (list_id: id) -> Response[object | objects]
24
+ def exclude: (contact_type: String, contact: String, ?list_ids: String | Array[id] | nil) -> Response[object | objects]
25
+ def export_contacts: (?notify_url: String | nil, ?list_id: id | nil, ?field_names: Array[String] | nil, ?email: String | nil, ?phone: String | nil, ?tag: String | nil, ?email_status: String | nil, ?phone_status: String | nil, ?contact_status: String | nil) -> Response[task_created]
26
+ def get_contact_count: (list_id: id, params: params) -> Response[contact_count]
27
+ def get_lists: () -> Response[Array[list]]
28
+ def get_total_contacts_count: (login: String) -> Response[total_contacts]
29
+ def import_contacts: (field_names: Array[String], data: Array[Array[parameter]], ?overwrite_tags: flag | nil, ?overwrite_lists: flag | nil, ?telegram_bot_channel_id: String | nil) -> Response[object]
30
+ def subscribe: (list_ids: String | Array[id], fields: params, ?tags: String | Array[id] | nil, ?double_optin: Integer | nil, ?overwrite: Integer | nil) -> Response[subscription]
31
+ def update_list: (list_id: id, title: String, ?before_subscribe_url: String | nil, ?after_subscribe_url: String | nil, ?sub_user_rights: params | nil) -> Response[object | objects]
32
+ def is_contact_in_lists: (email: String, list_ids: String | Array[id], condition: String) -> Response[bool]
33
+ def get_contact: (email: String, ?include_lists: flag | nil, ?include_fields: flag | nil, ?include_details: flag | nil) -> Response[object]
34
+ def unsubscribe: (contact_type: String, contact: String, ?list_ids: String | Array[id] | nil) -> Response[object | objects]
35
+ def cancel_campaign: (campaign_id: id) -> Response[objects]
36
+ def check_email: (email_id: String | Array[id]) -> Response[objects]
37
+ def check_sms: (sms_id: id) -> Response[delivery_status]
38
+ def create_campaign: (message_id: id, ?start_time: String | nil, ?timezone: String | nil, ?track_read: flag | nil, ?track_links: flag | nil, ?contacts: String | Array[id] | nil, ?contacts_url: String | nil, ?track_ga: flag | nil, ?payment_limit: Integer | Float | nil, ?payment_currency: String | nil, ?ga_medium: String | nil, ?ga_source: String | nil, ?ga_campaign: String | nil, ?ga_content: String | nil, ?ga_term: String | nil) -> Response[campaign_created]
39
+ def create_email_message: (sender_name: String, sender_email: String, list_id: id, ?subject: String | nil, ?body: String | nil, ?text_body: String | nil, ?generate_text: flag | nil, ?tag: String | nil, ?attachments: Hash[String, String] | nil, ?lang: String | nil, ?template_id: id | nil, ?system_template_id: id | nil, ?wrap_type: String | nil, ?categories: String | Array[id] | nil) -> Response[message_created]
40
+ def create_sms_message: (sender: String, body: String, list_id: id, ?tag: String | nil) -> Response[message_created]
41
+ def delete_message: (message_id: id) -> Response[object | objects]
42
+ def get_actual_message_version: (message_id: id) -> Response[message_version]
43
+ def get_web_version: (campaign_id: id) -> Response[object]
44
+ def send_email: (email: String, sender_name: String, sender_email: String, subject: String, body: String, list_id: id, ?attachments: Hash[String, String] | nil, ?lang: String | nil, ?track_read: flag | nil, ?track_links: flag | nil, ?cc: String | nil, ?headers: String | params | nil, ?images_as: String | nil, ?ref_key: String | nil, ?error_checking: flag | nil, ?metadata: params | nil) -> Response[object | objects]
45
+ def send_sms: (phone: String, sender: String, text: String) -> Response[object]
46
+ def send_test_email: (email: String | Array[id], id: id) -> Response[object | objects]
47
+ def update_email_message: (id: id, ?sender_name: String | nil, ?sender_email: String | nil, ?subject: String | nil, ?body: String | nil, ?list_id: id | nil, ?text_body: String | nil, ?lang: String | nil, ?categories: String | Array[id] | nil) -> Response[message_created]
48
+ def update_opt_in_email: (sender_name: String, sender_email: String, subject: String, body: String, list_id: id) -> Response[object | objects]
49
+ def get_sender_domain_list: (username: String, ?domain: String | nil, ?limit: Integer | nil, ?offset: Integer | nil) -> Response[objects]
50
+ def get_campaign_common_stats: (campaign_id: id) -> Response[object]
51
+ def get_campaign_delivery_stats: (campaign_id: id, ?notify_url: String | nil, ?changed_since: String | nil, ?field_ids: Array[id] | nil) -> Response[task_created]
52
+ def get_campaign_status: (campaign_id: id) -> Response[delivery_status]
53
+ def get_messages: (date_from: String, date_to: String, ?limit: Integer | nil, ?offset: Integer | nil) -> Response[objects]
54
+ def get_visited_links: (campaign_id: id, ?group: flag | nil) -> Response[object]
55
+ def list_messages: (date_from: String, date_to: String, ?limit: Integer | nil, ?offset: Integer | nil) -> Response[objects]
56
+ def get_campaigns: (from: String, ?to: String | nil, ?limit: Integer | nil, ?offset: Integer | nil) -> Response[objects]
57
+ def get_message: (id: id) -> Response[objects]
58
+ def get_task_result: (task_uuid: String) -> Response[object]
59
+ def create_email_template: (title: String, subject: String, body: String, ?description: String | nil, ?text_body: String | nil, ?lang: String | nil) -> Response[list_created]
60
+ def delete_template: (template_id: id) -> Response[object | objects]
61
+ def get_template: (?template_id: id | nil, ?system_template_id: id | nil) -> Response[object]
62
+ def get_templates: (?type: String | nil, ?date_from: String | nil, ?date_to: String | nil, ?limit: Integer | nil, ?offset: Integer | nil) -> Response[objects]
63
+ def list_templates: (?type: String | nil, ?date_from: String | nil, ?date_to: String | nil, ?limit: Integer | nil, ?offset: Integer | nil) -> Response[objects]
64
+ def update_email_template: (template_id: id, ?title: String | nil, ?description: String | nil, ?subject: String | nil, ?body: String | nil, ?text_body: String | nil, ?lang: String | nil) -> Response[list_created]
65
+ def create_field: (name: String, type: String, ?public_name: String | nil) -> Response[list_created]
66
+ def delete_field: (id: id) -> Response[object | objects]
67
+ def delete_tag: (id: id) -> Response[object | objects]
68
+ def get_fields: () -> Response[objects]
69
+ def get_tags: () -> Response[objects]
70
+ def update_field: (id: id, name: String, ?public_name: String | nil) -> Response[object | objects]
71
+ def get_contact_field_values: (email: String, field_ids: String | Array[id]) -> Response[object]
72
+ def create_subscriber_note: (subscriber_id: id, content: String) -> Response[list_created]
73
+ def update_subscriber_note: (id: id, content: String) -> Response[list_created]
74
+ def delete_subscriber_note: (id: id) -> Response[object | objects]
75
+ def get_subscriber_note: (id: id) -> Response[note]
76
+ def get_subscriber_notes: (subscriber_id: id, ?limit: Integer | nil, ?offset: Integer | nil, ?order_type: String | nil, ?order_by: String | nil, ?is_pinned: flag | nil) -> Response[objects]
77
+ end
78
+ end
data/sig/unisender.rbs ADDED
@@ -0,0 +1,218 @@
1
+ module Unisender
2
+ VERSION: String
3
+
4
+ type json = nil | bool | Integer | Float | String | Array[json] | Hash[String, json]
5
+ type parameter = nil | bool | Integer | Float | String | Array[parameter] | Hash[String | Symbol | Integer, parameter]
6
+ type params = Hash[String | Symbol | Integer, parameter]
7
+ type duration = Integer | Float
8
+
9
+ class HTTPResponse < Data
10
+ attr_reader status: Integer
11
+ attr_reader headers: Hash[String, String]
12
+ attr_reader body: String
13
+ def self.new: (status: Integer, headers: Hash[String, String], body: String) -> HTTPResponse
14
+ def inspect: () -> String
15
+ alias to_s inspect
16
+ end
17
+
18
+ class Response[out Result = json]
19
+ EMPTY_WARNINGS: Array[json]
20
+ @data: Hash[String, json]
21
+ @status: Integer
22
+ @headers: Hash[String, String]
23
+ attr_reader data: Hash[String, json]
24
+ attr_reader status: Integer
25
+ attr_reader headers: Hash[String, String]
26
+ def initialize: (data: Hash[String, json], status: Integer, headers: Hash[String, String]) -> void
27
+ def result: () -> Result
28
+ def warnings: () -> json
29
+ def inspect: () -> String
30
+ private
31
+ def immutable: (json) -> json
32
+ def immutable_hash: (Hash[String, json]) -> Hash[String, json]
33
+ end
34
+
35
+ class Error < StandardError
36
+ OPERATIONS: Array[String]
37
+ @operation: String?
38
+ attr_reader operation: String?
39
+ def with_operation: (String) -> self
40
+ def to_s: () -> String
41
+ def inspect: () -> String
42
+ end
43
+ class APIError < Error
44
+ SAFE_CODES: Array[String]
45
+ attr_reader response: Response
46
+ attr_reader code: json
47
+ attr_reader details: json
48
+ def initialize: (Response) -> void
49
+ end
50
+ class HTTPError < Error
51
+ attr_reader response: Response
52
+ def initialize: (Response) -> void
53
+ end
54
+ class InvalidResponseError < Error
55
+ end
56
+ class TransportError < Error
57
+ @retryable: bool
58
+ def initialize: (String, ?retryable: bool) -> void
59
+ def retryable?: () -> bool
60
+ end
61
+ class TimeoutError < TransportError
62
+ end
63
+ class EventError < HTTPError
64
+ def code: () -> json
65
+ def details: () -> json
66
+ def possibly_partially_accepted?: () -> bool
67
+ end
68
+
69
+ class BatchError < Error
70
+ attr_reader completed_responses: Array[Response]
71
+ attr_reader failed_range: Range[Integer]
72
+ attr_reader error: StandardError
73
+ def initialize: (completed_responses: Array[Response], failed_range: Range[Integer], error: StandardError) -> void
74
+ end
75
+ class PaginationError < Error
76
+ end
77
+ class PartialFailure < Error
78
+ attr_reader response: Response
79
+ def initialize: (Response) -> void
80
+ end
81
+ class TaskError < Error
82
+ attr_reader response: Response
83
+ def initialize: (Response) -> void
84
+ end
85
+
86
+ module PaginatedOperations
87
+ DEFINITIONS: Hash[String, [String, Array[Symbol], Array[Symbol], Integer?]]
88
+ def self.call: (Client, String | Symbol, params) -> Response[objects]
89
+ def self.page_limit: (String | Symbol) -> Integer?
90
+ def self.definition: (String | Symbol) -> [String, Array[Symbol], Array[Symbol], Integer?]
91
+ end
92
+
93
+ class Pagination
94
+ @client: Client
95
+ @method: String
96
+ @page_size: Integer
97
+ @max_pages: Integer
98
+ @params: params
99
+ def initialize: (Client, String | Symbol, Integer, Integer, params) -> void
100
+ def each: () -> Enumerator[Response, void]
101
+ private
102
+ def validate!: (Integer?) -> void
103
+ def validate_limit!: (Integer?) -> void
104
+ def page_length: (Response) -> Integer
105
+ def initial_offset: () -> Integer
106
+ def fetch_pages: (_ResponseSink, Integer) -> void
107
+ end
108
+
109
+ module Batches
110
+ def self.each: [T] (Enumerable[T], Integer, Integer) { (Array[T]) -> Response } -> Enumerator[Response, void]
111
+ def self.execute: [T] (Array[T], Integer, Array[Response]) { (Array[T]) -> Response } -> Response
112
+ end
113
+
114
+ class TaskWaiter
115
+ PENDING: Array[String]
116
+ @client: Client
117
+ @clock: ^() -> Float
118
+ @sleeper: ^(duration) -> void
119
+ def initialize: (Client, clock: ^() -> Float, sleeper: ^(duration) -> void) -> void
120
+ def call: (String, duration, duration) -> Response
121
+ private
122
+ def validate!: (duration, duration) -> void
123
+ def complete?: (Response) -> bool
124
+ def wait: (duration, Float) -> void
125
+ end
126
+
127
+ module EventPayload
128
+ def self.encode: (Array[params]) -> String
129
+ def self.validate: (json) -> Hash[String, json]
130
+ def self.validate_name: (json) -> void
131
+ def self.validate_email: (json) -> void
132
+ def self.normalize: (parameter, Integer) -> json
133
+ def self.normalize_hash: (params, Integer) -> Hash[String, json]
134
+ end
135
+
136
+ class RetryPolicy
137
+ READ_ONLY: Array[String]
138
+ TRANSIENT_CODES: Array[String]
139
+ @max_retries: Integer
140
+ @max_elapsed: duration
141
+ @clock: ^() -> Float
142
+ @sleeper: ^(Float) -> void
143
+ @random: ^() -> Float
144
+ def initialize: (?max_retries: Integer, ?max_elapsed: duration, ?clock: ( ^() -> Float)?, ?sleeper: ( ^(Float) -> void)?, ?random: ( ^() -> Float)?) -> void
145
+ def call: [T] (String) { () -> T } -> T
146
+ private
147
+ def transient?: (Error) -> bool
148
+ def retry_after: (Error) -> Float
149
+ def validate!: (Integer, duration) -> void
150
+ def wait?: (Error, Integer, Float) -> bool
151
+ end
152
+
153
+ module Protocol
154
+ def self.parse: (HTTPResponse, ?method: String?) -> Response
155
+ def self.result?: (Hash[String, json], String?) -> bool
156
+ def self.decode: (HTTPResponse) -> Hash[String, json]
157
+ end
158
+
159
+ module Form
160
+ def self.encode: (params) -> String
161
+ def self.pairs: (parameter, String?, Integer) -> Array[[String, String]]
162
+ def self.hash_pairs: (params, String?, Integer) -> Array[[String, String]]
163
+ def self.scalar: (parameter, String) -> String
164
+ def self.numeric: (parameter) -> String
165
+ def self.text: (String) -> String
166
+ end
167
+
168
+ module Validation
169
+ def self.operation!: (String, params) -> void
170
+ def self.template!: (params) -> void
171
+ def self.import!: (params) -> void
172
+ def self.email_message!: (params) -> void
173
+ def self.campaign!: (params) -> void
174
+ def self.rows!: (parameter, Integer) -> void
175
+ def self.required!: (params, Array[String]) -> void
176
+ def self.missing?: (parameter) -> bool
177
+ def self.comma_list: (Array[parameter]) -> String
178
+ end
179
+
180
+ interface _Transport
181
+ def post: (URI::Generic, headers: Hash[String, String], body: String) -> HTTPResponse
182
+ end
183
+
184
+ interface _ResponseSink
185
+ def <<: (Response) -> void
186
+ end
187
+
188
+ class Transport
189
+ @open_timeout: duration
190
+ @read_timeout: duration
191
+ @write_timeout: duration
192
+ @http_factory: ^(URI::Generic) -> Net::HTTP
193
+ def initialize: (?open_timeout: duration, ?read_timeout: duration, ?write_timeout: duration, ?http_factory: (^(URI::Generic) -> Net::HTTP)?) -> void
194
+ def post: (URI::Generic, headers: Hash[String, String], body: String) -> HTTPResponse
195
+ private
196
+ def configured_http: (URI::Generic) -> Net::HTTP
197
+ def exchange: (URI::Generic, headers: Hash[String, String], body: String) -> HTTPResponse
198
+ end
199
+
200
+ class Client
201
+ RESERVED_PARAMETERS: Array[String]
202
+ @api_key: String
203
+ @language: String
204
+ @transport: _Transport
205
+ @retry_policy: RetryPolicy
206
+ def initialize: (api_key: String, ?transport: _Transport, ?language: String | Symbol, ?retry_policy: RetryPolicy) -> void
207
+ def request: (String, ?params: params) -> Response
208
+ def track_events: (events: Array[params]) -> Response[event_accepted]
209
+ def each_page: (String | Symbol, ?page_size: Integer, ?max_pages: Integer, **parameter) -> Enumerator[Response, void]
210
+ def import_contacts_in_batches: (field_names: Array[String], data: Enumerable[Array[parameter]], ?batch_size: Integer, ?overwrite_tags: flag?, ?overwrite_lists: flag?, ?telegram_bot_channel_id: String?) -> Enumerator[Response, void]
211
+ def track_events_in_batches: (events: Enumerable[params], ?batch_size: Integer) -> Enumerator[Response, void]
212
+ def wait_for_task: (task_uuid: String, ?interval: duration, ?timeout: duration, ?clock: ( ^() -> Float)?, ?sleeper: ( ^(duration) -> void)?) -> Response[task_completed]
213
+ def inspect: () -> String
214
+ private
215
+ def invoke: [Result] (String, params, ?required: Array[String], ?comma: Array[String]) -> Response[Result]
216
+ def validate_request: (String, params) -> void
217
+ end
218
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unisender-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Leonid Svyatov
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: A Ruby library for integrating with the UniSender API.
13
+ email:
14
+ - leonid@svyatov.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - CHANGELOG.md
20
+ - CONTRIBUTING.md
21
+ - LICENSE.txt
22
+ - README.md
23
+ - docs/api.md
24
+ - examples/campaign.rb
25
+ - examples/check_connection.rb
26
+ - examples/events.rb
27
+ - examples/export.rb
28
+ - examples/import.rb
29
+ - examples/offline.rb
30
+ - examples/quickstart.rb
31
+ - examples/subscribe.rb
32
+ - lib/unisender.rb
33
+ - lib/unisender/batches.rb
34
+ - lib/unisender/client.rb
35
+ - lib/unisender/errors.rb
36
+ - lib/unisender/event_payload.rb
37
+ - lib/unisender/events.rb
38
+ - lib/unisender/form.rb
39
+ - lib/unisender/helpers.rb
40
+ - lib/unisender/operations/contacts_1.rb
41
+ - lib/unisender/operations/contacts_2.rb
42
+ - lib/unisender/operations/inputs_1.rb
43
+ - lib/unisender/operations/messages_1.rb
44
+ - lib/unisender/operations/messages_2.rb
45
+ - lib/unisender/operations/messages_3.rb
46
+ - lib/unisender/operations/notes_1.rb
47
+ - lib/unisender/operations/statistics_1.rb
48
+ - lib/unisender/operations/statistics_2.rb
49
+ - lib/unisender/operations/templates_1.rb
50
+ - lib/unisender/paginated_operations.rb
51
+ - lib/unisender/pagination.rb
52
+ - lib/unisender/protocol.rb
53
+ - lib/unisender/response.rb
54
+ - lib/unisender/retry_policy.rb
55
+ - lib/unisender/task_waiter.rb
56
+ - lib/unisender/transport.rb
57
+ - lib/unisender/validation.rb
58
+ - lib/unisender/version.rb
59
+ - sig/operations.rbs
60
+ - sig/unisender.rbs
61
+ homepage: https://github.com/svyatov/unisender-ruby
62
+ licenses:
63
+ - MIT
64
+ metadata:
65
+ rubygems_mfa_required: 'true'
66
+ source_code_uri: https://github.com/svyatov/unisender-ruby
67
+ bug_tracker_uri: https://github.com/svyatov/unisender-ruby/issues
68
+ documentation_uri: https://github.com/svyatov/unisender-ruby/blob/main/README.md
69
+ changelog_uri: https://github.com/svyatov/unisender-ruby/blob/main/CHANGELOG.md
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '3.3'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubygems_version: 4.0.23
85
+ specification_version: 4
86
+ summary: Ruby client for the UniSender API.
87
+ test_files: []