cp-sparrow 0.0.12 → 0.0.14
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.
- 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
|
-
|