angus-remote 0.0.1 → 0.0.2
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.
- data/lib/angus-remote.rb +4 -0
- data/lib/angus/remote/builder.rb +204 -0
- data/lib/angus/remote/client.rb +79 -0
- data/lib/angus/remote/exceptions.rb +50 -0
- data/lib/angus/remote/http/multipart.rb +54 -0
- data/lib/angus/remote/http/multipart_methods/multipart_base.rb +36 -0
- data/lib/angus/remote/http/multipart_methods/multipart_post.rb +11 -0
- data/lib/angus/remote/http/multipart_methods/multipart_put.rb +11 -0
- data/lib/angus/remote/http/query_params.rb +53 -0
- data/lib/angus/remote/message.rb +14 -0
- data/lib/angus/remote/proxy_client.rb +58 -0
- data/lib/angus/remote/proxy_client_utils.rb +70 -0
- data/lib/angus/remote/remote_response.rb +44 -0
- data/lib/angus/remote/representation.rb +18 -0
- data/lib/angus/remote/response/builder.rb +308 -0
- data/lib/angus/remote/response/hash.rb +47 -0
- data/lib/angus/remote/response/serializer.rb +43 -0
- data/lib/angus/remote/service_directory.rb +217 -0
- data/lib/angus/remote/utils.rb +119 -0
- data/lib/angus/remote/version.rb +4 -2
- data/lib/angus/unmarshalling.rb +33 -0
- data/spec/angus/remote/builder_spec.rb +105 -0
- data/spec/angus/remote/client_spec.rb +75 -0
- data/spec/angus/remote/http/multipart_methods/multipart_base_spec.rb +36 -0
- data/spec/angus/remote/http/multipart_spec.rb +120 -0
- data/spec/angus/remote/http/query_params_spec.rb +28 -0
- data/spec/angus/remote/proxy_client_utils_spec.rb +102 -0
- data/spec/angus/remote/response/builder_spec.rb +69 -0
- data/spec/angus/remote/service_directory_spec.rb +76 -0
- data/spec/angus/remote/utils_spec.rb +204 -0
- metadata +192 -32
- data/.gitignore +0 -17
- data/Gemfile +0 -4
- data/LICENSE.txt +0 -22
- data/README.md +0 -29
- data/Rakefile +0 -1
- data/angus-remote.gemspec +0 -23
- data/lib/angus/remote.rb +0 -7
@@ -0,0 +1,47 @@
|
|
1
|
+
module Angus
|
2
|
+
module Remote
|
3
|
+
module Response
|
4
|
+
|
5
|
+
module Hash
|
6
|
+
|
7
|
+
def elements
|
8
|
+
@elements ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
# Creates a hash base on the object
|
12
|
+
#
|
13
|
+
# The object must have an instance variable @elements that is a hash
|
14
|
+
# that keys => element name, value => element value
|
15
|
+
def to_hash
|
16
|
+
hash = {}
|
17
|
+
|
18
|
+
elements.each do |name, value|
|
19
|
+
if value.is_a?(Angus::Remote::Response::Hash)
|
20
|
+
value.to_hash
|
21
|
+
elsif value.is_a?(Array)
|
22
|
+
hash[name] = build_hash_from_array(value)
|
23
|
+
else
|
24
|
+
hash[name] = value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def build_hash_from_array(elements)
|
32
|
+
elements.map do |element|
|
33
|
+
if element.is_a?(Angus::Remote::Response::Hash)
|
34
|
+
element.to_hash
|
35
|
+
elsif element.is_a?(Array)
|
36
|
+
build_hash_from_array(element)
|
37
|
+
else
|
38
|
+
element
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
require_relative 'builder'
|
4
|
+
|
5
|
+
module Angus
|
6
|
+
module Remote
|
7
|
+
module Response
|
8
|
+
|
9
|
+
module Serializer
|
10
|
+
|
11
|
+
def self.serialize(response)
|
12
|
+
h = {}
|
13
|
+
h['status_code'] = response[:status_code]
|
14
|
+
h['body'] = response[:body]
|
15
|
+
h['service_code_name'] = response[:service_code_name]
|
16
|
+
h['service_version'] = response[:service_version]
|
17
|
+
h['operation_namespace'] = response[:operation_namespace]
|
18
|
+
h['operation_code_name'] = response[:operation_code_name]
|
19
|
+
|
20
|
+
h['body'].force_encoding('UTF-8')
|
21
|
+
|
22
|
+
JSON(h)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.unserialize(s)
|
26
|
+
s = JSON(s)
|
27
|
+
|
28
|
+
service_code_name = s['service_code_name']
|
29
|
+
version = s['service_version']
|
30
|
+
|
31
|
+
operation_code_name = s['operation_code_name']
|
32
|
+
operation_namespace = s['operation_namespace']
|
33
|
+
status_code = s['status_code']
|
34
|
+
body = s['body']
|
35
|
+
|
36
|
+
Response::Builder.build(status_code, body, service_code_name, version,
|
37
|
+
operation_namespace, operation_code_name)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
require 'angus/sdoc'
|
5
|
+
|
6
|
+
require_relative 'builder'
|
7
|
+
require_relative 'exceptions'
|
8
|
+
|
9
|
+
module Angus
|
10
|
+
module Remote
|
11
|
+
|
12
|
+
module ServiceDirectory
|
13
|
+
|
14
|
+
# Path to the configuration file that has the information about the
|
15
|
+
# doc_url and api_url
|
16
|
+
SERVICES_CONFIGURATION_FILE = 'config/services.yml'
|
17
|
+
|
18
|
+
# Builds and returns a Client object for the service and version received
|
19
|
+
def self.lookup(code_name, version = nil)
|
20
|
+
|
21
|
+
version ||= service_version(code_name)
|
22
|
+
|
23
|
+
@clients_cache ||= {}
|
24
|
+
if @clients_cache.include?([code_name, version])
|
25
|
+
return @clients_cache[[code_name, version]]
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
service_definition = self.service_definition(code_name, version)
|
30
|
+
client = Angus::Remote::Builder.build(code_name, service_definition,
|
31
|
+
self.api_url(code_name, version))
|
32
|
+
@clients_cache[[code_name, version]] = client
|
33
|
+
|
34
|
+
rescue Errno::ECONNREFUSED
|
35
|
+
raise RemoteConnectionError.new(self.api_url(code_name, version))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the documentation url from the configuration file
|
40
|
+
#
|
41
|
+
# @param [String] code_name Name of the service.
|
42
|
+
# @param [String] version Version of the service.
|
43
|
+
#
|
44
|
+
# If no version given, it reads the version from the configuration file.
|
45
|
+
#
|
46
|
+
# @raise (see .service_version)
|
47
|
+
def self.doc_url(code_name, version = nil)
|
48
|
+
version ||= service_version(code_name)
|
49
|
+
|
50
|
+
config = service_configuration(code_name)
|
51
|
+
|
52
|
+
config["v#{version}"]['doc_url']
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the documentation url for proxy operations hosted by the service.
|
56
|
+
#
|
57
|
+
# @param [String] code_name Service code name.
|
58
|
+
# @param [String] version Service version.
|
59
|
+
# @param [String] remote_code_name Service which implements proxy operations.
|
60
|
+
#
|
61
|
+
# @return [String]
|
62
|
+
def self.proxy_doc_url(code_name, version, remote_code_name)
|
63
|
+
doc_url = self.doc_url(code_name, version)
|
64
|
+
|
65
|
+
"#{doc_url}/proxy/#{remote_code_name}"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the api url from the configuration file
|
69
|
+
#
|
70
|
+
# @param [String] code_name Name of the service.
|
71
|
+
# @param [String] version Version of the service.
|
72
|
+
#
|
73
|
+
# If no version given, it reads the version from the configuration file.
|
74
|
+
#
|
75
|
+
# @raise (see .service_version)
|
76
|
+
def self.api_url(code_name, version = nil)
|
77
|
+
version ||= service_version(code_name)
|
78
|
+
|
79
|
+
config = service_configuration(code_name)
|
80
|
+
|
81
|
+
config["v#{version}"]["api_url"]
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns the configured version.
|
85
|
+
#
|
86
|
+
# @param [String] code_name Service name
|
87
|
+
#
|
88
|
+
# @return [String] Version. Ex: 0.1
|
89
|
+
#
|
90
|
+
# @raise [TooManyServiceVersions] When there are more than one configured version.
|
91
|
+
def self.service_version(code_name)
|
92
|
+
versions = service_configuration(code_name).keys
|
93
|
+
|
94
|
+
if versions.length == 1
|
95
|
+
versions.first.gsub(/^v/, '')
|
96
|
+
else
|
97
|
+
raise TooManyServiceVersions.new(code_name)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns the connection configuration for a given service.
|
102
|
+
#
|
103
|
+
# @param [String] code_name Service name
|
104
|
+
#
|
105
|
+
# @return [Hash]
|
106
|
+
#
|
107
|
+
# @raise [ServiceConfigurationNotFound] When no configuration for the given service
|
108
|
+
def self.service_configuration(code_name)
|
109
|
+
@services_configuration ||= YAML.load_file(SERVICES_CONFIGURATION_FILE)
|
110
|
+
|
111
|
+
@services_configuration[code_name] or
|
112
|
+
raise ServiceConfigurationNotFound.new(code_name)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Returns the service's definition for the given service name and version
|
116
|
+
#
|
117
|
+
# @param [String] code_name Service that acts as a proxy.
|
118
|
+
# @param [String] version Service version.
|
119
|
+
#
|
120
|
+
# @raise (see .service_version)
|
121
|
+
#
|
122
|
+
# @return [Angus::SDoc::Definitions::Service]
|
123
|
+
def self.service_definition(code_name, version = nil)
|
124
|
+
version ||= service_version(code_name)
|
125
|
+
|
126
|
+
@service_definitions_cache ||= {}
|
127
|
+
if @service_definitions_cache.include?([code_name, version])
|
128
|
+
return @service_definitions_cache[[code_name, version]]
|
129
|
+
end
|
130
|
+
|
131
|
+
service_definition = self.get_service_definition(code_name, version)
|
132
|
+
@service_definitions_cache[[code_name, version]] = service_definition
|
133
|
+
end
|
134
|
+
|
135
|
+
# Queries a service for definitions of proxy operations for the given remote service.
|
136
|
+
#
|
137
|
+
# Merges those definitions and returns the result.
|
138
|
+
#
|
139
|
+
# @param [String] code_name Service that acts as a proxy.
|
140
|
+
# @param [String] version Service version.
|
141
|
+
# @param [String] remote_code_name Remote service that implements operations for
|
142
|
+
# the proxy service
|
143
|
+
#
|
144
|
+
# @return [Angus::SDoc::Definitions::Service]
|
145
|
+
def self.join_proxy(code_name, version, remote_code_name)
|
146
|
+
|
147
|
+
service_definition = self.service_definition(code_name, version)
|
148
|
+
|
149
|
+
@service_definitions_proxies ||= []
|
150
|
+
if @service_definitions_proxies.include?([code_name, version, remote_code_name])
|
151
|
+
return service_definition
|
152
|
+
end
|
153
|
+
|
154
|
+
proxy_doc_url = self.proxy_doc_url(code_name, version, remote_code_name)
|
155
|
+
|
156
|
+
definition_hash = fetch_remote_service_definition(proxy_doc_url)
|
157
|
+
|
158
|
+
proxy_service_definition = Angus::SDoc::DefinitionsReader.build_service_definition(definition_hash)
|
159
|
+
|
160
|
+
service_definition.merge(proxy_service_definition)
|
161
|
+
|
162
|
+
service_definition
|
163
|
+
end
|
164
|
+
|
165
|
+
# Requests a service definition.
|
166
|
+
#
|
167
|
+
# @param [String] code_name Service code name
|
168
|
+
# @param [String] version Service version
|
169
|
+
#
|
170
|
+
# @raise (see .service_version)
|
171
|
+
#
|
172
|
+
# @return [Angus::SDoc::Definitions::Service]
|
173
|
+
def self.get_service_definition(code_name, version = nil)
|
174
|
+
version ||= service_version(code_name)
|
175
|
+
doc_url = self.doc_url(code_name, version)
|
176
|
+
|
177
|
+
if doc_url.match('file://(.*)') || doc_url.match('file:///(.*)')
|
178
|
+
Angus::SDoc::DefinitionsReader.service_definition($1)
|
179
|
+
else
|
180
|
+
definition_hash = fetch_remote_service_definition(doc_url)
|
181
|
+
Angus::SDoc::DefinitionsReader.build_service_definition(definition_hash)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Fetches a service definition from a remote http uri.
|
186
|
+
#
|
187
|
+
# @param [String] uri URI that publishes a service definition.
|
188
|
+
# That uri should handle JSON format.
|
189
|
+
#
|
190
|
+
# @return [Hash] Service definition hash
|
191
|
+
def self.fetch_remote_service_definition(uri)
|
192
|
+
uri = URI(uri)
|
193
|
+
uri.query = URI.encode_www_form({:format => :json})
|
194
|
+
|
195
|
+
connection = Net::HTTP.new(uri.host, uri.port)
|
196
|
+
|
197
|
+
if uri.scheme == 'https'
|
198
|
+
connection.use_ssl = true
|
199
|
+
connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
200
|
+
end
|
201
|
+
|
202
|
+
response = connection.start do |http|
|
203
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
204
|
+
|
205
|
+
http.request(request)
|
206
|
+
end
|
207
|
+
|
208
|
+
JSON(response.body)
|
209
|
+
rescue Exception
|
210
|
+
raise RemoteConnectionError.new(uri)
|
211
|
+
end
|
212
|
+
private_class_method :fetch_remote_service_definition
|
213
|
+
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
require_relative 'exceptions'
|
5
|
+
require_relative 'http/query_params'
|
6
|
+
require_relative 'http/multipart'
|
7
|
+
require_relative 'http/multipart_methods/multipart_put'
|
8
|
+
require_relative 'http/multipart_methods/multipart_post'
|
9
|
+
|
10
|
+
module Angus
|
11
|
+
module Remote
|
12
|
+
|
13
|
+
module Utils
|
14
|
+
|
15
|
+
HTTP_METHODS_WITH_BODY = %w(post put)
|
16
|
+
|
17
|
+
RE_PATH_PARAM = /:\w+/
|
18
|
+
|
19
|
+
SEVERE_STATUS_CODES = %w(500 501 503)
|
20
|
+
|
21
|
+
# Builds a request for the given method, path and params.
|
22
|
+
#
|
23
|
+
# @param [String] method
|
24
|
+
# @param [String] path
|
25
|
+
# @param [String] request_params
|
26
|
+
# @param [String] encode_as_json
|
27
|
+
#
|
28
|
+
# @return (see .build_base_request)
|
29
|
+
def self.build_request(method, path, request_params = {}, encode_as_json = false)
|
30
|
+
if encode_as_json
|
31
|
+
build_json_request(method, path, request_params)
|
32
|
+
else
|
33
|
+
build_normal_request(method, path, request_params)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.build_normal_request(method, path, params)
|
38
|
+
multipart_request = Http::Multipart.hash_contains_files?(params)
|
39
|
+
|
40
|
+
params = if multipart_request
|
41
|
+
Http::Multipart::QUERY_STRING_NORMALIZER.call(params)
|
42
|
+
else
|
43
|
+
Http::QueryParams.to_params(params)
|
44
|
+
end
|
45
|
+
|
46
|
+
if HTTP_METHODS_WITH_BODY.include?(method)
|
47
|
+
request = build_base_request(method, path, multipart_request)
|
48
|
+
request.body = params
|
49
|
+
else
|
50
|
+
uri = URI(path)
|
51
|
+
uri.query = params
|
52
|
+
|
53
|
+
request = build_base_request(method, uri.to_s)
|
54
|
+
end
|
55
|
+
|
56
|
+
request
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def self.build_base_request(method, path, multipart_request = false)
|
61
|
+
case method.to_s.downcase
|
62
|
+
when 'get'
|
63
|
+
Net::HTTP::Get.new(path)
|
64
|
+
when 'post'
|
65
|
+
multipart_request ? Http::MultipartMethods::Post.new(path) : Net::HTTP::Post.new(path)
|
66
|
+
when 'put'
|
67
|
+
multipart_request ? Http::MultipartMethods::Put.new(path) : Net::HTTP::Put.new(path)
|
68
|
+
when 'delete'
|
69
|
+
Net::HTTP::Delete.new(path)
|
70
|
+
else
|
71
|
+
raise MethodArgumentError.new(method)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.build_json_request(method, path, params)
|
76
|
+
request = build_base_request(method, path)
|
77
|
+
request.body = JSON(params)
|
78
|
+
request['Content-Type'] = 'application/json'
|
79
|
+
|
80
|
+
request
|
81
|
+
end
|
82
|
+
|
83
|
+
# Builds the URI path. It applies the params to the path
|
84
|
+
#
|
85
|
+
# @param [String] path the path with place holders
|
86
|
+
# @param [Array<String>] path_params Array of params to be used as values in the path
|
87
|
+
#
|
88
|
+
# @return [String] the URI path
|
89
|
+
#
|
90
|
+
# @raise ArgumentError when the length of path_params doesn't match the count of placeholders
|
91
|
+
#
|
92
|
+
# @example
|
93
|
+
# path = "/users/:user_id/profile/:profile_id"
|
94
|
+
# path_params = [4201, 2]
|
95
|
+
#
|
96
|
+
# build_path(path, path_params) #=> "/users/4201/profile/2"
|
97
|
+
def self.build_path(path, path_params)
|
98
|
+
matches = path.scan(RE_PATH_PARAM)
|
99
|
+
if matches.length != path_params.length
|
100
|
+
raise PathArgumentError.new(path_params.length, matches.length)
|
101
|
+
end
|
102
|
+
|
103
|
+
matches.each_with_index do |match, index|
|
104
|
+
path = path.sub(match, path_params[index].to_s)
|
105
|
+
end
|
106
|
+
|
107
|
+
path
|
108
|
+
end
|
109
|
+
|
110
|
+
# @param [#code] response
|
111
|
+
def self.severe_error_response?(response)
|
112
|
+
status_code = response.code.to_s
|
113
|
+
SEVERE_STATUS_CODES.include?(status_code)
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
data/lib/angus/remote/version.rb
CHANGED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
|
5
|
+
module Angus
|
6
|
+
module Unmarshalling
|
7
|
+
|
8
|
+
def self.unmarshal_scalar(scalar, type)
|
9
|
+
return nil if scalar.nil?
|
10
|
+
|
11
|
+
case type
|
12
|
+
when :string
|
13
|
+
#scalar.force_encoding(Encoding::UTF_8)
|
14
|
+
scalar
|
15
|
+
when :integer
|
16
|
+
scalar
|
17
|
+
when :boolean
|
18
|
+
scalar
|
19
|
+
when :date
|
20
|
+
Date.iso8601(scalar)
|
21
|
+
when :date_time
|
22
|
+
DateTime.iso8601(scalar)
|
23
|
+
when :decimal
|
24
|
+
BigDecimal.new(scalar)
|
25
|
+
when :object
|
26
|
+
scalar
|
27
|
+
else
|
28
|
+
raise ArgumentError, "Unkonwn type: #{type}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|