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
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0b0
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require "irb"
6
+ require "irb/completion"
7
+
8
+ require "#{::File.dirname(__FILE__)}/../lib/telnyx"
9
+
10
+ # Config IRB to enable --simple-prompt and auto indent
11
+ IRB.conf[:PROMPT_MODE] = :SIMPLE
12
+ IRB.conf[:AUTO_INDENT] = true
13
+
14
+ puts "Loaded gem 'telnyx'"
15
+
16
+ IRB.start
data/lib/telnyx.rb ADDED
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Telnyx Ruby bindings
4
+ # API spec at https://developers.telnyx.com/clients
5
+ require "cgi"
6
+ require "faraday"
7
+ require "json"
8
+ require "logger"
9
+ require "openssl"
10
+ require "rbconfig"
11
+ require "securerandom"
12
+ require "set"
13
+ require "socket"
14
+ require "uri"
15
+
16
+ # Version
17
+ require "telnyx/version"
18
+
19
+ # API operations
20
+ require "telnyx/api_operations/create"
21
+ require "telnyx/api_operations/delete"
22
+ require "telnyx/api_operations/list"
23
+ require "telnyx/api_operations/nested_resource"
24
+ require "telnyx/api_operations/request"
25
+ require "telnyx/api_operations/save"
26
+
27
+ # API resource support classes
28
+ require "telnyx/errors"
29
+ require "telnyx/util"
30
+ require "telnyx/telnyx_client"
31
+ require "telnyx/telnyx_object"
32
+ require "telnyx/telnyx_response"
33
+ require "telnyx/list_object"
34
+ require "telnyx/api_resource"
35
+ require "telnyx/singleton_api_resource"
36
+ require "telnyx/webhook"
37
+
38
+ require "telnyx/number_order"
39
+ require "telnyx/number_reservation"
40
+ require "telnyx/message"
41
+ require "telnyx/messaging_profile"
42
+ require "telnyx/event"
43
+ require "telnyx/messaging_phone_number"
44
+ require "telnyx/messaging_sender_id"
45
+ require "telnyx/messaging_short_code"
46
+ require "telnyx/available_phone_number"
47
+ require "telnyx/public_key"
48
+
49
+ module Telnyx
50
+ @app_info = nil
51
+
52
+ @api_base = ENV.fetch("TELNYX_API_BASE", "https://api.telnyx.com")
53
+
54
+ @log_level = nil
55
+ @logger = nil
56
+
57
+ @max_network_retries = 0
58
+ @max_network_retry_delay = 2
59
+ @initial_network_retry_delay = 0.5
60
+
61
+ @verify_ssl_certs = true
62
+
63
+ @open_timeout = 30
64
+ @read_timeout = 80
65
+
66
+ class << self
67
+ attr_accessor :telnyx_account, :api_key, :api_base, :verify_ssl_certs, :api_version, :client_id,
68
+ :open_timeout, :read_timeout
69
+
70
+ attr_reader :max_network_retry_delay, :initial_network_retry_delay
71
+ end
72
+
73
+ # Gets the application for a plugin that's identified some. See
74
+ # #set_app_info.
75
+ def self.app_info
76
+ @app_info
77
+ end
78
+
79
+ def self.app_info=(info)
80
+ @app_info = info
81
+ end
82
+
83
+ # map to the same values as the standard library's logger
84
+ LEVEL_DEBUG = Logger::DEBUG
85
+ LEVEL_ERROR = Logger::ERROR
86
+ LEVEL_INFO = Logger::INFO
87
+
88
+ # When set prompts the library to log some extra information to $stdout and
89
+ # $stderr about what it's doing. For example, it'll produce information about
90
+ # requests, responses, and errors that are received. Valid log levels are
91
+ # `debug` and `info`, with `debug` being a little more verbose in places.
92
+ #
93
+ # Use of this configuration is only useful when `.logger` is _not_ set. When
94
+ # it is, the decision what levels to print is entirely deferred to the logger.
95
+ def self.log_level
96
+ @log_level
97
+ end
98
+
99
+ def self.log_level=(val)
100
+ # Backwards compatibility for values that we briefly allowed
101
+ if val == "debug"
102
+ val = LEVEL_DEBUG
103
+ elsif val == "info"
104
+ val = LEVEL_INFO
105
+ end
106
+
107
+ if !val.nil? && ![LEVEL_DEBUG, LEVEL_ERROR, LEVEL_INFO].include?(val)
108
+ raise ArgumentError, "log_level should only be set to `nil`, `debug` or `info`"
109
+ end
110
+ @log_level = val
111
+ end
112
+
113
+ # Sets a logger to which logging output will be sent. The logger should
114
+ # support the same interface as the `Logger` class that's part of Ruby's
115
+ # standard library (hint, anything in `Rails.logger` will likely be
116
+ # suitable).
117
+ #
118
+ # If `.logger` is set, the value of `.log_level` is ignored. The decision on
119
+ # what levels to print is entirely deferred to the logger.
120
+ def self.logger
121
+ @logger
122
+ end
123
+
124
+ def self.logger=(val)
125
+ @logger = val
126
+ end
127
+
128
+ def self.max_network_retries
129
+ @max_network_retries
130
+ end
131
+
132
+ def self.max_network_retries=(val)
133
+ @max_network_retries = val.to_i
134
+ end
135
+
136
+ # Sets some basic information about the running application that's sent along
137
+ # with API requests. Useful for plugin authors to identify their plugin when
138
+ # communicating with Telnyx.
139
+ #
140
+ # Takes a name and optional partner program ID, plugin URL, and version.
141
+ def self.set_app_info(name, partner_id: nil, url: nil, version: nil)
142
+ @app_info = {
143
+ name: name,
144
+ partner_id: partner_id,
145
+ url: url,
146
+ version: version,
147
+ }
148
+ end
149
+ end
150
+
151
+ Telnyx.log_level = ENV["TELNYX_LOG_LEVEL"] unless ENV["TELNYX_LOG_LEVEL"].nil?
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Telnyx
4
+ module APIOperations
5
+ module Create
6
+ def create(params = {}, opts = {})
7
+ resp, opts = request(:post, resource_url, params, opts)
8
+ Util.convert_to_telnyx_object(resp.data, opts)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Telnyx
4
+ module APIOperations
5
+ module Delete
6
+ def delete(params = {}, opts = {})
7
+ opts = Util.normalize_opts(opts)
8
+ resp, opts = request(:delete, resource_url, params, opts)
9
+ initialize_from(resp.data[:data], opts)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Telnyx
4
+ module APIOperations
5
+ module List
6
+ def list(filters = {}, opts = {})
7
+ opts = Util.normalize_opts(opts)
8
+
9
+ resp, opts = request(:get, resource_url, filters, opts)
10
+ obj = ListObject.construct_from(resp.data.merge(url: resource_url), opts)
11
+
12
+ # set filters so that we can fetch the same limit, expansions, and
13
+ # predicates when accessing the next and previous pages
14
+ #
15
+ # just for general cleanliness, remove any paging options
16
+ obj.filters = filters.dup
17
+ obj.filters.delete(:page)
18
+
19
+ obj
20
+ end
21
+
22
+ # The original version of #list was given the somewhat unfortunate name of
23
+ # #all, and this alias allows us to maintain backward compatibility (the
24
+ # choice was somewhat misleading in the way that it only returned a single
25
+ # page rather than all objects).
26
+ alias all list
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Telnyx
4
+ module APIOperations
5
+ # Adds methods to help manipulate a subresource from its parent resource so
6
+ # that it's possible to do so from a static context (i.e. without a
7
+ # pre-existing collection of subresources on the parent).
8
+ #
9
+ # For examle, a transfer gains the static methods for reversals so that the
10
+ # methods `.create_reversal`, `.retrieve_reversal`, `.update_reversal`,
11
+ # etc. all become available.
12
+ module NestedResource
13
+ def nested_resource_class_methods(resource, path: nil, operations: nil)
14
+ path ||= "#{resource}s"
15
+ raise ArgumentError, "operations array required" if operations.nil?
16
+
17
+ resource_url_method = :"#{resource}s_url"
18
+ define_singleton_method(resource_url_method) do |id, nested_id = nil|
19
+ url = "#{resource_url}/#{CGI.escape(id)}/#{CGI.escape(path)}"
20
+ url += "/#{CGI.escape(nested_id)}" unless nested_id.nil?
21
+ url
22
+ end
23
+
24
+ operations.each do |operation|
25
+ case operation
26
+ when :create
27
+ define_singleton_method(:"create_#{resource}") do |id, params = {}, opts = {}|
28
+ url = send(resource_url_method, id)
29
+ resp, opts = request(:post, url, params, opts)
30
+ Util.convert_to_telnyx_object(resp.data, opts)
31
+ end
32
+ when :retrieve
33
+ define_singleton_method(:"retrieve_#{resource}") do |id, nested_id, opts = {}|
34
+ url = send(resource_url_method, id, nested_id)
35
+ resp, opts = request(:get, url, {}, opts)
36
+ Util.convert_to_telnyx_object(resp.data, opts)
37
+ end
38
+ when :update
39
+ define_singleton_method(:"update_#{resource}") do |id, nested_id, params = {}, opts = {}|
40
+ url = send(resource_url_method, id, nested_id)
41
+ resp, opts = request(:patch, url, params, opts)
42
+ Util.convert_to_telnyx_object(resp.data, opts)
43
+ end
44
+ when :delete
45
+ define_singleton_method(:"delete_#{resource}") do |id, nested_id, params = {}, opts = {}|
46
+ url = send(resource_url_method, id, nested_id)
47
+ resp, opts = request(:delete, url, params, opts)
48
+ Util.convert_to_telnyx_object(resp.data, opts)
49
+ end
50
+ when :list
51
+ define_singleton_method(:"list_#{resource}s") do |id, params = {}, opts = {}|
52
+ url = send(resource_url_method, id)
53
+ resp, opts = request(:get, url, params, opts)
54
+ Util.convert_to_telnyx_object(resp.data, opts)
55
+ end
56
+ else
57
+ raise ArgumentError, "Unknown operation: #{operation.inspect}"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Telnyx
4
+ module APIOperations
5
+ module Request
6
+ module ClassMethods
7
+ def request(method, url, params = {}, opts = {})
8
+ warn_on_opts_in_params(params)
9
+
10
+ opts = Util.normalize_opts(opts)
11
+ opts[:client] ||= TelnyxClient.active_client
12
+
13
+ headers = opts.clone
14
+ api_key = headers.delete(:api_key)
15
+ api_base = headers.delete(:api_base)
16
+ client = headers.delete(:client)
17
+ # Assume all remaining opts must be headers
18
+
19
+ resp, opts[:api_key] = client.execute_request(
20
+ method, url,
21
+ api_base: api_base, api_key: api_key,
22
+ headers: headers, params: params
23
+ )
24
+
25
+ # Hash#select returns an array before 1.9
26
+ opts_to_persist = {}
27
+ opts.each do |k, v|
28
+ opts_to_persist[k] = v if Util::OPTS_PERSISTABLE.include?(k)
29
+ end
30
+
31
+ [resp, opts_to_persist]
32
+ end
33
+
34
+ private
35
+
36
+ def warn_on_opts_in_params(params)
37
+ Util::OPTS_USER_SPECIFIED.each do |opt|
38
+ if params.key?(opt)
39
+ $stderr.puts("WARNING: #{opt} should be in opts instead of params.")
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ def self.included(base)
46
+ base.extend(ClassMethods)
47
+ end
48
+
49
+ protected
50
+
51
+ def request(method, url, params = {}, opts = {})
52
+ opts = @opts.merge(Util.normalize_opts(opts))
53
+ self.class.request(method, url, params, opts)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Telnyx
4
+ module APIOperations
5
+ module Save
6
+ module ClassMethods
7
+ # Updates an API resource
8
+ #
9
+ # Updates the identified resource with the passed in parameters.
10
+ #
11
+ # ==== Attributes
12
+ #
13
+ # * +id+ - ID of the resource to update.
14
+ # * +params+ - A hash of parameters to pass to the API
15
+ # * +opts+ - A Hash of additional options (separate from the params /
16
+ # object values) to be added to the request. E.g. to allow for the
17
+ # api_key to be overwritten. See {APIOperations::Request.request}.
18
+ def update(id, params = {}, opts = {})
19
+ params.each_key do |k|
20
+ if protected_fields.include?(k)
21
+ raise ArgumentError, "Cannot update protected field: #{k}"
22
+ end
23
+ end
24
+
25
+ resp, opts = request(:patch, "#{resource_url}/#{id}", params, opts)
26
+ Util.convert_to_telnyx_object(resp.data, opts)
27
+ end
28
+ end
29
+
30
+ # Creates or updates an API resource.
31
+ #
32
+ # If the resource doesn't yet have an assigned ID and the resource is one
33
+ # that can be created, then the method attempts to create the resource.
34
+ # The resource is updated otherwise.
35
+ #
36
+ # ==== Attributes
37
+ #
38
+ # * +params+ - Overrides any parameters in the resource's serialized data
39
+ # and includes them in the create or update. If +:req_url:+ is included
40
+ # in the list, it overrides the update URL used for the create or
41
+ # update.
42
+ # * +opts+ - A Hash of additional options (separate from the params /
43
+ # object values) to be added to the request. E.g. to allow for the
44
+ # api_key to be overwritten. See {APIOperations::Request.request}.
45
+ def save(params = {}, opts = {})
46
+ # We started unintentionally (sort of) allowing attributes sent to
47
+ # +save+ to override values used during the update. So as not to break
48
+ # the API, this makes that official here.
49
+ update_attributes(params)
50
+
51
+ # Now remove any parameters that look like object attributes.
52
+ params = params.reject { |k, _| respond_to?(k) }
53
+
54
+ values = serialize_params(self).merge(params)
55
+
56
+ # note that id gets removed here our call to #url above has already
57
+ # generated a uri for this object with an identifier baked in
58
+ values.delete(:id)
59
+
60
+ resp, opts = request(save_method, save_url, values, opts)
61
+
62
+ initialize_from(resp.data[:data], opts)
63
+ end
64
+
65
+ def self.included(base)
66
+ # Set `metadata` as additive so that when it's set directly we remember
67
+ # to clear keys that may have been previously set by sending empty
68
+ # values for them.
69
+ #
70
+ # It's possible that not every object with `Save` has `metadata`, but
71
+ # it's a close enough heuristic, and having this option set when there
72
+ # is no `metadata` field is not harmful.
73
+ base.additive_object_param(:metadata)
74
+
75
+ base.extend(ClassMethods)
76
+ end
77
+
78
+ private
79
+
80
+ def save_url
81
+ # This switch essentially allows us "upsert"-like functionality. If the
82
+ # API resource doesn't have an ID set (suggesting that it's new) and
83
+ # its class responds to .create (which comes from
84
+ # Telnyx::APIOperations::Create), then use the URL to create a new
85
+ # resource. Otherwise, generate a URL based on the object's identifier
86
+ # for a normal update.
87
+ if self[:id].nil? && self.class.respond_to?(:create)
88
+ self.class.resource_url
89
+ else
90
+ resource_url
91
+ end
92
+ end
93
+
94
+ def save_method
95
+ if self[:id].nil? && self.class.respond_to?(:create)
96
+ :post
97
+ else
98
+ :patch
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end