cp-sparrow 0.0.12 → 0.0.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.ruby-version +1 -1
- data/LICENSE.txt +1 -1
- data/README.md +96 -13
- data/lib/sparrow.rb +21 -4
- data/lib/sparrow/configuration.rb +23 -2
- data/lib/sparrow/core_ext/hash.rb +2 -0
- data/lib/sparrow/dependencies.rb +15 -0
- data/lib/sparrow/http_message.rb +103 -0
- data/lib/sparrow/logger.rb +53 -0
- data/lib/sparrow/middleware.rb +44 -63
- data/lib/sparrow/request_http_message.rb +22 -0
- data/lib/sparrow/request_middleware.rb +8 -10
- data/lib/sparrow/response_http_message.rb +49 -0
- data/lib/sparrow/response_middleware.rb +32 -28
- data/lib/sparrow/response_steward.rb +13 -0
- data/lib/sparrow/route_parser.rb +28 -5
- data/lib/sparrow/steward.rb +125 -0
- data/lib/sparrow/strategies.rb +6 -0
- data/lib/sparrow/strategies/form_hash.rb +27 -11
- data/lib/sparrow/strategies/ignore.rb +33 -5
- data/lib/sparrow/strategies/json_format_strategies.rb +4 -0
- data/lib/sparrow/strategies/json_format_strategies/array_json_format_strategy.rb +23 -0
- data/lib/sparrow/strategies/json_format_strategies/default_json_format_strategy.rb +11 -2
- data/lib/sparrow/strategies/json_format_strategies/json_format_strategy.rb +21 -6
- data/lib/sparrow/strategies/json_format_strategies/rack_body_json_format_strategy.rb +22 -0
- data/lib/sparrow/strategies/key_transformation.rb +2 -0
- data/lib/sparrow/strategies/key_transformation/camelize_key.rb +45 -17
- data/lib/sparrow/strategies/key_transformation/underscore_key.rb +17 -6
- data/lib/sparrow/strategies/raw_input.rb +29 -7
- data/lib/sparrow/strategies/transform_params.rb +46 -21
- data/lib/sparrow/transformable.rb +28 -20
- data/lib/sparrow/version.rb +1 -1
- data/sparrow.gemspec +1 -1
- data/spec/integration/apps/rails_app/config/boot.rb +1 -1
- data/spec/integration/rack/camel_caser_spec.rb +2 -2
- data/spec/unit/camelize_key_spec.rb +9 -7
- data/spec/unit/configuration_spec.rb +3 -0
- data/spec/unit/http_message.rb +66 -0
- data/spec/unit/logger_spec.rb +6 -0
- data/spec/unit/{camel_caser_spec.rb → sparrow_spec.rb} +8 -2
- data/spec/unit/steward_spec.rb +31 -0
- metadata +24 -13
- data/lib/sparrow/path_normalizer.rb +0 -10
- data/lib/sparrow/strategies/json_format_strategies/array_strategy.rb +0 -17
- data/lib/sparrow/strategies/json_format_strategies/rack_body.rb +0 -17
- data/lib/sparrow/strategies/key_transformation/key_normalizer.rb +0 -9
- data/spec/unit/path_normalizer_spec.rb +0 -23
@@ -0,0 +1,22 @@
|
|
1
|
+
module Sparrow
|
2
|
+
class RequestHttpMessage < HttpMessage
|
3
|
+
##
|
4
|
+
# @return [Hash] The HTTP Headers
|
5
|
+
def headers_hash
|
6
|
+
env
|
7
|
+
end
|
8
|
+
|
9
|
+
##
|
10
|
+
# @return [String] the request's path
|
11
|
+
def path
|
12
|
+
request.path || super
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# The HTTP Content Type Field
|
17
|
+
# @return [String] the HTTP Content Type
|
18
|
+
def content_type
|
19
|
+
request.content_type || super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'sparrow/middleware'
|
2
|
-
|
3
1
|
module Sparrow
|
4
2
|
class RequestMiddleware < Middleware
|
5
3
|
def convert(env)
|
@@ -9,21 +7,21 @@ module Sparrow
|
|
9
7
|
end
|
10
8
|
|
11
9
|
def content_type
|
12
|
-
|
13
|
-
last_env['CONTENT-TYPE'] ||
|
14
|
-
last_env['Content-Type'] ||
|
15
|
-
last_env['CONTENT_TYPE']
|
16
|
-
|
17
|
-
my_content_type.present? ? my_content_type : nil
|
10
|
+
http_message.content_type.presence
|
18
11
|
end
|
19
12
|
|
20
13
|
def strategy
|
21
|
-
if
|
22
|
-
|
14
|
+
if steward.has_processable_http_message? &&
|
15
|
+
http_message.form_hash?
|
16
|
+
Sparrow.logger.debug 'Choosing strategy FormHash'
|
23
17
|
Strategies::FormHash
|
24
18
|
else
|
25
19
|
super
|
26
20
|
end
|
27
21
|
end
|
22
|
+
|
23
|
+
def http_message
|
24
|
+
RequestHttpMessage.new(last_env)
|
25
|
+
end
|
28
26
|
end
|
29
27
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Sparrow
|
2
|
+
class ResponseHttpMessage < HttpMessage
|
3
|
+
##
|
4
|
+
# @return [Integer] the HTTP Response Code status
|
5
|
+
attr_accessor :status
|
6
|
+
|
7
|
+
##
|
8
|
+
# @return the HTTP response body
|
9
|
+
attr_accessor :body
|
10
|
+
|
11
|
+
##
|
12
|
+
# @return the HTTP header after the middleware was called
|
13
|
+
attr_accessor :headers
|
14
|
+
|
15
|
+
##
|
16
|
+
# The wrapped Response instance
|
17
|
+
# @return [Object] the response
|
18
|
+
def response
|
19
|
+
clazz = response_class
|
20
|
+
@response ||= if clazz.name == 'ActionDispatch::Response'
|
21
|
+
clazz.new(status, headers_hash, body)
|
22
|
+
else
|
23
|
+
clazz.new(body, status, headers_hash)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def path
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
def content_type
|
32
|
+
response.content_type.presence || super
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def response_class
|
38
|
+
if defined?(Rails)
|
39
|
+
ActionDispatch::Response
|
40
|
+
else
|
41
|
+
::Rack::Response
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def headers_hash
|
46
|
+
@headers_hash ||= env.merge(headers)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,44 +1,48 @@
|
|
1
|
-
require 'active_support/core_ext/object/inclusion'
|
2
|
-
require 'sparrow/middleware'
|
3
|
-
require 'sparrow/strategies/json_format_strategies/json_format_strategy'
|
4
|
-
|
5
1
|
module Sparrow
|
2
|
+
##
|
3
|
+
# Handles the Response conversion
|
6
4
|
class ResponseMiddleware < Middleware
|
5
|
+
##
|
6
|
+
# This call ends the rack chain
|
7
|
+
# @param [Hash] env the Rack environment
|
8
|
+
# @return [Array<String, Hash, Array<String>>] the Rack return Array
|
7
9
|
def call(env)
|
8
10
|
@last_env = env
|
9
|
-
@status, @headers, @body =
|
11
|
+
@status, @headers, @body = app.call(env)
|
10
12
|
[status, headers, converted_response_body]
|
11
13
|
end
|
12
14
|
|
15
|
+
private
|
16
|
+
|
17
|
+
def steward
|
18
|
+
ResponseSteward.new(http_message,
|
19
|
+
allowed_content_types: Sparrow.configuration.allowed_content_types,
|
20
|
+
allowed_accepts: Sparrow.configuration.allowed_accepts,
|
21
|
+
excluded_routes: Sparrow.configuration.excluded_routes,
|
22
|
+
ignored_response_codes: Sparrow.configuration.ignored_response_codes)
|
23
|
+
end
|
24
|
+
|
25
|
+
|
13
26
|
def converted_response_body
|
27
|
+
# return the original body if we are not going to process it
|
28
|
+
return body unless steward.has_processable_http_message?
|
29
|
+
|
14
30
|
response_body = Sparrow::Strategies::JsonFormatStrategy.convert(body)
|
15
31
|
|
16
|
-
|
17
|
-
return response_body if unprocessable_status?
|
18
|
-
|
19
|
-
if response_body.present?
|
20
|
-
response_strategy = strategy.new(last_env, :response, response_body)
|
21
|
-
response_strategy.handle
|
22
|
-
|
23
|
-
if response_body.is_a?(Array) then
|
24
|
-
response_body
|
25
|
-
else
|
26
|
-
Array(response_strategy.json_body)
|
27
|
-
end
|
28
|
-
else
|
29
|
-
[]
|
30
|
-
end
|
31
|
-
end
|
32
|
+
return [] if response_body.blank?
|
32
33
|
|
33
|
-
|
34
|
-
|
34
|
+
@headers.delete 'Content-Length'
|
35
|
+
response_strategy = strategy.new(last_env, :response, response_body)
|
36
|
+
response_strategy.handle
|
37
|
+
Array(response_strategy.json_body)
|
35
38
|
end
|
36
39
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
40
|
+
def http_message
|
41
|
+
response_message = ResponseHttpMessage.new(last_env)
|
42
|
+
response_message.status = status
|
43
|
+
response_message.body = body
|
44
|
+
response_message.headers = headers
|
45
|
+
response_message
|
42
46
|
end
|
43
47
|
end
|
44
48
|
end
|
data/lib/sparrow/route_parser.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
|
-
require 'active_support/core_ext/string/starts_ends_with'
|
2
|
-
require 'sparrow/path_normalizer'
|
3
|
-
|
4
1
|
module Sparrow
|
2
|
+
##
|
3
|
+
# Simple class the takes a list of routes and provides check methods
|
4
|
+
# to determine if a path is excluded (ignored) or not, i.e.
|
5
|
+
# the path is within the list as defined on initialization.
|
5
6
|
class RouteParser
|
6
|
-
include PathNormalizer
|
7
|
-
|
8
7
|
attr_accessor :excluded_routes
|
9
8
|
|
9
|
+
##
|
10
|
+
# Create a new Routeparser.
|
11
|
+
# @param [Array<String, Regexp>] excluded_routes A list of routes (path)
|
12
|
+
# that shall not be parsed.
|
13
|
+
# Each route +must+ not start with a leading slash (/).
|
10
14
|
def initialize(excluded_routes = Sparrow.configuration.excluded_routes)
|
11
15
|
self.excluded_routes = excluded_routes.map do |route|
|
12
16
|
if route.is_a?(Regexp)
|
@@ -17,10 +21,23 @@ module Sparrow
|
|
17
21
|
end
|
18
22
|
end
|
19
23
|
|
24
|
+
##
|
25
|
+
# States if the given path is +not+ within the excluded_routes
|
26
|
+
#
|
27
|
+
# @param [String] path the path to be checked
|
28
|
+
# @return [Boolean] path allowed
|
29
|
+
# @see #exclude?
|
30
|
+
# @see #excluded_routes
|
20
31
|
def allow?(path)
|
21
32
|
not exclude?(path)
|
22
33
|
end
|
23
34
|
|
35
|
+
##
|
36
|
+
# States if the given path is within the excluded_routes, i.e. it may be
|
37
|
+
# ignored when parsing.
|
38
|
+
#
|
39
|
+
# @param [String] path the path to be checked
|
40
|
+
# @return [Boolean] is path excluded
|
24
41
|
def exclude?(path)
|
25
42
|
normalized_path = normalize_path(path)
|
26
43
|
excluded_routes.each do |route|
|
@@ -28,5 +45,11 @@ module Sparrow
|
|
28
45
|
end
|
29
46
|
return false
|
30
47
|
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def normalize_path(path)
|
51
|
+
path[/./m] = '' if path.to_s.starts_with?('/')
|
52
|
+
path
|
53
|
+
end
|
31
54
|
end
|
32
55
|
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Sparrow
|
2
|
+
##
|
3
|
+
# Parses the http_mesage and provides information if it should be
|
4
|
+
# processed, i.e. if the content type or accept header is applicable
|
5
|
+
#
|
6
|
+
# @todo this class is requested to be removed by issue #7 in the feature and
|
7
|
+
# its internal will be moved to its counterparts such as HttpMessage itself.
|
8
|
+
# @version 0.0.16
|
9
|
+
class Steward
|
10
|
+
##
|
11
|
+
# @return [HttpMessage] the wrapped http message object to be checked
|
12
|
+
attr_reader :http_message
|
13
|
+
|
14
|
+
##
|
15
|
+
# @return [Array<String>]
|
16
|
+
# @see Sparrow::Configuration#allowed_accepts
|
17
|
+
attr_reader :allowed_accepts
|
18
|
+
|
19
|
+
##
|
20
|
+
# @return [Array<String>]
|
21
|
+
# @see Sparrow::Configuration#allowed_content_types
|
22
|
+
attr_reader :allowed_content_types
|
23
|
+
|
24
|
+
##
|
25
|
+
# @return [Array<String,Regexp>]
|
26
|
+
# @see Sparrow::Configuration#excluded_routes
|
27
|
+
attr_reader :excluded_routes
|
28
|
+
|
29
|
+
##
|
30
|
+
# @return [Array<Integer>]
|
31
|
+
# @see Configuration#ignored_response_codes
|
32
|
+
attr_reader :ignored_response_codes
|
33
|
+
|
34
|
+
# @return [RouteParser] the route parser for the #excluded_routes
|
35
|
+
# @see RouteParser
|
36
|
+
# @see #exluded_routes
|
37
|
+
attr_reader :route_parser
|
38
|
+
|
39
|
+
##
|
40
|
+
# Initialize the Steward with the HTTP message to check and the specific
|
41
|
+
# check options.
|
42
|
+
#
|
43
|
+
# @param options [Hash]
|
44
|
+
# @option options [HttpMessage] :http_message the message to be checked
|
45
|
+
# @option options [Array<String>] :allowed_accepts ([]) List of HTTP Accept
|
46
|
+
# Header options
|
47
|
+
# @option options [Array<String>] :allowed_content_types ([])
|
48
|
+
# List of HTTP Content Type Header options
|
49
|
+
# @option options [Array<String,Regexp>] :excluded_routes ([]) List of routes
|
50
|
+
# (paths) to not process
|
51
|
+
# (see Sparrow::Configuration#excluded_routes)
|
52
|
+
# @option options [Array<Integer>] ignored_response_codes ([])
|
53
|
+
# (see Sparrow::Configuration#ignored_response_codes)
|
54
|
+
# @see RouteParser#excluded_routes
|
55
|
+
# @see Configuration
|
56
|
+
def initialize(http_message, options = {})
|
57
|
+
@http_message = http_message
|
58
|
+
@allowed_accepts = options.fetch(:allowed_accepts, [])
|
59
|
+
@allowed_content_types = options.fetch(:allowed_content_types, [])
|
60
|
+
@excluded_routes = options.fetch(:excluded_routes, [])
|
61
|
+
@ignored_response_codes = options.fetch(:ignored_response_codes, [])
|
62
|
+
@route_parser = RouteParser.new(excluded_routes)
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Checks the +http_message+ against any given criteria specified by the
|
67
|
+
# +options+ within the constructor
|
68
|
+
#
|
69
|
+
# @return [Boolean] processable message
|
70
|
+
def has_processable_http_message?
|
71
|
+
includes_route? &&
|
72
|
+
allowed_content_type? &&
|
73
|
+
allowed_accept_header?
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
##
|
79
|
+
# @private
|
80
|
+
def includes_route?
|
81
|
+
route_parser.allow?(http_message.path)
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# @private
|
86
|
+
def allowed_content_type?
|
87
|
+
content_type = http_message.content_type
|
88
|
+
content_type_equals?(content_type) || content_type_matches?(content_type)
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# @private
|
93
|
+
def allowed_accept_header?
|
94
|
+
accept_header = http_message.accept
|
95
|
+
|
96
|
+
allowed_accepts.include?(nil) ||
|
97
|
+
accept_type_matches?(allowed_accepts, accept_header)
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# @private
|
102
|
+
def content_type_equals?(type)
|
103
|
+
allowed_content_types.include?(type)
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# @private
|
108
|
+
def content_type_matches?(type)
|
109
|
+
matches = allowed_content_types.map do |acceptable_content_type|
|
110
|
+
(acceptable_content_type &&
|
111
|
+
type.to_s.starts_with?(acceptable_content_type.to_s))
|
112
|
+
end
|
113
|
+
|
114
|
+
matches.any?
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# @private
|
119
|
+
def accept_type_matches?(accepted_headers, type)
|
120
|
+
accepted_headers.detect do |accept|
|
121
|
+
type.include?(accept)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -1,42 +1,58 @@
|
|
1
|
-
require 'active_support/core_ext/object/blank'
|
2
|
-
require 'sparrow/transformable'
|
3
|
-
|
4
1
|
module Sparrow
|
5
2
|
module Strategies
|
3
|
+
##
|
4
|
+
# Handles Form Hash transformations, i.e. HTTP messages that contain form
|
5
|
+
# data
|
6
6
|
class FormHash
|
7
|
-
REQUEST_FORM_HASH_KEY = 'rack.request.form_hash'
|
8
7
|
include Transformable
|
9
8
|
|
10
|
-
|
9
|
+
# @return [Hash] the Rack environment
|
10
|
+
# @see #initialize
|
11
|
+
attr_reader :env
|
12
|
+
# @return [Symbol] the HTTP message type. Either :request, or :response
|
13
|
+
# @see #initialize
|
14
|
+
attr_reader :type
|
11
15
|
|
16
|
+
##
|
17
|
+
# Shortcut for handling a HTTP Message with this strategy.
|
12
18
|
def self.handle(env, type)
|
13
19
|
self.new(env, type).handle
|
14
20
|
end
|
15
21
|
|
22
|
+
# Create a new FormHash Strategy
|
23
|
+
# @param [Hash] env the Rack environment
|
24
|
+
# @param [Symbol] type the HTTP message type. Must be either :request or
|
25
|
+
# :response
|
26
|
+
# @param [Hash] params the HTTP message parameters to be transformed
|
27
|
+
# if not already present in the env
|
16
28
|
def initialize(env, type = :request, params = nil)
|
17
29
|
@env = env
|
18
30
|
@params = params
|
19
31
|
@type = type
|
20
32
|
end
|
21
33
|
|
34
|
+
##
|
35
|
+
# Do the transformation
|
36
|
+
# @return [Hash] the converted JSON as a Hash representation
|
22
37
|
def handle
|
23
38
|
super
|
24
39
|
handle_form_hash
|
25
40
|
end
|
26
41
|
|
42
|
+
##
|
43
|
+
# If given in the constructor, this simply returns the given params
|
44
|
+
# argument. Otherwise it will look in the #env for form hash data params
|
45
|
+
# @return [Hash] the HTTP message params
|
46
|
+
# @see #initialize
|
27
47
|
def params
|
28
|
-
@params || env[
|
48
|
+
@params || env[HttpMessage::FORM_HASH_KEY]
|
29
49
|
end
|
30
50
|
|
31
51
|
private
|
32
52
|
|
33
53
|
def handle_form_hash
|
34
|
-
if params.present?
|
35
|
-
transformed_params = transform_params
|
36
|
-
@env[REQUEST_FORM_HASH_KEY] = transformed_params
|
37
|
-
end
|
54
|
+
env[HttpMessage::FORM_HASH_KEY] = transform_params if params.present?
|
38
55
|
end
|
39
56
|
end
|
40
57
|
end
|
41
58
|
end
|
42
|
-
|