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.
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
-