rack-app 3.6.0 → 4.0.1

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.rubocop.yml +2 -0
  4. data/.travis.yml +2 -0
  5. data/Gemfile +5 -1
  6. data/README.md +29 -18
  7. data/VERSION +1 -1
  8. data/lib/rack/app/constants.rb +4 -2
  9. data/lib/rack/app/instance_methods/core.rb +5 -2
  10. data/lib/rack/app/middlewares.rb +2 -1
  11. data/lib/rack/app/middlewares/params.rb +6 -0
  12. data/lib/rack/app/middlewares/params/definition.rb +22 -0
  13. data/lib/rack/app/middlewares/params/definition/options.rb +45 -0
  14. data/lib/rack/app/middlewares/params/parser.rb +40 -0
  15. data/lib/rack/app/middlewares/params/setter.rb +19 -0
  16. data/lib/rack/app/middlewares/params/validator.rb +102 -0
  17. data/lib/rack/app/params.rb +6 -2
  18. data/lib/rack/app/router.rb +11 -6
  19. data/lib/rack/app/router/base.rb +4 -4
  20. data/lib/rack/app/singleton_methods.rb +3 -1
  21. data/lib/rack/app/singleton_methods/http_methods.rb +4 -1
  22. data/lib/rack/app/singleton_methods/mounting.rb +14 -15
  23. data/lib/rack/app/singleton_methods/params_validator.rb +12 -0
  24. data/lib/rack/app/singleton_methods/route_handling.rb +12 -6
  25. data/lib/rack/app/singleton_methods/settings.rb +7 -1
  26. data/lib/rack/app/test.rb +7 -6
  27. data/lib/rack/app/test/utils.rb +6 -31
  28. data/lib/rack/app/utils.rb +2 -1
  29. data/lib/rack/app/utils/parser.rb +45 -0
  30. data/lib/rack/app/utils/parser/boolean.rb +30 -0
  31. data/lib/rack/app/utils/parser/custom.rb +16 -0
  32. data/lib/rack/app/utils/parser/date.rb +15 -0
  33. data/lib/rack/app/utils/parser/date_time.rb +18 -0
  34. data/lib/rack/app/utils/parser/float.rb +15 -0
  35. data/lib/rack/app/utils/parser/integer.rb +9 -0
  36. data/lib/rack/app/utils/parser/numeric.rb +26 -0
  37. data/lib/rack/app/utils/parser/string.rb +9 -0
  38. data/lib/rack/app/utils/parser/time.rb +15 -0
  39. data/src/Net__HTTP Cheat Sheet.pdf +0 -0
  40. metadata +21 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: daa93f45a998994d6b8185d95c868021905b191b
4
- data.tar.gz: 86656b162da4020cce261b1b74f9385e1e507bcc
3
+ metadata.gz: 6cf38306bf272547c7a780a6a6e92f7138d8ebe5
4
+ data.tar.gz: 05929c52596d77e1caca9c46895ade3aa6a5b36d
5
5
  SHA512:
6
- metadata.gz: 8c91d8539fcdf7e20ee8258b3a509a1b935cfb11ed32a18377a73114d7c854b4317620afc85603ce06b6034a49f90ee39cbb9c346969288304c00b4b407dd72a
7
- data.tar.gz: 43025f318e638dc90a3bbfb460a8730cd3c55b202e22d87b5c1a8c4ff828bd26dad111c7112d9a5996c43acfeec94c7f6ea3d3502c62608aa419263062cfed16
6
+ metadata.gz: acfe46345c8ac900aee026f9249f053e17252224354afc803732b0f3b878761c305a6dbc0e2c29054052e45ae004662d9380b82851559446dd7bbf74991752e5
7
+ data.tar.gz: b4f5c6d7ce3e35cc34b16626641ae10dabd369adca3606f48098f6aaaf9f1ed50f687ed75c2a3cb35e56346d43607d859a020d0fd8294fc950617ff053438f7a
data/.gitignore CHANGED
@@ -1 +1,3 @@
1
- /pkg/
1
+ /pkg/
2
+ Gemfile.lock
3
+ .vagrant/
@@ -0,0 +1,2 @@
1
+ Style/HashSyntax:
2
+ Enabled: false
@@ -12,6 +12,8 @@ rvm:
12
12
  - 2.0.0
13
13
  - 2.1.1
14
14
  - 2.1.2
15
+ - 2.2.2
16
+ - 2.3.1
15
17
 
16
18
  - jruby-18mode
17
19
  - jruby-19mode
data/Gemfile CHANGED
@@ -1,2 +1,6 @@
1
1
  source 'https://rubygems.org'
