bubbles-rest-client 0.0.6
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.
- checksums.yaml +7 -0
- data/.gitignore +45 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +85 -0
- data/LICENSE +373 -0
- data/README.md +24 -0
- data/Rakefile +10 -0
- data/bubbles.gemspec +45 -0
- data/fixtures/vcr_cassettes/get_students_authenticated.yml +46 -0
- data/fixtures/vcr_cassettes/get_version_unauthenticated.yml +42 -0
- data/lib/bubbles.rb +650 -0
- data/lib/bubbles/config.rb +201 -0
- data/lib/bubbles/endpoint.rb +181 -0
- data/lib/bubbles/rest_client_resources.rb +124 -0
- data/lib/bubbles/rest_environment.rb +68 -0
- data/lib/bubbles/version.rb +22 -0
- metadata +201 -0
@@ -0,0 +1,201 @@
|
|
1
|
+
require 'bubbles/rest_environment'
|
2
|
+
|
3
|
+
module Bubbles
|
4
|
+
class << self
|
5
|
+
attr_writer :configuration
|
6
|
+
end
|
7
|
+
|
8
|
+
##
|
9
|
+
# Configure the Bubbles instance.
|
10
|
+
#
|
11
|
+
# Use this method if you want to configure the Bubbles instance, typically during intialization of your Gem or
|
12
|
+
# application.
|
13
|
+
#
|
14
|
+
# @example In app/config/initializers/bubbles.rb
|
15
|
+
# Bubbles.configure do |config|
|
16
|
+
# config.endpoints = [
|
17
|
+
# {
|
18
|
+
# :type => :get,
|
19
|
+
# :location => :version,
|
20
|
+
# :authenticated => false,
|
21
|
+
# :api_key_required => false
|
22
|
+
# }
|
23
|
+
# ]
|
24
|
+
# end
|
25
|
+
def self.configure
|
26
|
+
yield(configuration)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.configuration
|
30
|
+
@configuration ||= Configuration.new
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# The configuration of the Bubbles rest client.
|
35
|
+
#
|
36
|
+
# Use this class if you want to retrieve configuration values set during initialization.
|
37
|
+
#
|
38
|
+
class Configuration
|
39
|
+
def initialize
|
40
|
+
@local_environment_scheme = 'http'
|
41
|
+
@local_environment_host = '127.0.0.1'
|
42
|
+
@local_environment_port = '1234'
|
43
|
+
|
44
|
+
@staging_environment_scheme = 'http'
|
45
|
+
@staging_environment_host = '127.0.0.1'
|
46
|
+
@staging_environment_port = '1234'
|
47
|
+
|
48
|
+
@production_environment_scheme = 'http'
|
49
|
+
@production_environment_host = '127.0.0.1'
|
50
|
+
@production_environment_port = '1234'
|
51
|
+
|
52
|
+
@endpoints = Hash.new
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Retrieve the local {RestEnvironment} object defined as part of this Configuration.
|
57
|
+
#
|
58
|
+
def local_environment
|
59
|
+
RestEnvironment.new(@local_environment_scheme, @local_environment_host, @local_environment_port)
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Set the local environment.
|
64
|
+
#
|
65
|
+
# @param [Object] env The environment, as a generic Ruby Object.
|
66
|
+
#
|
67
|
+
# @example In app/config/initializers/bubbles.rb
|
68
|
+
# Bubbles.configure do |config|
|
69
|
+
# config.local_environment = {
|
70
|
+
# :scheme => 'https',
|
71
|
+
# :host => 'api.somehost.com',
|
72
|
+
# :port => '443'
|
73
|
+
# }
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
def local_environment=(env)
|
77
|
+
@local_environment_scheme = env[:scheme]
|
78
|
+
@local_environment_host = env[:host]
|
79
|
+
@local_environment_port = env[:port]
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Retrieve the staging {RestEnvironment} object defined as part of this Configuration.
|
84
|
+
#
|
85
|
+
def staging_environment
|
86
|
+
RestEnvironment.new(@staging_environment_scheme, @staging_environment_host, @staging_environment_port)
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Set the staging environment.
|
91
|
+
#
|
92
|
+
# @param [Object] env The environment, as a generic Ruby Object.
|
93
|
+
#
|
94
|
+
# @example In app/config/initializers/bubbles.rb
|
95
|
+
# Bubbles.configure do |config|
|
96
|
+
# config.staging_environment = {
|
97
|
+
# :scheme => 'https',
|
98
|
+
# :host => 'api.somehost.com',
|
99
|
+
# :port => '443'
|
100
|
+
# }
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
def staging_environment=(env)
|
104
|
+
@staging_environment_scheme = env[:scheme]
|
105
|
+
@staging_environment_host = env[:host]
|
106
|
+
@staging_environment_port = env[:port]
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Retrieve the production {RestEnvironment} object defined as part of this Configuration.
|
111
|
+
#
|
112
|
+
def production_environment
|
113
|
+
RestEnvironment.new(@production_environment_scheme, @production_environment_host, @production_environment_port)
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Set the production environment.
|
118
|
+
#
|
119
|
+
# @param [Object] env The environment, as a generic Ruby Object.
|
120
|
+
#
|
121
|
+
# @example In app/config/initializers/bubbles.rb
|
122
|
+
# Bubbles.configure do |config|
|
123
|
+
# config.production_environment = {
|
124
|
+
# :scheme => 'https',
|
125
|
+
# :host => 'api.somehost.com',
|
126
|
+
# :port => '443'
|
127
|
+
# }
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
def production_environment=(env)
|
131
|
+
@production_environment_scheme = env[:scheme]
|
132
|
+
@production_environment_host = env[:host]
|
133
|
+
@production_environment_port = env[:port]
|
134
|
+
end
|
135
|
+
|
136
|
+
##
|
137
|
+
# Retrieve the list of +Endpoint+s configured in this +Configuration+ object.
|
138
|
+
#
|
139
|
+
# @return {Array} An Array of {Endpoint}s.
|
140
|
+
#
|
141
|
+
def endpoints
|
142
|
+
@endpoints
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Add all {Endpoint} objects within this {Configuration} instance.
|
147
|
+
#
|
148
|
+
# {Endpoint} objects are defined using two required parameters: type and location, and three optional parameters:
|
149
|
+
# authenticated, api_key_required and name.
|
150
|
+
# - method: Indicates the HTTP method used to access the endpoint. Must be one of {Endpoint::METHODS}.
|
151
|
+
# - location: Indicates the path at which the {Endpoint} can be accessed on the host environment.
|
152
|
+
# - authenticated: (Optional) A true or false value indicating whether the {Endpoint} requires an authorization
|
153
|
+
# token to access it. Defaults to false.
|
154
|
+
# - api_key_required: (Optional) A true or false value indicating whether the {Endpoint} requires a API key to
|
155
|
+
# access it. Defaults to false.
|
156
|
+
# - name: (Optional): A +String+ indicating the name of the method to add. If not provided, the method name will
|
157
|
+
# be the same as the +location+.
|
158
|
+
#
|
159
|
+
def endpoints=(endpoints)
|
160
|
+
new_endpoints = Hash.new
|
161
|
+
endpoints.each do |ep|
|
162
|
+
endpoint_object = Endpoint.new ep[:method], ep[:location].to_s, ep[:authenticated], ep[:api_key_required], ep[:name]
|
163
|
+
|
164
|
+
new_endpoints[endpoint_object.get_key_string] = endpoint_object
|
165
|
+
end
|
166
|
+
|
167
|
+
@endpoints = new_endpoints
|
168
|
+
|
169
|
+
# Define all of the endpoints as methods on RestEnvironment
|
170
|
+
@endpoints.values.each do |endpoint|
|
171
|
+
if endpoint.name != nil
|
172
|
+
endpoint_name_as_sym = endpoint.name.to_sym
|
173
|
+
else
|
174
|
+
endpoint_name_as_sym = endpoint.get_location_string.to_sym
|
175
|
+
end
|
176
|
+
|
177
|
+
if Bubbles::RestEnvironment.instance_methods(false).include? (endpoint_name_as_sym)
|
178
|
+
Bubbles::RestEnvironment.class_exec do
|
179
|
+
remove_method endpoint_name_as_sym
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
if endpoint.method == :get
|
184
|
+
if endpoint.authenticated?
|
185
|
+
Bubbles::RestEnvironment.class_exec do
|
186
|
+
define_method(endpoint_name_as_sym) do |auth_token|
|
187
|
+
RestClientResources.execute_get_authenticated self, endpoint, auth_token
|
188
|
+
end
|
189
|
+
end
|
190
|
+
else
|
191
|
+
Bubbles::RestEnvironment.class_exec do
|
192
|
+
define_method(endpoint_name_as_sym) do
|
193
|
+
RestClientResources.execute_get_unauthenticated self, endpoint
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'addressable/template'
|
2
|
+
|
3
|
+
module Bubbles
|
4
|
+
##
|
5
|
+
# Representation of a single API endpoint within the Bubbles infrastructure.
|
6
|
+
#
|
7
|
+
# In order to access an API Endpoint, an {RestEnvironment} must also be provided. This class is an abstract
|
8
|
+
# representation of an +Endpoint+, without any information provided as part of the Environment. In other words, an
|
9
|
+
# Endpoint can be used with any +RestEnvironment+.
|
10
|
+
#
|
11
|
+
class Endpoint
|
12
|
+
## Controls the method used to access the endpoint. Must be one of {Endpoint::Methods}.
|
13
|
+
# @return [Symbol] the method used to access the endpoint. Will always be one of the symbols defined in {Endpoint::METHODS}.
|
14
|
+
attr_accessor :method
|
15
|
+
|
16
|
+
## Controls the location, relative to the web root of the host, used to access the endpoint.
|
17
|
+
# @return [String] the location relative to the web root of the host used to access the endpoint
|
18
|
+
attr_accessor :location
|
19
|
+
|
20
|
+
## Controls whether authentication is required to access this endpoint. Defaults to false.
|
21
|
+
# @return [Boolean] true, if authentication is required to access this endpoint; false, otherwise.
|
22
|
+
attr_accessor :authentication_required
|
23
|
+
|
24
|
+
## Controls whether an API key is required to access this endpoint. Defaults to false.
|
25
|
+
# @return [Boolean] true, if an API key is required to access this endpoint; false, otherwise.
|
26
|
+
attr_accessor :api_key_required
|
27
|
+
|
28
|
+
## A template for specifying the complete URL for endpoints.
|
29
|
+
API_URL = ::Addressable::Template.new("{scheme}://{host}/{endpoint}")
|
30
|
+
|
31
|
+
## A template for specifying the complete URL for endpoints, with a port attached to the host.
|
32
|
+
API_URL_WITH_PORT = ::Addressable::Template.new("{scheme}://{host}:{port}/{endpoint}")
|
33
|
+
|
34
|
+
|
35
|
+
## The HTTP methods supported by a rest client utilizing Bubbles.
|
36
|
+
METHODS = %w[get].freeze
|
37
|
+
|
38
|
+
##
|
39
|
+
# Construct a new instance of an Endpoint.
|
40
|
+
#
|
41
|
+
# @param [Symbol] method The type of the new Endpoint to create. Must be one of the methods in {Endpoint::METHODS}.
|
42
|
+
# @param [String] location The location, relative to the root of the host, at which the endpoint resides.
|
43
|
+
# @param [Boolean] auth_required If true, then authorization/authentication is required to access this endpoint.
|
44
|
+
# Defaults to +false+.
|
45
|
+
# @param [Boolean] api_key_required If true, then an API key is required to access this endpoint. Defaults to
|
46
|
+
# +false+.
|
47
|
+
# @param [String] name An optional name which will be given to the method that will execute this {Endpoint} within
|
48
|
+
# the context of a {RestClientResources} object.
|
49
|
+
#
|
50
|
+
def initialize(method, location, auth_required = false, api_key_required = false, name = nil)
|
51
|
+
@method = method
|
52
|
+
@location = location
|
53
|
+
@auth_required = auth_required
|
54
|
+
@api_key_required = api_key_required
|
55
|
+
@name = name
|
56
|
+
|
57
|
+
# Strip the leading slash from the endpoint location, if it's there
|
58
|
+
if @location.to_s[0] == '/'
|
59
|
+
@location = @location.to_s.slice(1, @location.to_s.length)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Retrieve a +String+ that will identify this +Endpoint+ uniquely within a hash table.
|
65
|
+
#
|
66
|
+
# @return [String] A unique identifier for this Endpoint, including its method (get/post/put/etc..), location, whether or not it is authenticated, and whether it needs an API key to successfully execute.
|
67
|
+
#
|
68
|
+
def get_key_string
|
69
|
+
auth_string = '-unauthenticated'
|
70
|
+
if @auth_required
|
71
|
+
auth_string = '-authenticated'
|
72
|
+
end
|
73
|
+
|
74
|
+
api_key_string = ''
|
75
|
+
if @api_key_required
|
76
|
+
api_key_string = '-with-api-key'
|
77
|
+
end
|
78
|
+
|
79
|
+
method.to_s + "-" + @location.to_s + auth_string + api_key_string
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Retrieve the base URL template for this +Endpoint+, given a +RestEnvironment+.
|
84
|
+
#
|
85
|
+
# @param [RestEnvironment] env The +RestEnvironment+ to use to access this endpoint.
|
86
|
+
#
|
87
|
+
# @return [Addressable::Template] A +Template+ containing the URL to use to access this +Endpoint+.
|
88
|
+
#
|
89
|
+
def get_base_url(env)
|
90
|
+
unless env.port == 80 || env.port == 443
|
91
|
+
return API_URL_WITH_PORT
|
92
|
+
end
|
93
|
+
|
94
|
+
API_URL
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Retrieve the URL to access this +Endpoint+, as a +String+ with all parameters expanded.
|
99
|
+
#
|
100
|
+
# @param [RestEnvironment] env The +RestEnvironment+ to use to access this +Endpoint+.
|
101
|
+
#
|
102
|
+
# @return [String] A +String+ containing the full URL to access this +Endpoint+ on the given {RestEnvironment}.
|
103
|
+
#
|
104
|
+
def get_expanded_url(env)
|
105
|
+
url = get_base_url env
|
106
|
+
|
107
|
+
if is_complex?
|
108
|
+
special_url_string = '{scheme}://{environment_host}/'
|
109
|
+
unless @port == 80 || @port == 443
|
110
|
+
special_url_string = '{scheme}://{environment_host}:{port}/'
|
111
|
+
end
|
112
|
+
|
113
|
+
special_url_string = special_url_string + @location
|
114
|
+
url = ::Addressable::Template.new(special_url_string)
|
115
|
+
|
116
|
+
return url.expand(scheme: env.scheme, host: env.host, port: env.port)
|
117
|
+
end
|
118
|
+
|
119
|
+
url.expand(scheme: env.scheme, host: env.host, port: env.port, endpoint: @location)
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# Determine if the location for this Endpoint is complex.
|
124
|
+
#
|
125
|
+
# @return [Boolean] true, if the location for this Endpoint is complex (contains a '/'); false, otherwise.
|
126
|
+
def is_complex?
|
127
|
+
@location.include? '/'
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# Retrieve a String representing the location of this Endpoint.
|
132
|
+
#
|
133
|
+
# Complex Endpoints will have instances of '/' replaced with '_'.
|
134
|
+
#
|
135
|
+
# @return [String] The string representation of the location of this endpoint.
|
136
|
+
def get_location_string
|
137
|
+
unless is_complex?
|
138
|
+
return @location
|
139
|
+
end
|
140
|
+
|
141
|
+
@location.to_s.gsub('/', '_')
|
142
|
+
end
|
143
|
+
|
144
|
+
##
|
145
|
+
# Determine if this +Endpoint+ requires authentication/authorization to utilize
|
146
|
+
#
|
147
|
+
# @return [Boolean] true, if this +Endpoint+ requires authentication/authorization to use; false, otherwise.
|
148
|
+
def authenticated?
|
149
|
+
@auth_required
|
150
|
+
end
|
151
|
+
|
152
|
+
##
|
153
|
+
# Set the name of the method on {RestClientResources} used to access this {Endpoint}.
|
154
|
+
#
|
155
|
+
# @param [String] name The name of the method used to access this {Endpoint}.
|
156
|
+
#
|
157
|
+
def name=(name)
|
158
|
+
@name = name
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Retrieve the name of the method on {RestClientResources} used to access this {Endpoint}.
|
163
|
+
#
|
164
|
+
# @return [String] A String containing the name of the method on {RestClientResources} used to access this
|
165
|
+
# {Endpoint}, or +nil+ if one wasn't provided.
|
166
|
+
#
|
167
|
+
def name
|
168
|
+
@name
|
169
|
+
end
|
170
|
+
|
171
|
+
##
|
172
|
+
# Determine if this {Endpoint} has a method name, different from the +location+ name, specified for it.
|
173
|
+
#
|
174
|
+
# @return [Boolean] true, if this {Endpoint} has a method name that is different than the +location+ name specified
|
175
|
+
# for the +Endpoint+, to be defined on {RestClientResources}; false, otherwise.
|
176
|
+
#
|
177
|
+
def name?
|
178
|
+
@name == nil
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'bubbles/config'
|
2
|
+
require 'bubbles/rest_environment'
|
3
|
+
|
4
|
+
module Bubbles
|
5
|
+
class RestClientResources
|
6
|
+
def local_environment
|
7
|
+
Bubbles.configuration.local_environment
|
8
|
+
end
|
9
|
+
|
10
|
+
def staging_environment
|
11
|
+
Bubbles.configuration.staging_environment
|
12
|
+
end
|
13
|
+
|
14
|
+
def production_environment
|
15
|
+
Bubbles.configuration.production_environment
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Create a new instance of +RestClientResources+.
|
20
|
+
#
|
21
|
+
# @param env The +RestEnvironment+ that should be used for this set of resources.
|
22
|
+
# @param api_key The API key to use to send to the host for unauthenticated requests.
|
23
|
+
#
|
24
|
+
def initialize(env, api_key)
|
25
|
+
unless env
|
26
|
+
env = :local
|
27
|
+
end
|
28
|
+
|
29
|
+
unless api_key
|
30
|
+
api_key = ''
|
31
|
+
end
|
32
|
+
|
33
|
+
@environment = get_environment env
|
34
|
+
@api_key = api_key
|
35
|
+
@auth_token = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Execute a GET request without authentication.
|
40
|
+
#
|
41
|
+
# @param [RestEnvironment] env The +RestEnvironment+ to use to execute the request
|
42
|
+
# @param [Endpoint] endpoint The +Endpoint+ which should be requested
|
43
|
+
#
|
44
|
+
# @return [RestClient::Response] The +Response+ resulting from the execution of the GET call.
|
45
|
+
#
|
46
|
+
def self.execute_get_unauthenticated(env, endpoint)
|
47
|
+
url = endpoint.get_expanded_url env
|
48
|
+
|
49
|
+
begin
|
50
|
+
if env.scheme == 'https'
|
51
|
+
response = RestClient::Resource.new(url.to_s, :verify_ssl => OpenSSL::SSL::VERIFY_NONE)
|
52
|
+
.get({
|
53
|
+
:content_type => :json,
|
54
|
+
:accept => :json
|
55
|
+
})
|
56
|
+
else
|
57
|
+
response = RestClient.get(url.to_s,
|
58
|
+
{
|
59
|
+
:content_type => :json
|
60
|
+
})
|
61
|
+
end
|
62
|
+
rescue Errno::ECONNREFUSED
|
63
|
+
return {:error => 'Unable to connect to host ' + env.host.to_s + ':' + env.port.to_s}.to_json
|
64
|
+
end
|
65
|
+
|
66
|
+
response
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Execute a GET request with authentication.
|
71
|
+
#
|
72
|
+
# Currently, only Authorization: Bearer is supported.
|
73
|
+
#
|
74
|
+
# @param [RestEnvironment] env The +RestEnvironment+ to use to execute the request
|
75
|
+
# @param [Endpoint] endpoint The +Endpoint+ which should be requested
|
76
|
+
# @param [String] auth_token The authorization token to use for authentication.
|
77
|
+
#
|
78
|
+
# @return [RestClient::Response] The +Response+ resulting from the execution of the GET call.
|
79
|
+
#
|
80
|
+
def self.execute_get_authenticated(env, endpoint, auth_token)
|
81
|
+
url = endpoint.get_expanded_url env
|
82
|
+
|
83
|
+
begin
|
84
|
+
if env.scheme == 'https'
|
85
|
+
response = RestClient::Resource.new(url.to_s, :verify_ssl => OpenSSL::SSL::VERIFY_NONE)
|
86
|
+
.get({
|
87
|
+
:authorization => 'Bearer ' + auth_token,
|
88
|
+
:content_type => :json,
|
89
|
+
:accept => :json
|
90
|
+
})
|
91
|
+
else
|
92
|
+
response = RestClient.get(url.to_s,
|
93
|
+
{
|
94
|
+
:authorization => 'Bearer ' + auth_token,
|
95
|
+
:content_type => :json
|
96
|
+
})
|
97
|
+
end
|
98
|
+
rescue Errno::ECONNREFUSED
|
99
|
+
return {:error => 'Unable to connect to host ' + env.host.to_s + ':' + env.port.to_s}.to_json
|
100
|
+
end
|
101
|
+
|
102
|
+
response
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Retrieve the {RestEnvironment} to utilize from a {Symbol} describing it.
|
107
|
+
#
|
108
|
+
# @param [Symbol] environment A {Symbol} describing the environment to use. Must be one of:
|
109
|
+
# [:production, :staging, :local, nil]. If +nil+, note that +:production+ will be used.
|
110
|
+
#
|
111
|
+
# @return [RestEnvironment] The {RestEnvironment} corresponding to the given {Symbol}.
|
112
|
+
#
|
113
|
+
def get_environment(environment)
|
114
|
+
if !environment || environment == :production
|
115
|
+
return self.production_environment
|
116
|
+
elsif environment == :staging
|
117
|
+
return self.staging_environment
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
self.local_environment
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|