jellyfish 1.0.2 → 1.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 +4 -4
- data/.travis.yml +1 -0
- data/CHANGES.md +14 -0
- data/README.md +194 -119
- data/bench/bench_builder.rb +44 -0
- data/config.ru +5 -39
- data/jellyfish.gemspec +12 -18
- data/lib/jellyfish.rb +1 -3
- data/lib/jellyfish/builder.rb +52 -0
- data/lib/jellyfish/test.rb +8 -8
- data/lib/jellyfish/urlmap.rb +26 -0
- data/lib/jellyfish/version.rb +1 -1
- data/task/gemgem.rb +11 -2
- data/test/rack/test_builder.rb +154 -0
- data/test/rack/test_urlmap.rb +180 -0
- data/test/sinatra/test_base.rb +1 -1
- data/test/sinatra/test_routing.rb +0 -60
- data/test/test_from_readme.rb +31 -20
- metadata +15 -17
- data/lib/jellyfish/multi_actions.rb +0 -31
- data/lib/jellyfish/sinatra.rb +0 -13
- data/lib/jellyfish/swagger.rb +0 -166
- data/public/css/screen.css +0 -1070
- data/public/index.html +0 -45
- data/public/js/shred.bundle.js +0 -2765
- data/public/js/shred/content.js +0 -193
- data/public/js/swagger-ui.js +0 -2116
- data/public/js/swagger.js +0 -1400
- data/test/sinatra/test_multi_actions.rb +0 -217
- data/test/test_swagger.rb +0 -131
data/config.ru
CHANGED
@@ -17,20 +17,6 @@ class Jelly
|
|
17
17
|
end
|
18
18
|
}
|
19
19
|
|
20
|
-
def self.info
|
21
|
-
{:title => 'Jellyfish Swagger UI',
|
22
|
-
:description =>
|
23
|
-
'This is a simple example for using Jellyfish and' \
|
24
|
-
' Swagger UI altogether. You could also try the' \
|
25
|
-
' <a href="http://swagger.wordnik.com/">official Swagger UI app</a>,' \
|
26
|
-
' and fill it with the swagger URL.'
|
27
|
-
}
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.swagger_apiVersion
|
31
|
-
'1.0.0'
|
32
|
-
end
|
33
|
-
|
34
20
|
handle Jellyfish::NotFound do |e|
|
35
21
|
status 404
|
36
22
|
body %Q|{"error":{"name":"NotFound"}}\n|
|
@@ -46,30 +32,15 @@ class Jelly
|
|
46
32
|
body render('error' => {'name' => name, 'message' => message})
|
47
33
|
end
|
48
34
|
|
49
|
-
get '/users'
|
50
|
-
:summary => 'List users',
|
51
|
-
:notes => 'Note that we do not really have users.' do
|
35
|
+
get '/users' do
|
52
36
|
render [:name => 'jellyfish']
|
53
37
|
end
|
54
38
|
|
55
|
-
post '/users'
|
56
|
-
:summary => 'Create a user',
|
57
|
-
:notes => 'Here we demonstrate how to write the swagger doc.',
|
58
|
-
:parameters => {:name => {:type => :string, :required => true,
|
59
|
-
:description => 'The name of the user'},
|
60
|
-
:sane => {:type => :boolean,
|
61
|
-
:description => 'If the user is sane'},
|
62
|
-
:type => {:type => :string,
|
63
|
-
:description => 'What kind of user',
|
64
|
-
:enum => %w[good neutral evil]}},
|
65
|
-
:responseMessages => [{:code => 400, :message => 'Invalid name'}] do
|
39
|
+
post '/users' do
|
66
40
|
render :message => "jellyfish #{request.params['name']} created."
|
67
41
|
end
|
68
42
|
|
69
|
-
put %r{\A/users/(?<id>\d+)}
|
70
|
-
:summary => 'Update a user',
|
71
|
-
:parameters => {:id => {:type => :integer,
|
72
|
-
:description => 'The id of the user'}} do |match|
|
43
|
+
put %r{\A/users/(?<id>\d+)} do |match|
|
73
44
|
render :message => "jellyfish ##{match[:id]} updated."
|
74
45
|
end
|
75
46
|
|
@@ -77,8 +48,7 @@ class Jelly
|
|
77
48
|
render :message => "jellyfish ##{match[:id]} deleted."
|
78
49
|
end
|
79
50
|
|
80
|
-
get %r{\A/posts/(?<year>\d+)-(?<month>\d+)/(?<name>\w+)}
|
81
|
-
:summary => 'Get a post' do |match|
|
51
|
+
get %r{\A/posts/(?<year>\d+)-(?<month>\d+)/(?<name>\w+)} do |match|
|
82
52
|
render Hash[match.names.zip(match.captures)]
|
83
53
|
end
|
84
54
|
|
@@ -87,16 +57,12 @@ class Jelly
|
|
87
57
|
end
|
88
58
|
end
|
89
59
|
|
90
|
-
App =
|
60
|
+
App = Jellyfish::Builder.app do
|
91
61
|
use Rack::CommonLogger
|
92
62
|
use Rack::Chunked
|
93
63
|
use Rack::ContentLength
|
94
64
|
use Rack::Deflater
|
95
65
|
|
96
|
-
map '/swagger' do
|
97
|
-
run Jellyfish::Swagger.new('', Jelly)
|
98
|
-
end
|
99
|
-
|
100
66
|
run Rack::Cascade.new([Rack::File.new('public/index.html'),
|
101
67
|
Rack::File.new('public'),
|
102
68
|
Jelly.new])
|
data/jellyfish.gemspec
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: jellyfish 1.0
|
2
|
+
# stub: jellyfish 1.1.0 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "jellyfish"
|
6
|
-
s.version = "1.0
|
6
|
+
s.version = "1.1.0"
|
7
7
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
9
9
|
s.require_paths = ["lib"]
|
10
10
|
s.authors = ["Lin Jen-Shin (godfat)"]
|
11
|
-
s.date = "
|
12
|
-
s.description = "Pico web framework for building API-centric web applications.\nFor Rack applications or Rack
|
11
|
+
s.date = "2015-09-25"
|
12
|
+
s.description = "Pico web framework for building API-centric web applications.\nFor Rack applications or Rack middleware. Around 250 lines of code.\n\nCheck [jellyfish-contrib][] for extra extensions.\n\n[jellyfish-contrib]: https://github.com/godfat/jellyfish-contrib"
|
13
13
|
s.email = ["godfat (XD) godfat.org"]
|
14
14
|
s.files = [
|
15
15
|
".gitignore",
|
@@ -21,59 +21,53 @@ Gem::Specification.new do |s|
|
|
21
21
|
"README.md",
|
22
22
|
"Rakefile",
|
23
23
|
"TODO.md",
|
24
|
+
"bench/bench_builder.rb",
|
24
25
|
"config.ru",
|
25
26
|
"jellyfish.gemspec",
|
26
27
|
"jellyfish.png",
|
27
28
|
"lib/jellyfish.rb",
|
29
|
+
"lib/jellyfish/builder.rb",
|
28
30
|
"lib/jellyfish/chunked_body.rb",
|
29
31
|
"lib/jellyfish/json.rb",
|
30
|
-
"lib/jellyfish/multi_actions.rb",
|
31
32
|
"lib/jellyfish/newrelic.rb",
|
32
33
|
"lib/jellyfish/normalized_params.rb",
|
33
34
|
"lib/jellyfish/normalized_path.rb",
|
34
35
|
"lib/jellyfish/public/302.html",
|
35
36
|
"lib/jellyfish/public/404.html",
|
36
37
|
"lib/jellyfish/public/500.html",
|
37
|
-
"lib/jellyfish/sinatra.rb",
|
38
|
-
"lib/jellyfish/swagger.rb",
|
39
38
|
"lib/jellyfish/test.rb",
|
39
|
+
"lib/jellyfish/urlmap.rb",
|
40
40
|
"lib/jellyfish/version.rb",
|
41
41
|
"lib/jellyfish/websocket.rb",
|
42
|
-
"public/css/screen.css",
|
43
|
-
"public/index.html",
|
44
|
-
"public/js/shred.bundle.js",
|
45
|
-
"public/js/shred/content.js",
|
46
|
-
"public/js/swagger-ui.js",
|
47
|
-
"public/js/swagger.js",
|
48
42
|
"task/README.md",
|
49
43
|
"task/gemgem.rb",
|
44
|
+
"test/rack/test_builder.rb",
|
45
|
+
"test/rack/test_urlmap.rb",
|
50
46
|
"test/sinatra/test_base.rb",
|
51
47
|
"test/sinatra/test_chunked_body.rb",
|
52
48
|
"test/sinatra/test_error.rb",
|
53
|
-
"test/sinatra/test_multi_actions.rb",
|
54
49
|
"test/sinatra/test_routing.rb",
|
55
50
|
"test/test_from_readme.rb",
|
56
51
|
"test/test_inheritance.rb",
|
57
52
|
"test/test_log.rb",
|
58
53
|
"test/test_misc.rb",
|
59
|
-
"test/test_swagger.rb",
|
60
54
|
"test/test_threads.rb",
|
61
55
|
"test/test_websocket.rb"]
|
62
56
|
s.homepage = "https://github.com/godfat/jellyfish"
|
63
57
|
s.licenses = ["Apache License 2.0"]
|
64
|
-
s.rubygems_version = "2.4.
|
58
|
+
s.rubygems_version = "2.4.8"
|
65
59
|
s.summary = "Pico web framework for building API-centric web applications."
|
66
60
|
s.test_files = [
|
61
|
+
"test/rack/test_builder.rb",
|
62
|
+
"test/rack/test_urlmap.rb",
|
67
63
|
"test/sinatra/test_base.rb",
|
68
64
|
"test/sinatra/test_chunked_body.rb",
|
69
65
|
"test/sinatra/test_error.rb",
|
70
|
-
"test/sinatra/test_multi_actions.rb",
|
71
66
|
"test/sinatra/test_routing.rb",
|
72
67
|
"test/test_from_readme.rb",
|
73
68
|
"test/test_inheritance.rb",
|
74
69
|
"test/test_log.rb",
|
75
70
|
"test/test_misc.rb",
|
76
|
-
"test/test_swagger.rb",
|
77
71
|
"test/test_threads.rb",
|
78
72
|
"test/test_websocket.rb"]
|
79
73
|
end
|
data/lib/jellyfish.rb
CHANGED
@@ -1,14 +1,12 @@
|
|
1
1
|
|
2
2
|
module Jellyfish
|
3
3
|
autoload :VERSION , 'jellyfish/version'
|
4
|
-
autoload :Sinatra , 'jellyfish/sinatra'
|
5
|
-
autoload :Swagger , 'jellyfish/swagger'
|
6
4
|
autoload :NewRelic, 'jellyfish/newrelic'
|
7
5
|
|
8
|
-
autoload :MultiActions , 'jellyfish/multi_actions'
|
9
6
|
autoload :NormalizedParams, 'jellyfish/normalized_params'
|
10
7
|
autoload :NormalizedPath , 'jellyfish/normalized_path'
|
11
8
|
|
9
|
+
autoload :Builder , 'jellyfish/builder'
|
12
10
|
autoload :ChunkedBody, 'jellyfish/chunked_body'
|
13
11
|
autoload :WebSocket , 'jellyfish/websocket'
|
14
12
|
|
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
require 'jellyfish/urlmap'
|
3
|
+
|
4
|
+
module Jellyfish
|
5
|
+
class Builder
|
6
|
+
def self.app app=nil, &block
|
7
|
+
new(app, &block).to_app
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize app=nil, &block
|
11
|
+
@use, @map, @run, @warmup = [], nil, app, nil
|
12
|
+
instance_eval(&block) if block_given?
|
13
|
+
end
|
14
|
+
|
15
|
+
def use middleware, *args, &block
|
16
|
+
if @map
|
17
|
+
current_map, @map = @map, nil
|
18
|
+
@use.unshift(lambda{ |app| generate_map(current_map, app) })
|
19
|
+
end
|
20
|
+
@use.unshift(lambda{ |app| middleware.new(app, *args, &block) })
|
21
|
+
end
|
22
|
+
|
23
|
+
def run app
|
24
|
+
@run = app
|
25
|
+
end
|
26
|
+
|
27
|
+
def warmup lam=nil, &block
|
28
|
+
@warmup = lam || block
|
29
|
+
end
|
30
|
+
|
31
|
+
def map path, &block
|
32
|
+
(@map ||= {})[path] = block
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_app
|
36
|
+
run = if @map then generate_map(@map, @run) else @run end
|
37
|
+
fail 'missing run or map statement' unless run
|
38
|
+
app = @use.inject(run){ |a, m| m.call(a) }
|
39
|
+
@warmup.call(app) if @warmup
|
40
|
+
app
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def generate_map current_map, app
|
45
|
+
mapped = if app then {'' => app} else {} end
|
46
|
+
current_map.each do |path, block|
|
47
|
+
mapped[path.chomp('/')] = self.class.app(app, &block)
|
48
|
+
end
|
49
|
+
URLMap.new(mapped)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/jellyfish/test.rb
CHANGED
@@ -9,15 +9,15 @@ Pork::Executor.__send__(:include, Muack::API)
|
|
9
9
|
copy :jellyfish do
|
10
10
|
module_eval(%w[options get head post put delete patch].map{ |method|
|
11
11
|
<<-RUBY
|
12
|
-
def #{method} path='/',
|
12
|
+
def #{method} path='/', a=app, env={}
|
13
13
|
File.open(File::NULL) do |input|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
14
|
+
a.call({'PATH_INFO' => path ,
|
15
|
+
'REQUEST_METHOD' => '#{method}'.upcase,
|
16
|
+
'SCRIPT_NAME' => '' ,
|
17
|
+
'rack.input' => input ,
|
18
|
+
'rack.url_scheme'=> 'https' ,
|
19
|
+
'SERVER_NAME' => 'localhost' ,
|
20
|
+
'SERVER_PORT' => '8080'}.merge(env))
|
21
21
|
end
|
22
22
|
end
|
23
23
|
RUBY
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
module Jellyfish
|
3
|
+
class URLMap
|
4
|
+
def initialize mapped
|
5
|
+
string = mapped.keys.sort_by{ |k| -k.size }.
|
6
|
+
map{ |k| Regexp.escape(k).gsub('/', '/+') }.
|
7
|
+
join('|')
|
8
|
+
|
9
|
+
@mapped = mapped
|
10
|
+
@routes = Regexp.new("\\A(?:#{string})(?:/|\\z)", 'i', 'n')
|
11
|
+
end
|
12
|
+
|
13
|
+
def call env
|
14
|
+
path_info = env['PATH_INFO']
|
15
|
+
matched = @routes.match(path_info).to_s.chomp('/')
|
16
|
+
squeezed = matched.squeeze('/')
|
17
|
+
|
18
|
+
if app = @mapped[squeezed]
|
19
|
+
app.call(env.merge('PATH_INFO' => path_info[matched.size..-1],
|
20
|
+
'SCRIPT_NAME' => env['SCRIPT_NAME'] + squeezed))
|
21
|
+
else
|
22
|
+
[404, {}, []]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/jellyfish/version.rb
CHANGED
data/task/gemgem.rb
CHANGED
@@ -79,6 +79,11 @@ module Gemgem
|
|
79
79
|
end
|
80
80
|
|
81
81
|
def gem_check
|
82
|
+
unless git('status', '--porcelain').empty?
|
83
|
+
puts("\e[35mWorking copy is not clean.\e[0m")
|
84
|
+
exit(3)
|
85
|
+
end
|
86
|
+
|
82
87
|
ver = spec.version.to_s
|
83
88
|
|
84
89
|
if ENV['VERSION'].nil?
|
@@ -100,8 +105,12 @@ module Gemgem
|
|
100
105
|
if ENV['COV'] || ENV['CI']
|
101
106
|
require 'simplecov'
|
102
107
|
if ENV['CI']
|
103
|
-
|
104
|
-
|
108
|
+
begin
|
109
|
+
require 'coveralls'
|
110
|
+
SimpleCov.formatter = Coveralls::SimpleCov::Formatter
|
111
|
+
rescue LoadError => e
|
112
|
+
puts "Cannot load coveralls, skip: #{e}"
|
113
|
+
end
|
105
114
|
end
|
106
115
|
SimpleCov.start do
|
107
116
|
add_filter('test/')
|
@@ -0,0 +1,154 @@
|
|
1
|
+
|
2
|
+
require 'jellyfish/test'
|
3
|
+
|
4
|
+
require 'rack/lint'
|
5
|
+
require 'rack/mock'
|
6
|
+
require 'rack/showexceptions'
|
7
|
+
require 'rack/urlmap'
|
8
|
+
|
9
|
+
describe Jellyfish::Builder do
|
10
|
+
class NothingMiddleware
|
11
|
+
def initialize(app)
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
def call(env)
|
15
|
+
@@env = env
|
16
|
+
response = @app.call(env)
|
17
|
+
response
|
18
|
+
end
|
19
|
+
def self.env
|
20
|
+
@@env
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def builder_to_app(&block)
|
25
|
+
Rack::Lint.new Jellyfish::Builder.app(&block)
|
26
|
+
end
|
27
|
+
|
28
|
+
would "supports mapping" do
|
29
|
+
app = builder_to_app do
|
30
|
+
map '/' do |outer_env|
|
31
|
+
run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] }
|
32
|
+
end
|
33
|
+
map '/sub' do
|
34
|
+
run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
Rack::MockRequest.new(app).get("/").body.to_s.should.eq 'root'
|
38
|
+
Rack::MockRequest.new(app).get("/sub").body.to_s.should.eq 'sub'
|
39
|
+
end
|
40
|
+
|
41
|
+
would "chains apps by default" do
|
42
|
+
app = builder_to_app do
|
43
|
+
use Rack::ShowExceptions
|
44
|
+
run lambda { |env| raise "bzzzt" }
|
45
|
+
end
|
46
|
+
|
47
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
48
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
49
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
50
|
+
end
|
51
|
+
|
52
|
+
would "supports blocks on use" do
|
53
|
+
app = builder_to_app do
|
54
|
+
use Rack::ShowExceptions
|
55
|
+
use Rack::Auth::Basic do |username, password|
|
56
|
+
'secret' == password
|
57
|
+
end
|
58
|
+
|
59
|
+
run lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hi Boss']] }
|
60
|
+
end
|
61
|
+
|
62
|
+
response = Rack::MockRequest.new(app).get("/")
|
63
|
+
response.should.client_error?
|
64
|
+
response.status.should.eq 401
|
65
|
+
|
66
|
+
# with auth...
|
67
|
+
response = Rack::MockRequest.new(app).get("/",
|
68
|
+
'HTTP_AUTHORIZATION' => 'Basic ' + ["joe:secret"].pack("m*"))
|
69
|
+
response.status.should.eq 200
|
70
|
+
response.body.to_s.should.eq 'Hi Boss'
|
71
|
+
end
|
72
|
+
|
73
|
+
would "has explicit #to_app" do
|
74
|
+
app = builder_to_app do
|
75
|
+
use Rack::ShowExceptions
|
76
|
+
run lambda { |env| raise "bzzzt" }
|
77
|
+
end
|
78
|
+
|
79
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
80
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
81
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
82
|
+
end
|
83
|
+
|
84
|
+
would "can mix map and run for endpoints" do
|
85
|
+
app = builder_to_app do
|
86
|
+
map '/sub' do
|
87
|
+
run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] }
|
88
|
+
end
|
89
|
+
run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] }
|
90
|
+
end
|
91
|
+
|
92
|
+
Rack::MockRequest.new(app).get("/").body.to_s.should.eq 'root'
|
93
|
+
Rack::MockRequest.new(app).get("/sub").body.to_s.should.eq 'sub'
|
94
|
+
end
|
95
|
+
|
96
|
+
would "accepts middleware-only map blocks" do
|
97
|
+
app = builder_to_app do
|
98
|
+
map('/foo') { use Rack::ShowExceptions }
|
99
|
+
run lambda { |env| raise "bzzzt" }
|
100
|
+
end
|
101
|
+
|
102
|
+
proc { Rack::MockRequest.new(app).get("/") }.should.raise(RuntimeError)
|
103
|
+
Rack::MockRequest.new(app).get("/foo").should.server_error?
|
104
|
+
end
|
105
|
+
|
106
|
+
would "yields the generated app to a block for warmup" do
|
107
|
+
warmed_up_app = nil
|
108
|
+
|
109
|
+
app = Rack::Builder.new do
|
110
|
+
warmup { |a| warmed_up_app = a }
|
111
|
+
run lambda { |env| [200, {}, []] }
|
112
|
+
end.to_app
|
113
|
+
|
114
|
+
warmed_up_app.should.eq app
|
115
|
+
end
|
116
|
+
|
117
|
+
would "initialize apps once" do
|
118
|
+
app = builder_to_app do
|
119
|
+
class AppClass
|
120
|
+
def initialize
|
121
|
+
@called = 0
|
122
|
+
end
|
123
|
+
def call(env)
|
124
|
+
raise "bzzzt" if @called > 0
|
125
|
+
@called += 1
|
126
|
+
[200, {'Content-Type' => 'text/plain'}, ['OK']]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
use Rack::ShowExceptions
|
131
|
+
run AppClass.new
|
132
|
+
end
|
133
|
+
|
134
|
+
Rack::MockRequest.new(app).get("/").status.should.eq 200
|
135
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
136
|
+
end
|
137
|
+
|
138
|
+
would "allows use after run" do
|
139
|
+
app = builder_to_app do
|
140
|
+
run lambda { |env| raise "bzzzt" }
|
141
|
+
use Rack::ShowExceptions
|
142
|
+
end
|
143
|
+
|
144
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
145
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
146
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
147
|
+
end
|
148
|
+
|
149
|
+
would 'complains about a missing run' do
|
150
|
+
proc do
|
151
|
+
Rack::Lint.new Rack::Builder.app { use Rack::ShowExceptions }
|
152
|
+
end.should.raise(RuntimeError)
|
153
|
+
end
|
154
|
+
end
|