angus 0.0.6 → 0.0.7
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/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
|