angus 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/angus/base.rb +16 -5
- data/lib/angus/base_actions.rb +3 -1
- data/lib/angus/definition_reader.rb +2 -2
- data/lib/angus/generator.rb +1 -1
- data/lib/angus/generator/templates/{Gemfile → Gemfile.erb} +1 -1
- data/lib/angus/generator/templates/config.ru.erb +1 -1
- data/lib/angus/marshallings/base.rb +1 -1
- data/lib/angus/marshallings/exceptions.rb +17 -0
- data/lib/angus/marshallings/marshalling.rb +13 -31
- data/lib/angus/middleware/exception_handler.rb +7 -9
- data/lib/angus/proxy_actions.rb +75 -0
- data/lib/angus/renders/html_render.rb +1 -1
- data/lib/angus/request_handler.rb +10 -4
- data/lib/angus/resource_definition.rb +2 -3
- data/lib/angus/response.rb +3 -3
- data/lib/angus/responses.rb +28 -128
- data/lib/angus/status_codes.rb +6 -6
- data/lib/angus/version.rb +1 -1
- data/spec/angus/middleware/exception_handler_spec.rb +1 -1
- data/spec/functional/basic/definitions/messages.yml +12 -1
- data/spec/functional/basic/definitions/representations.yml +20 -4
- data/spec/functional/basic/definitions/users/operations.yml +27 -2
- data/spec/functional/basic/models/user.rb +1 -0
- data/spec/functional/basic/resources/users.rb +21 -2
- data/spec/functional/basic_spec.rb +113 -4
- data/spec/functional/empty_resource_spec.rb +2 -4
- data/spec/functional/no_resources_spec.rb +2 -4
- metadata +9 -6
- data/lib/angus/marshallings/unmarshalling.rb +0 -29
data/lib/angus/base.rb
CHANGED
@@ -9,6 +9,7 @@ require_relative 'responses'
|
|
9
9
|
require_relative 'renders/base'
|
10
10
|
require_relative 'marshallings/base'
|
11
11
|
require_relative 'base_actions'
|
12
|
+
require_relative 'proxy_actions'
|
12
13
|
require_relative 'definition_reader'
|
13
14
|
|
14
15
|
require 'angus/sdoc'
|
@@ -16,16 +17,20 @@ require 'angus/sdoc'
|
|
16
17
|
module Angus
|
17
18
|
class Base < RequestHandler
|
18
19
|
include BaseActions
|
20
|
+
include ProxyActions
|
19
21
|
|
20
22
|
FIRST_VERSION = '0.1'
|
21
23
|
|
22
|
-
PRODUCTION_ENV
|
23
|
-
DEVELOPMENT_ENV
|
24
|
-
TEST_ENV
|
25
|
-
DEFAULT_ENV
|
24
|
+
PRODUCTION_ENV = :production
|
25
|
+
DEVELOPMENT_ENV = :development
|
26
|
+
TEST_ENV = :test
|
27
|
+
DEFAULT_ENV = DEVELOPMENT_ENV
|
28
|
+
DEFAULT_DOC_LANGUAGE = :en
|
26
29
|
|
27
30
|
attr_reader :definitions
|
28
31
|
|
32
|
+
attr_accessor :default_doc_language
|
33
|
+
|
29
34
|
def initialize
|
30
35
|
super
|
31
36
|
|
@@ -35,9 +40,16 @@ module Angus
|
|
35
40
|
@configured = false
|
36
41
|
@definitions = nil
|
37
42
|
@logger = Logger.new(STDOUT)
|
43
|
+
@default_doc_language = DEFAULT_DOC_LANGUAGE
|
38
44
|
|
39
45
|
configure!
|
40
46
|
|
47
|
+
after_configure
|
48
|
+
end
|
49
|
+
|
50
|
+
def after_configure
|
51
|
+
super
|
52
|
+
|
41
53
|
register_base_routes
|
42
54
|
register_resources_routes
|
43
55
|
end
|
@@ -113,7 +125,6 @@ module Angus
|
|
113
125
|
response = Response.new
|
114
126
|
|
115
127
|
resource = resource_definition.resource_class.new(request, params)
|
116
|
-
response['Content-Type'] = 'application/json'
|
117
128
|
|
118
129
|
op_response = resource.send(operation.code_name)
|
119
130
|
op_response = {} unless op_response.is_a?(Hash)
|
data/lib/angus/base_actions.rb
CHANGED
@@ -27,7 +27,9 @@ module Angus
|
|
27
27
|
if params[:format] == 'json'
|
28
28
|
render(response, Angus::SDoc::JsonFormatter.format_service(@definitions), format: :json)
|
29
29
|
else
|
30
|
-
|
30
|
+
language = params[:lang] || self.default_doc_language
|
31
|
+
render(response, Angus::SDoc::HtmlFormatter.format_service(@definitions, language),
|
32
|
+
format: :html)
|
31
33
|
end
|
32
34
|
end
|
33
35
|
end
|
@@ -3,8 +3,8 @@ module Angus
|
|
3
3
|
|
4
4
|
attr_reader :definitions
|
5
5
|
|
6
|
-
def initialize
|
7
|
-
@definitions = SDoc::DefinitionsReader.service_definition('definitions')
|
6
|
+
def initialize(definitions = nil)
|
7
|
+
@definitions = definitions || SDoc::DefinitionsReader.service_definition('definitions')
|
8
8
|
end
|
9
9
|
|
10
10
|
def message_definition(key, level)
|
data/lib/angus/generator.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
require_relative 'marshalling'
|
2
|
-
require_relative '
|
2
|
+
require_relative 'exceptions'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Angus
|
2
|
+
module Marshalling
|
3
|
+
|
4
|
+
class InvalidGetterError < StandardError
|
5
|
+
|
6
|
+
def initialize(getter, bc_first_line = nil)
|
7
|
+
@getter = getter
|
8
|
+
@bc_first_line = bc_first_line
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
"The requested getter (#@getter) does not exist. #@bc_first_line"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -4,26 +4,6 @@ require 'date'
|
|
4
4
|
module Angus
|
5
5
|
module Marshalling
|
6
6
|
|
7
|
-
# Marshal an object
|
8
|
-
#
|
9
|
-
# This method is intended for scalar objects and arrays of scalar objects.
|
10
|
-
#
|
11
|
-
# For more complex objects:
|
12
|
-
# @see Angus::Marshalling.marshal_object
|
13
|
-
#
|
14
|
-
# @param object The object to be marshalled
|
15
|
-
# @return [Array] An object suitable for be converted easily to JSON notation
|
16
|
-
#
|
17
|
-
# If {object} is an array, this method returns an array with all the elements marshalled
|
18
|
-
# Else returns a marshalled representation of the object.
|
19
|
-
def self.marshal(object)
|
20
|
-
if object.is_a?(Array)
|
21
|
-
object.map { |element| marshal(element) }
|
22
|
-
else
|
23
|
-
marshal_scalar(object)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
7
|
# Marshal a complex object.
|
28
8
|
#
|
29
9
|
# @param object The object to be marshalled
|
@@ -36,8 +16,11 @@ module Angus
|
|
36
16
|
key = getter.keys[0]
|
37
17
|
value = get_value(object, key)
|
38
18
|
|
39
|
-
#
|
40
|
-
|
19
|
+
#HACK to support ActiveRecord::Relation
|
20
|
+
if defined?(ActiveRecord::Relation) && value.is_a?(ActiveRecord::Relation)
|
21
|
+
value = value.to_a
|
22
|
+
end
|
23
|
+
|
41
24
|
if value.is_a?(Array)
|
42
25
|
result[key] = value.map { |object| marshal_object(object, getter.values[0]) }
|
43
26
|
else
|
@@ -57,8 +40,8 @@ module Angus
|
|
57
40
|
#
|
58
41
|
# @param [Object] object The object to get the value from
|
59
42
|
# @param [Symbol, String] getter to request from the object
|
60
|
-
# @raise [InvalidGetterError] when getter is not present
|
61
|
-
#
|
43
|
+
# @raise [InvalidGetterError] when getter is not present in the object
|
44
|
+
#
|
62
45
|
# @return [Object] the requested value
|
63
46
|
def self.get_value(object, getter)
|
64
47
|
if object.is_a?(Hash)
|
@@ -72,8 +55,9 @@ module Angus
|
|
72
55
|
#
|
73
56
|
# @param [Object] object The object to get the value from
|
74
57
|
# @param [Symbol, String] method the method to invoke in the object
|
75
|
-
#
|
76
|
-
# to method as public.
|
58
|
+
#
|
59
|
+
# @raise [InvalidGetterError] when the object does not responds to method as public.
|
60
|
+
#
|
77
61
|
# @return [Object] the requested value
|
78
62
|
def self.get_value_from_object(object, method)
|
79
63
|
value = object.public_send method.to_sym
|
@@ -85,9 +69,8 @@ module Angus
|
|
85
69
|
else
|
86
70
|
value
|
87
71
|
end
|
88
|
-
|
89
|
-
|
90
|
-
# raise InvalidGetterError.new(method, error.backtrace.first)
|
72
|
+
rescue NoMethodError => error
|
73
|
+
raise Marshalling::InvalidGetterError.new(method, error.backtrace.first)
|
91
74
|
end
|
92
75
|
|
93
76
|
# Gets a value from a hash for a given key.
|
@@ -106,8 +89,7 @@ module Angus
|
|
106
89
|
elsif hash.has_key?(key.to_s)
|
107
90
|
hash[key.to_s]
|
108
91
|
else
|
109
|
-
|
110
|
-
raise NoMethodError.new(key.to_s)
|
92
|
+
raise Marshalling::InvalidGetterError.new(key)
|
111
93
|
end
|
112
94
|
end
|
113
95
|
|
@@ -8,9 +8,9 @@ module Angus
|
|
8
8
|
class ExceptionHandler
|
9
9
|
include Angus::StatusCodes
|
10
10
|
|
11
|
-
def initialize(app)
|
11
|
+
def initialize(app, definitions = nil)
|
12
12
|
@app = app
|
13
|
-
@definition_reader = Angus::DefinitionReader.new
|
13
|
+
@definition_reader = Angus::DefinitionReader.new(definitions)
|
14
14
|
end
|
15
15
|
|
16
16
|
def call(env)
|
@@ -29,7 +29,6 @@ module Angus
|
|
29
29
|
|
30
30
|
private
|
31
31
|
|
32
|
-
|
33
32
|
# Builds a service error response
|
34
33
|
def build_error_response(error)
|
35
34
|
error_messages = messages_from_error(error)
|
@@ -37,7 +36,6 @@ module Angus
|
|
37
36
|
JsonRender.convert(:status => :error, :messages => error_messages)
|
38
37
|
end
|
39
38
|
|
40
|
-
|
41
39
|
# Returns an array of messages errors to be sent in an operation response
|
42
40
|
#
|
43
41
|
# If {error} respond_to? :errors then the method returns one error message
|
@@ -59,16 +57,15 @@ module Angus
|
|
59
57
|
messages << {:level => level, :key => key, :dsc => description}
|
60
58
|
end
|
61
59
|
elsif error.respond_to?(:error_key)
|
62
|
-
messages << {:level => level, :key => error.error_key,
|
63
|
-
|
60
|
+
messages << { :level => level, :key => error.error_key,
|
61
|
+
:dsc => error_message(error) }
|
64
62
|
else
|
65
|
-
messages << {:level => level, :key => error.class.name, :dsc => error.message}
|
63
|
+
messages << { :level => level, :key => error.class.name, :dsc => error.message }
|
66
64
|
end
|
67
65
|
|
68
66
|
messages
|
69
67
|
end
|
70
68
|
|
71
|
-
|
72
69
|
# Returns the message for an error.
|
73
70
|
#
|
74
71
|
# It first tries to get the message from text attribute of the error definition
|
@@ -103,7 +100,8 @@ module Angus
|
|
103
100
|
|
104
101
|
# Returns a suitable HTTP status code for the given error
|
105
102
|
#
|
106
|
-
# If error param responds to #errors, then #{HTTP_STATUS_CODE_CONFLICT} will
|
103
|
+
# If error param responds to #errors, then #{StatusCodes::HTTP_STATUS_CODE_CONFLICT} will
|
104
|
+
# be returned.
|
107
105
|
#
|
108
106
|
# If error param responds to #error_key, then the status_code associated
|
109
107
|
# with the message will be returned.
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Angus
|
2
|
+
module ProxyActions
|
3
|
+
|
4
|
+
def after_configure
|
5
|
+
register_proxy_actions
|
6
|
+
register_proxy_routes
|
7
|
+
end
|
8
|
+
|
9
|
+
def register_proxy_routes
|
10
|
+
@definitions.proxy_operations.each do |proxy_operation|
|
11
|
+
register_proxy_route(proxy_operation)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def register_proxy_actions
|
16
|
+
router.on(:get, File.join(doc_path, '/proxy/:service')) do |env, params|
|
17
|
+
require 'picasso-remote'
|
18
|
+
response = Response.new
|
19
|
+
|
20
|
+
service = params[:service]
|
21
|
+
|
22
|
+
remote_definition = Picasso::Remote::ServiceDirectory.service_definition(service)
|
23
|
+
|
24
|
+
render(response, Picasso::SDoc::JsonFormatter.format_service(remote_definition),
|
25
|
+
format: :json)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def register_proxy_route(proxy_operation)
|
30
|
+
require 'picasso-remote'
|
31
|
+
|
32
|
+
remote_api_uri = Picasso::Remote::ServiceDirectory.api_url(proxy_operation.service_name)
|
33
|
+
|
34
|
+
proxy_client = get_proxy_client(remote_api_uri)
|
35
|
+
|
36
|
+
op_path = "#{api_path}#{proxy_operation.path}"
|
37
|
+
|
38
|
+
router.on(proxy_operation.http_method.to_sym, op_path) do |env, params|
|
39
|
+
request = Rack::Request.new(env)
|
40
|
+
request.body.rewind
|
41
|
+
raw_body = request.body.read
|
42
|
+
|
43
|
+
proxy_path = env['PATH_INFO'].gsub(api_path, '')
|
44
|
+
|
45
|
+
status, headers, body = proxy_client.make_request(
|
46
|
+
proxy_operation.http_method,
|
47
|
+
proxy_path,
|
48
|
+
env['QUERY_STRING'],
|
49
|
+
{},
|
50
|
+
raw_body
|
51
|
+
)
|
52
|
+
|
53
|
+
Response.new(body, status, headers)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.remote_operation(service_code_name, operation_code_name)
|
58
|
+
remote_service = Picasso::Remote::ServiceDirectory.service_definition(service_code_name)
|
59
|
+
remote_service.operation_definition(operation_code_name)
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_proxy_client(url)
|
63
|
+
proxy_clients[url] ||= build_proxy_client(url)
|
64
|
+
end
|
65
|
+
|
66
|
+
def build_proxy_client(url)
|
67
|
+
Angus::Remote::ProxyClient.new(url)
|
68
|
+
end
|
69
|
+
|
70
|
+
def proxy_clients
|
71
|
+
@proxy_clients ||= {}
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -31,10 +31,16 @@ module Angus
|
|
31
31
|
|
32
32
|
def to_app
|
33
33
|
inner_app = lambda { |env| self.dup.call!(env) }
|
34
|
-
@middleware.reverse.inject(inner_app) do |app, middleware|
|
34
|
+
@app ||= @middleware.reverse.inject(inner_app) do |app, middleware|
|
35
35
|
klass, args, block = middleware
|
36
36
|
|
37
|
-
|
37
|
+
# HACK to improve performance for now, in reality Middleware::ExceptionHandler should get
|
38
|
+
# the doc from a know place or the documentation should be available to all middleware.
|
39
|
+
if klass == Middleware::ExceptionHandler
|
40
|
+
klass.new(app, @definitions)
|
41
|
+
else
|
42
|
+
klass.new(app, *args, &block)
|
43
|
+
end
|
38
44
|
end
|
39
45
|
end
|
40
46
|
|
@@ -54,7 +60,7 @@ module Angus
|
|
54
60
|
response.finish
|
55
61
|
end
|
56
62
|
|
57
|
-
# TODO
|
63
|
+
# TODO add more formats in the future.
|
58
64
|
def render(response, content, options = {})
|
59
65
|
format = options[:format] || DEFAULT_RENDER
|
60
66
|
case(format)
|
@@ -96,4 +102,4 @@ module Angus
|
|
96
102
|
end
|
97
103
|
|
98
104
|
end
|
99
|
-
end
|
105
|
+
end
|
@@ -50,10 +50,9 @@ module Angus
|
|
50
50
|
result
|
51
51
|
else
|
52
52
|
field_name = response_representation.name
|
53
|
-
|
53
|
+
field_type_name = response_representation.type || response_representation.elements_type
|
54
54
|
|
55
|
-
|
56
|
-
representation = representation_by_name(field_type)
|
55
|
+
representation = representation_by_name(field_type_name)
|
57
56
|
|
58
57
|
if representation.nil?
|
59
58
|
field_name.to_sym
|
data/lib/angus/response.rb
CHANGED
@@ -2,7 +2,7 @@ module Angus
|
|
2
2
|
class Response < Rack::Response
|
3
3
|
def initialize(*)
|
4
4
|
super
|
5
|
-
headers['Content-Type'] ||= '
|
5
|
+
headers['Content-Type'] ||= 'application/json'
|
6
6
|
end
|
7
7
|
|
8
8
|
def body=(value)
|
@@ -18,8 +18,8 @@ module Angus
|
|
18
18
|
result = body
|
19
19
|
|
20
20
|
if drop_content_info?
|
21
|
-
headers.delete
|
22
|
-
headers.delete
|
21
|
+
headers.delete('Content-Length')
|
22
|
+
headers.delete'Content-Type'
|
23
23
|
end
|
24
24
|
|
25
25
|
if drop_body?
|
data/lib/angus/responses.rb
CHANGED
@@ -6,27 +6,6 @@ module Angus
|
|
6
6
|
module Responses
|
7
7
|
include Angus::StatusCodes
|
8
8
|
|
9
|
-
# Returns the error definition.
|
10
|
-
#
|
11
|
-
# If the error does not responds to error_key nil will be returned, see EvolutionError.
|
12
|
-
#
|
13
|
-
# @param [#error_key] error An error object
|
14
|
-
#
|
15
|
-
# @return [Hash]
|
16
|
-
def get_error_definition(error)
|
17
|
-
error_key = error.class.name
|
18
|
-
|
19
|
-
get_message_definition(error_key, Angus::SDoc::Definitions::Message::ERROR_LEVEL)
|
20
|
-
end
|
21
|
-
|
22
|
-
def get_message_definition(key, level)
|
23
|
-
message = @definitions.messages.find { |name, definition|
|
24
|
-
name == key.to_s && definition.level.downcase == level.downcase
|
25
|
-
}
|
26
|
-
|
27
|
-
message.last if message
|
28
|
-
end
|
29
|
-
|
30
9
|
# Builds a service success response
|
31
10
|
#
|
32
11
|
# @param [Hash<Symbol, Object>] messages Elements to be sent in the response
|
@@ -45,44 +24,6 @@ module Angus
|
|
45
24
|
json(elements)
|
46
25
|
end
|
47
26
|
|
48
|
-
# Builds a ResponseMessage object
|
49
|
-
#
|
50
|
-
# @param [#to_s] key Message key
|
51
|
-
# @param [#to_s] level Message level
|
52
|
-
# @param [*Object] params Objects to be used when formatting the message description
|
53
|
-
#
|
54
|
-
# @raise [NameError] when there's no message for the given key and level
|
55
|
-
#
|
56
|
-
# @return [ResponseMessage]
|
57
|
-
def build_message(key, level, *params)
|
58
|
-
message_definition = get_message_definition(key, level)
|
59
|
-
|
60
|
-
unless message_definition
|
61
|
-
raise NameError.new("Could not found message with key: #{key}, level: #{level}")
|
62
|
-
end
|
63
|
-
|
64
|
-
description = if message_definition.text
|
65
|
-
message_definition.text % params
|
66
|
-
else
|
67
|
-
message_definition.description
|
68
|
-
end
|
69
|
-
|
70
|
-
Angus::SDoc::Definitions::Message
|
71
|
-
|
72
|
-
message = Angus::SDoc::Definitions::Message.new
|
73
|
-
message.key = key
|
74
|
-
message.level = level
|
75
|
-
message.description = description
|
76
|
-
|
77
|
-
message
|
78
|
-
end
|
79
|
-
|
80
|
-
# Builds a service success response
|
81
|
-
def build_warning_response(error)
|
82
|
-
error_messages = messages_from_error(error, :warning)
|
83
|
-
build_response(:success, *error_messages)
|
84
|
-
end
|
85
|
-
|
86
27
|
# Builds a success response with the received data
|
87
28
|
#
|
88
29
|
# @param [Hash] data the hash to be returned in the response
|
@@ -96,21 +37,6 @@ module Angus
|
|
96
37
|
build_success_response(marshalled_data, messages)
|
97
38
|
end
|
98
39
|
|
99
|
-
# Builds a success response with no elements
|
100
|
-
#
|
101
|
-
# The response would include the following:
|
102
|
-
# - status
|
103
|
-
# - messages
|
104
|
-
#
|
105
|
-
# @param [Array<ResponseMessage>, Array<Symbol>] messages Message to be included in the response
|
106
|
-
#
|
107
|
-
# @return [String] JSON response
|
108
|
-
def build_no_data_response(messages = [])
|
109
|
-
messages = build_messages(Angus::SDoc::Definitions::Message::INFO_LEVEL, messages)
|
110
|
-
|
111
|
-
build_success_response({}, messages)
|
112
|
-
end
|
113
|
-
|
114
40
|
# Builds a list of messages with the following level
|
115
41
|
#
|
116
42
|
# ResponseMessage objects contained in messages param won't be modified, this method
|
@@ -124,72 +50,46 @@ module Angus
|
|
124
50
|
# @return [Array<ResponseMessage>]
|
125
51
|
def build_messages(level, messages)
|
126
52
|
(messages || []).map do |message|
|
127
|
-
|
128
|
-
message
|
129
|
-
else
|
130
|
-
build_message(message, level)
|
131
|
-
end
|
53
|
+
build_message(message, level)
|
132
54
|
end
|
133
55
|
end
|
134
56
|
|
135
|
-
#
|
136
|
-
def json(element)
|
137
|
-
#content_type :json
|
138
|
-
JSON(element, :ascii_only => true)
|
139
|
-
end
|
140
|
-
|
141
|
-
private
|
142
|
-
# Returns an array of messages errors to be sent in an operation response
|
143
|
-
#
|
144
|
-
# If {error} respond_to? :errors then the method returns one error message
|
145
|
-
# for each one.
|
57
|
+
# Builds a ResponseMessage object
|
146
58
|
#
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
# - description
|
59
|
+
# @param [#to_s] key Message key
|
60
|
+
# @param [#to_s] level Message level
|
61
|
+
# @param [*Object] params Objects to be used when formatting the message description
|
151
62
|
#
|
152
|
-
# @
|
63
|
+
# @raise [NameError] when there's no message for the given key and level
|
153
64
|
#
|
154
|
-
# @return [
|
155
|
-
def
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
messages << {:level => level, :key => key, :dsc => description}
|
161
|
-
end
|
162
|
-
elsif error.respond_to?(:error_key)
|
163
|
-
messages << {:level => level, :key => error.error_key,
|
164
|
-
:dsc => error_message(error)}
|
165
|
-
else
|
166
|
-
messages << {:level => level, :key => error.class.name, :dsc => error.message}
|
65
|
+
# @return [ResponseMessage]
|
66
|
+
def build_message(key, level, *params)
|
67
|
+
message_definition = get_message_definition(key, level)
|
68
|
+
|
69
|
+
unless message_definition
|
70
|
+
raise NameError.new("Could not found message with key: #{key}, level: #{level}")
|
167
71
|
end
|
168
72
|
|
169
|
-
|
73
|
+
description = if message_definition.text
|
74
|
+
message_definition.text % params
|
75
|
+
else
|
76
|
+
message_definition.description
|
77
|
+
end
|
78
|
+
|
79
|
+
{ :level => level, :key => key, :dsc => description }
|
170
80
|
end
|
171
81
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
# @param [Exception] error The error to get the message for.
|
179
|
-
#
|
180
|
-
# @return [String] the error message.
|
181
|
-
def error_message(error)
|
182
|
-
error_definition = get_error_definition(error)
|
183
|
-
|
184
|
-
if error_definition && !error_definition.text.blank?
|
185
|
-
error_definition.text
|
186
|
-
else
|
187
|
-
error.message
|
188
|
-
end
|
82
|
+
def get_message_definition(key, level)
|
83
|
+
message = @definitions.messages.find { |name, definition|
|
84
|
+
name == key.to_s && definition.level.downcase == level.downcase
|
85
|
+
}
|
86
|
+
|
87
|
+
message.last if message
|
189
88
|
end
|
190
89
|
|
191
|
-
|
192
|
-
|
90
|
+
# Serializes +element+ as json
|
91
|
+
def json(element)
|
92
|
+
JSON(element, :ascii_only => true)
|
193
93
|
end
|
194
94
|
|
195
95
|
end
|
data/lib/angus/status_codes.rb
CHANGED
@@ -2,14 +2,14 @@ module Angus
|
|
2
2
|
module StatusCodes
|
3
3
|
|
4
4
|
# TODO remove HTTP_STATUS from all constants
|
5
|
-
HTTP_STATUS_CODE_OK
|
5
|
+
HTTP_STATUS_CODE_OK = 200
|
6
6
|
|
7
|
-
HTTP_STATUS_CODE_FORBIDDEN
|
8
|
-
HTTP_STATUS_CODE_NOT_FOUND
|
9
|
-
HTTP_STATUS_CODE_CONFLICT
|
10
|
-
HTTP_STATUS_CODE_UNPROCESSABLE_ENTITY
|
7
|
+
HTTP_STATUS_CODE_FORBIDDEN = 403
|
8
|
+
HTTP_STATUS_CODE_NOT_FOUND = 404
|
9
|
+
HTTP_STATUS_CODE_CONFLICT = 409
|
10
|
+
HTTP_STATUS_CODE_UNPROCESSABLE_ENTITY = 422
|
11
11
|
|
12
|
-
HTTP_STATUS_CODE_INTERNAL_SERVER_ERROR
|
12
|
+
HTTP_STATUS_CODE_INTERNAL_SERVER_ERROR = 500
|
13
13
|
|
14
14
|
def self.included(base)
|
15
15
|
self.constants.each do |const|
|
data/lib/angus/version.rb
CHANGED
@@ -36,7 +36,7 @@ describe Angus::Middleware::ExceptionHandler, { :work_dir => work_dir } do
|
|
36
36
|
app.stub(:call).with(any_args).and_raise(error)
|
37
37
|
end
|
38
38
|
|
39
|
-
it 'returns
|
39
|
+
it 'returns HTTP_STATUS_CODE_CONFLICT ERROR' do
|
40
40
|
response = middleware.call(env)
|
41
41
|
|
42
42
|
response.first.should eq(Angus::StatusCodes::HTTP_STATUS_CODE_CONFLICT)
|
@@ -1,4 +1,15 @@
|
|
1
1
|
UserNotFound:
|
2
2
|
status_code: 404
|
3
3
|
level: error
|
4
|
-
description: User not found
|
4
|
+
description: User not found
|
5
|
+
|
6
|
+
UserCreatedSuccessfully:
|
7
|
+
status_code: 200
|
8
|
+
level: Info
|
9
|
+
description: The User has been created successfully.
|
10
|
+
text: The User has been created successfully.
|
11
|
+
|
12
|
+
UserDeletedSuccessfully:
|
13
|
+
status_code: 200
|
14
|
+
level: Info
|
15
|
+
description: The User has been deleted successfully.
|
@@ -1,9 +1,25 @@
|
|
1
|
-
|
1
|
+
user:
|
2
2
|
- field: id
|
3
|
-
description:
|
3
|
+
description: User identifier
|
4
4
|
required: true
|
5
5
|
type: integer
|
6
6
|
- field: name
|
7
|
-
description:
|
7
|
+
description: User name
|
8
8
|
required: true
|
9
|
-
type: string
|
9
|
+
type: string
|
10
|
+
- field: last_login
|
11
|
+
description: User last login time
|
12
|
+
required: true
|
13
|
+
type: datetime
|
14
|
+
- field: birth_date
|
15
|
+
description: User birth date
|
16
|
+
required: true
|
17
|
+
type: date
|
18
|
+
- field: gender
|
19
|
+
description: User gender
|
20
|
+
required: true
|
21
|
+
type: string
|
22
|
+
- field: roles
|
23
|
+
description: User roles
|
24
|
+
required: false
|
25
|
+
elements_type: integer
|
@@ -13,7 +13,7 @@ get_user:
|
|
13
13
|
- element: profile
|
14
14
|
description: Perfil del usuario
|
15
15
|
required: true
|
16
|
-
type:
|
16
|
+
type: user
|
17
17
|
|
18
18
|
messages:
|
19
19
|
- key: UserNotFound
|
@@ -30,4 +30,29 @@ get_users:
|
|
30
30
|
- element: users
|
31
31
|
description: Perfil del usuario
|
32
32
|
required: true
|
33
|
-
elements_type:
|
33
|
+
elements_type: user
|
34
|
+
|
35
|
+
create_user:
|
36
|
+
name: Create a new user
|
37
|
+
description: >
|
38
|
+
Creates and returns a new user.
|
39
|
+
|
40
|
+
path: /users
|
41
|
+
method: post
|
42
|
+
|
43
|
+
messages:
|
44
|
+
- key: UserCreatedSuccessfully
|
45
|
+
|
46
|
+
delete_user:
|
47
|
+
name: Delete user
|
48
|
+
description: >
|
49
|
+
Deletes a user.
|
50
|
+
|
51
|
+
path: /users/:id
|
52
|
+
method: delete
|
53
|
+
uri:
|
54
|
+
- element: id
|
55
|
+
description: User identifier.
|
56
|
+
|
57
|
+
messages:
|
58
|
+
- key: UserDeletedSuccessfully
|
@@ -0,0 +1 @@
|
|
1
|
+
User = Struct.new(:id, :name, :last_login, :birth_date, :gender, :roles)
|
@@ -1,10 +1,18 @@
|
|
1
1
|
require_relative '../exceptions/user_errors'
|
2
|
+
require_relative '../models/user'
|
3
|
+
|
2
4
|
|
3
5
|
class Users < Angus::BaseResource
|
4
|
-
USERS = [{ :id => 1, :name => 'ac/dc'
|
6
|
+
USERS = [{ :id => 1, :name => 'ac/dc', 'last_login' => DateTime.now, :birth_date => Date.today,
|
7
|
+
:gender => :male, :roles => [1, 2, 3] },
|
8
|
+
User.new(2, 'madonna', DateTime.now, Date.today, :female)]
|
5
9
|
|
6
10
|
def get_user
|
7
|
-
user =
|
11
|
+
user = if params[:user_id] == '3'
|
12
|
+
{ :id => 3 }
|
13
|
+
else
|
14
|
+
USERS.find { |user| user[:id] == params[:user_id].to_i }
|
15
|
+
end
|
8
16
|
|
9
17
|
raise UserNotFound.new(params[:user_id]) unless user
|
10
18
|
|
@@ -15,4 +23,15 @@ class Users < Angus::BaseResource
|
|
15
23
|
{ :users => USERS }
|
16
24
|
end
|
17
25
|
|
26
|
+
def create_user
|
27
|
+
{ :messages => [:UserCreatedSuccessfully] }
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete_user
|
31
|
+
if params[:id] == '2'
|
32
|
+
{ :messages => [:UserAlreadyDeleted] }
|
33
|
+
else
|
34
|
+
{ :messages => [:UserDeletedSuccessfully] }
|
35
|
+
end
|
36
|
+
end
|
18
37
|
end
|
@@ -7,14 +7,20 @@ require 'functional/basic/services/basic'
|
|
7
7
|
describe Spec::Functional::Basic, { :work_dir => "#{File.dirname(__FILE__ )}/basic" } do
|
8
8
|
include Rack::Test::Methods
|
9
9
|
|
10
|
-
|
11
|
-
Spec::Functional::Basic.new
|
12
|
-
end
|
10
|
+
subject(:app) { Rack::Lint.new(Spec::Functional::Basic.new) }
|
13
11
|
|
14
12
|
it 'responds to /' do
|
15
13
|
get '/'
|
16
14
|
|
17
15
|
last_response.status.should eq(200)
|
16
|
+
last_response.header['Content-Type'].should eq('application/json')
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'responds to /' do
|
20
|
+
get '/basic'
|
21
|
+
|
22
|
+
last_response.status.should eq(200)
|
23
|
+
last_response.header['Content-Type'].should eq('application/json')
|
18
24
|
end
|
19
25
|
|
20
26
|
describe 'the documentation url' do
|
@@ -29,7 +35,7 @@ describe Spec::Functional::Basic, { :work_dir => "#{File.dirname(__FILE__ )}/bas
|
|
29
35
|
it 'sets a html content type' do
|
30
36
|
get '/basic/doc/0.1'
|
31
37
|
|
32
|
-
last_response.header['Content-Type'].should eq('text/html')
|
38
|
+
last_response.header['Content-Type'].should eq('text/html;charset=utf-8')
|
33
39
|
end
|
34
40
|
end
|
35
41
|
|
@@ -81,6 +87,109 @@ describe Spec::Functional::Basic, { :work_dir => "#{File.dirname(__FILE__ )}/bas
|
|
81
87
|
'dsc' => 'User with id=-1 not found' })}
|
82
88
|
end
|
83
89
|
end
|
90
|
+
|
91
|
+
context 'when an attribute is missing from the response' do
|
92
|
+
it 'sets the correct status code' do
|
93
|
+
get '/basic/api/0.1/users/3'
|
94
|
+
|
95
|
+
last_response.status.should eq(500)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'sets a json content type' do
|
99
|
+
get '/basic/api/0.1/users/3'
|
100
|
+
|
101
|
+
last_response.header['Content-Type'].should eq('application/json')
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'the response body' do
|
105
|
+
subject(:body) {
|
106
|
+
get '/basic/api/0.1/users/3'
|
107
|
+
JSON(last_response.body)
|
108
|
+
}
|
109
|
+
|
110
|
+
its(['status']) { should eq('error')}
|
111
|
+
its(['messages']) { should include({ 'level' => 'error', 'key' => 'Angus::Marshalling::InvalidGetterError',
|
112
|
+
'dsc' => 'The requested getter (name) does not exist. ' })}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'when a message is returned' do
|
117
|
+
context 'when the message has a given text' do
|
118
|
+
it 'sets the correct status code' do
|
119
|
+
post '/basic/api/0.1/users'
|
120
|
+
|
121
|
+
last_response.status.should eq(200)
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'sets a json content type' do
|
125
|
+
post '/basic/api/0.1/users'
|
126
|
+
|
127
|
+
last_response.header['Content-Type'].should eq('application/json')
|
128
|
+
end
|
129
|
+
|
130
|
+
describe 'the response body' do
|
131
|
+
subject(:body) {
|
132
|
+
post'/basic/api/0.1/users'
|
133
|
+
JSON(last_response.body)
|
134
|
+
}
|
135
|
+
|
136
|
+
its(['status']) { should eq('success')}
|
137
|
+
its(['messages']) { should include({ 'level' => 'info', 'key' => 'UserCreatedSuccessfully',
|
138
|
+
'dsc' => 'The User has been created successfully.' })}
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
context 'when a message does not have a given text' do
|
143
|
+
it 'sets the correct status code' do
|
144
|
+
delete '/basic/api/0.1/users/1'
|
145
|
+
|
146
|
+
last_response.status.should eq(200)
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'sets a json content type' do
|
150
|
+
delete '/basic/api/0.1/users/1'
|
151
|
+
|
152
|
+
last_response.header['Content-Type'].should eq('application/json')
|
153
|
+
end
|
154
|
+
|
155
|
+
describe 'the response body' do
|
156
|
+
subject(:body) {
|
157
|
+
delete '/basic/api/0.1/users/1'
|
158
|
+
JSON(last_response.body)
|
159
|
+
}
|
160
|
+
|
161
|
+
its(['status']) { should eq('success')}
|
162
|
+
its(['messages']) { should include({ 'level' => 'info', 'key' => 'UserDeletedSuccessfully',
|
163
|
+
'dsc' => 'The User has been deleted successfully.' })}
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context 'when an inexistent message is returned' do
|
168
|
+
it 'sets the correct status code' do
|
169
|
+
delete '/basic/api/0.1/users/2'
|
170
|
+
|
171
|
+
last_response.status.should eq(500)
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'sets a json content type' do
|
175
|
+
delete '/basic/api/0.1/users/2'
|
176
|
+
|
177
|
+
last_response.header['Content-Type'].should eq('application/json')
|
178
|
+
end
|
179
|
+
|
180
|
+
describe 'the response body' do
|
181
|
+
subject(:body) {
|
182
|
+
delete '/basic/api/0.1/users/2'
|
183
|
+
JSON(last_response.body)
|
184
|
+
}
|
185
|
+
|
186
|
+
its(['status']) { should eq('error')}
|
187
|
+
its(['messages']) { should include({ 'level' => 'error', 'key' => 'NameError',
|
188
|
+
'dsc' => 'Could not found message with key: UserAlreadyDeleted, level: info' })}
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
84
193
|
end
|
85
194
|
|
86
195
|
end
|
@@ -8,9 +8,7 @@ describe Spec::Functional::EmptyResource,
|
|
8
8
|
{ :work_dir => "#{File.dirname(__FILE__ )}/empty_resource" } do
|
9
9
|
include Rack::Test::Methods
|
10
10
|
|
11
|
-
|
12
|
-
Spec::Functional::EmptyResource.new
|
13
|
-
end
|
11
|
+
subject(:app) { Rack::Lint.new(Spec::Functional::EmptyResource.new) }
|
14
12
|
|
15
13
|
it 'responds to /' do
|
16
14
|
get '/'
|
@@ -50,7 +48,7 @@ describe Spec::Functional::EmptyResource,
|
|
50
48
|
it 'sets a html content type' do
|
51
49
|
get url
|
52
50
|
|
53
|
-
last_response.header['Content-Type'].should eq('text/html')
|
51
|
+
last_response.header['Content-Type'].should eq('text/html;charset=utf-8')
|
54
52
|
end
|
55
53
|
end
|
56
54
|
|
@@ -8,9 +8,7 @@ describe Spec::Functional::NoResources,
|
|
8
8
|
{ :work_dir => "#{File.dirname(__FILE__ )}/no_resources" } do
|
9
9
|
include Rack::Test::Methods
|
10
10
|
|
11
|
-
|
12
|
-
Spec::Functional::NoResources.new
|
13
|
-
end
|
11
|
+
subject(:app) { Rack::Lint.new(Spec::Functional::NoResources.new) }
|
14
12
|
|
15
13
|
it 'responds to /' do
|
16
14
|
get '/'
|
@@ -50,7 +48,7 @@ describe Spec::Functional::NoResources,
|
|
50
48
|
it 'sets a html content type' do
|
51
49
|
get url
|
52
50
|
|
53
|
-
last_response.header['Content-Type'].should eq('text/html')
|
51
|
+
last_response.header['Content-Type'].should eq('text/html;charset=utf-8')
|
54
52
|
end
|
55
53
|
end
|
56
54
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: angus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2013-12-
|
14
|
+
date: 2013-12-30 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: thor
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
version: '0.0'
|
40
40
|
- - ! '>='
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version: 0.0.
|
42
|
+
version: 0.0.5
|
43
43
|
type: :runtime
|
44
44
|
prerelease: false
|
45
45
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -50,7 +50,7 @@ dependencies:
|
|
50
50
|
version: '0.0'
|
51
51
|
- - ! '>='
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: 0.0.
|
53
|
+
version: 0.0.5
|
54
54
|
- !ruby/object:Gem::Dependency
|
55
55
|
name: angus-router
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
@@ -210,8 +210,8 @@ extensions: []
|
|
210
210
|
extra_rdoc_files: []
|
211
211
|
files:
|
212
212
|
- lib/angus/responses.rb
|
213
|
-
- lib/angus/marshallings/unmarshalling.rb
|
214
213
|
- lib/angus/marshallings/marshalling.rb
|
214
|
+
- lib/angus/marshallings/exceptions.rb
|
215
215
|
- lib/angus/marshallings/base.rb
|
216
216
|
- lib/angus/generator.rb
|
217
217
|
- lib/angus/status_codes.rb
|
@@ -221,8 +221,8 @@ files:
|
|
221
221
|
- lib/angus/renders/html_render.rb
|
222
222
|
- lib/angus/renders/base.rb
|
223
223
|
- lib/angus/renders/json_render.rb
|
224
|
+
- lib/angus/generator/templates/Gemfile.erb
|
224
225
|
- lib/angus/generator/templates/resources/resource.rb.erb
|
225
|
-
- lib/angus/generator/templates/Gemfile
|
226
226
|
- lib/angus/generator/templates/README.md
|
227
227
|
- lib/angus/generator/templates/config.ru.erb
|
228
228
|
- lib/angus/generator/templates/services/service.rb.erb
|
@@ -246,6 +246,7 @@ files:
|
|
246
246
|
- lib/angus/utils.rb
|
247
247
|
- lib/angus/definition_reader.rb
|
248
248
|
- lib/angus/base.rb
|
249
|
+
- lib/angus/proxy_actions.rb
|
249
250
|
- lib/angus.rb
|
250
251
|
- spec/spec_helper.rb
|
251
252
|
- spec/angus/request_handler_spec.rb
|
@@ -254,6 +255,7 @@ files:
|
|
254
255
|
- spec/functional/no_resources_spec.rb
|
255
256
|
- spec/functional/empty_resource_spec.rb
|
256
257
|
- spec/functional/basic/exceptions/user_errors.rb
|
258
|
+
- spec/functional/basic/models/user.rb
|
257
259
|
- spec/functional/basic/resources/users.rb
|
258
260
|
- spec/functional/basic/services/basic.rb
|
259
261
|
- spec/functional/basic/definitions/representations.yml
|
@@ -305,6 +307,7 @@ test_files:
|
|
305
307
|
- spec/functional/no_resources_spec.rb
|
306
308
|
- spec/functional/empty_resource_spec.rb
|
307
309
|
- spec/functional/basic/exceptions/user_errors.rb
|
310
|
+
- spec/functional/basic/models/user.rb
|
308
311
|
- spec/functional/basic/resources/users.rb
|
309
312
|
- spec/functional/basic/services/basic.rb
|
310
313
|
- spec/functional/basic/definitions/representations.yml
|
@@ -1,29 +0,0 @@
|
|
1
|
-
require 'bigdecimal'
|
2
|
-
require 'date'
|
3
|
-
|
4
|
-
module Angus
|
5
|
-
module Unmarshalling
|
6
|
-
|
7
|
-
def self.unmarshal_scalar(scalar, type)
|
8
|
-
return nil if scalar.nil?
|
9
|
-
|
10
|
-
case type
|
11
|
-
when :string
|
12
|
-
scalar
|
13
|
-
when :integer
|
14
|
-
scalar
|
15
|
-
when :boolean
|
16
|
-
scalar
|
17
|
-
when :date
|
18
|
-
Date.iso8601(scalar)
|
19
|
-
when :date_time
|
20
|
-
DateTime.iso8601(scalar)
|
21
|
-
when :decimal
|
22
|
-
BigDecimal.new(scalar.to_s)
|
23
|
-
else
|
24
|
-
raise ArgumentError, "Unknown type: #{type}"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|
29
|
-
end
|