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