unisender-ruby 0.1.1 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +89 -0
- data/CONTRIBUTING.md +10 -1
- data/README.md +55 -112
- data/docs/api.md +395 -210
- data/docs/execution.md +89 -0
- data/docs/import-recovery.md +41 -0
- data/docs/live-contracts.md +34 -0
- data/docs/migration-0.2.md +129 -0
- data/docs/rails.md +248 -0
- data/docs/releases.md +4 -4
- data/docs/testing.md +83 -0
- data/docs/usage.md +144 -0
- data/docs/webhooks.md +41 -0
- data/examples/campaign.rb +5 -6
- data/examples/campaign_lifecycle.rb +28 -0
- data/examples/check_connection.rb +1 -1
- data/examples/events.rb +1 -1
- data/examples/export.rb +20 -6
- data/examples/import.rb +3 -2
- data/examples/import_recovery.rb +70 -0
- data/examples/quickstart.rb +1 -1
- data/examples/subscribe.rb +2 -2
- data/lib/unisender/acceptance.rb +104 -0
- data/lib/unisender/attachment.rb +47 -0
- data/lib/unisender/batch_report.rb +115 -0
- data/lib/unisender/batch_summary.rb +87 -0
- data/lib/unisender/batches.rb +41 -12
- data/lib/unisender/campaign_workflow.rb +72 -0
- data/lib/unisender/client.rb +65 -33
- data/lib/unisender/client_configuration.rb +50 -0
- data/lib/unisender/client_execution.rb +43 -0
- data/lib/unisender/client_state.rb +31 -0
- data/lib/unisender/client_validation.rb +97 -0
- data/lib/unisender/configuration.rb +151 -0
- data/lib/unisender/contact_records.rb +251 -0
- data/lib/unisender/diagnostic_urls.rb +68 -0
- data/lib/unisender/diagnostics.rb +83 -0
- data/lib/unisender/download_target.rb +61 -0
- data/lib/unisender/download_transfer.rb +43 -0
- data/lib/unisender/downloader.rb +99 -0
- data/lib/unisender/error_diagnostics.rb +83 -0
- data/lib/unisender/errors.rb +125 -8
- data/lib/unisender/event_batches.rb +40 -0
- data/lib/unisender/event_payload.rb +50 -22
- data/lib/unisender/events.rb +21 -5
- data/lib/unisender/execution_context.rb +114 -0
- data/lib/unisender/form.rb +20 -12
- data/lib/unisender/helpers.rb +70 -10
- data/lib/unisender/hooks.rb +79 -0
- data/lib/unisender/import_cells.rb +62 -0
- data/lib/unisender/input_types.rb +30 -0
- data/lib/unisender/inputs.rb +262 -0
- data/lib/unisender/iteration.rb +15 -0
- data/lib/unisender/iterators.rb +60 -0
- data/lib/unisender/mail_inputs.rb +37 -0
- data/lib/unisender/named_inputs.rb +52 -0
- data/lib/unisender/observation.rb +30 -0
- data/lib/unisender/operation_contracts.rb +106 -0
- data/lib/unisender/operations/contacts_1.rb +4 -3
- data/lib/unisender/operations/contacts_2.rb +23 -4
- data/lib/unisender/operations/messages_1.rb +7 -4
- data/lib/unisender/operations/messages_2.rb +8 -3
- data/lib/unisender/operations/messages_3.rb +1 -1
- data/lib/unisender/operations/notes_1.rb +1 -1
- data/lib/unisender/operations/statistics_1.rb +3 -3
- data/lib/unisender/operations/templates_1.rb +2 -2
- data/lib/unisender/outcomes.rb +320 -0
- data/lib/unisender/page_response.rb +48 -0
- data/lib/unisender/paginated_operations.rb +43 -2
- data/lib/unisender/pagination.rb +53 -20
- data/lib/unisender/persistent_transport.rb +104 -0
- data/lib/unisender/rate_limiter.rb +72 -0
- data/lib/unisender/record.rb +210 -0
- data/lib/unisender/record_rows.rb +69 -0
- data/lib/unisender/records.rb +614 -0
- data/lib/unisender/request_execution.rb +108 -0
- data/lib/unisender/response.rb +13 -10
- data/lib/unisender/result_registry.rb +72 -0
- data/lib/unisender/results.rb +105 -0
- data/lib/unisender/retry_policy.rb +10 -16
- data/lib/unisender/serialization.rb +54 -0
- data/lib/unisender/statistic_records.rb +129 -0
- data/lib/unisender/task_response.rb +85 -0
- data/lib/unisender/task_waiter.rb +25 -11
- data/lib/unisender/testing/assertions.rb +48 -0
- data/lib/unisender/testing/fixtures.rb +24 -0
- data/lib/unisender/testing.rb +91 -0
- data/lib/unisender/transport.rb +17 -9
- data/lib/unisender/validation.rb +11 -9
- data/lib/unisender/version.rb +1 -1
- data/lib/unisender/webhooks/tokens.rb +38 -0
- data/lib/unisender/webhooks.rb +230 -0
- data/lib/unisender.rb +42 -0
- data/sig/acceptance.rbs +30 -0
- data/sig/attachment.rbs +15 -0
- data/sig/batch_summary.rbs +38 -0
- data/sig/campaign_workflow.rbs +20 -0
- data/sig/configuration.rbs +62 -0
- data/sig/connection_controls.rbs +55 -0
- data/sig/diagnostics.rbs +37 -0
- data/sig/domain_records.rbs +96 -0
- data/sig/downloads.rbs +53 -0
- data/sig/execution.rbs +52 -0
- data/sig/execution_context.rbs +46 -0
- data/sig/hooks.rbs +18 -0
- data/sig/inputs.rbs +84 -0
- data/sig/iterators.rbs +11 -0
- data/sig/mail_inputs.rbs +8 -0
- data/sig/manifest.yaml +11 -0
- data/sig/operation_contracts.rbs +28 -0
- data/sig/operations.rbs +73 -69
- data/sig/outcomes.rbs +90 -0
- data/sig/record.rbs +77 -0
- data/sig/records.rbs +182 -0
- data/sig/serialization.rbs +13 -0
- data/sig/tasks.rbs +26 -0
- data/sig/testing.rbs +54 -0
- data/sig/unisender.rbs +128 -39
- data/sig/webhooks.rbs +55 -0
- metadata +84 -3
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unisender
|
|
4
|
+
# Documented identifier record fields.
|
|
5
|
+
class IdentifierRecord < Record
|
|
6
|
+
def id
|
|
7
|
+
identifier!("id")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
protected
|
|
11
|
+
|
|
12
|
+
def normalize
|
|
13
|
+
super
|
|
14
|
+
@values["id"] = id
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Transport metadata and IdentifierRecord field access.
|
|
19
|
+
class IdentifierRecordResponse < ObjectResponse
|
|
20
|
+
def id
|
|
21
|
+
result.id
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Documented list fields.
|
|
26
|
+
class List < IdentifierRecord
|
|
27
|
+
def title
|
|
28
|
+
text!("title")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
protected
|
|
32
|
+
|
|
33
|
+
def normalize
|
|
34
|
+
super
|
|
35
|
+
@values["title"] = title
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Transport metadata and List field access.
|
|
40
|
+
class ListResponse < ObjectResponse
|
|
41
|
+
def id
|
|
42
|
+
result.id
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def title
|
|
46
|
+
result.title
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Documented message fields.
|
|
51
|
+
class Message < IdentifierRecord
|
|
52
|
+
def subject
|
|
53
|
+
text("subject")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def body
|
|
57
|
+
text("body")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def sender_name
|
|
61
|
+
text("sender_name")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def sender_email
|
|
65
|
+
text("sender_email")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
protected
|
|
69
|
+
|
|
70
|
+
def normalize
|
|
71
|
+
super
|
|
72
|
+
@values["subject"] = subject if @values.key?("subject")
|
|
73
|
+
@values["body"] = body if @values.key?("body")
|
|
74
|
+
@values["sender_name"] = sender_name if @values.key?("sender_name")
|
|
75
|
+
@values["sender_email"] = sender_email if @values.key?("sender_email")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Transport metadata and Message field access.
|
|
80
|
+
class MessageResponse < ObjectResponse
|
|
81
|
+
def id
|
|
82
|
+
result.id
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def subject
|
|
86
|
+
result.subject
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def body
|
|
90
|
+
result.body
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def sender_name
|
|
94
|
+
result.sender_name
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def sender_email
|
|
98
|
+
result.sender_email
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Documented campaign fields.
|
|
103
|
+
class Campaign < IdentifierRecord
|
|
104
|
+
def status
|
|
105
|
+
text("status")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def subject
|
|
109
|
+
text("subject")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
protected
|
|
113
|
+
|
|
114
|
+
def normalize
|
|
115
|
+
super
|
|
116
|
+
@values["status"] = status if @values.key?("status")
|
|
117
|
+
@values["subject"] = subject if @values.key?("subject")
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Transport metadata and Campaign field access.
|
|
122
|
+
class CampaignResponse < ObjectResponse
|
|
123
|
+
def id
|
|
124
|
+
result.id
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def status
|
|
128
|
+
result.status
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def subject
|
|
132
|
+
result.subject
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Documented template fields.
|
|
137
|
+
class Template < IdentifierRecord
|
|
138
|
+
def title
|
|
139
|
+
text("title")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def subject
|
|
143
|
+
text("subject")
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def body
|
|
147
|
+
text("body")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
protected
|
|
151
|
+
|
|
152
|
+
def normalize
|
|
153
|
+
super
|
|
154
|
+
@values["title"] = title if @values.key?("title")
|
|
155
|
+
@values["subject"] = subject if @values.key?("subject")
|
|
156
|
+
@values["body"] = body if @values.key?("body")
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Transport metadata and Template field access.
|
|
161
|
+
class TemplateResponse < ObjectResponse
|
|
162
|
+
def id
|
|
163
|
+
result.id
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def title
|
|
167
|
+
result.title
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def subject
|
|
171
|
+
result.subject
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def body
|
|
175
|
+
result.body
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Documented field fields.
|
|
180
|
+
class Field < IdentifierRecord
|
|
181
|
+
def name
|
|
182
|
+
text!("name")
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def type
|
|
186
|
+
text!("type")
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
protected
|
|
190
|
+
|
|
191
|
+
def normalize
|
|
192
|
+
super
|
|
193
|
+
@values["name"] = name
|
|
194
|
+
@values["type"] = type
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Transport metadata and Field field access.
|
|
199
|
+
class FieldResponse < ObjectResponse
|
|
200
|
+
def id
|
|
201
|
+
result.id
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def name
|
|
205
|
+
result.name
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def type
|
|
209
|
+
result.type
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Documented tag fields.
|
|
214
|
+
class Tag < IdentifierRecord
|
|
215
|
+
def name
|
|
216
|
+
text!("name")
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
protected
|
|
220
|
+
|
|
221
|
+
def normalize
|
|
222
|
+
super
|
|
223
|
+
@values["name"] = name
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Transport metadata and Tag field access.
|
|
228
|
+
class TagResponse < ObjectResponse
|
|
229
|
+
def id
|
|
230
|
+
result.id
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def name
|
|
234
|
+
result.name
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Documented note fields.
|
|
239
|
+
class Note < IdentifierRecord
|
|
240
|
+
def content
|
|
241
|
+
text!("content")
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def user_id
|
|
245
|
+
self["user_id"].nil? ? nil : identifier!("user_id")
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def subscriber_id
|
|
249
|
+
self["subscriber_id"].nil? ? nil : identifier!("subscriber_id")
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def origin
|
|
253
|
+
text("origin")
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# rubocop:disable-next Naming/PredicatePrefix -- Retains the documented field name.
|
|
257
|
+
def is_pinned
|
|
258
|
+
value = self["is_pinned"]
|
|
259
|
+
case value
|
|
260
|
+
when true, false, nil then value
|
|
261
|
+
else invalid!("is_pinned")
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def created_at
|
|
266
|
+
text("created_at")
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def updated_at
|
|
270
|
+
text("updated_at")
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def pinned_at
|
|
274
|
+
text("pinned_at")
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
protected
|
|
278
|
+
|
|
279
|
+
def normalize
|
|
280
|
+
super
|
|
281
|
+
@values["content"] = content
|
|
282
|
+
user_id
|
|
283
|
+
subscriber_id
|
|
284
|
+
origin
|
|
285
|
+
is_pinned
|
|
286
|
+
created_at
|
|
287
|
+
updated_at
|
|
288
|
+
pinned_at
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Transport metadata and Note field access.
|
|
293
|
+
class NoteResponse < ObjectResponse
|
|
294
|
+
def id
|
|
295
|
+
result.id
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def content
|
|
299
|
+
result.content
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def user_id
|
|
303
|
+
result.user_id
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def subscriber_id
|
|
307
|
+
result.subscriber_id
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def origin
|
|
311
|
+
result.origin
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# rubocop:disable-next Naming/PredicatePrefix -- Retains the documented field name.
|
|
315
|
+
def is_pinned
|
|
316
|
+
result.is_pinned
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def created_at
|
|
320
|
+
result.created_at
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def updated_at
|
|
324
|
+
result.updated_at
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def pinned_at
|
|
328
|
+
result.pinned_at
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Documented message created fields.
|
|
333
|
+
class MessageCreated < Record
|
|
334
|
+
def message_id
|
|
335
|
+
identifier!("message_id")
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
protected
|
|
339
|
+
|
|
340
|
+
def normalize
|
|
341
|
+
super
|
|
342
|
+
@values["message_id"] = message_id
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Transport metadata and MessageCreated field access.
|
|
347
|
+
class MessageCreatedResponse < ObjectResponse
|
|
348
|
+
def message_id
|
|
349
|
+
result.message_id
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Documented subscription fields.
|
|
354
|
+
class Subscription < Record
|
|
355
|
+
def person_id
|
|
356
|
+
identifier!("person_id")
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
protected
|
|
360
|
+
|
|
361
|
+
def normalize
|
|
362
|
+
super
|
|
363
|
+
@values["person_id"] = person_id
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# Transport metadata and Subscription field access.
|
|
368
|
+
class SubscriptionResponse < ObjectResponse
|
|
369
|
+
def person_id
|
|
370
|
+
result.person_id
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Documented campaign created fields.
|
|
375
|
+
class CampaignCreated < Record
|
|
376
|
+
def campaign_id
|
|
377
|
+
identifier!("campaign_id")
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def status
|
|
381
|
+
text!("status")
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def count
|
|
385
|
+
integer("count")
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
protected
|
|
389
|
+
|
|
390
|
+
def normalize
|
|
391
|
+
super
|
|
392
|
+
@values["campaign_id"] = campaign_id
|
|
393
|
+
@values["status"] = status
|
|
394
|
+
@values["count"] = count if @values.key?("count")
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# Transport metadata and CampaignCreated field access.
|
|
399
|
+
class CampaignCreatedResponse < ObjectResponse
|
|
400
|
+
def campaign_id
|
|
401
|
+
result.campaign_id
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def status
|
|
405
|
+
result.status
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def count
|
|
409
|
+
result.count
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Documented status fields.
|
|
414
|
+
class Status < Record
|
|
415
|
+
def status
|
|
416
|
+
text!("status")
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
protected
|
|
420
|
+
|
|
421
|
+
def normalize
|
|
422
|
+
super
|
|
423
|
+
@values["status"] = status
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# Transport metadata and Status field access.
|
|
428
|
+
class StatusResponse < ObjectResponse
|
|
429
|
+
def status
|
|
430
|
+
result.status
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Documented contact count fields.
|
|
435
|
+
class ContactCount < Record
|
|
436
|
+
def count
|
|
437
|
+
integer!("count")
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
protected
|
|
441
|
+
|
|
442
|
+
def normalize
|
|
443
|
+
super
|
|
444
|
+
@values["count"] = count
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# Transport metadata and ContactCount field access.
|
|
449
|
+
class ContactCountResponse < ObjectResponse
|
|
450
|
+
def count
|
|
451
|
+
result.count
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
# Documented total contacts fields.
|
|
456
|
+
class TotalContacts < Record
|
|
457
|
+
def total
|
|
458
|
+
integer!("total")
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
protected
|
|
462
|
+
|
|
463
|
+
def normalize
|
|
464
|
+
super
|
|
465
|
+
@values["total"] = total
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
# Transport metadata and TotalContacts field access.
|
|
470
|
+
class TotalContactsResponse < ObjectResponse
|
|
471
|
+
def total
|
|
472
|
+
result.total
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
# Documented message version fields.
|
|
477
|
+
class MessageVersion < Record
|
|
478
|
+
def message_id
|
|
479
|
+
identifier!("message_id")
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def actual_version_id
|
|
483
|
+
identifier!("actual_version_id")
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
protected
|
|
487
|
+
|
|
488
|
+
def normalize
|
|
489
|
+
super
|
|
490
|
+
@values["message_id"] = message_id
|
|
491
|
+
@values["actual_version_id"] = actual_version_id
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# Transport metadata and MessageVersion field access.
|
|
496
|
+
class MessageVersionResponse < ObjectResponse
|
|
497
|
+
def message_id
|
|
498
|
+
result.message_id
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
def actual_version_id
|
|
502
|
+
result.actual_version_id
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
# Documented web version fields.
|
|
507
|
+
class WebVersion < Record
|
|
508
|
+
def letter_id
|
|
509
|
+
identifier!("letter_id")
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
def web_letter_link
|
|
513
|
+
text!("web_letter_link")
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
protected
|
|
517
|
+
|
|
518
|
+
def normalize
|
|
519
|
+
super
|
|
520
|
+
@values["letter_id"] = letter_id
|
|
521
|
+
@values["web_letter_link"] = web_letter_link
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# Transport metadata and WebVersion field access.
|
|
526
|
+
class WebVersionResponse < ObjectResponse
|
|
527
|
+
def letter_id
|
|
528
|
+
result.letter_id
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
def web_letter_link
|
|
532
|
+
result.web_letter_link
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
# Documented task result fields.
|
|
537
|
+
class TaskResult < Record
|
|
538
|
+
def task_uuid
|
|
539
|
+
text("task_uuid")
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def status
|
|
543
|
+
text!("status")
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
def file_to_download
|
|
547
|
+
text("file_to_download")
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
protected
|
|
551
|
+
|
|
552
|
+
def normalize
|
|
553
|
+
super
|
|
554
|
+
@values["task_uuid"] = task_uuid if @values.key?("task_uuid")
|
|
555
|
+
@values["status"] = status
|
|
556
|
+
@values["file_to_download"] = file_to_download if @values.key?("file_to_download")
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
# Transport metadata and TaskResult field access.
|
|
561
|
+
class TaskResultResponse < ObjectResponse
|
|
562
|
+
def task_uuid
|
|
563
|
+
result.task_uuid
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def status
|
|
567
|
+
result.status
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
def file_to_download
|
|
571
|
+
result.file_to_download
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
# An identifier omitted by legacy empty mutation responses.
|
|
576
|
+
class OptionalIdentifier < Record
|
|
577
|
+
def id
|
|
578
|
+
self["id"].nil? ? nil : identifier!("id")
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
protected
|
|
582
|
+
|
|
583
|
+
def normalize
|
|
584
|
+
id
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
# Transport metadata and an optional mutation identifier.
|
|
589
|
+
class OptionalIdentifierResponse < ObjectResponse
|
|
590
|
+
def id
|
|
591
|
+
result.id
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
# A service notice returned after a test send.
|
|
596
|
+
class MessageNotice < Record
|
|
597
|
+
def message
|
|
598
|
+
text("message")
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
protected
|
|
602
|
+
|
|
603
|
+
def normalize
|
|
604
|
+
message
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
# Transport metadata and a service notice.
|
|
609
|
+
class MessageNoticeResponse < ObjectResponse
|
|
610
|
+
def message
|
|
611
|
+
result.message
|
|
612
|
+
end
|
|
613
|
+
end
|
|
614
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unisender
|
|
4
|
+
# Observes the complete named response decode inside each request attempt.
|
|
5
|
+
class Client
|
|
6
|
+
def request(method, params: {})
|
|
7
|
+
perform_request(method, params, named: false) # : Response[json]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# rubocop:disable-next Metrics/MethodLength -- One attempt owns request construction, decoding, and error context.
|
|
13
|
+
def perform_request(method, params, named:)
|
|
14
|
+
uri, headers, body = request_values(method, params)
|
|
15
|
+
attempt = 0
|
|
16
|
+
with_retries(method) do
|
|
17
|
+
attempt += 1
|
|
18
|
+
exchange(method, uri, headers:, body:, attempt:,
|
|
19
|
+
input_count: named ? Acceptance.input_count(method, params) : nil) do |http|
|
|
20
|
+
decode_response(http, method, params, named)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
rescue Error, ValidationError => e
|
|
24
|
+
raise e.with_operation(method), cause: nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def with_retries(method, &)
|
|
28
|
+
# rubocop:disable-next Style/ClassEqualityComparison -- Preserve custom subclass types in Steep's fallback.
|
|
29
|
+
if @retry_policy.class == RetryPolicy
|
|
30
|
+
@retry_policy.call(method, execution: @execution, &)
|
|
31
|
+
else
|
|
32
|
+
@retry_policy.call(method, &)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def request_values(method, params)
|
|
37
|
+
validate_request(method, params)
|
|
38
|
+
uri = URI("https://api.unisender.com/#{@language}/api/#{method}")
|
|
39
|
+
headers = { "Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8", "User-Agent" => USER_AGENT }
|
|
40
|
+
body = Form.encode(params.merge("api_key" => @api_key, "format" => "json"))
|
|
41
|
+
[uri, headers, body]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def decode_response(http, method, params, named)
|
|
45
|
+
response = Protocol.parse(http, method:)
|
|
46
|
+
return response unless named
|
|
47
|
+
|
|
48
|
+
result = bind_task(method, Results.build(method, response), params)
|
|
49
|
+
observe_acceptance(method, Acceptance.input_count(method, params), result, nil)
|
|
50
|
+
result
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# rubocop:disable-next Metrics/MethodLength, Metrics/ParameterLists -- Keep attempt metadata with its exchange.
|
|
54
|
+
def exchange(operation, uri, headers:, body:, attempt: 1, input_count: nil)
|
|
55
|
+
check_execution!
|
|
56
|
+
acquire_limit(operation)
|
|
57
|
+
check_execution!
|
|
58
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
59
|
+
response = nil
|
|
60
|
+
begin
|
|
61
|
+
response = post_request(uri, headers, body)
|
|
62
|
+
result = yield response
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
e.with_http(status: response&.status, request_id: response&.headers&.[]("x-request-id")) if e.is_a?(Error)
|
|
65
|
+
observe_acceptance(operation, input_count, nil, e)
|
|
66
|
+
observe(operation, attempt, started, response, e)
|
|
67
|
+
raise
|
|
68
|
+
end
|
|
69
|
+
observe(operation, attempt, started, response, nil)
|
|
70
|
+
result
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def acquire_limit(operation)
|
|
74
|
+
limiter = operation == "track_events" ? @configuration.event_rate_limiter : @configuration.rate_limiter
|
|
75
|
+
return unless limiter
|
|
76
|
+
|
|
77
|
+
# rubocop:disable-next Style/ClassEqualityComparison -- Preserve custom subclass types in Steep's fallback.
|
|
78
|
+
if limiter.class == RateLimiter
|
|
79
|
+
limiter.acquire(operation, execution: @execution)
|
|
80
|
+
else
|
|
81
|
+
limiter.acquire(operation)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def post_request(uri, headers, body)
|
|
86
|
+
transport = @transport
|
|
87
|
+
case transport
|
|
88
|
+
when Transport
|
|
89
|
+
if [Transport, PersistentTransport].include?(transport.class)
|
|
90
|
+
return transport.post(uri, headers:, body:, execution: @execution)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
transport.post(uri, headers:, body:)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def observe(operation, attempt, started, response, error)
|
|
97
|
+
event = Observation.event(operation, attempt, started, response, error)
|
|
98
|
+
Observation.emit(@configuration.instrumentation, event)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def validate_request(method, params)
|
|
102
|
+
raise ArgumentError, "Invalid API method name" unless method.match?(%r{\A(?:async/)?[A-Za-z][A-Za-z0-9]*\z})
|
|
103
|
+
return unless params.keys.any? { |key| RESERVED_PARAMETERS.include?(key.to_s) }
|
|
104
|
+
|
|
105
|
+
raise ArgumentError, "api_key and format are managed by the client"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|