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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.ruby-version +1 -1
  4. data/LICENSE.txt +1 -1
  5. data/README.md +96 -13
  6. data/lib/sparrow.rb +21 -4
  7. data/lib/sparrow/configuration.rb +23 -2
  8. data/lib/sparrow/core_ext/hash.rb +2 -0
  9. data/lib/sparrow/dependencies.rb +15 -0
  10. data/lib/sparrow/http_message.rb +103 -0
  11. data/lib/sparrow/logger.rb +53 -0
  12. data/lib/sparrow/middleware.rb +44 -63
  13. data/lib/sparrow/request_http_message.rb +22 -0
  14. data/lib/sparrow/request_middleware.rb +8 -10
  15. data/lib/sparrow/response_http_message.rb +49 -0
  16. data/lib/sparrow/response_middleware.rb +32 -28
  17. data/lib/sparrow/response_steward.rb +13 -0
  18. data/lib/sparrow/route_parser.rb +28 -5
  19. data/lib/sparrow/steward.rb +125 -0
  20. data/lib/sparrow/strategies.rb +6 -0
  21. data/lib/sparrow/strategies/form_hash.rb +27 -11
  22. data/lib/sparrow/strategies/ignore.rb +33 -5
  23. data/lib/sparrow/strategies/json_format_strategies.rb +4 -0
  24. data/lib/sparrow/strategies/json_format_strategies/array_json_format_strategy.rb +23 -0
  25. data/lib/sparrow/strategies/json_format_strategies/default_json_format_strategy.rb +11 -2
  26. data/lib/sparrow/strategies/json_format_strategies/json_format_strategy.rb +21 -6
  27. data/lib/sparrow/strategies/json_format_strategies/rack_body_json_format_strategy.rb +22 -0
  28. data/lib/sparrow/strategies/key_transformation.rb +2 -0
  29. data/lib/sparrow/strategies/key_transformation/camelize_key.rb +45 -17
  30. data/lib/sparrow/strategies/key_transformation/underscore_key.rb +17 -6
  31. data/lib/sparrow/strategies/raw_input.rb +29 -7
  32. data/lib/sparrow/strategies/transform_params.rb +46 -21
  33. data/lib/sparrow/transformable.rb +28 -20
  34. data/lib/sparrow/version.rb +1 -1
  35. data/sparrow.gemspec +1 -1
  36. data/spec/integration/apps/rails_app/config/boot.rb +1 -1
  37. data/spec/integration/rack/camel_caser_spec.rb +2 -2
  38. data/spec/unit/camelize_key_spec.rb +9 -7
  39. data/spec/unit/configuration_spec.rb +3 -0
  40. data/spec/unit/http_message.rb +66 -0
  41. data/spec/unit/logger_spec.rb +6 -0
  42. data/spec/unit/{camel_caser_spec.rb → sparrow_spec.rb} +8 -2
  43. data/spec/unit/steward_spec.rb +31 -0
  44. metadata +24 -13
  45. data/lib/sparrow/path_normalizer.rb +0 -10
  46. data/lib/sparrow/strategies/json_format_strategies/array_strategy.rb +0 -17
  47. data/lib/sparrow/strategies/json_format_strategies/rack_body.rb +0 -17
  48. data/lib/sparrow/strategies/key_transformation/key_normalizer.rb +0 -9
  49. 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
- my_content_type = request.content_type ||
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 is_processable? && last_env[Strategies::FormHash::REQUEST_FORM_HASH_KEY]
22
- Rails.logger.debug 'Choosing strategy FormHash' if defined? Rails
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 = @app.call(env)
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
- # just pass the response if something went wrong inside the application
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
- def unprocessable_status?
34
- @status.in?(500..511) || @status == 404
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 content_type
38
- headers['Content-Type'].split(';').first #||
39
- # last_env['CONTENT-TYPE'] ||
40
- # last_env['Content-Type'] ||
41
- # last_env['CONTENT_TYPE']
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
@@ -0,0 +1,13 @@
1
+ module Sparrow
2
+ class ResponseSteward < Steward
3
+ def has_processable_http_message?
4
+ super && processable_status?
5
+ end
6
+
7
+ private
8
+
9
+ def processable_status?
10
+ !http_message.status.in?(ignored_response_codes)
11
+ end
12
+ end
13
+ end
@@ -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
@@ -0,0 +1,6 @@
1
+ require 'sparrow/strategies/json_format_strategies'
2
+ require 'sparrow/strategies/key_transformation'
3
+ require 'sparrow/strategies/transform_params'
4
+ require 'sparrow/strategies/form_hash'
5
+ require 'sparrow/strategies/raw_input'
6
+ require 'sparrow/strategies/ignore'
@@ -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
- attr_reader :env, :type
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[REQUEST_FORM_HASH_KEY]
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
-