rasti-web 2.0.1 → 2.1.0

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
  SHA256:
3
- metadata.gz: d5105988210acdb104ba5ad0088ab6b79083d43e27f29769a77d12c23c8af81f
4
- data.tar.gz: 4d046ecf2410d04a1a655575c502d8c9b335f761820137ee96ff04fe51319484
3
+ metadata.gz: 250a8b04a93b51665f10b49206453b490ffc83c81b8684ca466d4259ea23e75a
4
+ data.tar.gz: 9c32ed8ab0506cb4a4b407bb50fa6f12dc19bafc457e05181f18ba16ad9c5032
5
5
  SHA512:
6
- metadata.gz: 455368d0bd93f5c0b0a385a31165ecc089247d2c33359a328302556cb88201719c7103ded8a647cfbeda87badb280057e820d75eb9c178d7488c9f86375c6bb1
7
- data.tar.gz: 8ab0436ee72e1a20f267cc2f7f87be68bffe46ce6438a870381ab94e4551e708c581841d1eda67d9de60445a5f2e598081d4c48e9f1499d87c2fdb6826bd08d3
6
+ metadata.gz: f30ddbfed0f369d8eb9bcabe5e43b2a9891b9a53d3be55bbe3b4d492cf1724984e25b6a1ca91285879bedc14b8ec691525a345bf62dff48cc72f2d47eab80a8f
7
+ data.tar.gz: 120e466b7433332064adcc6ebfaf8cfc337433221954fddeb867a91cc801e53e8856a1b00a331c6867a1845d3c248f9febe1ac24a4e1864d3cd08ec511d3326d
data/README.md CHANGED
@@ -4,7 +4,6 @@
4
4
  [![Build Status](https://travis-ci.org/gabynaiman/rasti-web.svg?branch=master)](https://travis-ci.org/gabynaiman/rasti-web)
5
5
  [![Coverage Status](https://coveralls.io/repos/gabynaiman/rasti-web/badge.svg?branch=master)](https://coveralls.io/r/gabynaiman/rasti-web?branch=master)
6
6
  [![Code Climate](https://codeclimate.com/github/gabynaiman/rasti-web.svg)](https://codeclimate.com/github/gabynaiman/rasti-web)
7
- [![Dependency Status](https://gemnasium.com/gabynaiman/rasti-web.svg)](https://gemnasium.com/gabynaiman/rasti-web)
8
7
 
9
8
  Web blocks to build robust applications
10
9
 
@@ -16,6 +16,7 @@ require_relative 'web/application'
16
16
  require_relative 'web/template'
17
17
  require_relative 'web/view_context'
18
18
  require_relative 'web/render'
19
+ require_relative 'web/headers'
19
20
  require_relative 'web/request'
20
21
  require_relative 'web/controller'
21
22
  require_relative 'web/version'
@@ -1,7 +1,7 @@
1
1
  module Rasti
2
2
  module Web
3
3
  class Controller
4
-
4
+
5
5
  extend Forwardable
6
6
 
7
7
  def_delegators :request, :params, :session
@@ -19,7 +19,7 @@ module Rasti
19
19
 
20
20
  def action(action_name)
21
21
  raise "Undefined action '#{action_name}' in #{name}" unless instance_methods.include? action_name.to_sym
22
-
22
+
23
23
  Endpoint.new do |req, res, render|
24
24
  controller = new req, res, render
25
25
  begin
@@ -1,7 +1,7 @@
1
1
  module Rasti
2
2
  module Web
3
3
  class Endpoint
4
-
4
+
5
5
  def initialize(&block)
6
6
  @block = block
7
7
  end
@@ -0,0 +1,48 @@
1
+ module Rasti
2
+ module Web
3
+ class Headers
4
+
5
+ CONTENT_TYPES = {
6
+ text: 'text/plain',
7
+ html: 'text/html',
8
+ json: 'application/json',
9
+ js: 'application/javascript',
10
+ css: 'text/css'
11
+ }
12
+
13
+ class << self
14
+
15
+ CONTENT_TYPES.each do |key, value|
16
+ define_method "for_#{key}" do |charset:'utf-8'|
17
+ content_type value, charset: charset
18
+ end
19
+ end
20
+
21
+ def for_file(filename, attachment:true, charset:'utf-8')
22
+ merge content_type(MIME::Types.of(filename).first.content_type, charset: charset),
23
+ content_disposition(filename, attachment: attachment)
24
+ end
25
+
26
+ private
27
+
28
+ def content_type(type, charset:'utf-8')
29
+ {'Content-Type' => "#{type}; charset=#{charset}"}
30
+ end
31
+
32
+ def content_disposition(filename, attachment:true)
33
+ args = []
34
+ args << 'attachment' if attachment
35
+ args << "filename=\"#{File.basename(filename)}\""
36
+ {'Content-Disposition' => args.join("; ")}
37
+ end
38
+
39
+ def merge(*poperties)
40
+ poperties.inject({}) do |result, prop|
41
+ result.merge prop
42
+ end
43
+ end
44
+
45
+ end
46
+ end
47
+ end
48
+ end
@@ -11,60 +11,63 @@ module Rasti
11
11
  end
12
12
 
13
13
  def status(status, *args)
14
- respond_with status,
15
- extract_headers(args),
14
+ respond_with status,
15
+ extract_headers(args),
16
16
  extract_body(args)
17
17
  end
18
18
 
19
19
  def text(text, *args)
20
- respond_with extract_status(args),
21
- extract_headers(args).merge('Content-Type' => 'text/plain; charset=utf-8'),
20
+ respond_with extract_status(args),
21
+ extract_headers(args).merge(Headers.for_text),
22
22
  text
23
23
  end
24
24
 
25
25
  def html(html, *args)
26
- respond_with extract_status(args),
27
- extract_headers(args).merge('Content-Type' => 'text/html; charset=utf-8'),
26
+ respond_with extract_status(args),
27
+ extract_headers(args).merge(Headers.for_html),
28
28
  html
29
29
  end
30
30
 
31
31
  def json(object, *args)
32
- respond_with extract_status(args),
33
- extract_headers(args).merge('Content-Type' => 'application/json; charset=utf-8'),
32
+ respond_with extract_status(args),
33
+ extract_headers(args).merge(Headers.for_json),
34
34
  object.is_a?(String) ? object : JSON.dump(object)
35
35
  end
36
36
 
37
37
  def js(script, *args)
38
- respond_with extract_status(args),
39
- extract_headers(args).merge('Content-Type' => 'application/javascript; charset=utf-8'),
38
+ respond_with extract_status(args),
39
+ extract_headers(args).merge(Headers.for_js),
40
40
  script
41
41
  end
42
42
 
43
43
  def css(stylesheet, *args)
44
- respond_with extract_status(args),
45
- extract_headers(args).merge('Content-Type' => 'text/css; charset=utf-8'),
44
+ respond_with extract_status(args),
45
+ extract_headers(args).merge(Headers.for_css),
46
46
  stylesheet
47
47
  end
48
48
 
49
49
  def file(filename, *args)
50
- content_type = MIME::Types.of(filename).first.content_type
51
- body = File.read filename
50
+ respond_with extract_status(args),
51
+ Headers.for_file(filename).merge(extract_headers(args)),
52
+ File.read(filename)
53
+ end
52
54
 
53
- respond_with extract_status(args),
54
- extract_headers(args).merge('Content-Type' => content_type),
55
- body
55
+ def data(content, *args)
56
+ respond_with extract_status(args),
57
+ extract_headers(args),
58
+ content
56
59
  end
57
60
 
58
61
  def partial(template, locals={})
59
- response['Content-Type'] = 'text/html; charset=utf-8'
62
+ response.headers.merge! Headers.for_html
60
63
  response.write view_context.render(template, locals)
61
64
  end
62
65
 
63
66
  def layout(template=nil, &block)
64
67
  content = block.call if block
65
68
  layout = view_context.render(template || Web.default_layout) { content }
66
-
67
- response['Content-Type'] = 'text/html; charset=utf-8'
69
+
70
+ response.headers.merge! Headers.for_html
68
71
  response.write layout
69
72
  end
70
73
 
@@ -12,7 +12,7 @@ module Rasti
12
12
  end
13
13
 
14
14
  def body_text
15
- @body_text ||= begin
15
+ @body_text ||= begin
16
16
  text = body.read
17
17
  body.rewind
18
18
  text
@@ -20,8 +20,8 @@ module Rasti
20
20
  end
21
21
 
22
22
  def json?
23
- !content_type.nil? && ContentType.parse(content_type).mime_type == 'application/json'
24
- rescue
23
+ !content_type.nil? && ContentType.parse(content_type).mime_type == 'application/json'
24
+ rescue
25
25
  false
26
26
  end
27
27
 
@@ -17,7 +17,7 @@ module Rasti
17
17
  end
18
18
 
19
19
  def call(env)
20
- route = route_for env
20
+ route = route_for env
21
21
  env[ROUTE_PARAMS] = route.extract_params env['PATH_INFO']
22
22
  route.call env
23
23
  end
@@ -7,7 +7,7 @@ module Rasti
7
7
  template_file = files.detect { |f| File.exists? f }
8
8
 
9
9
  raise "Missing template #{template} [#{files.join(', ')}]" unless template_file
10
-
10
+
11
11
  tilt = cache.fetch(template_file) { Tilt.new template_file }
12
12
  tilt.render(context, locals, &block)
13
13
  end
@@ -17,7 +17,7 @@ module Rasti
17
17
  def self.cache
18
18
  Thread.current[:templates_cache] ||= Tilt::Cache.new
19
19
  end
20
-
20
+
21
21
  end
22
22
  end
23
23
  end
@@ -1,5 +1,5 @@
1
1
  module Rasti
2
2
  module Web
3
- VERSION = '2.0.1'
3
+ VERSION = '2.1.0'
4
4
  end
5
5
  end
@@ -8,7 +8,7 @@ class TestMiddleware
8
8
  def call(env)
9
9
  if env['PATH_INFO'] == '/private'
10
10
  [403, {}, ['Permission denied']]
11
- else
11
+ else
12
12
  @app.call env
13
13
  end
14
14
  end
@@ -50,7 +50,7 @@ describe Rasti::Web::Application do
50
50
 
51
51
  it 'Defined route' do
52
52
  get '/'
53
-
53
+
54
54
  last_response.status.must_equal 200
55
55
  last_response['Content-Type'].must_equal 'text/html; charset=utf-8'
56
56
  last_response.body.must_equal 'Page content'
@@ -58,7 +58,7 @@ describe Rasti::Web::Application do
58
58
 
59
59
  it 'Not found' do
60
60
  get '/not_found'
61
-
61
+
62
62
  last_response.status.must_equal 404
63
63
  last_response.body.must_equal 'Page not found'
64
64
  end
@@ -77,5 +77,5 @@ describe Rasti::Web::Application do
77
77
  last_response['Content-Type'].must_equal 'application/json; charset=utf-8'
78
78
  last_response.body.must_equal '{"id":123}'
79
79
  end
80
-
80
+
81
81
  end
@@ -1,7 +1,7 @@
1
1
  require 'minitest_helper'
2
2
 
3
3
  class TestController < Rasti::Web::Controller
4
-
4
+
5
5
  CustomError = Class.new StandardError
6
6
 
7
7
  def self.hooks_log
@@ -16,14 +16,14 @@ class TestController < Rasti::Web::Controller
16
16
  self.class.hooks_log << "After all: #{action_name}"
17
17
  end
18
18
 
19
- before_action :test do
19
+ before_action :test do
20
20
  self.class.hooks_log << 'Before single: test'
21
21
  end
22
22
 
23
- after_action :test do
23
+ after_action :test do
24
24
  self.class.hooks_log << 'After single: test'
25
25
  end
26
-
26
+
27
27
  def test
28
28
  render.html 'Test HTML'
29
29
  end
@@ -55,7 +55,7 @@ describe Rasti::Web::Controller do
55
55
  before do
56
56
  TestController.hooks_log.clear
57
57
  end
58
-
58
+
59
59
  it 'Action endpoint' do
60
60
  action = TestController.action :test
61
61
  env = Rack::MockRequest.env_for '/test'
@@ -65,7 +65,7 @@ describe Rasti::Web::Controller do
65
65
  status.must_equal 200
66
66
  headers['Content-Type'].must_equal 'text/html; charset=utf-8'
67
67
  response.body.must_equal ['Test HTML']
68
-
68
+
69
69
  TestController.hooks_log.must_equal [
70
70
  'Before single: test',
71
71
  'After single: test'
@@ -84,6 +84,7 @@ describe Rasti::Web::Controller do
84
84
  status, headers, response = action.call env
85
85
 
86
86
  status.must_equal 500
87
+ headers['Content-Type'].must_be_nil
87
88
  response.body.must_equal ['Explicit error']
88
89
 
89
90
  TestController.hooks_log.must_equal [
@@ -98,6 +99,7 @@ describe Rasti::Web::Controller do
98
99
  status, headers, response = action.call env
99
100
 
100
101
  status.must_equal 500
102
+ headers['Content-Type'].must_be_nil
101
103
  response.body.must_equal ['Implicit error']
102
104
 
103
105
  TestController.hooks_log.must_equal [
@@ -109,7 +111,7 @@ describe Rasti::Web::Controller do
109
111
  it 'Unexpected exception' do
110
112
  action = TestController.action :exception
111
113
  env = Rack::MockRequest.env_for '/exception'
112
-
114
+
113
115
  error = proc { action.call env }.must_raise RuntimeError
114
116
  error.message.must_equal 'Unexpected error'
115
117
 
@@ -8,7 +8,7 @@ describe Rasti::Web::Endpoint do
8
8
  res.must_be_instance_of Rack::Response
9
9
 
10
10
  render.text 'Content'
11
- end
11
+ end
12
12
 
13
13
  env = Rack::MockRequest.env_for '/'
14
14
 
@@ -10,7 +10,7 @@ describe Rasti::Web::Render do
10
10
 
11
11
  it 'Code' do
12
12
  render.status 404
13
-
13
+
14
14
  response.status.must_equal 404
15
15
  response['Content-Type'].must_be_nil
16
16
  response.body.must_equal []
@@ -18,7 +18,7 @@ describe Rasti::Web::Render do
18
18
 
19
19
  it 'Code and body' do
20
20
  render.status 500, 'Internal server error'
21
-
21
+
22
22
  response.status.must_equal 500
23
23
  response['Content-Type'].must_be_nil
24
24
  response.body.must_equal ['Internal server error']
@@ -167,7 +167,7 @@ describe Rasti::Web::Render do
167
167
  end
168
168
 
169
169
  describe 'Javascript' do
170
-
170
+
171
171
  it 'Body' do
172
172
  render.js 'alert("hello");'
173
173
 
@@ -205,7 +205,7 @@ describe Rasti::Web::Render do
205
205
  end
206
206
 
207
207
  describe 'CSS' do
208
-
208
+
209
209
  it 'Body' do
210
210
  render.css 'body{margin:0}'
211
211
 
@@ -250,7 +250,8 @@ describe Rasti::Web::Render do
250
250
  render.file filename
251
251
 
252
252
  response.status.must_equal 200
253
- response['Content-Type'].must_equal 'application/zip'
253
+ response['Content-Type'].must_equal 'application/zip; charset=utf-8'
254
+ response['Content-Disposition'].must_equal 'attachment; filename="sample_file.zip"'
254
255
  response.body.must_equal [File.read(filename)]
255
256
  end
256
257
 
@@ -258,7 +259,8 @@ describe Rasti::Web::Render do
258
259
  render.file filename, 206
259
260
 
260
261
  response.status.must_equal 206
261
- response['Content-Type'].must_equal 'application/zip'
262
+ response['Content-Type'].must_equal 'application/zip; charset=utf-8'
263
+ response['Content-Disposition'].must_equal 'attachment; filename="sample_file.zip"'
262
264
  response.body.must_equal [File.read(filename)]
263
265
  end
264
266
 
@@ -266,7 +268,7 @@ describe Rasti::Web::Render do
266
268
  render.file filename, 'Content-Disposition' => 'attachment; filename=test_file.zip'
267
269
 
268
270
  response.status.must_equal 200
269
- response['Content-Type'].must_equal 'application/zip'
271
+ response['Content-Type'].must_equal 'application/zip; charset=utf-8'
270
272
  response['Content-Disposition'].must_equal 'attachment; filename=test_file.zip'
271
273
  response.body.must_equal [File.read(filename)]
272
274
  end
@@ -275,13 +277,53 @@ describe Rasti::Web::Render do
275
277
  render.file filename, 206, 'Content-Disposition' => 'attachment; filename=test_file.zip'
276
278
 
277
279
  response.status.must_equal 206
278
- response['Content-Type'].must_equal 'application/zip'
280
+ response['Content-Type'].must_equal 'application/zip; charset=utf-8'
279
281
  response['Content-Disposition'].must_equal 'attachment; filename=test_file.zip'
280
282
  response.body.must_equal [File.read(filename)]
281
283
  end
282
284
 
283
285
  end
284
286
 
287
+ describe 'Data' do
288
+
289
+ let(:content) { 'Response data' }
290
+
291
+ it 'Body' do
292
+ render.data content
293
+
294
+ response.status.must_equal 200
295
+ response['Content-Type'].must_be_nil
296
+ response.body.must_equal [content]
297
+ end
298
+
299
+ it 'Body and status' do
300
+ render.data content, 206
301
+
302
+ response.status.must_equal 206
303
+ response['Content-Type'].must_be_nil
304
+ response.body.must_equal [content]
305
+ end
306
+
307
+ it 'Body and headers' do
308
+ render.data content, Rasti::Web::Headers.for_file('test_file.txt')
309
+
310
+ response.status.must_equal 200
311
+ response['Content-Type'].must_equal 'text/plain; charset=utf-8'
312
+ response['Content-Disposition'].must_equal 'attachment; filename="test_file.txt"'
313
+ response.body.must_equal [content]
314
+ end
315
+
316
+ it 'Body, status and headers' do
317
+ render.data content, 206, Rasti::Web::Headers.for_file('test_file.txt')
318
+
319
+ response.status.must_equal 206
320
+ response['Content-Type'].must_equal 'text/plain; charset=utf-8'
321
+ response['Content-Disposition'].must_equal 'attachment; filename="test_file.txt"'
322
+ response.body.must_equal [content]
323
+ end
324
+
325
+ end
326
+
285
327
  it 'Partial' do
286
328
  render.partial 'context_and_locals', title: 'Welcome', text: 'Hello world'
287
329
 
@@ -291,7 +333,7 @@ describe Rasti::Web::Render do
291
333
  end
292
334
 
293
335
  describe 'Layout' do
294
-
336
+
295
337
  it 'Default' do
296
338
  render.layout { 'Page content' }
297
339
 
@@ -322,7 +364,7 @@ describe Rasti::Web::Render do
322
364
 
323
365
  it 'Default layout' do
324
366
  render.view 'context_and_locals', title: 'Welcome', text: 'Hello world'
325
-
367
+
326
368
  response.status.must_equal 200
327
369
  response['Content-Type'].must_equal 'text/html; charset=utf-8'
328
370
  response.body.must_equal ['<html><body><h1>Welcome</h1><div>Hello world</div></body></html>']
@@ -330,7 +372,7 @@ describe Rasti::Web::Render do
330
372
 
331
373
  it 'Custom layout' do
332
374
  render.view 'context_and_locals', {title: 'Welcome', text: 'Hello world'}, 'custom_layout'
333
-
375
+
334
376
  response.status.must_equal 200
335
377
  response['Content-Type'].must_equal 'text/html; charset=utf-8'
336
378
  response.body.must_equal ['<html><body class="custom"><h1>Welcome</h1><div>Hello world</div></body></html>']
@@ -1,7 +1,7 @@
1
1
  require 'minitest_helper'
2
2
 
3
3
  describe Rasti::Web::Request do
4
-
4
+
5
5
  it 'Route params' do
6
6
  env = Rack::MockRequest.env_for '/10/20'
7
7
  env[Rasti::Web::ROUTE_PARAMS] = {'lat' => '10', 'lon' => '20'}
@@ -3,12 +3,12 @@ require 'minitest_helper'
3
3
  describe Rasti::Web::Route do
4
4
 
5
5
  ROUTES = [
6
- '/',
6
+ '/',
7
7
  '/*/wildcard/action',
8
8
  '/wildcard/*/action',
9
9
  '/wildcard/*/action/:id',
10
10
  '/wildcard/*',
11
- '/resource',
11
+ '/resource',
12
12
  '/resource/:id/:action',
13
13
  '/:resource(/:id(/:action))'
14
14
  ]
@@ -72,7 +72,7 @@ describe Rasti::Web::Route do
72
72
 
73
73
  route.pattern.must_equal '/*/wildcard/action'
74
74
  route.extract_params(path).must_equal wildcard: 'section/sub_section'
75
- route.call({}).must_equal RESPONSE
75
+ route.call({}).must_equal RESPONSE
76
76
  end
77
77
 
78
78
  it 'Middle' do
@@ -97,7 +97,7 @@ describe Rasti::Web::Route do
97
97
 
98
98
  it 'Params' do
99
99
  path = '/wildcard/section/sub_section/action/123'
100
-
100
+
101
101
  route = route_for path
102
102
 
103
103
  route.pattern.must_equal '/wildcard/*/action/:id'
@@ -11,7 +11,7 @@ describe Rasti::Web::Router do
11
11
  def post(path)
12
12
  Rack::MockRequest.env_for path, method: :post
13
13
  end
14
-
14
+
15
15
  it 'Verbs' do
16
16
  %w(delete get head options patch post put).each do |verb|
17
17
  router.must_respond_to verb
@@ -5,7 +5,7 @@ describe Rasti::Web::Template do
5
5
  class Context
6
6
  include ContextMethodHelper
7
7
  end
8
-
8
+
9
9
  it 'Plain HTML' do
10
10
  Rasti::Web::Template.render('plain_html').must_equal '<div>Hello world</div>'
11
11
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rasti-web
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriel Naiman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-27 00:00:00.000000000 Z
11
+ date: 2020-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -247,6 +247,7 @@ files:
247
247
  - lib/rasti/web/application.rb
248
248
  - lib/rasti/web/controller.rb
249
249
  - lib/rasti/web/endpoint.rb
250
+ - lib/rasti/web/headers.rb
250
251
  - lib/rasti/web/render.rb
251
252
  - lib/rasti/web/request.rb
252
253
  - lib/rasti/web/route.rb