cp-sparrow 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +66 -0
  8. data/Rakefile +32 -0
  9. data/lib/sparrow.rb +25 -0
  10. data/lib/sparrow/configuration.rb +36 -0
  11. data/lib/sparrow/core_ext/hash.rb +21 -0
  12. data/lib/sparrow/middleware.rb +99 -0
  13. data/lib/sparrow/path_normalizer.rb +10 -0
  14. data/lib/sparrow/railtie.rb +11 -0
  15. data/lib/sparrow/request_middleware.rb +11 -0
  16. data/lib/sparrow/response_middleware.rb +37 -0
  17. data/lib/sparrow/route_parser.rb +32 -0
  18. data/lib/sparrow/strategies/form_hash.rb +42 -0
  19. data/lib/sparrow/strategies/ignore.rb +42 -0
  20. data/lib/sparrow/strategies/json_format_strategies/array_strategy.rb +17 -0
  21. data/lib/sparrow/strategies/json_format_strategies/default_json_format_strategy.rb +17 -0
  22. data/lib/sparrow/strategies/json_format_strategies/json_format_strategy.rb +35 -0
  23. data/lib/sparrow/strategies/json_format_strategies/rack_body.rb +17 -0
  24. data/lib/sparrow/strategies/key_transformation/camelize_key.rb +27 -0
  25. data/lib/sparrow/strategies/key_transformation/key_normalizer.rb +9 -0
  26. data/lib/sparrow/strategies/key_transformation/underscore_key.rb +13 -0
  27. data/lib/sparrow/strategies/raw_input.rb +48 -0
  28. data/lib/sparrow/strategies/transform_params.rb +38 -0
  29. data/lib/sparrow/transformable.rb +76 -0
  30. data/lib/sparrow/version.rb +3 -0
  31. data/sparrow.gemspec +35 -0
  32. data/spec/integration/apps/rack_app/app.rb +28 -0
  33. data/spec/integration/apps/rack_app/config.ru +12 -0
  34. data/spec/integration/apps/rails_app/README.rdoc +261 -0
  35. data/spec/integration/apps/rails_app/Rakefile +7 -0
  36. data/spec/integration/apps/rails_app/app/assets/javascripts/application.js +15 -0
  37. data/spec/integration/apps/rails_app/app/assets/stylesheets/application.css +13 -0
  38. data/spec/integration/apps/rails_app/app/controllers/application_controller.rb +3 -0
  39. data/spec/integration/apps/rails_app/app/controllers/welcome_controller.rb +50 -0
  40. data/spec/integration/apps/rails_app/app/helpers/application_helper.rb +2 -0
  41. data/spec/integration/apps/rails_app/app/mailers/.gitkeep +0 -0
  42. data/spec/integration/apps/rails_app/app/models/.gitkeep +0 -0
  43. data/spec/integration/apps/rails_app/app/views/layouts/application.html.erb +14 -0
  44. data/spec/integration/apps/rails_app/config.ru +4 -0
  45. data/spec/integration/apps/rails_app/config/application.rb +72 -0
  46. data/spec/integration/apps/rails_app/config/boot.rb +10 -0
  47. data/spec/integration/apps/rails_app/config/environment.rb +5 -0
  48. data/spec/integration/apps/rails_app/config/environments/development.rb +31 -0
  49. data/spec/integration/apps/rails_app/config/environments/production.rb +64 -0
  50. data/spec/integration/apps/rails_app/config/environments/test.rb +32 -0
  51. data/spec/integration/apps/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  52. data/spec/integration/apps/rails_app/config/initializers/inflections.rb +15 -0
  53. data/spec/integration/apps/rails_app/config/initializers/mime_types.rb +5 -0
  54. data/spec/integration/apps/rails_app/config/initializers/secret_token.rb +7 -0
  55. data/spec/integration/apps/rails_app/config/initializers/session_store.rb +8 -0
  56. data/spec/integration/apps/rails_app/config/initializers/wrap_parameters.rb +10 -0
  57. data/spec/integration/apps/rails_app/config/locales/en.yml +5 -0
  58. data/spec/integration/apps/rails_app/config/routes.rb +64 -0
  59. data/spec/integration/apps/rails_app/lib/assets/.gitkeep +0 -0
  60. data/spec/integration/apps/rails_app/public/404.html +26 -0
  61. data/spec/integration/apps/rails_app/public/422.html +26 -0
  62. data/spec/integration/apps/rails_app/public/500.html +25 -0
  63. data/spec/integration/apps/rails_app/public/favicon.ico +0 -0
  64. data/spec/integration/apps/rails_app/script/rails +6 -0
  65. data/spec/integration/rack/camel_caser_spec.rb +49 -0
  66. data/spec/integration/rails/camel_caser_accept_header_spec.rb +55 -0
  67. data/spec/integration/rails/camel_caser_spec.rb +207 -0
  68. data/spec/spec_helper.rb +12 -0
  69. data/spec/support/rack_app_helper.rb +19 -0
  70. data/spec/support/rails_app_helper.rb +21 -0
  71. data/spec/support/unit_spec_helper.rb +5 -0
  72. data/spec/unit/camel_caser_spec.rb +23 -0
  73. data/spec/unit/camelize_key_spec.rb +19 -0
  74. data/spec/unit/configuration_spec.rb +47 -0
  75. data/spec/unit/path_normalizer_spec.rb +23 -0
  76. data/spec/unit/route_parser_spec.rb +45 -0
  77. metadata +295 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9052fb54e2eab83298497e0037a54c517bd7429a
