voiceml 0.7.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7644aa34cf82b78bbc8744567b54be9a00115a4715bb490da3974c28cf40f990
4
+ data.tar.gz: 5133a70ebe35ac5ad190fcb3f2ffb61dfe7197e697ea98bbc78dd672f6c02bfe
5
+ SHA512:
6
+ metadata.gz: 3a5017e8cd3263cb6b32c8c28c8291d5e6f94b71472350fefec20362dc54dfb825195f1693344dca599bf8313cc7a1a16c7ace2d85ee9a733093ec71d1a37b33
7
+ data.tar.gz: cba24d62dbda0ff451f0cd1c23173bf45be879675f6afa86cb9dc3faa98a34583ef144955f180674f60d667a4fb67eab1ec189deee558df655e8d6a152ec12cd
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VoiceTel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,265 @@
1
+ # ๐Ÿ“ž VoiceML Ruby SDK
2
+
3
+ The official Ruby client for the [VoiceML REST API](https://voicetel.com/docs/api/v0.7/voiceml/) โ€” Twilio-compatible outbound voice and answering-machine-detection from VoiceTel, with idiomatic snake_case kwargs, structured errors, and a Twilio-compatible wire format.
4
+
5
+ ![Version](https://img.shields.io/badge/version-0.7.1.1-blue)
6
+ ![Ruby](https://img.shields.io/badge/ruby-3.0%2B-red)
7
+ ![License](https://img.shields.io/badge/license-MIT-green)
8
+ ![Tests](https://img.shields.io/badge/tests-65%20specs-brightgreen)
9
+ ![Gem](https://img.shields.io/badge/gem-voiceml-blue)
10
+
11
+ ## ๐Ÿ“š Table of Contents
12
+
13
+ - [Features](#-features)
14
+ - [Installation](#-installation)
15
+ - [Quickstart](#-quickstart)
16
+ - [Authentication](#-authentication)
17
+ - [Resource Reference](#-resource-reference)
18
+ - [Error Handling](#-error-handling)
19
+ - [Pagination](#-pagination)
20
+ - [Migration from twilio-ruby](#-migration-from-twilio-ruby)
21
+ - [Rate Limits](#-rate-limits)
22
+ - [Development](#-development)
23
+ - [API Documentation](#-api-documentation)
24
+ - [Contributors](#-contributors)
25
+ - [Sponsors](#-sponsors)
26
+ - [License](#-license)
27
+
28
+ ## โœจ Features
29
+
30
+ ### ๐Ÿงฑ Idiomatic Ruby End-to-End
31
+ - **snake_case kwargs everywhere** โ€” `to:`, `from:`, `machine_detection:`, `status_callback:` โ€” automatically translated to Twilio's PascalCase wire field names internally.
32
+ - **Resource-style API** โ€” `client.calls.create(...)`, `client.queues.list`, `client.messages.each` โ€” flat, predictable, no `account(sid).calls.create(...)` chain.
33
+ - **Twilio-compatible wire shapes** โ€” `AccountSid`, `From`, `To`, status callbacks, pagination envelopes โ€” match what Twilio's Programmable Voice API documents, so existing Twilio client patterns map directly.
34
+
35
+ ### ๐Ÿ” Production-Grade Transport
36
+ - **Automatic retry** with exponential backoff on 429 / 5xx and transport errors โ€” honors `Retry-After` headers.
37
+ - **Configurable timeouts** per client.
38
+ - **HTTP Basic auth** with `AccountSid:ApiKey` โ€” exactly what the Twilio Ruby SDK uses, so existing credentials work unchanged. Accepts the `auth_token:` keyword as an alias for migration-compatible drop-in.
39
+ - **Structured exception hierarchy** โ€” `RateLimitError`, `AuthenticationError`, `NotFoundError`, etc. all subclasses of `VoiceML::ApiError` you can rescue broadly or narrowly.
40
+
41
+ ### ๐Ÿ“ž Complete API Coverage
42
+ - **Calls** โ€” originate, fetch, terminate, update + per-call recordings, streams, siprec, transcriptions, notifications, events, and the `/Calls/{sid}/Payments` lifecycle (Pay TwiML companion).
43
+ - **Conferences** โ€” list, fetch, end conferences, plus participants (mute / hold / kick) and conference-scoped recordings.
44
+ - **Queues** โ€” create, list, update, delete, peek, dequeue (front or specific member).
45
+ - **Applications** โ€” CRUD on stored TwiML + callback bundles.
46
+ - **Recordings** โ€” account-wide list, metadata fetch, audio fetch (follows S3 redirect), delete.
47
+ - **Messages** โ€” create, fetch, list (To/From/DateSent filters + pagination), update (Body redaction; Status=canceled), delete.
48
+ - **IncomingPhoneNumbers** โ€” list, fetch, update.
49
+ - **Notifications** โ€” fetch, list.
50
+ - **Diagnostics** โ€” `/health` deep probe, OpenAPI spec.
51
+
52
+ ### ๐Ÿงช Tested
53
+ - **65 specs** with mocked HTTP layer (`webmock`) covering every resource and pagination edge cases โ€” spec drift gets caught at parse time.
54
+ - Integration smoke spec gated by env vars โ€” safe for CI.
55
+
56
+ ### ๐Ÿ“ฆ Clean Distribution
57
+ - Zero codegen footprint โ€” every byte hand-written.
58
+ - Pure-Ruby, no native extensions.
59
+ - Ships via [RubyGems](https://rubygems.org/gems/voiceml).
60
+
61
+ ## ๐Ÿš€ Installation
62
+
63
+ ```bash
64
+ gem install voiceml
65
+ ```
66
+
67
+ Or in your `Gemfile`:
68
+
69
+ ```ruby
70
+ gem 'voiceml', '~> 0.7'
71
+ ```
72
+
73
+ Requires Ruby 3.0 or later.
74
+
75
+ ## ๐Ÿ Quickstart
76
+
77
+ ```ruby
78
+ require 'voiceml'
79
+
80
+ client = VoiceML::Client.new(
81
+ account_sid: 'AC00000000000000000000000000000001',
82
+ api_key: ENV.fetch('VOICEML_API_KEY')
83
+ )
84
+
85
+ call = client.calls.create(
86
+ to: '+18005551234',
87
+ from: '+18005550000',
88
+ url: 'https://example.com/twiml',
89
+ machine_detection: 'DetectMessageEnd'
90
+ )
91
+
92
+ puts call.sid, call.status
93
+
94
+ client.queues.list.queues.each do |q|
95
+ puts "#{q.friendly_name}: #{q.current_size}"
96
+ end
97
+ ```
98
+
99
+ ## ๐Ÿ”‘ Authentication
100
+
101
+ Every endpoint uses **HTTP Basic** with your `AccountSid` as the username and your per-tenant API key as the password โ€” identical to Twilio's auth shape, so credentials issued for Twilio code work here unchanged.
102
+
103
+ - **Username** = your `AccountSid` (Twilio-format `AC` + 32 hex chars).
104
+ - **Password** = your per-tenant API key (pass as `api_key:` or, for migration-compatible drop-in, `auth_token:`).
105
+
106
+ ```ruby
107
+ client = VoiceML::Client.new(account_sid: 'AC...', api_key: '...')
108
+ client.diagnostics.health # uses your AccountSid + key on every call
109
+ ```
110
+
111
+ > Don't have credentials yet? See **[voicetel.com/docs/api/v0.7/voiceml/](https://voicetel.com/docs/api/v0.7/voiceml/)** for issuance and rotation.
112
+
113
+ ## ๐Ÿ—บ๏ธ Resource Reference
114
+
115
+ | Resource | Methods | Covers |
116
+ |---|---|---|
117
+ | `client.calls` | originate, fetch, list, terminate, update | + per-call recordings, streams, siprec, transcriptions, notifications, events, payments |
118
+ | `client.conferences` | list, fetch, end | participants (mute / hold / kick), conference-scoped recordings |
119
+ | `client.queues` | create, list, update, delete | peek, dequeue (front or specific member) |
120
+ | `client.applications` | CRUD on TwiML + callback bundles | |
121
+ | `client.recordings` | account-wide list, metadata, audio fetch, delete | follows S3 redirect for audio |
122
+ | `client.messages` | create, fetch, list, update, delete | To/From/DateSent filters; Body redaction; Status=canceled |
123
+ | `client.incoming_phone_numbers` | list, fetch, update | |
124
+ | `client.notifications` | fetch, list | |
125
+ | `client.diagnostics` | `/health`, OpenAPI spec | |
126
+
127
+ Methods accept idiomatic snake_case keyword arguments โ€” they're translated to Twilio's PascalCase wire field names internally:
128
+
129
+ ```ruby
130
+ client = VoiceML::Client.new(account_sid: 'AC...', api_key: '...')
131
+
132
+ call = client.calls.create(
133
+ to: '+18005551234',
134
+ from: '+18005550000',
135
+ url: 'https://example.com/twiml'
136
+ )
137
+
138
+ # On a live call, open a Pay session:
139
+ session = client.calls.start_payment(
140
+ call.sid,
141
+ idempotency_key: 'order-482917',
142
+ status_callback: 'https://example.com/pay-status'
143
+ )
144
+ puts session.sid, session.status
145
+ ```
146
+
147
+ ## ๐Ÿšจ Error Handling
148
+
149
+ All errors inherit from `VoiceML::Error`. The `VoiceML::ApiError` family carries the HTTP status, the Twilio-compatible error code, and the parsed response body. Rescue broadly or narrowly:
150
+
151
+ | Status | Exception |
152
+ |--------|-----------|
153
+ | 400 | `BadRequestError` |
154
+ | 401 | `AuthenticationError` |
155
+ | 403 | `PermissionDeniedError` |
156
+ | 404 | `NotFoundError` |
157
+ | 409 | `ConflictError` |
158
+ | 410 | `GoneError` |
159
+ | 429 | `RateLimitError` |
160
+ | 501 | `NotImplementedAPIError` |
161
+ | 5xx | `ServerError` |
162
+ | other | `ApiError` |
163
+
164
+ ```ruby
165
+ begin
166
+ client.calls.get('CA00000000000000000000000000000aaa')
167
+ rescue VoiceML::NotFoundError => e
168
+ warn "That call isn't on your account: #{e.message}"
169
+ rescue VoiceML::RateLimitError => e
170
+ warn "Slow down โ€” status #{e.status_code}, code #{e.code}"
171
+ rescue VoiceML::ApiError => e
172
+ warn "API error #{e.status_code}: #{e.message}"
173
+ end
174
+ ```
175
+
176
+ The Twilio-compatible error body (`code`, `message`, `more_info`, `status`) is parsed onto `error.code` / `error.message` with the raw payload on `error.body`.
177
+
178
+ ## ๐Ÿ“„ Pagination
179
+
180
+ List operations return a typed response with a Twilio-compatible pagination envelope (`page`, `page_size`, `next_page_uri`, `previous_page_uri`, โ€ฆ). For `/Calls`, `/Conferences`, `/Queues`, `/Recordings`, and `/Messages`, use the `each` helper to walk every page transparently. Block form yields each item; without a block, returns an `Enumerator`:
181
+
182
+ ```ruby
183
+ # Block form โ€” yields each Call across all pages
184
+ client.calls.each(status: 'completed', page_size: 200) do |call|
185
+ process(call)
186
+ end
187
+
188
+ # Enumerator form โ€” chain with Enumerable methods
189
+ recent = client.messages.each(from: '+18005550000', page_size: 200).first(50)
190
+
191
+ client.queues.each do |q|
192
+ archive(q) if q.current_size.zero?
193
+ end
194
+ ```
195
+
196
+ For other resources, page manually with `client.<resource>.list(page: n, page_size: 100)`.
197
+
198
+ ## ๐Ÿ” Migration from twilio-ruby
199
+
200
+ The `account_sid` + auth-token pair `Twilio::REST::Client.new` validates in its constructor works unchanged here. The SDK accepts `auth_token:` as a Twilio-compatible alias for `api_key:`:
201
+
202
+ ```ruby
203
+ # Before โ€” twilio-ruby
204
+ require 'twilio-ruby'
205
+ client = Twilio::REST::Client.new('AC...', '<token>')
206
+
207
+ # After โ€” voiceml (Twilio-compatible)
208
+ require 'voiceml'
209
+ client = VoiceML::Client.new(account_sid: 'AC...', auth_token: '<api-key>')
210
+ ```
211
+
212
+ Method names follow the resource map above (`client.calls.create(...)`, `client.queues.list`, โ€ฆ) rather than twilio-ruby's `client.api.v2010.accounts(sid).calls.create(...)` chain โ€” flatter, fewer keystrokes, same wire format on the way out.
213
+
214
+ ## โฑ๏ธ Rate Limits
215
+
216
+ VoiceML applies per-tenant rate limits at the edge. The SDK automatically retries 429 responses with `Retry-After` honored, up to `max_retries` (default `2`). To bump it:
217
+
218
+ ```ruby
219
+ client = VoiceML::Client.new(
220
+ account_sid: 'AC...',
221
+ api_key: '...',
222
+ max_retries: 4,
223
+ timeout: 60
224
+ )
225
+ ```
226
+
227
+ ## ๐Ÿ› ๏ธ Development
228
+
229
+ ```bash
230
+ git clone https://github.com/voicetel/voiceml-ruby-sdk
231
+ cd voiceml-ruby-sdk
232
+ bundle install
233
+
234
+ # Run the full spec suite (fast, no network)
235
+ bundle exec rspec
236
+
237
+ # Run a single file
238
+ bundle exec rspec spec/pagination_spec.rb
239
+
240
+ # Build the gem
241
+ gem build voiceml.gemspec
242
+ ```
243
+
244
+ ## ๐Ÿ“– API Documentation
245
+
246
+ - **Reference docs:** [voicetel.com/docs/api/v0.7/voiceml/](https://voicetel.com/docs/api/v0.7/voiceml/)
247
+ - **Validator:** [voicetel.com/voiceml/validator/](https://voicetel.com/voiceml/validator/)
248
+ - **SDK catalogue:** [voicetel.com/docs/voiceml-sdks/](https://voicetel.com/docs/voiceml-sdks/)
249
+ - **YARD comments:** every resource method carries `@param` / `@return` docs โ€” `yard doc lib` builds them locally.
250
+
251
+ ## ๐Ÿ™Œ Contributors
252
+
253
+ - [Michael Mavroudis](https://github.com/mavroudis) โ€” Lead Developer
254
+
255
+ Contributions welcome. Open an issue describing the change you want to make, or send a pull request against `main`.
256
+
257
+ ## ๐Ÿ’– Sponsors
258
+
259
+ | Sponsor | Contribution |
260
+ |---------|--------------|
261
+ | [VoiceTel Communications](https://voicetel.com) | Primary development and production hosting |
262
+
263
+ ## ๐Ÿ“„ License
264
+
265
+ MIT. See [LICENSE](LICENSE) and [voicetel.com/legal/](https://voicetel.com/legal/).
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'transport'
4
+ require_relative 'resources/calls'
5
+ require_relative 'resources/conferences'
6
+ require_relative 'resources/queues'
7
+ require_relative 'resources/applications'
8
+ require_relative 'resources/recordings'
9
+ require_relative 'resources/incoming_phone_numbers'
10
+ require_relative 'resources/notifications'
11
+ require_relative 'resources/diagnostics'
12
+ require_relative 'resources/messages'
13
+
14
+ module VoiceML
15
+ # Synchronous client for the VoiceML REST API.
16
+ #
17
+ # VoiceML uses HTTP Basic auth: the `account_sid` (Twilio-format `AC` + 32 hex) is the
18
+ # username and `api_key` is the password. Drop-in compatible with the Twilio Ruby SDK
19
+ # constructor signature.
20
+ #
21
+ # @example
22
+ # client = VoiceML::Client.new(account_sid: 'AC...', api_key: '...')
23
+ # call = client.calls.create(
24
+ # to: '+18005551234',
25
+ # from: '+18005550000',
26
+ # url: 'https://example.com/twiml'
27
+ # )
28
+ # puts call.sid, call.status
29
+ class Client
30
+ attr_reader :calls, :conferences, :queues, :applications, :recordings,
31
+ :incoming_phone_numbers, :notifications, :diagnostics, :messages
32
+
33
+ # @param account_sid [String] Twilio-format AccountSid (`AC` + 32 hex).
34
+ # @param api_key [String, nil] per-tenant API key. Pass either `api_key:` or the
35
+ # Twilio-compatible alias `auth_token:` (not both โ€” `ArgumentError` if you do).
36
+ # @param auth_token [String, nil] Twilio-compatible alias for `api_key`. Lets twilio-ruby
37
+ # code (`VoiceML::Client.new(account_sid: sid, auth_token: token)`) work unchanged.
38
+ # @param base_url [String] server base URL. Defaults to `https://voiceml.voicetel.com`.
39
+ # @param timeout [Numeric] per-request timeout in seconds. Defaults to 30.
40
+ # @param max_retries [Integer] retry attempts for 429/5xx and transport errors. Defaults to 2.
41
+ # @param user_agent [String, nil] override the `User-Agent` header. Defaults to
42
+ # `"voiceml-ruby/#{VERSION}"`.
43
+ def initialize(account_sid:, api_key: nil, auth_token: nil,
44
+ base_url: Transport::DEFAULT_BASE_URL,
45
+ timeout: Transport::DEFAULT_TIMEOUT,
46
+ max_retries: Transport::DEFAULT_MAX_RETRIES,
47
+ user_agent: nil, http_client: nil)
48
+ if !api_key.nil? && !auth_token.nil?
49
+ raise ArgumentError, 'pass either api_key: or auth_token:, not both'
50
+ end
51
+
52
+ resolved_key = api_key || auth_token
53
+
54
+ @transport = Transport.new(
55
+ account_sid: account_sid,
56
+ api_key: resolved_key,
57
+ base_url: base_url,
58
+ timeout: timeout,
59
+ max_retries: max_retries,
60
+ user_agent: user_agent,
61
+ http_client: http_client
62
+ )
63
+
64
+ @calls = CallsResource.new(@transport)
65
+ @conferences = ConferencesResource.new(@transport)
66
+ @queues = QueuesResource.new(@transport)
67
+ @applications = ApplicationsResource.new(@transport)
68
+ @recordings = RecordingsResource.new(@transport)
69
+ @incoming_phone_numbers = IncomingPhoneNumbersResource.new(@transport)
70
+ @notifications = NotificationsResource.new(@transport)
71
+ @diagnostics = DiagnosticsResource.new(@transport)
72
+ @messages = MessagesResource.new(@transport)
73
+ end
74
+
75
+ def account_sid
76
+ @transport.account_sid
77
+ end
78
+
79
+ def base_url
80
+ @transport.base_url
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VoiceML
4
+ # Base class for every error raised by this SDK. Catch `VoiceML::Error` to handle them all.
5
+ class Error < StandardError; end
6
+
7
+ # Raised when the client is constructed with conflicting or missing config.
8
+ class ConfigurationError < Error; end
9
+
10
+ # Raised when the API returns a non-2xx response.
11
+ #
12
+ # The Twilio-compatible error body (`{ code, message, more_info, status }`) is parsed into
13
+ # `#code`, `#message`, and `#more_info` when present, with the raw payload exposed on
14
+ # `#body`. `#more_info` is the Twilio documentation URL for the error code (e.g.
15
+ # `https://www.twilio.com/docs/errors/20404`).
16
+ class ApiError < Error
17
+ attr_reader :status_code, :code, :body, :more_info
18
+
19
+ def initialize(message, status_code:, code: nil, body: nil, more_info: nil)
20
+ super(message)
21
+ @status_code = status_code
22
+ @code = code
23
+ @body = body
24
+ @more_info = more_info
25
+ end
26
+ end
27
+
28
+ # HTTP 400 โ€” the request was malformed or failed server-side validation.
29
+ class BadRequestError < ApiError; end
30
+
31
+ # HTTP 401 โ€” Basic auth missing, account unknown, key wrong, or source IP not allowed.
32
+ class AuthenticationError < ApiError; end
33
+
34
+ # HTTP 403 โ€” authenticated, but not allowed to perform this action.
35
+ class PermissionDeniedError < ApiError; end
36
+
37
+ # HTTP 404 โ€” the resource does not exist (or belongs to a different tenant).
38
+ class NotFoundError < ApiError; end
39
+
40
+ # HTTP 409 โ€” request conflicts with current resource state.
41
+ class ConflictError < ApiError; end
42
+
43
+ # HTTP 410 โ€” recording audio is no longer available (no local file, no S3 key).
44
+ class GoneError < ApiError; end
45
+
46
+ # HTTP 429 โ€” per-account rate limit exceeded.
47
+ class RateLimitError < ApiError; end
48
+
49
+ # HTTP 501 โ€” endpoint is mounted as a stub (e.g. UserDefinedMessages).
50
+ class NotImplementedAPIError < ApiError; end
51
+
52
+ # HTTP 5xx โ€” the server hit an error processing the request.
53
+ class ServerError < ApiError; end
54
+
55
+ # @api private
56
+ ERROR_CLASSES = {
57
+ 400 => BadRequestError,
58
+ 401 => AuthenticationError,
59
+ 403 => PermissionDeniedError,
60
+ 404 => NotFoundError,
61
+ 409 => ConflictError,
62
+ 410 => GoneError,
63
+ 429 => RateLimitError,
64
+ 501 => NotImplementedAPIError
65
+ }.freeze
66
+
67
+ # Map an HTTP status to the most specific `ApiError` subclass.
68
+ # @api private
69
+ def self.error_from_response(status_code, message, code: nil, body: nil, more_info: nil)
70
+ klass = ERROR_CLASSES[status_code]
71
+ klass ||= ServerError if status_code >= 500 && status_code < 600
72
+ klass ||= ApiError
73
+ klass.new(message, status_code: status_code, code: code, body: body, more_info: more_info)
74
+ end
75
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'common'
4
+
5
+ module VoiceML
6
+ # Persistent TwiML+callback bundle dispatched by `<Dial><Application>`.
7
+ class Application
8
+ ATTRIBUTES = %w[
9
+ sid account_sid friendly_name api_version voice_url voice_method
10
+ voice_fallback_url voice_fallback_method voice_caller_id_lookup
11
+ status_callback status_callback_method status_callback_event
12
+ date_created date_updated uri
13
+ ].freeze
14
+
15
+ attr_reader(*ATTRIBUTES.map(&:to_sym))
16
+
17
+ def initialize(attrs = {})
18
+ ATTRIBUTES.each do |field|
19
+ value = attrs.key?(field) ? attrs[field] : attrs[field.to_sym]
20
+ instance_variable_set("@#{field}", value)
21
+ end
22
+ end
23
+
24
+ def self.from_hash(hash)
25
+ return nil if hash.nil?
26
+
27
+ new(hash)
28
+ end
29
+ end
30
+
31
+ # Paginated `GET /Applications` response.
32
+ class ApplicationList
33
+ include Pageable
34
+
35
+ attr_reader :applications
36
+
37
+ def initialize(hash = {})
38
+ assign_page_fields(hash)
39
+ @applications = (hash['applications'] || []).map { |a| Application.from_hash(a) }
40
+ end
41
+
42
+ def self.from_hash(hash)
43
+ new(hash || {})
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'common'
4
+
5
+ module VoiceML
6
+ # A Twilio-compatible Call resource. Returned by `client.calls.create`, `client.calls.get`,
7
+ # `client.calls.update`, and listed in `CallList#calls`.
8
+ class Call
9
+ ATTRIBUTES = %w[
10
+ sid account_sid api_version to to_formatted from from_formatted
11
+ parent_call_sid caller_name forwarded_from status direction
12
+ answered_by start_time end_time duration price price_unit
13
+ phone_number_sid annotation group_sid queue_time trunk_sid
14
+ date_created date_updated uri subresource_uris
15
+ ].freeze
16
+
17
+ attr_reader(*ATTRIBUTES.map(&:to_sym))
18
+
19
+ def initialize(attrs = {})
20
+ ATTRIBUTES.each do |field|
21
+ value = attrs.key?(field) ? attrs[field] : attrs[field.to_sym]
22
+ instance_variable_set("@#{field}", value)
23
+ end
24
+ end
25
+
26
+ def self.from_hash(hash)
27
+ return nil if hash.nil?
28
+
29
+ new(hash)
30
+ end
31
+ end
32
+
33
+ # Paginated `GET /Calls` response.
34
+ class CallList
35
+ include Pageable
36
+
37
+ attr_reader :calls
38
+
39
+ def initialize(hash = {})
40
+ assign_page_fields(hash)
41
+ @calls = (hash['calls'] || []).map { |c| Call.from_hash(c) }
42
+ end
43
+
44
+ def self.from_hash(hash)
45
+ new(hash || {})
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VoiceML
4
+ # Twilio-compatible pagination envelope fields. Mix into list-response classes to expose
5
+ # `page`, `page_size`, `total`, `next_page_uri`, `previous_page_uri`, `first_page_uri`,
6
+ # `uri`, `num_pages`, `start`, `end`. The list items themselves are declared on each
7
+ # concrete subclass (`calls`, `conferences`, etc).
8
+ module Pageable
9
+ PAGE_FIELDS = %w[
10
+ page page_size num_pages total start end
11
+ first_page_uri next_page_uri previous_page_uri uri
12
+ ].freeze
13
+
14
+ PAGE_FIELDS.each do |field|
15
+ attr_reader field.to_sym
16
+ end
17
+
18
+ def assign_page_fields(hash)
19
+ PAGE_FIELDS.each do |field|
20
+ instance_variable_set("@#{field}", hash[field])
21
+ end
22
+ end
23
+ end
24
+
25
+ # Twilio-compatible error body. Surface only โ€” the transport raises a VoiceML::ApiError
26
+ # subclass with this payload attached as `error.body`.
27
+ class ErrorBody
28
+ attr_reader :code, :message, :more_info, :status
29
+
30
+ def initialize(code: nil, message: nil, more_info: nil, status: nil)
31
+ @code = code
32
+ @message = message
33
+ @more_info = more_info
34
+ @status = status
35
+ end
36
+
37
+ def self.from_hash(hash)
38
+ return nil if hash.nil?
39
+
40
+ new(
41
+ code: hash['code'],
42
+ message: hash['message'],
43
+ more_info: hash['more_info'],
44
+ status: hash['status']
45
+ )
46
+ end
47
+ end
48
+
49
+ # One tripped check from the `/health` deep probe.
50
+ class HealthFailure
51
+ attr_reader :check, :detail
52
+
53
+ def initialize(check:, detail:)
54
+ @check = check
55
+ @detail = detail
56
+ end
57
+
58
+ def self.from_hash(hash)
59
+ return nil if hash.nil?
60
+
61
+ new(check: hash['check'], detail: hash['detail'])
62
+ end
63
+ end
64
+ end