angus-remote 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/lib/angus-remote.rb +4 -0
  2. data/lib/angus/remote/builder.rb +204 -0
  3. data/lib/angus/remote/client.rb +79 -0
  4. data/lib/angus/remote/exceptions.rb +50 -0
  5. data/lib/angus/remote/http/multipart.rb +54 -0
  6. data/lib/angus/remote/http/multipart_methods/multipart_base.rb +36 -0
  7. data/lib/angus/remote/http/multipart_methods/multipart_post.rb +11 -0
  8. data/lib/angus/remote/http/multipart_methods/multipart_put.rb +11 -0
  9. data/lib/angus/remote/http/query_params.rb +53 -0
  10. data/lib/angus/remote/message.rb +14 -0
  11. data/lib/angus/remote/proxy_client.rb +58 -0
  12. data/lib/angus/remote/proxy_client_utils.rb +70 -0
  13. data/lib/angus/remote/remote_response.rb +44 -0
  14. data/lib/angus/remote/representation.rb +18 -0
  15. data/lib/angus/remote/response/builder.rb +308 -0
  16. data/lib/angus/remote/response/hash.rb +47 -0
  17. data/lib/angus/remote/response/serializer.rb +43 -0
  18. data/lib/angus/remote/service_directory.rb +217 -0
  19. data/lib/angus/remote/utils.rb +119 -0
  20. data/lib/angus/remote/version.rb +4 -2
  21. data/lib/angus/unmarshalling.rb +33 -0
  22. data/spec/angus/remote/builder_spec.rb +105 -0
  23. data/spec/angus/remote/client_spec.rb +75 -0
  24. data/spec/angus/remote/http/multipart_methods/multipart_base_spec.rb +36 -0
  25. data/spec/angus/remote/http/multipart_spec.rb +120 -0
  26. data/spec/angus/remote/http/query_params_spec.rb +28 -0
  27. data/spec/angus/remote/proxy_client_utils_spec.rb +102 -0
  28. data/spec/angus/remote/response/builder_spec.rb +69 -0
  29. data/spec/angus/remote/service_directory_spec.rb +76 -0
  30. data/spec/angus/remote/utils_spec.rb +204 -0
  31. metadata +192 -32
  32. data/.gitignore +0 -17
  33. data/Gemfile +0 -4
  34. data/LICENSE.txt +0 -22
  35. data/README.md +0 -29
  36. data/Rakefile +0 -1
  37. data/angus-remote.gemspec +0 -23
  38. 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
@@ -1,5 +1,7 @@
1
1
  module Angus
2
2
  module Remote
3
- VERSION = "0.0.1"
3
+
4
+ VERSION = '0.0.2'
5
+
4
6
  end
5
- end
7
+ end
@@ -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