angelo 0.1.9 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b5543dd789ee112be7d4e19f9cb94d85d838369b
4
- data.tar.gz: d06335e0f1524c29b8b04d8b3c84d1d0a18a0069
3
+ metadata.gz: 809986637c600091f82231a4d8b5abc98d8901f6
4
+ data.tar.gz: 4d85c02a50c030f4405fcb7c957e44fd7c27e3d4
5
5
  SHA512:
6
- metadata.gz: 1a62cffec398cb4ae84c238f78bdd7d733edaf2f0c12a60c95df4ba78e7f1651165dbe1ff32977b6e6313ae291e79200e4dbb092b61b98bab366c52d8f5f2882
7
- data.tar.gz: da2f279b41793155d5e3e24737d193a1acdff58ce19e7063ea1750ffa14eb1657d611fc04db568c72cc84d2364b70cdfecd01a9429515510662f94894be2b09d
6
+ metadata.gz: a61261e0ff70da449f4f3b15c55ec9261947d8b49b7b6747a4d660f76862268e25d50254977ef5e428215dbfb732ae03885f9c0c4ee07a14f9ac69b405e0a1b0
7
+ data.tar.gz: 5558f936798f39acb2a2f56ae98a3ad90fc66fd3a47be8992753f1cf6f5fec24cee5a79833e7a4cad26e2022258aed89084bf54b420c7430479ff37eb876b134
data/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
1
  changelog
2
2
  =========
3
3
 
4
+ ### 0.1.10 12 jun 2014
5
+
6
+ * add RequestError
7
+
8
+ ### 0.1.9 5 jun 2014
9
+
4
10
  * make Mustermann::RouteMap not descend from Hash
5
11
  * replace WebsocketsArray < Array with Stash
6
12
  * fix WebsocketRepsonder.close_socket
data/README.md CHANGED
@@ -157,6 +157,71 @@ get '/' do
157
157
  end
