telnyx 0.0.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 +7 -0
- data/.gitattributes +4 -0
- data/.github/ISSUE_TEMPLATE.md +5 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +32 -0
- data/.rubocop_todo.yml +50 -0
- data/.travis.yml +42 -0
- data/CHANGELOG.md +2 -0
- data/CONTRIBUTORS +0 -0
- data/Gemfile +40 -0
- data/Guardfile +8 -0
- data/LICENSE +22 -0
- data/README.md +173 -0
- data/Rakefile +28 -0
- data/VERSION +1 -0
- data/bin/telnyx-console +16 -0
- data/lib/telnyx.rb +151 -0
- data/lib/telnyx/api_operations/create.rb +12 -0
- data/lib/telnyx/api_operations/delete.rb +13 -0
- data/lib/telnyx/api_operations/list.rb +29 -0
- data/lib/telnyx/api_operations/nested_resource.rb +63 -0
- data/lib/telnyx/api_operations/request.rb +57 -0
- data/lib/telnyx/api_operations/save.rb +103 -0
- data/lib/telnyx/api_resource.rb +69 -0
- data/lib/telnyx/available_phone_number.rb +9 -0
- data/lib/telnyx/errors.rb +166 -0
- data/lib/telnyx/event.rb +9 -0
- data/lib/telnyx/list_object.rb +155 -0
- data/lib/telnyx/message.rb +9 -0
- data/lib/telnyx/messaging_phone_number.rb +10 -0
- data/lib/telnyx/messaging_profile.rb +32 -0
- data/lib/telnyx/messaging_sender_id.rb +12 -0
- data/lib/telnyx/messaging_short_code.rb +10 -0
- data/lib/telnyx/number_order.rb +11 -0
- data/lib/telnyx/number_reservation.rb +11 -0
- data/lib/telnyx/public_key.rb +7 -0
- data/lib/telnyx/singleton_api_resource.rb +24 -0
- data/lib/telnyx/telnyx_client.rb +545 -0
- data/lib/telnyx/telnyx_object.rb +521 -0
- data/lib/telnyx/telnyx_response.rb +50 -0
- data/lib/telnyx/util.rb +328 -0
- data/lib/telnyx/version.rb +5 -0
- data/lib/telnyx/webhook.rb +66 -0
- data/telnyx.gemspec +25 -0
- data/test/api_stub_helpers.rb +1 -0
- data/test/openapi/README.md +9 -0
- data/test/telnyx/api_operations_test.rb +85 -0
- data/test/telnyx/api_resource_test.rb +293 -0
- data/test/telnyx/available_phone_number_test.rb +14 -0
- data/test/telnyx/errors_test.rb +23 -0
- data/test/telnyx/list_object_test.rb +244 -0
- data/test/telnyx/message_test.rb +19 -0
- data/test/telnyx/messaging_phone_number_test.rb +33 -0
- data/test/telnyx/messaging_profile_test.rb +70 -0
- data/test/telnyx/messaging_sender_id_test.rb +46 -0
- data/test/telnyx/messaging_short_code_test.rb +33 -0
- data/test/telnyx/number_order_test.rb +39 -0
- data/test/telnyx/number_reservation_test.rb +12 -0
- data/test/telnyx/public_key_test.rb +13 -0
- data/test/telnyx/telnyx_client_test.rb +631 -0
- data/test/telnyx/telnyx_object_test.rb +497 -0
- data/test/telnyx/telnyx_response_test.rb +49 -0
- data/test/telnyx/util_test.rb +380 -0
- data/test/telnyx/webhook_test.rb +108 -0
- data/test/telnyx_mock.rb +78 -0
- data/test/telnyx_test.rb +40 -0
- data/test/test_data.rb +149 -0
- data/test/test_helper.rb +73 -0
- metadata +162 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Telnyx
|
4
|
+
class APIResource < TelnyxObject
|
5
|
+
include Telnyx::APIOperations::Request
|
6
|
+
|
7
|
+
# A flag that can be set a behavior that will cause this resource to be
|
8
|
+
# encoded and sent up along with an update of its parent resource. This is
|
9
|
+
# usually not desirable because resources are updated individually on their
|
10
|
+
# own endpoints, but there are certain cases where this is allowed.
|
11
|
+
attr_accessor :save_with_parent
|
12
|
+
|
13
|
+
def self.class_name
|
14
|
+
name.split("::")[-1]
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.resource_url
|
18
|
+
if self == APIResource
|
19
|
+
raise NotImplementedError, "APIResource is an abstract class. You should perform actions on its subclasses"
|
20
|
+
end
|
21
|
+
# Namespaces are separated in object names with periods (.) and in URLs
|
22
|
+
# with forward slashes (/), so replace the former with the latter.
|
23
|
+
"/v2/#{self::OBJECT_NAME.downcase.tr('.', '/')}s"
|
24
|
+
end
|
25
|
+
|
26
|
+
# A metaprogramming call that specifies that a field of a resource can be
|
27
|
+
# its own type of API resource (say a nested card under an account for
|
28
|
+
# example), and if that resource is set, it should be transmitted to the
|
29
|
+
# API on a create or update. Doing so is not the default behavior because
|
30
|
+
# API resources should normally be persisted on their own RESTful
|
31
|
+
# endpoints.
|
32
|
+
def self.save_nested_resource(name)
|
33
|
+
define_method(:"#{name}=") do |value|
|
34
|
+
super(value)
|
35
|
+
|
36
|
+
# The parent setter will perform certain useful operations like
|
37
|
+
# converting to an APIResource if appropriate. Refresh our argument
|
38
|
+
# value to whatever it mutated to.
|
39
|
+
value = send(name)
|
40
|
+
|
41
|
+
# Note that the value may be subresource, but could also be a scalar
|
42
|
+
# (like a tokenized card's ID for example), so we check the type before
|
43
|
+
# setting #save_with_parent here.
|
44
|
+
value.save_with_parent = true if value.is_a?(APIResource)
|
45
|
+
|
46
|
+
value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def resource_url
|
51
|
+
unless (id = self["id"])
|
52
|
+
raise InvalidRequestError, "Could not determine which URL to request: #{self.class} instance has invalid ID: #{id.inspect}"
|
53
|
+
end
|
54
|
+
"#{self.class.resource_url}/#{CGI.escape(id)}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def refresh
|
58
|
+
resp, opts = request(:get, resource_url, @retrieve_params, @opts)
|
59
|
+
initialize_from(resp.data[:data], opts)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.retrieve(id, opts = {})
|
63
|
+
opts = Util.normalize_opts(opts)
|
64
|
+
instance = new(id, opts)
|
65
|
+
instance.refresh
|
66
|
+
instance
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Telnyx
|
4
|
+
# TelnyxError is the base error from which all other more specific Telnyx
|
5
|
+
# errors derive.
|
6
|
+
class TelnyxError < StandardError
|
7
|
+
# Full details for all errors returned in response
|
8
|
+
attr_reader :errors
|
9
|
+
|
10
|
+
# Response contains a TelnyxResponse object that has some basic information
|
11
|
+
# about the response that conveyed the error.
|
12
|
+
attr_accessor :response
|
13
|
+
|
14
|
+
attr_reader :http_body
|
15
|
+
attr_reader :http_headers
|
16
|
+
attr_reader :http_status
|
17
|
+
attr_reader :json_body # equivalent to #data
|
18
|
+
attr_reader :request_id
|
19
|
+
|
20
|
+
# Initializes a TelnyxError.
|
21
|
+
def initialize(errors = nil, http_status: nil, http_body: nil, json_body: nil, http_headers: nil)
|
22
|
+
@http_status = http_status
|
23
|
+
@http_body = http_body
|
24
|
+
@http_headers = http_headers || {}
|
25
|
+
@json_body = json_body
|
26
|
+
@request_id = @http_headers[:request_id]
|
27
|
+
@errors = stringify_errors(errors)
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
status_string = @http_status.nil? ? "" : "(Status #{@http_status}) "
|
32
|
+
id_string = @request_id.nil? ? "" : "(Request #{@request_id}) "
|
33
|
+
instruction = "Full details: #{@errors}"
|
34
|
+
"#{status_string}#{id_string}#{message}#{other_errors_message}#{instruction}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def other_errors_message
|
38
|
+
count = error_count
|
39
|
+
if count > 2
|
40
|
+
"plus #{count} other errors. "
|
41
|
+
elsif count == 2
|
42
|
+
"plus 1 other error. "
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def message
|
47
|
+
case @errors
|
48
|
+
when Array
|
49
|
+
@errors[0]["title"] + " "
|
50
|
+
else
|
51
|
+
@errors
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def error_count
|
56
|
+
case @errors
|
57
|
+
when Array
|
58
|
+
@errors.count
|
59
|
+
else
|
60
|
+
1
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def stringify_hash(h)
|
65
|
+
str_hash = {}
|
66
|
+
h.each_key do |k|
|
67
|
+
str_hash[k.to_s] = h[k]
|
68
|
+
end
|
69
|
+
str_hash
|
70
|
+
end
|
71
|
+
|
72
|
+
def stringify_errors(errors)
|
73
|
+
if errors.is_a? Array
|
74
|
+
errors.map { |h| stringify_hash(h) }
|
75
|
+
elsif errors.is_a? Hash
|
76
|
+
stringify_hash errors
|
77
|
+
else
|
78
|
+
errors
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# InvalidRequestError is raised when a request cannot be parsed by Telnyx
|
84
|
+
class InvalidRequestError < TelnyxError
|
85
|
+
STATUS_CODE = 400
|
86
|
+
end
|
87
|
+
|
88
|
+
# AuthenticationError is raised when invalid credentials are used to connect
|
89
|
+
# to Telnyx's servers.
|
90
|
+
class AuthenticationError < TelnyxError
|
91
|
+
STATUS_CODE = 401
|
92
|
+
end
|
93
|
+
|
94
|
+
# PermissionError is raised in cases where access was attempted on a resource
|
95
|
+
# that wasn't allowed.
|
96
|
+
class PermissionError < TelnyxError
|
97
|
+
STATUS_CODE = 403
|
98
|
+
end
|
99
|
+
|
100
|
+
# ResourceNotFoundError is raised when a resource or path does not exist.
|
101
|
+
# that wasn't allowed.
|
102
|
+
class ResourceNotFoundError < TelnyxError
|
103
|
+
STATUS_CODE = 404
|
104
|
+
end
|
105
|
+
|
106
|
+
# MethodNotSupportedError is raised a request is made using a method that
|
107
|
+
# is not supported by the endpoint.
|
108
|
+
class MethodNotSupportedError < TelnyxError
|
109
|
+
STATUS_CODE = 405
|
110
|
+
end
|
111
|
+
|
112
|
+
# TimeoutError is raised when the request times out while being processed by
|
113
|
+
# Telnyx's servers.
|
114
|
+
class TimeoutError < TelnyxError
|
115
|
+
STATUS_CODE = 408
|
116
|
+
end
|
117
|
+
|
118
|
+
# UnsupportedMediaTypeError is raised when the media type of the request is
|
119
|
+
# not supported.
|
120
|
+
class UnsupportedMediaTypeError < TelnyxError
|
121
|
+
STATUS_CODE = 415
|
122
|
+
end
|
123
|
+
|
124
|
+
# InvalidParametersError is raised when a request is made with invaid parameters
|
125
|
+
class InvalidParametersError < TelnyxError
|
126
|
+
STATUS_CODE = 422
|
127
|
+
end
|
128
|
+
|
129
|
+
# RateLimitError is raised in cases where an account is putting too much load
|
130
|
+
# on Telnyx's API servers (usually by performing too many requests). Please
|
131
|
+
# back off on request rate.
|
132
|
+
class RateLimitError < TelnyxError
|
133
|
+
STATUS_CODE = 429
|
134
|
+
end
|
135
|
+
|
136
|
+
# APIError is a generic error that may be raised in cases where none of the
|
137
|
+
# other named errors cover the problem. It could also be raised in the case
|
138
|
+
# that a new error has been introduced in the API, but this version of the
|
139
|
+
# Ruby SDK doesn't know how to handle it.
|
140
|
+
class APIError < TelnyxError
|
141
|
+
STATUS_CODE = 500
|
142
|
+
end
|
143
|
+
|
144
|
+
# Service unavailable error is raise when a request receives a response status
|
145
|
+
# code of 503 Service Unavailable.
|
146
|
+
class ServiceUnavailableError < TelnyxError
|
147
|
+
STATUS_CODE = 503
|
148
|
+
end
|
149
|
+
|
150
|
+
# APIConnectionError is raised in the event that the SDK can't connect to
|
151
|
+
# Telnyx's servers. That can be for a variety of different reasons from a
|
152
|
+
# downed network to a bad TLS certificate.
|
153
|
+
class APIConnectionError < TelnyxError
|
154
|
+
end
|
155
|
+
|
156
|
+
# SignatureVerificationError is raised when the signature verification for a
|
157
|
+
# webhook fails
|
158
|
+
class SignatureVerificationError < TelnyxError
|
159
|
+
attr_accessor :sig_header
|
160
|
+
|
161
|
+
def initialize(message, sig_header, http_body: nil)
|
162
|
+
super(message, http_body: http_body)
|
163
|
+
@sig_header = sig_header
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
data/lib/telnyx/event.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Telnyx
|
4
|
+
class ListObject < TelnyxObject
|
5
|
+
include Enumerable
|
6
|
+
include Telnyx::APIOperations::List
|
7
|
+
include Telnyx::APIOperations::Request
|
8
|
+
include Telnyx::APIOperations::Create
|
9
|
+
|
10
|
+
# This accessor allows a `ListObject` to inherit various filters that were
|
11
|
+
# given to a predecessor. This allows for things like consistent limits,
|
12
|
+
# expansions, and predicates as a user pages through resources.
|
13
|
+
attr_accessor :filters
|
14
|
+
|
15
|
+
# An empty list object. This is returned from +next+ when we know that
|
16
|
+
# there isn't a next page in order to replicate the behavior of the API
|
17
|
+
# when it attempts to return a page beyond the last.
|
18
|
+
def self.empty_list(opts = {})
|
19
|
+
ListObject.construct_from({ data: [] }, opts)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(*args)
|
23
|
+
super
|
24
|
+
self.filters = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def [](k)
|
28
|
+
case k
|
29
|
+
when String, Symbol
|
30
|
+
super
|
31
|
+
else
|
32
|
+
raise ArgumentError, "You tried to access the #{k.inspect} index, but ListObject types only support String keys. (HINT: List calls return an object with a 'data' (which is the data array). You likely want to call #data[#{k.inspect}])"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Iterates through each resource in the page represented by the current
|
37
|
+
# `ListObject`.
|
38
|
+
#
|
39
|
+
# Note that this method makes no effort to fetch a new page when it gets to
|
40
|
+
# the end of the current page's resources. See also +auto_paging_each+.
|
41
|
+
def each(&blk)
|
42
|
+
data.each(&blk)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Iterates through each resource in all pages, making additional fetches to
|
46
|
+
# the API as necessary.
|
47
|
+
#
|
48
|
+
# Note that this method will make as many API calls as necessary to fetch
|
49
|
+
# all resources. For more granular control, please see +each+ and
|
50
|
+
# +next_page+.
|
51
|
+
def auto_paging_each(&blk)
|
52
|
+
return enum_for(:auto_paging_each) unless block_given?
|
53
|
+
|
54
|
+
page = self
|
55
|
+
loop do
|
56
|
+
page.each(&blk)
|
57
|
+
page = page.next_page
|
58
|
+
break if page.empty?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Iterates through each resource in all pages, making additional fetches to
|
63
|
+
# the API as necessary.
|
64
|
+
#
|
65
|
+
# Note that this method will make as many API calls as necessary to fetch
|
66
|
+
# all resources. For more granular control, please see +each+ and
|
67
|
+
# +next_page_by_token+.
|
68
|
+
def auto_paging_each_by_token(&blk)
|
69
|
+
return enum_for(:auto_paging_each_by_token) unless block_given?
|
70
|
+
|
71
|
+
page = self
|
72
|
+
loop do
|
73
|
+
page.each(&blk)
|
74
|
+
page = page.next_page_by_token
|
75
|
+
break if page.empty?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns true if the page object contains no elements.
|
80
|
+
def empty?
|
81
|
+
data.empty?
|
82
|
+
end
|
83
|
+
|
84
|
+
def retrieve(id, opts = {})
|
85
|
+
id, retrieve_params = Util.normalize_id(id)
|
86
|
+
resp, opts = request(:get, "#{resource_url}/#{CGI.escape(id)}", retrieve_params, opts)
|
87
|
+
Util.convert_to_telnyx_object(resp.data, opts)
|
88
|
+
end
|
89
|
+
|
90
|
+
def more?
|
91
|
+
!data.empty? && meta[:page_number] && meta[:total_pages] && meta[:total_pages] > meta[:page_number]
|
92
|
+
end
|
93
|
+
|
94
|
+
# Fetches the next page in the resource list (if there is one).
|
95
|
+
#
|
96
|
+
# This method will try to respect the limit of the current page. If none
|
97
|
+
# was given, the default limit will be fetched again.
|
98
|
+
def next_page(params = {}, opts = {})
|
99
|
+
return self.class.empty_list(opts) unless more?
|
100
|
+
next_page_number = page_number.to_i + 1
|
101
|
+
pagination = { number: next_page_number, size: page_size(filters) }
|
102
|
+
params = filters.merge(params).merge(page: pagination)
|
103
|
+
|
104
|
+
list(params, opts)
|
105
|
+
end
|
106
|
+
|
107
|
+
def next_page_by_token(params = {}, opts = {})
|
108
|
+
return self.class.empty_list(opts) unless token
|
109
|
+
pagination = { token: token }
|
110
|
+
params = filters.merge(params).merge(page: pagination)
|
111
|
+
|
112
|
+
list(params, opts)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Fetches the previous page in the resource list (if there is one).
|
116
|
+
#
|
117
|
+
# This method will try to respect the limit of the current page. If none
|
118
|
+
# was given, the default limit will be fetched again.
|
119
|
+
def previous_page(params = {}, opts = {})
|
120
|
+
prev_page_number = page_number.to_i - 1
|
121
|
+
prev_page_number = [prev_page_number, 1].max
|
122
|
+
pagination = { number: prev_page_number, size: page_size(filters) }
|
123
|
+
params = filters.merge(params).merge(page: pagination)
|
124
|
+
|
125
|
+
list(params, opts)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Fetch the current page size
|
129
|
+
def page_size(params)
|
130
|
+
if params && params[:page] && params[:page][:size]
|
131
|
+
params[:page][:size]
|
132
|
+
else
|
133
|
+
20
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Fetch the current page number
|
138
|
+
def page_number
|
139
|
+
if meta && meta[:page_number]
|
140
|
+
meta.page_number
|
141
|
+
else
|
142
|
+
1
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def token
|
147
|
+
return meta.next_page_token if meta && meta[:next_page_token]
|
148
|
+
end
|
149
|
+
|
150
|
+
def resource_url
|
151
|
+
url ||
|
152
|
+
raise(ArgumentError, "List object does not contain a 'url' field.")
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Telnyx
|
4
|
+
class MessagingProfile < APIResource
|
5
|
+
include Telnyx::APIOperations::Save
|
6
|
+
include Telnyx::APIOperations::Delete
|
7
|
+
extend Telnyx::APIOperations::List
|
8
|
+
extend Telnyx::APIOperations::Create
|
9
|
+
extend Telnyx::APIOperations::NestedResource
|
10
|
+
|
11
|
+
OBJECT_NAME = "messaging_profile".freeze
|
12
|
+
|
13
|
+
nested_resource_class_methods :phone_number,
|
14
|
+
operations: %i[list]
|
15
|
+
nested_resource_class_methods :sender_id,
|
16
|
+
operations: %i[list]
|
17
|
+
nested_resource_class_methods :short_code,
|
18
|
+
operations: %i[list]
|
19
|
+
|
20
|
+
def phone_numbers(params = {}, opts = {})
|
21
|
+
self.class.list_phone_numbers(id, params, opts)
|
22
|
+
end
|
23
|
+
|
24
|
+
def sender_ids(params = {}, opts = {})
|
25
|
+
self.class.list_sender_ids(id, params, opts)
|
26
|
+
end
|
27
|
+
|
28
|
+
def short_codes(params = {}, opts = {})
|
29
|
+
self.class.list_short_codes(id, params, opts)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|