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.
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