telnyx 0.0.1

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