angelo 0.1.9 → 0.1.10

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