158
158
  ```
159
159
 
160
+ ### Errors
161
+
162
+ In Sinatra, one can `halt` and optionally pass status codes and a body. While that functionality
163
+ is not (yet?) present in Angelo, the ability to `raise` a `RequestError` is. Raising an instance
164
+ of this always\* causes a 400 status code response, and the message in the instance is the body
165
+ of the the response. If the route or class was set to respond with JSON, the body is converted
166
+ to a JSON object with one key, `error`, that has a value of the message. If the message is a
167
+ `Hash`, the hash is converted to a JSON object, or to a string for other content types.
168
+
169
+ \* If you want to return a different status code, you can pass it as a second argument to
170
+ `RequestError.new`. See example below.
171
+
172
+ ##### Example
173
+
174
+ ```ruby
175
+ get '/' do
176
+ raise RequestError.new '"foo" is a required parameter' unless params[:foo]
177
+ params[:foo]
178
+ end
179
+
180
+ get '/json' do
181
+ content_type :json
182
+ raise RequestError.new foo: "required!"
183
+ {foo: params[:foo]}
184
+ end
185
+
186
+ get '/not_found' do
187
+ raise RequestError.new 'not found', 404
188
+ end
189
+ ```
190
+
191
+ ```
192
+ $ curl -i http://127.0.0.1:4567/
193
+ HTTP/1.1 400 Bad Request
194
+ Content-Type: text/html
195
+ Connection: Keep-Alive
196
+ Content-Length: 29
197
+
198
+ "foo" is a required parameter
199
+
200
+ $ curl -i http://127.0.0.1:4567/?foo=bar
201
+ HTTP/1.1 200 OK
202
+ Content-Type: text/html
203
+ Connection: Keep-Alive
204
+ Content-Length: 3
205
+
206
+ bar
207
+
208
+ $ curl -i http://127.0.0.1:4567/json
209
+ HTTP/1.1 400 Bad Request
210
+ Content-Type: application/json
211
+ Connection: Keep-Alive
212
+ Content-Length: 29
213
+
214
+ {"error":{"foo":"required!"}}
215
+
216
+ $ curl -i http://127.0.0.1:4567/not_found
217
+ HTTP/1.1 404 Not Found
218
+ Content-Type: text/html
219
+ Connection: Keep-Alive
220
+ Content-Length: 9
221
+
222
+ not found
223
+ ```
224
+
160
225
  ### WORK LEFT TO DO
161
226
 
162
227
  Lots of work left to do!
data/lib/angelo/base.rb CHANGED
@@ -27,20 +27,31 @@ module Angelo
27
27
  attr_accessor :app_file, :server
28
28
 
29
29
  def inherited subclass
30
+
31
+ # set app_file from caller stack
32
+ #
30
33
  subclass.app_file = caller(1).map {|l| l.split(/:(?=|in )/, 3)[0,1]}.flatten[0]
31
34
 
32
- def subclass.root
33
- @root ||= File.expand_path '..', app_file
34
- end
35
+ # bring RequestError into this namespace
36
+ #
37
+ subclass.class_eval 'class RequestError < Angelo::RequestError; end'
35
38
 
36
- def subclass.view_dir
37
- v = self.class_variable_get(:@@views) rescue DEFAULT_VIEW_DIR
38
- File.join root, v
39
- end
39
+ class << subclass
40
+
41
+ def root
42
+ @root ||= File.expand_path '..', app_file
43
+ end
44
+
45
+ def view_dir
46
+ v = self.class_variable_get(:@@views) rescue DEFAULT_VIEW_DIR
47
+ File.join root, v
48
+ end
49
+
50
+ def public_dir
51
+ p = self.class_variable_get(:@@public_dir) rescue DEFAULT_PUBLIC_DIR
52
+ File.join root, p
53
+ end
40
54
 
41
- def subclass.public_dir
42
- p = self.class_variable_get(:@@public_dir) rescue DEFAULT_PUBLIC_DIR
43
- File.join root, p
44
55
  end
45
56
 
46
57
  end
@@ -29,9 +29,7 @@ module Angelo
29
29
 
30
30
  def hc
31
31
  @hc ||= HTTPClient.new
32
- @hc
33
32
  end
34
- private :hc
35
33
 
36
34
  def hc_req method, path, params = {}, headers = {}
37
35
  url = HTTP_URL % [DEFAULT_ADDR, DEFAULT_PORT]
@@ -39,8 +37,30 @@ module Angelo
39
37
  end
40
38
  private :hc_req
41
39
 
40
+ def http_req method, path, params = {}, headers = {}
41
+ url = HTTP_URL % [DEFAULT_ADDR, DEFAULT_PORT] + path
42
+ params = case params
43
+ when String; {body: params}
44
+ when Hash
45
+ case method
46
+ when :get, :delete, :options
47
+ {params: params}
48
+ else
49
+ {form: params}
50
+ end
51
+ end
52
+ @last_response = case
53
+ when !headers.empty?
54
+ ::HTTP.with(headers).__send__ method, url, params
55
+ else
56
+ ::HTTP.__send__ method, url, params
57
+ end
58
+ end
59
+ private :http_req
60
+
42
61
  [:get, :post, :put, :delete, :options, :head].each do |m|
43
62
  define_method m do |path, params = {}, headers = {}|
63
+ # http_req m, path, params, headers
44
64
  hc_req m, path, params, headers
45
65
  end
46
66
  end
@@ -63,13 +83,13 @@ module Angelo
63
83
 
64
84
  def last_response_must_be_html body = ''
65
85
  last_response.status.must_equal 200
66
- last_response.body.must_equal body
86
+ last_response.body.to_s.must_equal body
67
87
  last_response.headers['Content-Type'].split(';').must_include HTML_TYPE
68
88
  end
69
89
 
70
90
  def last_response_must_be_json obj = {}
71
91
  last_response.status.must_equal 200
72
- JSON.parse(last_response.body).must_equal obj
92
+ JSON.parse(last_response.body.to_s).must_equal obj
73
93
  last_response.headers['Content-Type'].split(';').must_include JSON_TYPE
74
94
  end
75
95
 
@@ -49,7 +49,6 @@ module Angelo
49
49
  @redirect = nil
50
50
  @request = request
51
51
  handle_request
52
- respond
53
52
  end
54
53
 
55
54
  def handle_request
@@ -57,6 +56,7 @@ module Angelo
57
56
  @base.before if @base.respond_to? :before
58
57
  @body = @response_handler.bind(@base).call || ''
59
58
  @base.after if @base.respond_to? :after
59
+ respond
60
60
  else
61
61
  raise NotImplementedError
62
62
  end
@@ -64,6 +64,8 @@ module Angelo
64
64
  handle_error jpe, :bad_request, false
65
65
  rescue FormEncodingError => fee
66
66
  handle_error fee, :bad_request, false
67
+ rescue RequestError => re
68
+ handle_error re, re.type, false
67
69
  rescue => e
68
70
  handle_error e
69
71
  end
@@ -84,7 +86,12 @@ module Angelo
84
86
  when respond_with?(:json)
85
87
  { error: _error.message }.to_json
86
88
  else
87
- _error.message
89
+ case _error.message
90
+ when Hash
91
+ _error.message.to_s
92
+ else
93
+ _error.message
94
+ end
88
95
  end
89
96
  end
90
97
 
@@ -1,3 +1,3 @@
1
1
  module Angelo
2
- VERSION = '0.1.9'
2
+ VERSION = '0.1.10'
3
3
  end
data/lib/angelo.rb CHANGED
@@ -73,6 +73,31 @@ module Angelo
73
73
 
74
74
  end
75
75
 
76
+ class RequestError < Reel::RequestError
77
+
78
+ attr_accessor :type
79
+ alias_method :code=, :type=
80
+
81
+ def initialize msg = nil, type = nil
82
+ case msg
83
+ when Hash
84
+ @msg_hash = msg
85
+ else
86
+ super(msg)
87
+ end
88
+ self.type = type if type
89
+ end
90
+
91
+ def type
92
+ @type ||= :bad_request
93
+ end
94
+
95
+ def message
96
+ @msg_hash || super
97
+ end
98
+
99
+ end
100
+
76
101
  end
77
102
 
78
103
  require 'angelo/version'
@@ -0,0 +1,110 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe Angelo::Base do
4
+
5
+ describe :handle_error do
6
+
7
+ define_app do
8
+
9
+ Angelo::HTTPABLE.each do |m|
10
+ __send__ m, '/' do
11
+
12
+ # this specificity (self.class::) is uneeded in actual practice
13
+ # something about the anonymous class nature of self at this point
14
+ # see Angelo::Minittest::Helpers#define_app
15
+ #
16
+ raise self.class::RequestError.new 'error message'
17
+ end
18
+ end
19
+
20
+ Angelo::HTTPABLE.each do |m|
21
+ __send__ m, '/json' do
22
+ content_type :json
23
+ raise self.class::RequestError.new 'error message' # see above
24
+ end
25
+ end
26
+
27
+ Angelo::HTTPABLE.each do |m|
28
+ __send__ m, '/msg_hash' do
29
+ raise self.class::RequestError.new msg: 'error', foo: 'bar' # see above
30
+ end
31
+ end
32
+
33
+ Angelo::HTTPABLE.each do |m|
34
+ __send__ m, '/msg_hash_json' do
35
+ content_type :json
36
+ raise self.class::RequestError.new msg: 'error', foo: 'bar' # see above
37
+ end
38
+ end
39
+
40
+ Angelo::HTTPABLE.each do |m|
41
+ __send__ m, '/not_found' do
42
+ raise self.class::RequestError.new 'not found', 404 # see above
43
+ end
44
+ end
45
+
46
+ Angelo::HTTPABLE.each do |m|
47
+ __send__ m, '/enhance_your_calm' do
48
+ raise self.class::RequestError.new 'enhance your calm, bro', 420 # see above
49
+ end
50
+ end
51
+
52
+ end
53
+
54
+ it 'handles raised errors correctly' do
55
+ Angelo::HTTPABLE.each do |m|
56
+ __send__ m, '/'
57
+ last_response.status.must_equal 400
58
+ last_response.headers['Content-Type'].split(';').must_include Angelo::HTML_TYPE
59
+ last_response.body.to_s.must_equal 'error message'
60
+ end
61
+ end
62
+
63
+ it 'handles raised errors with json content type correctly' do
64
+ Angelo::HTTPABLE.each do |m|
65
+ __send__ m, '/json'
66
+ last_response.status.must_equal 400
67
+ last_response.headers['Content-Type'].split(';').must_include Angelo::JSON_TYPE
68
+ last_response.body.to_s.must_equal({error: 'error message'}.to_json)
69
+ end
70
+ end
71
+
72
+ it 'handles raised errors with hash messages correctly' do
73
+ Angelo::HTTPABLE.each do |m|
74
+ __send__ m, '/msg_hash'
75
+ last_response.status.must_equal 400
76
+ last_response.headers['Content-Type'].split(';').must_include Angelo::HTML_TYPE
77
+ last_response.body.to_s.must_equal '{:msg=>"error", :foo=>"bar"}'
78
+ end
79
+ end
80
+
81
+ it 'handles raised errors with hash messages with json content type correctly' do
82
+ Angelo::HTTPABLE.each do |m|
83
+ __send__ m, '/msg_hash_json'
84
+ last_response.status.must_equal 400
85
+ last_response.headers['Content-Type'].split(';').must_include Angelo::JSON_TYPE
86
+ last_response.body.to_s.must_equal({error: {msg: 'error', foo: 'bar'}}.to_json)
87
+ end
88
+ end
89
+
90
+ it 'handles raising errors with other status codes correctly' do
91
+
92
+ Angelo::HTTPABLE.each do |m|
93
+ __send__ m, '/not_found'
94
+ last_response.status.must_equal 404
95
+ last_response.headers['Content-Type'].split(';').must_include Angelo::HTML_TYPE
96
+ last_response.body.to_s.must_equal 'not found'
97
+ end
98
+
99
+ Angelo::HTTPABLE.each do |m|
100
+ __send__ m, '/enhance_your_calm'
101
+ last_response.status.must_equal 420
102
+ last_response.headers['Content-Type'].split(';').must_include Angelo::HTML_TYPE
103
+ last_response.body.to_s.must_equal 'enhance your calm, bro'
104
+ end
105
+
106
+ end
107
+
108
+ end
109
+
110
+ end
@@ -1,4 +1,4 @@
1
- if RUBY_VERSION =~ /^2\./
1
+ if RUBY_VERSION =~ /^2\./ and RUBY_PLATFORM != 'java'
2
2
 
3
3
  require_relative '../spec_helper'
4
4
  require 'angelo/mustermann'
@@ -27,8 +27,8 @@ describe Angelo::Server do
27
27
  last_response.headers['Content-Disposition'].must_equal 'attachment; filename=test.css'
28
28
  last_response.headers['Content-Length'].must_equal '116'
29
29
  last_response.headers['Etag'].must_equal css_etag
30
- last_response.body.length.must_equal 116
31
- last_response.body.must_equal File.read(File.join TEST_APP_ROOT, 'public', 'test.css')
30
+ last_response.body.to_s.length.must_equal 116
31
+ last_response.body.to_s.must_equal File.read(File.join TEST_APP_ROOT, 'public', 'test.css')
32
32
  end
33
33
 
34
34
  it 'serves headers for static files on head' do
@@ -38,7 +38,7 @@ describe Angelo::Server do
38
38
  last_response.headers['Content-Disposition'].must_equal 'attachment; filename=test.css'
39
39
  last_response.headers['Content-Length'].must_equal '116'
40
40
  last_response.headers['Etag'].must_equal css_etag
41
- last_response.body.length.must_equal 0
41
+ last_response.body.to_s.length.must_equal 0
42
42
  end
43
43
 
44
44
  it 'serves static file over route' do
@@ -46,7 +46,7 @@ describe Angelo::Server do
46
46
  last_response.status.must_equal 200
47
47
  last_response.headers['Content-Type'].must_equal 'text/html'
48
48
  last_response.headers['Content-Disposition'].must_equal 'attachment; filename=test.html'
49
- last_response.body.must_equal File.read(File.join TEST_APP_ROOT, 'public', 'test.html')
49
+ last_response.body.to_s.must_equal File.read(File.join TEST_APP_ROOT, 'public', 'test.html')
50
50
  end
51
51
 
52
52
  it 'not modifieds when if-none-match matched etag' do
@@ -3,7 +3,7 @@ require_relative '../spec_helper'
3
3
  describe Angelo::WebsocketResponder do
4
4
 
5
5
  def websocket_wait_for path, latch, expectation, key = :swf, &block
6
- Reactor.testers[key] = Array.new(CONCURRENCY).map do
6
+ Reactor.testers[key] = Array.new CONCURRENCY do
7
7
  wsh = websocket_helper path
8
8
  wsh.on_message = ->(e) {
9
9
  expectation[e] if Proc === expectation
data/test/angelo_spec.rb CHANGED
@@ -114,6 +114,7 @@ describe Angelo::Base do
114
114
  it 'runs after filters after routes' do
115
115
  a = %w[2 6 14 30]
116
116
  b = [4, 12, 28, 60]
117
+
117
118
  Angelo::HTTPABLE.each_with_index do |m,i|
118
119
  __send__ m, '/after', obj
119
120
  last_response_must_be_html a[i]
@@ -1 +1 @@
1
- setTimeout(function(){ alert('hi'); }, 3000);
1
+ // setTimeout(function(){ alert('hi'); }, 3000);
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: angelo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenichi Nakamura
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-05 00:00:00.000000000 Z
11
+ date: 2014-06-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: reel
@@ -65,6 +65,7 @@ files:
65
65
  - lib/angelo/tilt/erb.rb
66
66
  - lib/angelo/version.rb
67
67
  - test/angelo/erb_spec.rb
68
+ - test/angelo/error_spec.rb
68
69
  - test/angelo/mustermann_spec.rb
69
70
  - test/angelo/params_spec.rb
70
71
  - test/angelo/static_spec.rb