telnyx 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|