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
@@ -1,17 +1,33 @@
|
|
1
1
|
module Sparrow
|
2
2
|
module Strategies
|
3
|
+
##
|
4
|
+
# This strategy is called when the HTTP message shall +not+ be transformed,
|
5
|
+
# i.e. ignored.
|
6
|
+
# This is inspired by the NullObject-Pattern to be convenient.
|
3
7
|
class Ignore
|
4
8
|
include Transformable
|
5
9
|
|
10
|
+
##
|
11
|
+
# Create a new IgnoreStrategy
|
12
|
+
# @param [Hash] env the Rack environment
|
13
|
+
# @param [Symbol] type HTTP message type. Must be either :request or
|
14
|
+
# :response
|
15
|
+
# @param [Hash] params The HTTP message params if not given in the env
|
6
16
|
def initialize(env, type = :request, params = nil)
|
7
17
|
@env = env
|
8
18
|
@params = params
|
9
19
|
@type = type
|
10
20
|
end
|
11
21
|
|
22
|
+
##
|
23
|
+
# Although we are ignoring any kind of conversion we still need to read
|
24
|
+
# the parameters from the environment to be convenient with all other
|
25
|
+
# calls in the chains and architecture *sigh*
|
26
|
+
# Checks env['rack.input']
|
27
|
+
# @return [Hash] the params
|
12
28
|
def params
|
13
|
-
ret = @params || @env[
|
14
|
-
@env[
|
29
|
+
ret = @params || @env[HttpMessage::RACK_INPUT_KEY].send(:read)
|
30
|
+
@env[HttpMessage::RACK_INPUT_KEY].rewind
|
15
31
|
ret
|
16
32
|
end
|
17
33
|
|
@@ -19,25 +35,37 @@ module Sparrow
|
|
19
35
|
new(env, type).handle
|
20
36
|
end
|
21
37
|
|
38
|
+
##
|
39
|
+
# handles the conversion, i.e. here "do nothing"
|
40
|
+
# Which is not strictly true - at write the rack.input to the form hash
|
41
|
+
# key for convenience reasons to enable further middlewares to work with
|
42
|
+
# it
|
43
|
+
# @return [Hash] the rack env
|
22
44
|
def handle
|
23
45
|
# synchronize rack.input and form hash values
|
24
|
-
input = @env[
|
46
|
+
input = @env[HttpMessage::RACK_INPUT_KEY].gets
|
25
47
|
|
26
48
|
begin
|
27
|
-
@env[
|
49
|
+
@env[HttpMessage::FORM_HASH_KEY] = MultiJson.load(input)
|
28
50
|
rescue MultiJson::ParseError
|
29
51
|
# ignore
|
30
52
|
ensure
|
31
|
-
@env[
|
53
|
+
@env[HttpMessage::RACK_INPUT_KEY].rewind
|
32
54
|
end if input.present?
|
33
55
|
|
34
56
|
@env
|
35
57
|
end
|
36
58
|
|
59
|
+
##
|
60
|
+
# Alias for #params
|
61
|
+
# @see #params
|
37
62
|
def json_body
|
38
63
|
params
|
39
64
|
end
|
40
65
|
|
66
|
+
##
|
67
|
+
# Transforms the params to a Ruby JSON Hash representation
|
68
|
+
# @return [Hash] the JSON
|
41
69
|
def transform_params
|
42
70
|
ensure_json
|
43
71
|
end
|
@@ -0,0 +1,4 @@
|
|
1
|
+
require 'sparrow/strategies/json_format_strategies/default_json_format_strategy'
|
2
|
+
require 'sparrow/strategies/json_format_strategies/json_format_strategy'
|
3
|
+
require 'sparrow/strategies/json_format_strategies/array_json_format_strategy'
|
4
|
+
require 'sparrow/strategies/json_format_strategies/rack_body_json_format_strategy'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Sparrow
|
2
|
+
module Strategies
|
3
|
+
class ArrayJsonFormatStrategy < JsonFormatStrategy
|
4
|
+
register_json_format
|
5
|
+
|
6
|
+
##
|
7
|
+
# Matches if input is an Array
|
8
|
+
# @param [Object] input the JSON object
|
9
|
+
# @return true if the input is an Array
|
10
|
+
def match?(input)
|
11
|
+
input.is_a? Array
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Takes the first element from the Array and returns it
|
16
|
+
# @param [Array] input the input Array
|
17
|
+
# @return [String] the first element of the input as a String
|
18
|
+
def convert(input)
|
19
|
+
input.first.to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,14 +1,23 @@
|
|
1
|
-
require 'singleton'
|
2
|
-
|
3
1
|
module Sparrow
|
4
2
|
module Strategies
|
3
|
+
##
|
4
|
+
# NullObject JSON Format Strategy
|
5
|
+
# Does no direct conversion except casting the given object to a string
|
5
6
|
class DefaultJsonFormatStrategy
|
6
7
|
include Singleton
|
7
8
|
|
9
|
+
##
|
10
|
+
# Matches always since this is the NullObjectStrategy and thus the
|
11
|
+
# fallback.
|
12
|
+
# @param [Object] input the JSON input
|
13
|
+
# @return [Boolean] True
|
8
14
|
def match?(input)
|
9
15
|
true
|
10
16
|
end
|
11
17
|
|
18
|
+
##
|
19
|
+
# @param [#to_s] input the JSON object
|
20
|
+
# @return [String] the input as a String
|
12
21
|
def convert(input)
|
13
22
|
input.to_s
|
14
23
|
end
|
@@ -1,22 +1,37 @@
|
|
1
|
-
require 'sparrow/strategies/json_format_strategies/default_json_format_strategy'
|
2
|
-
require 'active_support/core_ext/object/blank'
|
3
|
-
|
4
1
|
module Sparrow
|
5
2
|
module Strategies
|
3
|
+
##
|
4
|
+
# Superclass for all JSON format strategies.
|
5
|
+
# Contains no own instance logic, but keeps track of the registration
|
6
|
+
# of all JSON format strategies with its Singleton class methods.
|
7
|
+
# @abstract Not exactly a abstract class but contains no own logic but
|
8
|
+
# singleton class methods
|
6
9
|
class JsonFormatStrategy
|
10
|
+
##
|
11
|
+
# Empty constructor. Does nothing.
|
7
12
|
def initialize(*args)
|
8
|
-
|
9
13
|
end
|
10
14
|
|
15
|
+
##
|
16
|
+
# Register a new JSON Format strategy
|
17
|
+
# @param [Object] args the arguments for the new strategy
|
18
|
+
# @return [Array] args the updated registered JSON Format strategies
|
19
|
+
# available
|
11
20
|
def self.register_json_format(*args)
|
12
21
|
init(args)
|
13
22
|
@@json_format_strategies << self.new(args)
|
14
23
|
end
|
15
24
|
|
25
|
+
##
|
26
|
+
# Start a JSON conversion by its given string
|
27
|
+
# @param [Object] body a JSON object representation.
|
28
|
+
# can be any type a JSON format strategy is registered,
|
29
|
+
# i.e. an Array, a String or a RackBody
|
30
|
+
# @return [String] the formatted JSON
|
16
31
|
def self.convert(body)
|
17
|
-
strategy = json_format_strategies.
|
32
|
+
strategy = json_format_strategies.detect do |strategy|
|
18
33
|
strategy.match?(body)
|
19
|
-
end
|
34
|
+
end
|
20
35
|
strategy.convert(body)
|
21
36
|
end
|
22
37
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Sparrow
|
2
|
+
module Strategies
|
3
|
+
class RackBodyJsonFormatStrategy < JsonFormatStrategy
|
4
|
+
register_json_format
|
5
|
+
|
6
|
+
##
|
7
|
+
# Checks if the input is a RackBody
|
8
|
+
# @param [#body] input the possible JSON input object
|
9
|
+
# @return [Boolean] True if the given input responds to #body
|
10
|
+
def match?(input)
|
11
|
+
input.respond_to?(:body)
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# @param [#body] input the JSON input object
|
16
|
+
# @return [String] the input body
|
17
|
+
def convert(input)
|
18
|
+
input.body
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,25 +1,53 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/key_normalizer'
|
2
|
-
|
3
1
|
module Sparrow
|
4
2
|
module Strategies
|
5
|
-
|
6
|
-
|
3
|
+
module KeyTransformation
|
4
|
+
##
|
5
|
+
# Strategy class for converting JSON to a camelized format.
|
6
|
+
# Meaning snake_case => snakeCase
|
7
|
+
class CamelizeKey
|
8
|
+
##
|
9
|
+
# @return [Symbol] the camelizing strategy
|
10
|
+
# @see #initialize
|
11
|
+
attr_accessor :strategy
|
7
12
|
|
8
|
-
|
13
|
+
##
|
14
|
+
# @return [Boolean] Defines whether complete uppercased keys will be
|
15
|
+
# transformed
|
16
|
+
# @see #initialize
|
17
|
+
attr_accessor :camelize_ignore_uppercase_keys
|
9
18
|
|
10
|
-
|
11
|
-
|
12
|
-
|
19
|
+
##
|
20
|
+
# Initialize a new CamelizeKey strategy
|
21
|
+
# @param [Hash] options camelize options
|
22
|
+
# @option options [Symbol] :strategy ('lower') defines the camelizing
|
23
|
+
# strategy type. Defines how to camelize, which comes down to starting
|
24
|
+
# with a lowercased character or with an uppercased character.
|
25
|
+
# Thus possible values are :lower and :upper.
|
26
|
+
#
|
27
|
+
# @option options [Boolean] :camelize_ignore_uppercase_keys (true)
|
28
|
+
# Defines if already completely uppercased keys should not be
|
29
|
+
# transformed. I.e. JSON stays JSON if
|
30
|
+
# this is set to true. If it is set to false JSON will be transformed
|
31
|
+
# to Json.
|
32
|
+
def initialize(options = {})
|
33
|
+
self.strategy = options.fetch(:strategy, :lower)
|
34
|
+
self.camelize_ignore_uppercase_keys =
|
35
|
+
options.fetch(:camelize_ignore_uppercase_keys, true)
|
36
|
+
end
|
13
37
|
|
14
|
-
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
38
|
+
##
|
39
|
+
# Transform the given key to camelCase based on the configuration
|
40
|
+
# options set on initialization.
|
41
|
+
# @see #initialize
|
42
|
+
# @param [String, #to_s] key the key value to be transformed
|
43
|
+
# @return [String] the transformed key
|
44
|
+
def transform_key(key)
|
45
|
+
key = key.to_s
|
46
|
+
if camelize_ignore_uppercase_keys && key.upcase == key
|
47
|
+
key
|
48
|
+
else
|
49
|
+
key.camelize(strategy)
|
50
|
+
end
|
23
51
|
end
|
24
52
|
end
|
25
53
|
end
|
@@ -1,12 +1,23 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/key_normalizer'
|
2
|
-
|
3
1
|
module Sparrow
|
4
2
|
module Strategies
|
5
|
-
|
6
|
-
|
3
|
+
module KeyTransformation
|
4
|
+
##
|
5
|
+
# Strategy class for snake_casing keys
|
6
|
+
class UnderscoreKey
|
7
|
+
##
|
8
|
+
# Create a new UnderscoreKey Strategy
|
9
|
+
# Does nothing except returning a plain instance.
|
10
|
+
def initialize(*args)
|
11
|
+
# no initialization needed
|
12
|
+
end
|
7
13
|
|
8
|
-
|
9
|
-
|
14
|
+
##
|
15
|
+
# Transforms the given key to snake_case format
|
16
|
+
# @param [String] key the key to be transformed
|
17
|
+
# @return [String] the snake_cased key
|
18
|
+
def transform_key(key)
|
19
|
+
key.to_s.underscore
|
20
|
+
end
|
10
21
|
end
|
11
22
|
end
|
12
23
|
end
|
@@ -1,32 +1,54 @@
|
|
1
|
-
require 'active_support/core_ext/object/blank'
|
2
|
-
|
3
1
|
module Sparrow
|
4
2
|
module Strategies
|
3
|
+
##
|
4
|
+
# Handles plain JSON input parameter conversion which is sent via
|
5
|
+
# env['rack.input']
|
5
6
|
class RawInput
|
6
7
|
include Transformable
|
7
8
|
|
8
|
-
|
9
|
+
# @return [Hash] the Rack environment
|
10
|
+
# @see #initialize
|
11
|
+
attr_reader :env
|
12
|
+
# @return [Symbol] the HTTP message type
|
13
|
+
# @see #initialize
|
14
|
+
attr_reader :type
|
9
15
|
|
16
|
+
##
|
17
|
+
# Do the transformation
|
18
|
+
# @return [Hash] the converted JSON as a Hash representation
|
10
19
|
def self.handle(env, type)
|
11
20
|
self.new(env, type).handle
|
12
21
|
end
|
13
22
|
|
23
|
+
# Create a new RawInput Strategy
|
24
|
+
# @param [Hash] env the Rack environment
|
25
|
+
# @param [Symbol] type the HTTP message type. Must be either :request or
|
26
|
+
# :response
|
27
|
+
# @param [Hash] params the HTTP message parameters to be transformed
|
28
|
+
# if not already present in the env
|
14
29
|
def initialize(env, type = :request, params = nil)
|
15
30
|
@env = env || {}
|
16
31
|
@params = params
|
17
32
|
@type = type
|
18
33
|
end
|
19
34
|
|
35
|
+
##
|
36
|
+
# Starts the conversion
|
37
|
+
# @return [Hash] the converted Rack environment
|
20
38
|
def handle
|
21
39
|
super
|
22
40
|
handle_raw_rack
|
23
41
|
end
|
24
42
|
|
43
|
+
##
|
44
|
+
# The parameters to be transformed
|
45
|
+
# @return [Hash] the JSON parameters
|
46
|
+
# @see #initialize
|
25
47
|
def params
|
26
48
|
if @params
|
27
49
|
@params
|
28
50
|
else
|
29
|
-
input_io = @env[
|
51
|
+
input_io = @env[HttpMessage::RACK_INPUT_KEY]
|
30
52
|
params = input_io.send(:read)
|
31
53
|
input_io.rewind
|
32
54
|
params
|
@@ -37,9 +59,9 @@ module Sparrow
|
|
37
59
|
|
38
60
|
def handle_raw_rack
|
39
61
|
if params.present?
|
40
|
-
new_raw_input
|
41
|
-
@env[
|
42
|
-
@env[
|
62
|
+
new_raw_input = json_body.force_encoding("BINARY")
|
63
|
+
@env[HttpMessage::RACK_INPUT_KEY] = StringIO.new(new_raw_input)
|
64
|
+
@env[HttpMessage::RACK_INPUT_KEY].rewind
|
43
65
|
@env['CONTENT_LENGTH'] = new_raw_input.length
|
44
66
|
end
|
45
67
|
end
|
@@ -1,37 +1,62 @@
|
|
1
|
-
require 'active_support/core_ext/string'
|
2
|
-
if ActiveSupport::VERSION::STRING.match(/3\.\d+\.\d+/)
|
3
|
-
require 'sparrow/core_ext/hash'
|
4
|
-
end
|
5
|
-
|
6
|
-
require 'sparrow/strategies/key_transformation/underscore_key'
|
7
|
-
require 'sparrow/strategies/key_transformation/camelize_key'
|
8
|
-
|
9
1
|
module Sparrow
|
10
2
|
module Strategies
|
3
|
+
##
|
4
|
+
# Core class to trigger the conversion
|
11
5
|
class TransformParams
|
6
|
+
##
|
7
|
+
# the strategy stating how to convert the JSON
|
8
|
+
# @return [KeyTransformation] key transformation strategy
|
9
|
+
# @see #initialize
|
12
10
|
attr_accessor :key_transformation_strategy
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
##
|
13
|
+
# Key Transformation Strategy options. Possible values differ by
|
14
|
+
# the specific strategy. E.g. for CamelizeKey options may be
|
15
|
+
# the strategy and camlize_uppercase_params
|
16
|
+
# @see KeyTransformationStrategy::CamelizeKey
|
17
|
+
# @see KeyTransformationStrategy::UnderscoreKey
|
18
|
+
attr_accessor :options
|
19
|
+
|
20
|
+
##
|
21
|
+
# Create a new TransformParams instance
|
22
|
+
# @param [String] key_transformation_strategy_buzzword the strategy
|
23
|
+
# buzzword stating how to transform. Must be either 'camelize' or
|
24
|
+
# 'underscore'
|
25
|
+
# @param [Hash] options options for the key transformation strategy
|
26
|
+
def initialize(key_transformation_strategy_buzzword, options = {})
|
27
|
+
self.options = options
|
28
|
+
key_transformation_strategy = create_key_transformation_strategy(
|
29
|
+
key_transformation_strategy_buzzword)
|
17
30
|
self.key_transformation_strategy = key_transformation_strategy
|
18
31
|
end
|
19
32
|
|
33
|
+
##
|
34
|
+
# Do the transformation
|
35
|
+
# @param [Array|Hash] collection_or_hash the object to be transformed
|
36
|
+
# if it is an Array each object inside of it will be transformed, i.e.
|
37
|
+
# each Hash's keys
|
38
|
+
# if it is a Hash each key will be transformed. Recursively.
|
39
|
+
# @return [Array|Hash] the transformed input
|
20
40
|
def transform(collection_or_hash)
|
21
41
|
case collection_or_hash
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
42
|
+
when Array
|
43
|
+
collection_or_hash.map { |element| transform(element) }
|
44
|
+
when Hash
|
45
|
+
collection_or_hash.deep_transform_keys do |key|
|
46
|
+
key_transformation_strategy.transform_key(key)
|
47
|
+
end
|
28
48
|
end
|
29
49
|
end
|
30
50
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
51
|
+
private
|
52
|
+
|
53
|
+
def create_key_transformation_strategy(key_transformation_strategy)
|
54
|
+
class_name = "#{key_transformation_strategy.to_s}_key"
|
55
|
+
class_name = class_name.camelize
|
56
|
+
full_strategy_class_name =
|
57
|
+
"Sparrow::Strategies::KeyTransformation::#{class_name}"
|
58
|
+
strategy_class = full_strategy_class_name.constantize
|
59
|
+
strategy_class.new(options)
|
35
60
|
end
|
36
61
|
end
|
37
62
|
end
|