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
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