2
- gemspec
2
+ gemspec
3
+
4
+ if RUBY_VERSION < '2.2.2'
5
+ gem 'rack', '< 2.0.0'
6
+ end
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  ![Rack::App](http://rack-app-website.herokuapp.com/img/msruby_old.png)
8
8
 
9
- Your next favourite rack based micro framework that is totally addition free!
9
+ Your next favourite rack based micro framework that is totally addition free!
10
10
  Have a cup of awesomeness with your performance designed framework!
11
11
 
12
12
  The idea behind is simple.
@@ -62,17 +62,17 @@ Yes, in fact it's already powering heroku hosted micro-services.
62
62
 
63
63
  ## Features
64
64
 
65
- * easy to understand syntax
65
+ * easy to understand syntax
66
66
  * module method level endpoint definition inspirited heavily by the Sinatra DSL
67
67
  * unified error handling
68
- * syntax sugar for default header definitions
68
+ * syntax sugar for default header definitions
69
69
  * namespaces for endpoint request path declarations so it can be dry and unified
70
- * no Class method bloat, so you can enjoy pure ruby without any surprises
70
+ * no Class method bloat, so you can enjoy pure ruby without any surprises
71
71
  * App mounting so you can crete separated controllers for different task
72
72
  * Null time look up routing
73
73
  * allows as many endpoint registration to you as you want, without impact on route look up speed
74
74
  * only basic sets for instance method lvl for the must need tools, such as params, payload
75
- * simple to use class level response serializer
75
+ * simple to use class level response serializer
76
76
  * so you can choose what type of serialization you want without any enforced convention
77
77
  * static file serving so you can mount even filesystem based endpoints too
78
78
  * built in testing module so your app can easily written with BDD approach
@@ -81,6 +81,7 @@ Yes, in fact it's already powering heroku hosted micro-services.
81
81
  * you can define middleware stack before endpoints and it will only applied to them, similar like protected method workflow
82
82
  * File Upload and file download in a efficient and elegant way with minimal memory consuming
83
83
  * note that this is not only memory friendly way pure rack solution, but also 2x faster than the usualy solution which includes buffering in memory
84
+ * params validation with ease
84
85
 
85
86
  ## [Contributors](CONTRIBUTORS.md)
86
87
 
@@ -90,7 +91,7 @@ Yes, in fact it's already powering heroku hosted micro-services.
90
91
 
91
92
  config.ru
92
93
 
93
- #### basic
94
+ #### basic
94
95
 
95
96
  ```ruby
96
97
 
@@ -116,7 +117,7 @@ require 'rack/app'
116
117
  class App < Rack::App
117
118
 
118
119
  apply_extensions :front_end
119
-
120
+
120
121
  mount SomeAppClass
121
122
 
122
123
  headers 'Access-Control-Allow-Origin' => '*',
@@ -127,21 +128,27 @@ class App < Rack::App
127
128
  end
128
129
 
129
130
  desc 'some hello endpoint'
131
+ validate_params do
132
+ required 'words', :class => Array, :of => String, :desc => 'some word', :example => ['pug']
133
+ optional 'word', :class => String, :desc => 'one word', :example => 'pug'
134
+ end
130
135
  get '/hello' do
136
+ puts(validate_params['words'])
137
+
131
138
  return 'Hello World!'
132
139
  end
133
140
 
134
- namespace '/users' do
135
-
141
+ namespace '/users' do
142
+
136
143
  desc 'some restful endpoint'
137
144
  get '/:user_id' do
138
145
  response.status = 201
139
146
  params['user_id'] #=> restful parameter :user_id
140
147
  say #=> "hello world!"
141
148
  end
142
-
149
+
143
150
  end
144
-
151
+
145
152
  desc 'some endpoint that has error and will be rescued'
146
153
  get '/make_error' do
147
154
  raise(StandardError,'error block rescued')
@@ -161,12 +168,12 @@ end
161
168
 
162
169
  ```
163
170
 
164
- you can access Rack::Request with the request method and
165
- Rack::Response as response method.
171
+ you can access Rack::Request with the request method and
172
+ Rack::Response as response method.
166
173
 
167
174
  By default if you dont write anything to the response 'body' the endpoint block logic return will be used
168
175
 
169
- ## Testing
176
+ ## Testing
170
177
 
171
178
  for testing use rack/test or the bundled testing module for writing unit test for your rack application
172
179
 
@@ -214,9 +221,13 @@ end
214
221
 
215
222
  ## Example Apps To start with
216
223
 
224
+ * [Official website How To examples](http://rack-app.com/)
225
+
226
+ * [Rack::App Team Github repositories](https://github.com/rack-app)
227
+
217
228
  * [Basic](https://github.com/rack-app/rack-app-example-basic)
218
- * bare bone simple example app
219
-
229
+ * bare bone simple example app
230
+
220
231
  * [Escher Authorized Api](https://github.com/rack-app/rack-app-example-escher)
221
232
  * complex authorization for corporal level api use
222
233
 
@@ -250,7 +261,7 @@ the benchmarking was taken on the following hardware specification:
250
261
 
251
262
  For more reports check the Benchmark repo out :)
252
263
 
253
- ## Roadmap
264
+ ## Roadmap
254
265
 
255
266
  ### Team [Backlog](https://docs.google.com/spreadsheets/d/19GGX51i6uCQQz8pQ-lvsIxu43huKCX-eC1526-RL3YA/edit?usp=sharing)
256
267
 
@@ -267,6 +278,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
267
278
  Bug reports and pull requests are welcome on GitHub at https://github.com/adamluzsi/rack-app.rb This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
268
279
 
269
280
  ## License and Copyright
270
-
281
+
271
282
  Rack::App is free software released under the [Apache License V2](https://opensource.org/licenses/Apache-2.0) License.
272
283
  The logo was designed by Zsófia Gebauer. It is Copyright © 2015 Adam Luzsi. All Rights Reserved.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.6.0
1
+ 4.0.1
@@ -15,5 +15,7 @@ module Rack::App::Constants
15
15
  PATH_PARAMS_MATCHER = 'rack-app.path_params_matcher'.freeze
16
16
  RACK_BASED_APPLICATION = '[Mounted Rack Application]'.freeze
17
17
  MOUNTED_DIRECTORY = '[Mounted Directory]'.freeze
18
-
19
- end
18
+ VALIDATED_PARAMS = 'rack-app.validated_params'
19
+ PARSED_PARAMS = 'rack-app.parsed_params'
20
+
21
+ end
@@ -3,7 +3,11 @@ module Rack::App::InstanceMethods::Core
3
3
  attr_writer :request, :response
4
4
 
5
5
  def params
6
- @__params__ ||= Rack::App::Params.new(request.env).to_hash
6
+ request.env[::Rack::App::Constants::PARSED_PARAMS] ||= Rack::App::Params.new(request.env).to_hash
7
+ end
8
+
9
+ def validated_params
10
+ request.env[::Rack::App::Constants::VALIDATED_PARAMS]
7
11
  end
8
12
 
9
13
  def request
@@ -15,4 +19,3 @@ module Rack::App::InstanceMethods::Core
15
19
  end
16
20
 
17
21
  end
18
-
@@ -1,3 +1,4 @@
1
1
  module Rack::App::Middlewares
2
2
  require 'rack/app/middlewares/header_setter'
3
- end
3
+ require 'rack/app/middlewares/params'
4
+ end
@@ -0,0 +1,6 @@
1
+ module Rack::App::Middlewares::Params
2
+ require "rack/app/middlewares/params/definition"
3
+ require "rack/app/middlewares/params/parser"
4
+ require "rack/app/middlewares/params/setter"
5
+ require "rack/app/middlewares/params/validator"
6
+ end
@@ -0,0 +1,22 @@
1
+ class Rack::App::Middlewares::Params::Definition
2
+
3
+ require "rack/app/middlewares/params/definition/options"
4
+
5
+ def initialize(&descriptor)
6
+ @required = {}
7
+ @optional = {}
8
+ instance_exec(&descriptor)
9
+ end
10
+
11
+ def required(params_key,options)
12
+ @required[params_key.to_s]= self.class::Options.new(options).formatted
13
+ end
14
+
15
+ def optional(params_key,options)
16
+ @optional[params_key.to_s]= self.class::Options.new(options).formatted
17
+ end
18
+
19
+ def to_descriptor
20
+ {:required => @required, :optional => @optional}
21
+ end
22
+ end
@@ -0,0 +1,45 @@
1
+ class Rack::App::Middlewares::Params::Definition::Options
2
+
3
+ ERROR_EACH = 'class must implement #each method to use :of expression in parameter definition'
4
+
5
+ def initialize(descriptor)
6
+ @descriptor = descriptor
7
+ end
8
+
9
+ def formatted
10
+ {
11
+ :class => parameter_class,
12
+ :of => parameter_class_elements,
13
+ :description => parameter_description,
14
+ :example => parameter_example
15
+ }
16
+ end
17
+
18
+ protected
19
+
20
+ def parameter_example
21
+ @descriptor[:example]
22
+ end
23
+
24
+ def parameter_description
25
+ @descriptor[:desc] || @descriptor[:description]
26
+ end
27
+
28
+ def parameter_class
29
+ @descriptor[:class] || @descriptor[:type]
30
+ end
31
+
32
+ def parameter_class_elements
33
+ if @descriptor[:of]
34
+ raise "#{parameter_class} #{ERROR_EACH}" unless parameter_class_iterable?
35
+ @descriptor[:of]
36
+ end
37
+ end
38
+
39
+ def parameter_class_iterable?
40
+ parameter_class.instance_method(:each)
41
+ rescue NameError
42
+ false
43
+ end
44
+
45
+ end
@@ -0,0 +1,40 @@
1
+ class Rack::App::Middlewares::Params::Parser
2
+
3
+ def initialize(app, descriptor)
4
+ @app = app
5
+ @descriptor = descriptor
6
+ @merged_params_descriptor = descriptor.values.reduce(:merge)
7
+ end
8
+
9
+ def call(env)
10
+ set_params(env)
11
+
12
+ @app.call(env)
13
+ end
14
+
15
+ protected
16
+
17
+ def set_params(env)
18
+ params = Rack::App::Params.new(env).to_hash
19
+ validated_params = (env[::Rack::App::Constants::VALIDATED_PARAMS] ||= {})
20
+ parse_params(validated_params, params)
21
+ end
22
+
23
+ def parse_params(validated_params, params)
24
+ @merged_params_descriptor.each do |key, properties|
25
+ next if params[key].nil?
26
+
27
+ if properties[:of]
28
+ validated_params[key]= [*params[key]].map{ |str| parse(properties[:of], str) }
29
+ else
30
+ validated_params[key]= parse(properties[:class], params[key])
31
+ end
32
+
33
+ end
34
+ end
35
+
36
+ def parse(type, str)
37
+ Rack::App::Utils::Parser.parse(type, str)
38
+ end
39
+
40
+ end
@@ -0,0 +1,19 @@
1
+ class Rack::App::Middlewares::Params::Setter
2
+
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ env[::Rack::App::Constants::PARSED_PARAMS] ||= params_hash(env)
9
+
10
+ @app.call(env)
11
+ end
12
+
13
+ protected
14
+
15
+ def params_hash(env)
16
+ Rack::App::Params.new(env).to_hash
17
+ end
18
+
19
+ end
@@ -0,0 +1,102 @@
1
+ class Rack::App::Middlewares::Params::Validator
2
+ ValidateError = Class.new(ArgumentError)
3
+
4
+ def initialize(app, descriptor)
5
+ @descriptor = descriptor
6
+ @app = app
7
+ end
8
+
9
+ # => 422 required field missing | "422 Unprocessable Entity"
10
+ def call(env)
11
+ validate(env)
12
+ @app.call(env)
13
+ rescue ValidateError => ex
14
+ unprocessable_response(ex)
15
+ end
16
+
17
+ protected
18
+
19
+ def unprocessable_response(validate_error)
20
+ response = Rack::Response.new
21
+ response.status = 422
22
+ response.write("422 Unprocessable Entity\n")
23
+ response.write(validate_error.message)
24
+ response.finish
25
+ end
26
+
27
+ def validate(env)
28
+ params = Rack::App::Params.new(env).to_hash
29
+ validation_results(env, params)
30
+ end
31
+
32
+ def validation_results(env, params)
33
+ validate_required_params(env, params)
34
+ validate_optional_params(env, params)
35
+ validate_invalid_params(params)
36
+ end
37
+
38
+ def validate_required_params(env, params)
39
+ @descriptor[:required].each do |key, properties|
40
+ validate_key(key,properties,params)
41
+ end
42
+ end
43
+
44
+ def validate_optional_params(env, params)
45
+ @descriptor[:optional].each do |key, properties|
46
+ next unless params.has_key?(key)
47
+
48
+ validate_key(key,properties,params)
49
+ end
50
+ end
51
+
52
+ def validate_key(key,properties,params)
53
+ unless params.has_key?(key)
54
+ missing_key_error(key, properties[:class])
55
+ end
56
+
57
+ if properties[:of]
58
+ validate_array(properties[:class], properties[:of], key, *params[key])
59
+ elsif parse(properties[:class], params[key]).nil?
60
+ invalid_type_error(key, properties[:class])
61
+ end
62
+ end
63
+
64
+ def validate_invalid_params(params)
65
+ valid_keys = @descriptor[:required].keys + @descriptor[:optional].keys
66
+ (params.keys - valid_keys).each do |key|
67
+ invalid_key_error(key)
68
+ end
69
+ end
70
+
71
+ def validate_array(type, elements_type, key, *elements)
72
+ values = elements.map{ |str| parse(elements_type, str) }
73
+
74
+ if values.include?(nil)
75
+ invalid_type_of_error(key, type, elements_type)
76
+ end
77
+ end
78
+
79
+ def parse(type, str)
80
+ ::Rack::App::Utils::Parser.parse(type, str)
81
+ end
82
+
83
+ def error(message)
84
+ raise(ValidateError.new(message))
85
+ end
86
+
87
+ def missing_key_error(key, klass)
88
+ error "missing key: #{key}(#{klass})"
89
+ end
90
+
91
+ def invalid_key_error(key)
92
+ error "invalid key: #{key}"
93
+ end
94
+
95
+ def invalid_type_error(key,klass)
96
+ error "invalid type for #{key}: #{klass} expected"
97
+ end
98
+
99
+ def invalid_type_of_error(key,klass,of)
100
+ error "invalid type for #{key}: #{klass} of #{of} expected"
101
+ end
102
+ end
@@ -2,7 +2,11 @@ require 'cgi'
2
2
  class Rack::App::Params
3
3
 
4
4
  def to_hash
5
- query_params.merge(request_path_params)
5
+ if @request_env[::Rack::App::Constants::PARSED_PARAMS]
6
+ @request_env[::Rack::App::Constants::PARSED_PARAMS]
7
+ else
8
+ query_params.merge(request_path_params)
9
+ end
6
10
  end
7
11
 
8
12
  protected
@@ -57,4 +61,4 @@ class Rack::App::Params
57
61
  @request_env[::Rack::App::Constants::PATH_PARAMS_MATCHER] || {}
58
62
  end
59
63
 
60
- end
64
+ end
@@ -19,21 +19,21 @@ class Rack::App::Router
19
19
 
20
20
  wd0 = endpoints.map { |endpoint| endpoint[:request_method].to_s.length }.max
21
21
  wd1 = endpoints.map { |endpoint| endpoint[:request_path].to_s.length }.max
22
- wd2 = endpoints.map { |endpoint| endpoint[:description].to_s.length }.max
22
+ wd2 = endpoints.map { |endpoint| extract_description(endpoint).to_s.length }.max
23
23
 
24
24
  return endpoints.sort_by { |endpoint| [endpoint[:request_method], endpoint[:request_path]] }.map do |endpoint|
25
25
  [
26
26
  endpoint[:request_method].to_s.ljust(wd0),
27
27
  endpoint[:request_path].to_s.ljust(wd1),
28
- endpoint[:description].to_s.ljust(wd2)
28
+ extract_description(endpoint).to_s.ljust(wd2)
29
29
  ].join(' ')
30
30
  end
31
31
 
32
32
  end
33
33
 
34
- def register_endpoint!(request_method, request_path, description, endpoint)
34
+ def register_endpoint!(request_method, request_path, endpoint, properties)
35
35
  router = router_for(request_path)
36
- router.register_endpoint!(request_method, request_path, description, endpoint)
36
+ router.register_endpoint!(request_method, request_path, endpoint, properties)
37
37
  end
38
38
 
39
39
  def merge_router!(router, prop={})
@@ -44,8 +44,8 @@ class Rack::App::Router
44
44
  register_endpoint!(
45
45
  endpoint[:request_method],
46
46
  request_path,
47
- endpoint[:description],
48
- endpoint[:endpoint]
47
+ endpoint[:endpoint],
48
+ endpoint[:properties]
49
49
  )
50
50
  end
51
51
  nil
@@ -70,4 +70,9 @@ class Rack::App::Router
70
70
  path_str.include?(Rack::App::Constants::RACK_BASED_APPLICATION)
71
71
  end
72
72
 
73
+ def extract_description(endpoint_struct)
74
+ if endpoint_struct[:properties]
75
+ endpoint_struct[:properties][:description]
76
+ end
77
+ end
73
78
  end
@@ -20,13 +20,13 @@ class Rack::App::Router::Base
20
20
  @endpoints ||= []
21
21
  end
22
22
 
23
- def register_endpoint!(request_method, request_path, description, endpoint)
23
+ def register_endpoint!(request_method, request_path, endpoint, properties={})
24
24
  endpoints.push(
25
25
  {
26
26
  :request_method => request_method,
27
27
  :request_path => Rack::App::Utils.normalize_path(request_path),
28
- :description => description,
29
- :endpoint => endpoint
28
+ :endpoint => endpoint,
29
+ :properties => properties
30
30
  }
31
31
  )
32
32
 
@@ -44,4 +44,4 @@ class Rack::App::Router::Base
44
44
  raise('IMPLEMENTATION MISSING ERROR')
45
45
  end
46
46
 
47
- end
47
+ end
@@ -3,6 +3,7 @@ module Rack::App::SingletonMethods
3
3
  require 'rack/app/singleton_methods/http_methods'
4
4
  require 'rack/app/singleton_methods/inheritance'
5
5
  require 'rack/app/singleton_methods/mounting'
6
+ require 'rack/app/singleton_methods/params_validator'
6
7
  require 'rack/app/singleton_methods/rack_interface'
7
8
  require 'rack/app/singleton_methods/route_handling'
8
9
  require 'rack/app/singleton_methods/settings'
@@ -11,9 +12,10 @@ module Rack::App::SingletonMethods
11
12
  include Rack::App::SingletonMethods::HttpMethods
12
13
  include Rack::App::SingletonMethods::Inheritance
13
14
  include Rack::App::SingletonMethods::Mounting
15
+ include Rack::App::SingletonMethods::ParamsValidator
14
16
  include Rack::App::SingletonMethods::RackInterface
15
17
  include Rack::App::SingletonMethods::RouteHandling
16
18
  include Rack::App::SingletonMethods::Settings
17
19
  include Rack::App::SingletonMethods::Extensions
18
20
 
19
- end
21
+ end
@@ -35,7 +35,10 @@ module Rack::App::SingletonMethods::HttpMethods
35
35
  original_request_path = Rack::App::Utils.normalize_path(original_request_path)
36
36
 
37
37
  router.endpoints.select { |ep| ep[:request_path] == original_request_path }.each do |endpoint|
38
- router.register_endpoint!(endpoint[:request_method], new_request_path, endpoint[:description], endpoint[:endpoint])
38
+ router.register_endpoint!(
39
+ endpoint[:request_method], new_request_path,
40
+ endpoint[:endpoint], endpoint[:properties]
41
+ )
39
42
  end
40
43
  end
41
44
 
@@ -1,18 +1,16 @@
1
1
  module Rack::App::SingletonMethods::Mounting
2
+ MOUNT = Rack::App::SingletonMethods::Mounting
2
3
 
3
- def mount(api_class, properties={})
4
- unless api_class.is_a?(Class) and api_class <= Rack::App
5
- raise(ArgumentError, 'Invalid class given for mount, must be a Rack::App')
6
- end
4
+ def mount(app, options={})
5
+ options.freeze
7
6
 
8
- duplication = ::Rack::App::Utils.deep_dup(api_class)
9
- duplication.on_mounted.each do |on_mount|
10
- duplication.class_exec(::Rack::App::Utils.deep_dup(properties), &on_mount)
7
+ unless app.is_a?(Class) and app <= Rack::App
8
+ raise(ArgumentError, 'Invalid class given for mount, must be a Rack::App')
11
9
  end
12
10
 
13
- cli.merge!(duplication.cli)
14
- merge_prop = {:namespaces => [@namespaces, properties[:to]].flatten}
15
- router.merge_router!(duplication.router, merge_prop)
11
+ cli.merge!(app.cli)
12
+ merge_prop = {:namespaces => [@namespaces, options[:to]].flatten}
13
+ router.merge_router!(app.router, merge_prop)
16
14
 
17
15
  nil
18
16
  end
@@ -41,16 +39,17 @@ module Rack::App::SingletonMethods::Mounting
41
39
  def serve_files_from(file_path, options={})
42
40
  file_server = Rack::App::FileServer.new(Rack::App::Utils.expand_path(file_path))
43
41
  request_path = Rack::App::Utils.join(@namespaces, options[:to], '**', '*')
44
- router.register_endpoint!('GET', request_path, @last_description, file_server)
42
+ router.register_endpoint!('GET', request_path, file_server, route_registration_properties)
45
43
  @last_description = nil
46
44
  end
47
45
 
48
46
  def mount_rack_based_application(rack_based_app, options={})
49
47
  router.register_endpoint!(
50
- ::Rack::App::Constants::HTTP::ANY,
51
- Rack::App::Utils.join(@namespaces, options[:to], ::Rack::App::Constants::RACK_BASED_APPLICATION),
52
- @last_description,
53
- rack_based_app)
48
+ ::Rack::App::Constants::HTTP::ANY,
49
+ Rack::App::Utils.join(@namespaces, options[:to], ::Rack::App::Constants::RACK_BASED_APPLICATION),
50
+ rack_based_app,
51
+ route_registration_properties
52
+ )
54
53
 
55
54
  @last_description = nil
56
55
  end
@@ -0,0 +1,12 @@
1
+ module Rack::App::SingletonMethods::ParamsValidator
2
+ def validate_params(&block)
3
+ descriptor = Rack::App::Middlewares::Params::Definition.new(&block).to_descriptor
4
+ route_registration_properties[:params]= descriptor
5
+ only_next_endpoint_middlewares do |builder|
6
+ builder.use(Rack::App::Middlewares::Params::Setter)
7
+ builder.use(Rack::App::Middlewares::Params::Validator, descriptor)
8
+ builder.use(Rack::App::Middlewares::Params::Parser, descriptor)
9
+ end
10
+ end
11
+ alias params validate_params
12
+ end
@@ -7,11 +7,15 @@ module Rack::App::SingletonMethods::RouteHandling
7
7
  protected
8
8
 
9
9
  def root(endpoint_path)
10
- alias_endpoint '/', endpoint_path
10
+ alias_endpoint('/', endpoint_path)
11
+ end
12
+
13
+ def route_registration_properties
14
+ @route_registration_properties ||= {}
11
15
  end
12
16
 
13
17
  def description(*description_texts)
14
- @last_description = description_texts.join("\n")
18
+ route_registration_properties[:description]= description_texts.join("\n")
15
19
  end
16
20
 
17
21
  alias desc description
@@ -21,17 +25,18 @@ module Rack::App::SingletonMethods::RouteHandling
21
25
  request_path = ::Rack::App::Utils.join(@namespaces, request_path)
22
26
 
23
27
  builder = Rack::Builder.new
24
- middlewares.each do |builder_block|
28
+ (middlewares + only_next_endpoint_middlewares).each do |builder_block|
25
29
  builder_block.call(builder)
26
30
  end
27
31
 
32
+ only_next_endpoint_middlewares.clear
33
+
28
34
  properties = {
29
35
  :user_defined_logic => block,
30
36
  :request_method => request_method,
31
37
  :request_path => request_path,
32
38
 
33
39
  :error_handler => error,
34
- :description => @last_description,
35
40
  :serializer => serializer,
36
41
  :middleware => builder,
37
42
  :app_class => self
@@ -39,8 +44,9 @@ module Rack::App::SingletonMethods::RouteHandling
39
44
 
40
45
 
41
46
  endpoint = Rack::App::Endpoint.new(properties)
42
- router.register_endpoint!(request_method, request_path, @last_description, endpoint)
47
+ router.register_endpoint!(request_method, request_path, endpoint, route_registration_properties)
43
48
 
49
+ @route_registration_properties = nil
44
50
  @last_description = nil
45
51
  return endpoint
46
52
 
@@ -55,4 +61,4 @@ module Rack::App::SingletonMethods::RouteHandling
55
61
  nil
56
62
  end
57
63
 
58
- end
64
+ end
@@ -43,4 +43,10 @@ module Rack::App::SingletonMethods::Settings
43
43
 
44
44
  alias middleware middlewares
45
45
 
46
- end
46
+ def only_next_endpoint_middlewares(&block)
47
+ @only_next_endpoint_middlewares ||= []
48
+ @only_next_endpoint_middlewares << block unless block.nil?
49
+ @only_next_endpoint_middlewares
50
+ end
51
+
52
+ end
@@ -16,10 +16,9 @@ module Rack::App::Test
16
16
 
17
17
  properties = args.select { |e| e.is_a?(Hash) }.reduce({}, &:merge!)
18
18
  url = args.select { |e| e.is_a?(String) }.first || properties.delete(:url)
19
- request = Rack::MockRequest.new(rack_app)
20
- env = Rack::App::Test::Utils.env_by(properties)
21
-
22
- return @last_response = request.__send__(request_method, url, env)
19
+ mock_request = Rack::MockRequest.new(rack_app)
20
+ request_env = Rack::App::Test::Utils.env_by(properties)
21
+ return @last_response = mock_request.request(request_method, url, request_env)
23
22
 
24
23
  end
25
24
  end
@@ -29,7 +28,9 @@ module Rack::App::Test
29
28
  @app ||= lambda do
30
29
  app_class = defined?(__rack_app_class__) ? __rack_app_class__ : nil
31
30
  constructors = []
32
- constructors << __rack_app_constructor__ if defined?(__rack_app_constructor__) and __rack_app_constructor__.is_a?(Proc)
31
+ if defined?(__rack_app_constructor__) and __rack_app_constructor__.is_a?(Proc)
32
+ constructors << __rack_app_constructor__
33
+ end
33
34
  Rack::App::Test::Utils.rack_app_by(app_class, constructors)
34
35
  end.call
35
36
 
@@ -37,4 +38,4 @@ module Rack::App::Test
37
38
 
38
39
  end
39
40
 
40
- end
41
+ end
@@ -1,3 +1,4 @@
1
+ require "cgi"
1
2
  module Rack::App::Test::Utils
2
3
 
3
4
  extend self
@@ -10,13 +11,10 @@ module Rack::App::Test::Utils
10
11
  properties
11
12
  end
12
13
 
13
- def rack_app_by(subject_class, constructors, &block)
14
-
15
- app_class = subject_class.respond_to?(:call) ? subject_class : Rack::App
16
- app = Rack::App::Utils.deep_dup(app_class)
17
- constructors.each { |constructor| app.class_eval(&constructor) }
18
-
19
- return app
14
+ def rack_app_by(subject_class, constructors)
15
+ app_class = subject_class.respond_to?(:call) ? subject_class : Class.new(Rack::App)
16
+ constructors.each { |constructor| app_class.class_eval(&constructor) }
17
+ return app_class
20
18
  end
21
19
 
22
20
  def env_by(properties)
@@ -28,7 +26,6 @@ module Rack::App::Test::Utils
28
26
  env[::Rack::QUERY_STRING]= encode_www_form(properties[:params].to_a)
29
27
  env.merge!(properties[:env] || {})
30
28
 
31
-
32
29
  env
33
30
  end
34
31
 
@@ -67,30 +64,8 @@ module Rack::App::Test::Utils
67
64
  end.join('&')
68
65
  end
69
66
 
70
- TBLENCWWWCOMP_ = {} # :nodoc:
71
- 256.times do |i|
72
- TBLENCWWWCOMP_['%%%02X' % i] = i.chr
73
- end
74
- TBLENCWWWCOMP_[' '] = '+'
75
- TBLENCWWWCOMP_.freeze
76
- TBLDECWWWCOMP = {} # :nodoc:
77
- 256.times do |i|
78
- h, l = i>>4, i&15
79
- TBLDECWWWCOMP[i.chr]= '%%%X%X' % [h, l]
80
- TBLDECWWWCOMP[i.chr]= '%%%X%X' % [h, l]
81
- TBLDECWWWCOMP[i.chr]= '%%%x%X' % [h, l]
82
- TBLDECWWWCOMP[i.chr]= '%%%X%x' % [h, l]
83
- TBLDECWWWCOMP[i.chr]= '%%%x%x' % [h, l]
84
- end
85
- TBLDECWWWCOMP['+'] = ' '
86
- TBLDECWWWCOMP.freeze
87
-
88
67
  def encode_www_form_component(str)
89
- str = str.to_s.dup
90
- TBLENCWWWCOMP_.each do |from, to|
91
- str.gsub!(from, to)
92
- end
93
- str
68
+ CGI.escape(str.to_s)
94
69
  end
95
70
 
96
71
  end
@@ -3,6 +3,7 @@ module Rack::App::Utils
3
3
  extend self
4
4
 
5
5
  require 'rack/app/utils/deep_dup'
6
+ require 'rack/app/utils/parser'
6
7
 
7
8
  # Normalizes URI path.
8
9
  #
@@ -103,4 +104,4 @@ module Rack::App::Utils
103
104
  RUBY_PLATFORM =~ /mswin|mingw/ ? 'NUL:' : '/dev/null'
104
105
  end
105
106
 
106
- end
107
+ end
@@ -0,0 +1,45 @@
1
+ module Rack::App::Utils::Parser
2
+ extend(self)
3
+
4
+ require 'rack/app/utils/parser/custom'
5
+ require 'rack/app/utils/parser/string'
6
+ require 'rack/app/utils/parser/boolean'
7
+ require 'rack/app/utils/parser/date'
8
+ require 'rack/app/utils/parser/time'
9
+ require 'rack/app/utils/parser/date_time'
10
+ require 'rack/app/utils/parser/float'
11
+ require 'rack/app/utils/parser/integer'
12
+ require 'rack/app/utils/parser/numeric'
13
+
14
+ def parse(type, str)
15
+ string = str.to_s
16
+ parser = get_parser(type)
17
+ return unless parser.validate(string)
18
+ parser.parse(string)
19
+ end
20
+
21
+ protected
22
+
23
+ def get_parser(type)
24
+ case true
25
+ when [::String, :string].include?(type)
26
+ self::String.new
27
+ when [::TrueClass, ::FalseClass, :boolean].include?(type)
28
+ self::Boolean.new
29
+ when [::Date, :date].include?(type)
30
+ self::Date.new
31
+ when [::Time, :time].include?(type)
32
+ self::Time.new
33
+ when [::DateTime, :date_time, :datetime].include?(type)
34
+ self::DateTime.new
35
+ when [::Integer, :integer].include?(type)
36
+ self::Integer.new
37
+ when [::Float, :float].include?(type)
38
+ self::Float.new
39
+ when [::Numeric, :numeric].include?(type)
40
+ self::Numeric.new
41
+ else
42
+ self::Custom.new(type)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,30 @@
1
+ class Rack::App::Utils::Parser::Boolean
2
+ def parse(str)
3
+ case true
4
+
5
+ when true?(str)
6
+ true
7
+
8
+ when false?(str)
9
+ false
10
+
11
+ else
12
+ str
13
+
14
+ end
15
+ end
16
+
17
+ def validate(str)
18
+ false?(str) || true?(str)
19
+ end
20
+
21
+ protected
22
+
23
+ def false?(obj)
24
+ !!(obj =~ /^false$/)
25
+ end
26
+
27
+ def true?(obj)
28
+ !!(obj =~ /^true$/)
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ class Rack::App::Utils::Parser::Custom
2
+ def initialize(type)
3
+ @type = type
4
+ end
5
+
6
+ def parse(str)
7
+ @type.parse(str)
8
+ end
9
+
10
+ def validate(str)
11
+ parse(str)
12
+ return true
13
+ rescue Exception
14
+ return false
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ require 'date'
2
+ class Rack::App::Utils::Parser::Date
3
+ def parse(str)
4
+ ::Date.parse(str)
5
+ end
6
+
7
+ def validate(str)
8
+ [
9
+ /^\d+-\d\d-\d\d$/,
10
+ /^\w+, \d+ \w+ \d+$/
11
+ ].any? do |regexp|
12
+ !!(str =~ regexp)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ require 'date'
2
+ class Rack::App::Utils::Parser::DateTime
3
+ def parse(str)
4
+ ::DateTime.parse(str)
5
+ end
6
+
7
+ def validate(str)
8
+ [
9
+ /(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})[+-](\d{2})\:(\d{2})/,
10
+ /^\w+, \d+ \w+ \d+ \d\d:\d\d:\d\d \+\d+$/,
11
+ /^-?\d+-\d\d-\d\d\w\d\d:\d\d:\d\d\+\d\d:\d\d$/,
12
+ /\w+ \w+ \d+ \d+ \d+:\d+:\d+ \w+\+\d+ \(\w+\)/,
13
+ /^-?\d+-\d\d?-\d\d?\w\d\d?:\d\d?:\d\d?\w$/
14
+ ].any? do |regexp|
15
+ !!(str =~ regexp)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ class Rack::App::Utils::Parser::Float
2
+ def parse(str)
3
+ str.to_f
4
+ end
5
+
6
+ def validate(str)
7
+ case str.to_s
8
+ when /^\d+\.\d+$/, /^\d+,\d+$/
9
+ return true
10
+ else
11
+ return false
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,9 @@
1
+ class Rack::App::Utils::Parser::Integer
2
+ def parse(str)
3
+ str.to_s.to_i
4
+ end
5
+
6
+ def validate(str)
7
+ !!(str =~ /^\d+$/)
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ class Rack::App::Utils::Parser::Numeric
2
+ def parse(str)
3
+ case true
4
+ when int_parser.validate(str)
5
+ int_parser.parse(str)
6
+ when float_parser.validate(str)
7
+ float_parser.parse(str)
8
+ else
9
+ str
10
+ end
11
+ end
12
+
13
+ def validate(str)
14
+ int_parser.validate(str) || float_parser.validate(str)
15
+ end
16
+
17
+ protected
18
+
19
+ def int_parser
20
+ Rack::App::Utils::Parser::Integer.new
21
+ end
22
+
23
+ def float_parser
24
+ Rack::App::Utils::Parser::Float.new
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ class Rack::App::Utils::Parser::String
2
+ def parse(str)
3
+ str.to_s
4
+ end
5
+
6
+ def validate(str)
7
+ true
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ require 'time'
2
+ class Rack::App::Utils::Parser::Time
3
+ def parse(str)
4
+ ::Time.parse(str)
5
+ end
6
+
7
+ def validate(str)
8
+ [
9
+ /(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})[+-](\d{2})\:(\d{2})/,
10
+ /^\d+-\d\d-\d\d \d\d:\d\d:\d\d \+\d+$/
11
+ ].any? do |regexp|
12
+ !!(str =~ regexp)
13
+ end
14
+ end
15
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-app
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.0
4
+ version: 4.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Luzsi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-21 00:00:00.000000000 Z
11
+ date: 2016-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -77,6 +77,7 @@ extra_rdoc_files: []
77
77
  files:
78
78
  - ".gitignore"
79
79
  - ".rspec"
80
+ - ".rubocop.yml"
80
81
  - ".travis.yml"
81
82
  - CODE_OF_CONDUCT.md
82
83
  - CONTRIBUTING.md
@@ -112,6 +113,12 @@ files:
112
113
  - lib/rack/app/instance_methods/upload.rb
113
114
  - lib/rack/app/middlewares.rb
114
115
  - lib/rack/app/middlewares/header_setter.rb
116
+ - lib/rack/app/middlewares/params.rb
117
+ - lib/rack/app/middlewares/params/definition.rb
118
+ - lib/rack/app/middlewares/params/definition/options.rb
119
+ - lib/rack/app/middlewares/params/parser.rb
120
+ - lib/rack/app/middlewares/params/setter.rb
121
+ - lib/rack/app/middlewares/params/validator.rb
115
122
  - lib/rack/app/params.rb
116
123
  - lib/rack/app/request_configurator.rb
117
124
  - lib/rack/app/router.rb
@@ -126,6 +133,7 @@ files:
126
133
  - lib/rack/app/singleton_methods/http_methods.rb
127
134
  - lib/rack/app/singleton_methods/inheritance.rb
128
135
  - lib/rack/app/singleton_methods/mounting.rb
136
+ - lib/rack/app/singleton_methods/params_validator.rb
129
137
  - lib/rack/app/singleton_methods/rack_interface.rb
130
138
  - lib/rack/app/singleton_methods/route_handling.rb
131
139
  - lib/rack/app/singleton_methods/settings.rb
@@ -134,8 +142,19 @@ files:
134
142
  - lib/rack/app/test/utils.rb
135
143
  - lib/rack/app/utils.rb
136
144
  - lib/rack/app/utils/deep_dup.rb
145
+ - lib/rack/app/utils/parser.rb
146
+ - lib/rack/app/utils/parser/boolean.rb
147
+ - lib/rack/app/utils/parser/custom.rb
148
+ - lib/rack/app/utils/parser/date.rb
149
+ - lib/rack/app/utils/parser/date_time.rb
150
+ - lib/rack/app/utils/parser/float.rb
151
+ - lib/rack/app/utils/parser/integer.rb
152
+ - lib/rack/app/utils/parser/numeric.rb
153
+ - lib/rack/app/utils/parser/string.rb
154
+ - lib/rack/app/utils/parser/time.rb
137
155
  - lib/rack/app/version.rb
138
156
  - rack-app.gemspec
157
+ - src/Net__HTTP Cheat Sheet.pdf
139
158
  homepage: http://www.rack-app.com/
140
159
  licenses:
141
160
  - Apache License 2.0