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.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +4 -0
  3. data/.github/ISSUE_TEMPLATE.md +5 -0
  4. data/.gitignore +9 -0
  5. data/.rubocop.yml +32 -0
  6. data/.rubocop_todo.yml +50 -0
  7. data/.travis.yml +42 -0
  8. data/CHANGELOG.md +2 -0
  9. data/CONTRIBUTORS +0 -0
  10. data/Gemfile +40 -0
  11. data/Guardfile +8 -0
  12. data/LICENSE +22 -0
  13. data/README.md +173 -0
  14. data/Rakefile +28 -0
  15. data/VERSION +1 -0
  16. data/bin/telnyx-console +16 -0
  17. data/lib/telnyx.rb +151 -0
  18. data/lib/telnyx/api_operations/create.rb +12 -0
  19. data/lib/telnyx/api_operations/delete.rb +13 -0
  20. data/lib/telnyx/api_operations/list.rb +29 -0
  21. data/lib/telnyx/api_operations/nested_resource.rb +63 -0
  22. data/lib/telnyx/api_operations/request.rb +57 -0
  23. data/lib/telnyx/api_operations/save.rb +103 -0
  24. data/lib/telnyx/api_resource.rb +69 -0
  25. data/lib/telnyx/available_phone_number.rb +9 -0
  26. data/lib/telnyx/errors.rb +166 -0
  27. data/lib/telnyx/event.rb +9 -0
  28. data/lib/telnyx/list_object.rb +155 -0
  29. data/lib/telnyx/message.rb +9 -0
  30. data/lib/telnyx/messaging_phone_number.rb +10 -0
  31. data/lib/telnyx/messaging_profile.rb +32 -0
  32. data/lib/telnyx/messaging_sender_id.rb +12 -0
  33. data/lib/telnyx/messaging_short_code.rb +10 -0
  34. data/lib/telnyx/number_order.rb +11 -0
  35. data/lib/telnyx/number_reservation.rb +11 -0
  36. data/lib/telnyx/public_key.rb +7 -0
  37. data/lib/telnyx/singleton_api_resource.rb +24 -0
  38. data/lib/telnyx/telnyx_client.rb +545 -0
  39. data/lib/telnyx/telnyx_object.rb +521 -0
  40. data/lib/telnyx/telnyx_response.rb +50 -0
  41. data/lib/telnyx/util.rb +328 -0
  42. data/lib/telnyx/version.rb +5 -0
  43. data/lib/telnyx/webhook.rb +66 -0
  44. data/telnyx.gemspec +25 -0
  45. data/test/api_stub_helpers.rb +1 -0
  46. data/test/openapi/README.md +9 -0
  47. data/test/telnyx/api_operations_test.rb +85 -0
  48. data/test/telnyx/api_resource_test.rb +293 -0
  49. data/test/telnyx/available_phone_number_test.rb +14 -0
  50. data/test/telnyx/errors_test.rb +23 -0
  51. data/test/telnyx/list_object_test.rb +244 -0
  52. data/test/telnyx/message_test.rb +19 -0
  53. data/test/telnyx/messaging_phone_number_test.rb +33 -0
  54. data/test/telnyx/messaging_profile_test.rb +70 -0
  55. data/test/telnyx/messaging_sender_id_test.rb +46 -0
  56. data/test/telnyx/messaging_short_code_test.rb +33 -0
  57. data/test/telnyx/number_order_test.rb +39 -0
  58. data/test/telnyx/number_reservation_test.rb +12 -0
  59. data/test/telnyx/public_key_test.rb +13 -0
  60. data/test/telnyx/telnyx_client_test.rb +631 -0
  61. data/test/telnyx/telnyx_object_test.rb +497 -0
  62. data/test/telnyx/telnyx_response_test.rb +49 -0
  63. data/test/telnyx/util_test.rb +380 -0
  64. data/test/telnyx/webhook_test.rb +108 -0
  65. data/test/telnyx_mock.rb +78 -0
  66. data/test/telnyx_test.rb +40 -0
  67. data/test/test_data.rb +149 -0
  68. data/test/test_helper.rb +73 -0
  69. 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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Telnyx
4
+ class AvailablePhoneNumber < APIResource
5
+ extend Telnyx::APIOperations::List
6
+
7
+ OBJECT_NAME = "available_phone_number".freeze
8
+ end
9
+ 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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Telnyx
4
+ class Event < APIResource
5
+ extend Telnyx::APIOperations::List
6
+
7
+ OBJECT_NAME = "event".freeze
8
+ end
9
+ end
@@ -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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Telnyx
4
+ class Message < APIResource
5
+ extend Telnyx::APIOperations::Create
6
+
7
+ OBJECT_NAME = "message".freeze
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Telnyx
4
+ class MessagingPhoneNumber < APIResource
5
+ include Telnyx::APIOperations::Save
6
+ extend Telnyx::APIOperations::List
7
+
8
+ OBJECT_NAME = "messaging_phone_number".freeze
9
+ end
10
+ 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