opensms 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.
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "uri"
5
+
6
+ require_relative "../models"
7
+
8
+ module Opensms
9
+ # Resource groups hanging off {Opensms::Client}. Each resource is thin: build
10
+ # the path, query and body, call the transport, decode the model.
11
+ module Resources
12
+ # Shared plumbing for resources. Not part of the public API.
13
+ class Base
14
+ # @param transport [Opensms::Transport]
15
+ def initialize(transport)
16
+ @transport = transport
17
+ end
18
+
19
+ private
20
+
21
+ # Build a path from literal segments and escaped values:
22
+ # path("/v1/messages", id, "attempts").
23
+ def path(prefix, *segments)
24
+ ([prefix] + segments.map { |s| s.is_a?(Symbol) ? s.to_s : escape(s) }).join("/")
25
+ end
26
+
27
+ # Escape a path segment ("a/b" -> "a%2Fb", space -> "%20").
28
+ def escape(value)
29
+ URI.encode_www_form_component(value.to_s).gsub("+", "%20")
30
+ end
31
+
32
+ # Validate that an id argument is present.
33
+ def id!(value, name = "id")
34
+ raise ArgumentError, "Opensms: `#{name}` is required." if value.nil? || value.to_s.strip.empty?
35
+
36
+ value
37
+ end
38
+
39
+ # Idempotency-Key for methods marked req/opt: the caller's key or a new
40
+ # UUIDv4, generated once per call and reused on every retry.
41
+ def idem(key)
42
+ key.nil? ? SecureRandom.uuid : key.to_s
43
+ end
44
+
45
+ # Merge a positional Hash with keyword arguments.
46
+ def merge(params, kwargs)
47
+ Models.symbolize(params || {}).merge(kwargs)
48
+ end
49
+
50
+ def http_get(p, query = nil)
51
+ @transport.request(:get, p, query: query)
52
+ end
53
+
54
+ def http_page(p, query = nil)
55
+ Page.from(http_get(p, query))
56
+ end
57
+
58
+ def http_delete(p, idempotency_key: nil)
59
+ @transport.request(:delete, p, idempotency_key: idempotency_key)
60
+ nil
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ require_relative "base"
6
+
7
+ module Opensms
8
+ module Resources
9
+ # The +batches+ resource: bulk sends that are validated first and started
10
+ # explicitly. Accessed as +client.batches+.
11
+ class Batches < Base
12
+ ITEM_FIELDS = %i[to text sender_id traffic_type callback_url metadata].freeze
13
+ ITEMS_QUERY = %i[status limit cursor].freeze
14
+
15
+ # Create a batch from items. POST /v1/messages/batch -> 202 Batch
16
+ # (status "ready"; nothing is sent until {#start}).
17
+ #
18
+ # @param params [Hash] :items (Array of {to:, text:, sender_id:, ...}), :dedupe
19
+ # @param idempotency_key [String, nil]
20
+ # @return [Hash] Batch
21
+ def create(params = nil, idempotency_key: nil, **kwargs)
22
+ body = Models.build(merge(params, kwargs), %i[items dedupe], required: %i[items])
23
+ body[:items] = Array(body[:items]).map { |item| Models.build(item, ITEM_FIELDS) }
24
+ @transport.request(:post, "/v1/messages/batch", body: body, idempotency_key: idem(idempotency_key))
25
+ end
26
+
27
+ # Create a batch from CSV text (header row "to,text[,sender_id,...]").
28
+ # Sent as text/csv. With +dedupe: false+ the CSV is sent as a
29
+ # multipart/form-data upload instead, because that is the only form in
30
+ # which the API accepts a dedupe flag next to CSV.
31
+ #
32
+ # @param csv [String]
33
+ # @param dedupe [Boolean, nil] default true on the server
34
+ # @param idempotency_key [String, nil]
35
+ # @return [Hash] Batch
36
+ def create_from_csv(csv, dedupe: nil, idempotency_key: nil)
37
+ raise ArgumentError, "Opensms: `csv` is required." if csv.nil? || csv.to_s.empty?
38
+
39
+ key = idem(idempotency_key)
40
+ if dedupe == false
41
+ boundary = "OpensmsBoundary#{SecureRandom.hex(12)}"
42
+ return @transport.request(:post, "/v1/messages/batch",
43
+ raw_body: multipart(boundary, csv.to_s),
44
+ content_type: "multipart/form-data; boundary=#{boundary}",
45
+ idempotency_key: key)
46
+ end
47
+ @transport.request(:post, "/v1/messages/batch", raw_body: csv.to_s, content_type: "text/csv",
48
+ idempotency_key: key)
49
+ end
50
+
51
+ # GET /v1/batches/{id} -> Batch.
52
+ def get(id)
53
+ http_get(path("/v1/batches", id!(id)))
54
+ end
55
+
56
+ # Per-row validation report. GET /v1/batches/{id}/validation.
57
+ # @return [Hash] { rows:, total:, valid:, invalid:, duplicates:, suppressed: }
58
+ def validation(id)
59
+ http_get(path("/v1/batches", id!(id), :validation))
60
+ end
61
+
62
+ # Start a ready batch. POST /v1/batches/{id}/start -> Batch.
63
+ def start(id, idempotency_key: nil)
64
+ @transport.request(:post, path("/v1/batches", id!(id), :start), idempotency_key: idem(idempotency_key))
65
+ end
66
+
67
+ # Stop a batch. POST /v1/batches/{id}/stop -> { id:, status: "stopped", cancelled: }.
68
+ def stop(id, idempotency_key: nil)
69
+ @transport.request(:post, path("/v1/batches", id!(id), :stop), idempotency_key: idem(idempotency_key))
70
+ end
71
+
72
+ # Messages created by a batch. GET /v1/batches/{id}/items -> Page<BatchItem>.
73
+ #
74
+ # @param params [Hash] :status, :limit, :cursor
75
+ # @return [Opensms::Page]
76
+ def list_items(id, params = nil, **kwargs)
77
+ http_page(path("/v1/batches", id!(id), :items), Models.build(merge(params, kwargs), ITEMS_QUERY))
78
+ end
79
+
80
+ private
81
+
82
+ def multipart(boundary, csv)
83
+ [
84
+ "--#{boundary}\r\n",
85
+ %(Content-Disposition: form-data; name="dedupe"\r\n\r\n),
86
+ "false\r\n",
87
+ "--#{boundary}\r\n",
88
+ %(Content-Disposition: form-data; name="file"; filename="batch.csv"\r\n),
89
+ "Content-Type: text/csv\r\n\r\n",
90
+ csv.b,
91
+ "\r\n--#{boundary}--\r\n"
92
+ ].map(&:b).join
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Opensms
6
+ module Resources
7
+ # The +compliance+ resource: per-country rules and content rules.
8
+ # Accessed as +client.compliance+.
9
+ class Compliance < Base
10
+ # GET /v1/compliance/countries -> Array<CountryRules>.
11
+ def list_countries
12
+ http_get("/v1/compliance/countries")
13
+ end
14
+
15
+ # GET /v1/compliance/countries/{iso2} -> CountryRules.
16
+ def get_country(iso2)
17
+ http_get(path("/v1/compliance/countries", id!(iso2, "iso2")))
18
+ end
19
+
20
+ # GET /v1/content-rules -> Array<ContentRule>.
21
+ def list_content_rules
22
+ http_get("/v1/content-rules")
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Opensms
6
+ module Resources
7
+ # The +contact_groups+ resource. Accessed as +client.contact_groups+.
8
+ class ContactGroups < Base
9
+ FIELDS = %i[name contact_ids].freeze
10
+ SEND_FIELDS = %i[text template_id variables sender_id traffic_type callback_url].freeze
11
+
12
+ # GET /v1/contact-groups -> Page<Group>.
13
+ # @param params [Hash] :limit, :cursor
14
+ def list(params = nil, **kwargs)
15
+ http_page("/v1/contact-groups", Models.build(merge(params, kwargs), %i[limit cursor]))
16
+ end
17
+
18
+ # POST /v1/contact-groups -> 201 Group.
19
+ # @param params [Hash] :name (required), :contact_ids
20
+ def create(params = nil, idempotency_key: nil, **kwargs)
21
+ body = Models.build(merge(params, kwargs), FIELDS, required: %i[name])
22
+ @transport.request(:post, "/v1/contact-groups", body: body, idempotency_key: idem(idempotency_key))
23
+ end
24
+
25
+ # GET /v1/contact-groups/{id} -> Group.
26
+ def get(id)
27
+ http_get(path("/v1/contact-groups", id!(id)))
28
+ end
29
+
30
+ # PATCH /v1/contact-groups/{id} -> Group.
31
+ # @param params [Hash] :name, :contact_ids
32
+ def update(id, params = nil, **kwargs)
33
+ body = Models.build(merge(params, kwargs), FIELDS)
34
+ @transport.request(:patch, path("/v1/contact-groups", id!(id)), body: body)
35
+ end
36
+
37
+ # DELETE /v1/contact-groups/{id} -> 204.
38
+ # @return [nil]
39
+ def delete(id)
40
+ http_delete(path("/v1/contact-groups", id!(id)))
41
+ end
42
+
43
+ # Send to every contact in the group. POST /v1/contact-groups/{id}/send
44
+ # -> Batch (already "running").
45
+ #
46
+ # @param params [Hash] :text or :template_id, :variables, :sender_id,
47
+ # :traffic_type, :callback_url
48
+ def send(id, params = nil, idempotency_key: nil, **kwargs)
49
+ body = Models.build(merge(params, kwargs), SEND_FIELDS)
50
+ @transport.request(:post, path("/v1/contact-groups", id!(id), :send), body: body,
51
+ idempotency_key: idem(idempotency_key))
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Opensms
6
+ module Resources
7
+ # The +contacts+ resource. Accessed as +client.contacts+.
8
+ class Contacts < Base
9
+ FIELDS = %i[e164 name attributes].freeze
10
+
11
+ # GET /v1/contacts -> Page<Contact>.
12
+ # @param params [Hash] :limit, :cursor
13
+ # @return [Opensms::Page]
14
+ def list(params = nil, **kwargs)
15
+ http_page("/v1/contacts", Models.build(merge(params, kwargs), %i[limit cursor]))
16
+ end
17
+
18
+ # POST /v1/contacts -> 201 Contact.
19
+ # @param params [Hash] :e164 (required), :name, :attributes
20
+ def create(params = nil, idempotency_key: nil, **kwargs)
21
+ body = Models.build(merge(params, kwargs), FIELDS, required: %i[e164])
22
+ @transport.request(:post, "/v1/contacts", body: body, idempotency_key: idem(idempotency_key))
23
+ end
24
+
25
+ # GET /v1/contacts/{id} -> Contact.
26
+ def get(id)
27
+ http_get(path("/v1/contacts", id!(id)))
28
+ end
29
+
30
+ # Partial update. PATCH /v1/contacts/{id} -> Contact.
31
+ # @param params [Hash] :e164, :name, :attributes
32
+ def update(id, params = nil, **kwargs)
33
+ body = Models.build(merge(params, kwargs), FIELDS)
34
+ @transport.request(:patch, path("/v1/contacts", id!(id)), body: body)
35
+ end
36
+
37
+ # DELETE /v1/contacts/{id} -> 204.
38
+ # @return [nil]
39
+ def delete(id)
40
+ http_delete(path("/v1/contacts", id!(id)))
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Opensms
6
+ module Resources
7
+ # The +countries+ resource: the public country catalog. Accessed as
8
+ # +client.countries+.
9
+ class Countries < Base
10
+ # GET /v1/countries -> Array<Country>.
11
+ def list
12
+ http_get("/v1/countries")
13
+ end
14
+
15
+ # GET /v1/countries/{iso2}/carriers -> Array<{ id:, name:, mcc_mnc:, prefixes: }>.
16
+ def carriers(iso2)
17
+ http_get(path("/v1/countries", id!(iso2, "iso2"), :carriers))
18
+ end
19
+
20
+ # GET /v1/countries/{iso2}/routes -> Array<Route>.
21
+ def routes(iso2)
22
+ http_get(path("/v1/countries", id!(iso2, "iso2"), :routes))
23
+ end
24
+
25
+ # GET /v1/countries/{iso2}/compliance -> CountryRules.
26
+ def compliance(iso2)
27
+ http_get(path("/v1/countries", id!(iso2, "iso2"), :compliance))
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Opensms
6
+ module Resources
7
+ # The +inbound+ resource: messages received on your numbers. Accessed as
8
+ # +client.inbound+.
9
+ class Inbound < Base
10
+ # GET /v1/inbound -> Page<InboundMessage>.
11
+ def list(params = nil, **kwargs)
12
+ http_page("/v1/inbound", Models.build(merge(params, kwargs), %i[limit cursor]))
13
+ end
14
+
15
+ # Reply to an inbound message (live keys only).
16
+ # POST /v1/inbound/{id}/reply -> 201 Message.
17
+ #
18
+ # @param params [Hash] :text (required)
19
+ def reply(id, params = nil, idempotency_key: nil, **kwargs)
20
+ body = Models.build(merge(params, kwargs), %i[text], required: %i[text])
21
+ @transport.request(:post, path("/v1/inbound", id!(id), :reply), body: body,
22
+ idempotency_key: idem(idempotency_key))
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Opensms
6
+ module Resources
7
+ # The +lookups+ resource: number lookups (country, carrier, porting).
8
+ # Accessed as +client.lookups+.
9
+ class Lookups < Base
10
+ # POST /v1/lookup -> 200 (completed) or 202 (pending) Lookup.
11
+ #
12
+ # @param params [Hash] :to (E.164, required)
13
+ # @return [Hash] Lookup
14
+ def create(params = nil, idempotency_key: nil, **kwargs)
15
+ body = Models.build(merge(params, kwargs), %i[to], required: %i[to])
16
+ @transport.request(:post, "/v1/lookup", body: body, idempotency_key: idem(idempotency_key))
17
+ end
18
+
19
+ # GET /v1/lookup/{id} -> Lookup.
20
+ def get(id)
21
+ http_get(path("/v1/lookup", id!(id)))
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Opensms
6
+ module Resources
7
+ # The +messages+ resource: send, list, inspect and cancel SMS. Accessed as
8
+ # +client.messages+.
9
+ class Messages < Base
10
+ SEND_FIELDS = %i[to text sender_id traffic_type scheduled_at callback_url metadata].freeze
11
+ LIST_FIELDS = %i[limit cursor status to country date_from date_to].freeze
12
+
13
+ # Send one SMS. POST /v1/messages -> 201 Message.
14
+ #
15
+ # @param params [Hash] :to (E.164, required), :text (required), :sender_id,
16
+ # :traffic_type, :scheduled_at (Time or RFC 3339), :callback_url, :metadata
17
+ # @param idempotency_key [String, nil] generated when omitted
18
+ # @return [Hash] Message
19
+ def send(params = nil, idempotency_key: nil, **kwargs)
20
+ body = Models.build(merge(params, kwargs), SEND_FIELDS, required: %i[to text], times: %i[scheduled_at])
21
+ @transport.request(:post, "/v1/messages", body: body, idempotency_key: idem(idempotency_key))
22
+ end
23
+
24
+ # List messages, newest first. GET /v1/messages -> Page<Message>.
25
+ #
26
+ # @param params [Hash] :limit (1..100), :cursor, :status, :to, :country,
27
+ # :date_from, :date_to
28
+ # @return [Opensms::Page]
29
+ def list(params = nil, **kwargs)
30
+ http_page("/v1/messages", Models.build(merge(params, kwargs), LIST_FIELDS, times: %i[date_from date_to]))
31
+ end
32
+
33
+ # GET /v1/messages/{id} -> Message.
34
+ # @return [Hash]
35
+ def get(id)
36
+ http_get(path("/v1/messages", id!(id)))
37
+ end
38
+
39
+ # Delivery attempts for a message. GET /v1/messages/{id}/attempts.
40
+ # @return [Array<Hash>]
41
+ def attempts(id)
42
+ http_get(path("/v1/messages", id!(id), :attempts))
43
+ end
44
+
45
+ # Cancel a queued or scheduled message. Never auto-retried.
46
+ # POST /v1/messages/{id}/cancel -> Message.
47
+ # @return [Hash]
48
+ def cancel(id)
49
+ @transport.request(:post, path("/v1/messages", id!(id), :cancel))
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Opensms
6
+ module Resources
7
+ # The +numbers+ resource: virtual numbers and their inbound rules.
8
+ # Everything except +list+ and +available+ needs a live key. Accessed as
9
+ # +client.numbers+.
10
+ class Numbers < Base
11
+ RULE_FIELDS = %i[match pattern action target position].freeze
12
+
13
+ # GET /v1/numbers -> Page<Number>.
14
+ def list(params = nil, **kwargs)
15
+ http_page("/v1/numbers", Models.build(merge(params, kwargs), %i[limit cursor]))
16
+ end
17
+
18
+ # GET /v1/numbers/available -> Array<Number>.
19
+ # @param params [Hash] :country, :kind
20
+ def available(params = nil, **kwargs)
21
+ http_get("/v1/numbers/available", Models.build(merge(params, kwargs), %i[country kind]))
22
+ end
23
+
24
+ # Assign (buy) a number; charges the wallet. POST /v1/numbers -> 201 Number.
25
+ # @param params [Hash] :country (required), :kind (required)
26
+ def assign(params = nil, idempotency_key: nil, **kwargs)
27
+ body = Models.build(merge(params, kwargs), %i[country kind], required: %i[country kind])
28
+ @transport.request(:post, "/v1/numbers", body: body, idempotency_key: idem(idempotency_key))
29
+ end
30
+
31
+ # DELETE /v1/numbers/{id} -> 204.
32
+ # @return [nil]
33
+ def release(id)
34
+ http_delete(path("/v1/numbers", id!(id)))
35
+ end
36
+
37
+ # GET /v1/numbers/{id}/rules -> Page<Rule>.
38
+ def list_rules(id, params = nil, **kwargs)
39
+ http_page(path("/v1/numbers", id!(id), :rules), Models.build(merge(params, kwargs), %i[limit cursor]))
40
+ end
41
+
42
+ # POST /v1/numbers/{id}/rules -> 201 Rule.
43
+ # @param rule [Hash] :match, :pattern, :action, :target, :position
44
+ def create_rule(id, rule = nil, idempotency_key: nil, **kwargs)
45
+ body = Models.build(merge(rule, kwargs), RULE_FIELDS, required: %i[match action target])
46
+ @transport.request(:post, path("/v1/numbers", id!(id), :rules), body: body,
47
+ idempotency_key: idem(idempotency_key))
48
+ end
49
+
50
+ # PUT /v1/numbers/{id}/rules/{rule_id} -> Rule.
51
+ def update_rule(id, rule_id, rule = nil, **kwargs)
52
+ body = Models.build(merge(rule, kwargs), RULE_FIELDS, required: %i[match action target])
53
+ @transport.request(:put, path("/v1/numbers", id!(id), :rules, id!(rule_id, "rule_id")), body: body)
54
+ end
55
+
56
+ # DELETE /v1/numbers/{id}/rules/{rule_id} -> 204.
57
+ # @return [nil]
58
+ def delete_rule(id, rule_id)
59
+ http_delete(path("/v1/numbers", id!(id), :rules, id!(rule_id, "rule_id")))
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Opensms
6
+ module Resources
7
+ # The +otp+ resource: send and verify one-time passcodes. Accessed as
8
+ # +client.otp+.
9
+ class Otp < Base
10
+ # Send a code. POST /v1/otp/send -> 201 { otp_id: }.
11
+ #
12
+ # @param params [Hash] :to (required), :sender_id, :template (must contain
13
+ # "{{code}}"), :length (4..10), :ttl_seconds (30..86400)
14
+ # @return [Hash] { otp_id: }
15
+ def send(params = nil, idempotency_key: nil, **kwargs)
16
+ body = Models.build(merge(params, kwargs), %i[to sender_id template length ttl_seconds], required: %i[to])
17
+ @transport.request(:post, "/v1/otp/send", body: body, idempotency_key: idem(idempotency_key))
18
+ end
19
+
20
+ # Check a code. A wrong code returns { valid: false } and burns an
21
+ # attempt, so this call is never auto-retried.
22
+ # POST /v1/otp/verify -> { valid:, attempts_left: }.
23
+ #
24
+ # @param params [Hash] :otp_id, :code
25
+ # @return [Hash]
26
+ def verify(params = nil, **kwargs)
27
+ body = Models.build(merge(params, kwargs), %i[otp_id code], required: %i[otp_id code])
28
+ @transport.request(:post, "/v1/otp/verify", body: body)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Opensms
6
+ module Resources
7
+ # The +pricing+ resource. Accessed as +client.pricing+.
8
+ class Pricing < Base
9
+ # GET /v1/pricing -> PriceList.
10
+ # @param params [Hash] :product (sms|lookup|number_monthly), :country (ISO2)
11
+ def get(params = nil, **kwargs)
12
+ http_get("/v1/pricing", Models.build(merge(params, kwargs), %i[product country]))
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Opensms
6
+ module Resources
7
+ # The +sandbox+ resource: the rendered text of sandbox sends, including
8
+ # OTP codes. Accessed as +client.sandbox+.
9
+ class Sandbox < Base
10
+ # GET /v1/sandbox/messages -> Page<SandboxMessage>.
11
+ def list_messages(params = nil, **kwargs)
12
+ http_page("/v1/sandbox/messages", Models.build(merge(params, kwargs), %i[limit cursor]))
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Opensms
6
+ module Resources
7
+ # The +sender_ids+ resource: registered sender IDs, availability checks,
8
+ # fee quotes, documents and drafts. Accessed as +client.sender_ids+.
9
+ class SenderIds < Base
10
+ CREATE_FIELDS = %i[value kind countries use_case sample_message documents draft_id draft_version quote_id].freeze
11
+ UPDATE_FIELDS = %i[use_case countries documents sample_message].freeze
12
+ DRAFT_FIELDS = %i[source value kind countries use_case sample_message documents].freeze
13
+ DRAFT_UPDATE_FIELDS = %i[version value kind countries use_case sample_message documents].freeze
14
+
15
+ # GET /v1/sender-ids -> Page<SenderId>.
16
+ def list(params = nil, **kwargs)
17
+ http_page("/v1/sender-ids", Models.build(merge(params, kwargs), %i[limit cursor]))
18
+ end
19
+
20
+ # GET /v1/sender-ids/{id} -> SenderId.
21
+ def get(id)
22
+ http_get(path("/v1/sender-ids", id!(id)))
23
+ end
24
+
25
+ # Register a sender ID. May charge fees, so it is never auto-retried.
26
+ # POST /v1/sender-ids -> 201 SenderId.
27
+ #
28
+ # @param params [Hash] :value, :kind, :countries, :documents (required),
29
+ # :use_case, :sample_message, :draft_id, :draft_version, :quote_id
30
+ def create(params = nil, **kwargs)
31
+ body = Models.build(merge(params, kwargs), CREATE_FIELDS, required: %i[value kind countries documents])
32
+ @transport.request(:post, "/v1/sender-ids", body: body)
33
+ end
34
+
35
+ # Amend a sender ID. PATCH /v1/sender-ids/{id} -> SenderId.
36
+ # @param params [Hash] :use_case, :countries, :documents (required), :sample_message
37
+ def update(id, params = nil, **kwargs)
38
+ body = Models.build(merge(params, kwargs), UPDATE_FIELDS, required: %i[use_case countries documents])
39
+ @transport.request(:patch, path("/v1/sender-ids", id!(id)), body: body)
40
+ end
41
+
42
+ # DELETE /v1/sender-ids/{id} -> 204.
43
+ # @return [nil]
44
+ def delete(id)
45
+ http_delete(path("/v1/sender-ids", id!(id)))
46
+ end
47
+
48
+ # GET /v1/sender-ids/check -> { valid:, available:, reserved:, reason: }.
49
+ # @param params [Hash] :value (required), :country
50
+ def check(params = nil, **kwargs)
51
+ http_get("/v1/sender-ids/check", Models.build(merge(params, kwargs), %i[value country], required: %i[value]))
52
+ end
53
+
54
+ # Registration fee quote. GET /v1/sender-ids/quote?countries=KE,NG.
55
+ # @param params [Hash] :countries (Array or comma-separated String)
56
+ def quote(params = nil, **kwargs)
57
+ http_get("/v1/sender-ids/quote", Models.build(merge(params, kwargs), %i[countries], required: %i[countries]))
58
+ end
59
+
60
+ # Uploaded registration documents. GET /v1/sender-documents.
61
+ # @return [Array<Hash>] the +items+ of the response (no cursor)
62
+ def list_documents
63
+ body = http_get("/v1/sender-documents")
64
+ body.is_a?(Hash) ? (body[:items] || []) : body
65
+ end
66
+
67
+ # GET /v1/sender-id-drafts -> Page<Draft>.
68
+ def list_drafts(params = nil, **kwargs)
69
+ http_page("/v1/sender-id-drafts", Models.build(merge(params, kwargs), %i[limit cursor]))
70
+ end
71
+
72
+ # POST /v1/sender-id-drafts -> 201 Draft. Not auto-retried.
73
+ def create_draft(params = nil, **kwargs)
74
+ @transport.request(:post, "/v1/sender-id-drafts", body: Models.build(merge(params, kwargs), DRAFT_FIELDS))
75
+ end
76
+
77
+ # GET /v1/sender-id-drafts/{id} -> Draft.
78
+ def get_draft(id)
79
+ http_get(path("/v1/sender-id-drafts", id!(id)))
80
+ end
81
+
82
+ # PATCH /v1/sender-id-drafts/{id} -> Draft. Requires the current
83
+ # +version+ (optimistic lock; 409 on mismatch).
84
+ def update_draft(id, params = nil, **kwargs)
85
+ body = Models.build(merge(params, kwargs), DRAFT_UPDATE_FIELDS, required: %i[version])
86
+ @transport.request(:patch, path("/v1/sender-id-drafts", id!(id)), body: body)
87
+ end
88
+
89
+ # DELETE /v1/sender-id-drafts/{id} -> 204.
90
+ # @return [nil]
91
+ def delete_draft(id)
92
+ http_delete(path("/v1/sender-id-drafts", id!(id)))
93
+ end
94
+ end
95
+ end
96
+ end