4
+ data.tar.gz: 3c871cad86f27adf06bbdcc2cbf35bac4ed479fe
5
+ SHA512:
6
+ metadata.gz: 92ec954024212d99c3e5d606072f1703131301774de50721360057aaffd256bfa5ef5659e62599153a2672720f5014c33d9023961351b9595e65237cfece1c55
7
+ data.tar.gz: 7c8ffca7814f4968e898b37d0a827871ce41d311795c92aca695076e6fd408741148f7ba1dafd3f5108dce76d83fee3f877c5c3da9de74c3a41a9cd165d1cd07
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ *.swo
7
+ *.swp
8
+ *.log
9
+ spec/integration/apps/rails_app/log
10
+ Gemfile.lock
11
+ InstalledFiles
12
+ _yardoc
13
+ coverage
14
+ doc/
15
+ lib/bundler/man
16
+ pkg
17
+ rdoc
18
+ spec/reports
19
+ test/tmp
20
+ test/version_tmp
21
+ tmp
22
+ .idea
23
+ .project
@@ -0,0 +1 @@
1
+ sparrow
@@ -0,0 +1 @@
1
+ 2.1.5
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sparrow.gemspec
4
+ gemspec
5
+
6
+ gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw]
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Daniel Schmidt
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,66 @@
1
+ # Sparrow
2
+
3
+ A Rack middleware for converting the params keys and JSON response keys of a Rack application.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'cp-sparrow', require: 'sparrow'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install sparrow
18
+
19
+ ## Usage
20
+
21
+ If you're using Rails, that's it. You haven't to do anything more. If you're not using Rails, you will have to add to your config.ru:
22
+
23
+ ```rb
24
+ require 'sparrow'
25
+
26
+ use Sparrow::Middleware
27
+ ```
28
+
29
+ ## Configuration
30
+
31
+ There are various configuration options as well as HTTP headers available to
32
+ make the middleware act as you want it to.
33
+
34
+ in your configuration file (such as application.rb if you are using Rails)
35
+
36
+ ```ruby
37
+ Sparrow.configure do |config|
38
+ config.excluded_routes = [
39
+ Regexp.new('api/model/certificates')
40
+ ]
41
+ end
42
+ ```
43
+
44
+ There are several options available
45
+
46
+ | Option | Example | Meaning |
47
+ |---------|---------|----------------------|
48
+ | excluded_routes | see above | An Array of Strings and/or Regexps defining which paths should not be touched by the middleware. The entries should matchs paths for your application. They should *not* start with a leading slash. |
49
+ | default_json_request_key_transformation_strategy | "underscore" | Defines how the middleware should treat incoming parameters via Request. Which means how they get tranformed, i.e. defining _underscore_ here means that incoming parameters get underscore. Possible values are _underscore_ and _camelize_ |
50
+ | default_json_response_key_transformation_strategy | "camelize" | Same as *default_json_request_key_transformation_strategy, but for responses. I.e. this defines to which format the keys get transformed when the response gets sent.
51
+ | json_request_format_header| "request-json-format" | Defines the HTTP Header key which sets the request transformation strategy as in default_json_request_key_transformation_strategy*. This definition has higher priority than the default definition. Any valid HTTP Header String value is possible. Defaults to 'request-json-format' |
52
+ | json_response_format_header| "response-json-format" | Same as *json_request_format_header*, but for the response handling. Defaults to 'response-json-format' |
53
+ | camelize_ignore_uppercase_keys | `true` | A boolean that indicates to not camelize keys that are all Uppercase, like CountryCodes "EN" ... |
54
+ | allowed_content_types |`['application/json', 'text/x-json']` | A list of HTTP content types upon which the middleware shall trigger and possibly start conversion. Defaults to `['application/json', 'application/x-www-form-urlencoded', 'text/x-json']`. If `nil` is present in the list, requests/responses with *no* Content-Type header will be processed as well. Possible values can also be the start of the content-type-header like ```application/``` which matches everything which starts with ```application/``` like ```application/json``` |
55
+ | allowed_accepts | `['application/json', 'text/x-json']` | Same as **allowed_content_types**, but reacts to the HTTP Accept Header. Applies to the same possible options, behavior. Defaults to the same set of MIME types, but also includes `nil` by default, which ignores checking the Accept header in default configuration |
56
+
57
+ ## Tests
58
+
59
+ Just run `rake` and you are off to go. This runs the whole suite including
60
+ specs for unit- & integrationtests for Rails and Rack including different versions of Rails.
61
+
62
+ If you want to test a specific version of Rails:
63
+
64
+ ```
65
+ export RAILS_VERSION=3.2.17; bundle update; bundle exec rspec
66
+ ```
@@ -0,0 +1,32 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ RAILS_VERSIONS = [
7
+ "3.2.17",
8
+ "4.0.13"
9
+ ]
10
+
11
+ def run_tests_for_version(version)
12
+ commands = []
13
+
14
+ commands << "rm Gemfile.lock"
15
+ commands << "export RAILS_VERSION=#{version}"
16
+ commands << "bundle update"
17
+ commands << "bundle exec rspec"
18
+
19
+ system(commands.join(';'))
20
+ end
21
+
22
+ task :default do
23
+ RAILS_VERSIONS.each do |version|
24
+ puts "Testing gem for rails version: #{version}"
25
+ success = run_tests_for_version(version)
26
+
27
+ if not success
28
+ puts "Test suite aborted, errors occured."
29
+ exit($?.exitstatus)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ require 'active_support/version'
2
+ require 'sparrow/version'
3
+ require 'sparrow/configuration'
4
+ require 'sparrow/route_parser'
5
+ require 'sparrow/request_middleware'
6
+ require 'sparrow/response_middleware'
7
+ require 'sparrow/strategies/json_format_strategies/rack_body'
8
+ require 'sparrow/strategies/json_format_strategies/array_strategy'
9
+ require 'sparrow/railtie' if defined?(Rails)
10
+
11
+ module Sparrow
12
+ class << self
13
+ def configure
14
+ yield configuration
15
+ end
16
+
17
+ def configuration
18
+ @configuration ||= Configuration.new
19
+ end
20
+
21
+ def reset_configuration
22
+ @configuration = nil
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,36 @@
1
+ module Sparrow
2
+ class Configuration
3
+ attr_accessor :json_request_format_header,
4
+ :json_response_format_header,
5
+ :excluded_routes,
6
+ :default_json_request_key_transformation_strategy,
7
+ :default_json_response_key_transformation_strategy,
8
+ :camelize_ignore_uppercase_keys,
9
+ :allowed_content_types,
10
+ :allowed_accepts
11
+
12
+ def initialize
13
+ @json_request_format_header = 'request-json-format'
14
+ @json_response_format_header = 'response-json-format'
15
+ @excluded_routes = []
16
+ @default_json_request_key_transformation_strategy = :camelize
17
+ @default_json_response_key_transformation_strategy = :camelize
18
+ @camelize_ignore_uppercase_keys = true
19
+ @allowed_content_types = %w[
20
+ application/json
21
+ application/x-www-form-urlencoded
22
+ text/x-json
23
+ ]
24
+
25
+ @allowed_accepts = @allowed_content_types + [nil]
26
+ end
27
+
28
+ def json_format_header(type)
29
+ public_send("json_#{type}_format_header")
30
+ end
31
+
32
+ def default_json_key_transformation_strategy(type)
33
+ public_send("default_json_#{type}_key_transformation_strategy")
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,21 @@
1
+ class Hash
2
+ def deep_transform_keys(&block)
3
+ deep_transform_in_object(self, &block)
4
+ end
5
+
6
+ private
7
+
8
+ def deep_transform_in_object(obj, &block)
9
+ case obj
10
+ when Hash
11
+ obj.each_with_object({}) do |(key,value), result|
12
+ result[yield(key)] = deep_transform_in_object(value, &block)
13
+ end
14
+ when Array
15
+ obj.map { |el| deep_transform_in_object(el, &block) }
16
+ else
17
+ obj
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,99 @@
1
+ require 'active_support/core_ext/object/blank'
2
+ require 'sparrow/strategies/form_hash'
3
+ require 'sparrow/strategies/raw_input'
4
+ require 'sparrow/strategies/ignore'
5
+
6
+ module Sparrow
7
+ class Middleware
8
+ attr_reader :app, :body, :status, :headers
9
+
10
+ def initialize(app)
11
+ @app = app
12
+ end
13
+
14
+ def call(env)
15
+ @last_env = env
16
+ @status, @headers, @body = @app.call(convert(env))
17
+ end
18
+
19
+ def convert(env)
20
+ env
21
+ end
22
+
23
+ private
24
+
25
+ def strategy
26
+ if is_processable?
27
+ if last_env[Strategies::FormHash::REQUEST_FORM_HASH_KEY]
28
+ Strategies::FormHash
29
+ else
30
+ Strategies::RawInput
31
+ end
32
+ else
33
+ Strategies::Ignore
34
+ end
35
+ end
36
+
37
+ def is_processable?
38
+ accepted_content_type? && accepted_accept_header? && includes_route?
39
+ end
40
+
41
+ def includes_route?
42
+ path = request.path || last_env['PATH_INFO']
43
+ RouteParser.new.allow?(path)
44
+ end
45
+
46
+ def accepted_content_type?
47
+ content_type_equals?(request_content_type) || content_type_matches?(request_content_type)
48
+ end
49
+
50
+ def accepted_accept_header?
51
+ allowed_accepts = Sparrow.configuration.allowed_accepts
52
+ accept_header = last_env['ACCEPT'] || last_env['Accept']
53
+
54
+ allowed_accepts.include?(nil) || accept_type_matches?(allowed_accepts, accept_header)
55
+ end
56
+
57
+ def last_env
58
+ @last_env || {}
59
+ end
60
+
61
+ def request
62
+ request_class = if defined?(Rails) then
63
+ ActionDispatch::Request
64
+ else
65
+ Rack::Request
66
+ end
67
+
68
+ request_class.new(last_env)
69
+ end
70
+
71
+ def request_content_type
72
+ content_type = request.content_type ||
73
+ last_env['CONTENT-TYPE'] ||
74
+ last_env['Content-Type'] ||
75
+ last_env['CONTENT_TYPE']
76
+
77
+ content_type.present? ? content_type : nil
78
+ end
79
+
80
+ def content_type_equals?(type)
81
+ Sparrow.configuration.allowed_content_types.include?(type)
82
+ end
83
+
84
+ def content_type_matches?(type)
85
+ matches = Sparrow.configuration.allowed_content_types.map do |acceptable_content_type|
86
+ (acceptable_content_type && type.to_s.starts_with?(acceptable_content_type.to_s))
87
+ end
88
+
89
+ matches.any?
90
+ end
91
+
92
+ def accept_type_matches?(accepted_headers, type)
93
+ accepted_headers.detect do |accept|
94
+ type.include?(accept)
95
+ end
96
+ end
97
+ end
98
+ end
99
+
@@ -0,0 +1,10 @@
1
+ require 'active_support/core_ext/string/starts_ends_with'
2
+
3
+ module Sparrow
4
+ module PathNormalizer
5
+ def normalize_path(path)
6
+ path[/./m] = '' if path.starts_with?('/')
7
+ path
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ module Sparrow
2
+ class Railtie < Rails::Railtie
3
+ initializer 'sparrow.insert_middleware' do |app|
4
+ # handle request
5
+ app.config.middleware.insert_after 'Rails::Rack::Logger',
6
+ 'Sparrow::RequestMiddleware'
7
+ # handle response
8
+ app.config.middleware.use 'Sparrow::ResponseMiddleware'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'sparrow/middleware'
2
+
3
+ module Sparrow
4
+ class RequestMiddleware < Middleware
5
+ def convert(env)
6
+ super
7
+ strategy.handle(env, :request)
8
+ env
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ require 'active_support/core_ext/object/inclusion'
2
+ require 'sparrow/middleware'
3
+ require 'sparrow/strategies/json_format_strategies/json_format_strategy'
4
+
5
+ module Sparrow
6
+ class ResponseMiddleware < Middleware
7
+ def call(env)
8
+ @last_env = env
9
+ @status, @headers, @body = @app.call(env)
10
+ [status, headers, converted_response_body]
11
+ end
12
+
13
+ def converted_response_body
14
+ response_body = Sparrow::Strategies::JsonFormatStrategy.convert(body)
15
+
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
+
33
+ def unprocessable_status?
34
+ @status.in?(500..511) || @status == 404
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,32 @@
1
+ require 'active_support/core_ext/string/starts_ends_with'
2
+ require 'sparrow/path_normalizer'
3
+
4
+ module Sparrow
5
+ class RouteParser
6
+ include PathNormalizer
7
+
8
+ attr_accessor :excluded_routes
9
+
10
+ def initialize(excluded_routes = Sparrow.configuration.excluded_routes)
11
+ self.excluded_routes = excluded_routes.map do |route|
12
+ if route.is_a?(Regexp)
13
+ route
14
+ else
15
+ Regexp.new(route.to_s)
16
+ end
17
+ end
18
+ end
19
+
20
+ def allow?(path)
21
+ not exclude?(path)
22
+ end
23
+
24
+ def exclude?(path)
25
+ normalized_path = normalize_path(path)
26
+ excluded_routes.each do |route|
27
+ return true if normalized_path =~ route
28
+ end
29
+ return false
30
+ end
31
+ end
32
+ end