cp-sparrow 0.0.11

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