bubbles-rest-client 0.0.6

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