jellyfish 0.9.2 → 1.0.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/.gitignore +1 -2
- data/.travis.yml +3 -5
- data/CHANGES.md +18 -0
- data/Gemfile +10 -1
- data/README.md +121 -22
- data/Rakefile +6 -15
- data/config.ru +105 -0
- data/jellyfish.gemspec +21 -24
- data/lib/jellyfish.rb +24 -17
- data/lib/jellyfish/json.rb +64 -0
- data/lib/jellyfish/multi_actions.rb +1 -1
- data/lib/jellyfish/swagger.rb +166 -0
- data/lib/jellyfish/test.rb +4 -1
- data/lib/jellyfish/version.rb +1 -1
- data/public/css/screen.css +1070 -0
- data/public/index.html +45 -0
- data/public/js/shred.bundle.js +2765 -0
- data/public/js/shred/content.js +193 -0
- data/public/js/swagger-ui.js +2116 -0
- data/public/js/swagger.js +1400 -0
- data/task/README.md +54 -0
- data/task/gemgem.rb +151 -157
- data/test/sinatra/test_error.rb +31 -4
- data/test/test_from_readme.rb +2 -1
- data/test/test_log.rb +42 -0
- data/test/test_misc.rb +24 -0
- data/test/test_swagger.rb +131 -0
- data/test/test_threads.rb +1 -1
- metadata +25 -52
- data/task/.gitignore +0 -1
data/jellyfish.gemspec
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: jellyfish 0.
|
2
|
+
# stub: jellyfish 1.0.0 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "jellyfish"
|
6
|
-
s.version = "0.
|
6
|
+
s.version = "1.0.0"
|
7
7
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
9
|
+
s.require_paths = ["lib"]
|
9
10
|
s.authors = ["Lin Jen-Shin (godfat)"]
|
10
|
-
s.date = "
|
11
|
+
s.date = "2014-03-17"
|
11
12
|
s.description = "Pico web framework for building API-centric web applications.\nFor Rack applications or Rack middlewares. Around 250 lines of code."
|
12
13
|
s.email = ["godfat (XD) godfat.org"]
|
13
14
|
s.files = [
|
@@ -20,10 +21,12 @@ Gem::Specification.new do |s|
|
|
20
21
|
"README.md",
|
21
22
|
"Rakefile",
|
22
23
|
"TODO.md",
|
24
|
+
"config.ru",
|
23
25
|
"jellyfish.gemspec",
|
24
26
|
"jellyfish.png",
|
25
27
|
"lib/jellyfish.rb",
|
26
28
|
"lib/jellyfish/chunked_body.rb",
|
29
|
+
"lib/jellyfish/json.rb",
|
27
30
|
"lib/jellyfish/multi_actions.rb",
|
28
31
|
"lib/jellyfish/newrelic.rb",
|
29
32
|
"lib/jellyfish/normalized_params.rb",
|
@@ -32,9 +35,16 @@ Gem::Specification.new do |s|
|
|
32
35
|
"lib/jellyfish/public/404.html",
|
33
36
|
"lib/jellyfish/public/500.html",
|
34
37
|
"lib/jellyfish/sinatra.rb",
|
38
|
+
"lib/jellyfish/swagger.rb",
|
35
39
|
"lib/jellyfish/test.rb",
|
36
40
|
"lib/jellyfish/version.rb",
|
37
|
-
"
|
41
|
+
"public/css/screen.css",
|
42
|
+
"public/index.html",
|
43
|
+
"public/js/shred.bundle.js",
|
44
|
+
"public/js/shred/content.js",
|
45
|
+
"public/js/swagger-ui.js",
|
46
|
+
"public/js/swagger.js",
|
47
|
+
"task/README.md",
|
38
48
|
"task/gemgem.rb",
|
39
49
|
"test/sinatra/test_base.rb",
|
40
50
|
"test/sinatra/test_chunked_body.rb",
|
@@ -43,11 +53,13 @@ Gem::Specification.new do |s|
|
|
43
53
|
"test/sinatra/test_routing.rb",
|
44
54
|
"test/test_from_readme.rb",
|
45
55
|
"test/test_inheritance.rb",
|
56
|
+
"test/test_log.rb",
|
57
|
+
"test/test_misc.rb",
|
58
|
+
"test/test_swagger.rb",
|
46
59
|
"test/test_threads.rb"]
|
47
60
|
s.homepage = "https://github.com/godfat/jellyfish"
|
48
61
|
s.licenses = ["Apache License 2.0"]
|
49
|
-
s.
|
50
|
-
s.rubygems_version = "2.1.5"
|
62
|
+
s.rubygems_version = "2.2.2"
|
51
63
|
s.summary = "Pico web framework for building API-centric web applications."
|
52
64
|
s.test_files = [
|
53
65
|
"test/sinatra/test_base.rb",
|
@@ -57,23 +69,8 @@ Gem::Specification.new do |s|
|
|
57
69
|
"test/sinatra/test_routing.rb",
|
58
70
|
"test/test_from_readme.rb",
|
59
71
|
"test/test_inheritance.rb",
|
72
|
+
"test/test_log.rb",
|
73
|
+
"test/test_misc.rb",
|
74
|
+
"test/test_swagger.rb",
|
60
75
|
"test/test_threads.rb"]
|
61
|
-
|
62
|
-
if s.respond_to? :specification_version then
|
63
|
-
s.specification_version = 4
|
64
|
-
|
65
|
-
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
66
|
-
s.add_development_dependency(%q<rack>, [">= 0"])
|
67
|
-
s.add_development_dependency(%q<bacon>, [">= 0"])
|
68
|
-
s.add_development_dependency(%q<muack>, [">= 0"])
|
69
|
-
else
|
70
|
-
s.add_dependency(%q<rack>, [">= 0"])
|
71
|
-
s.add_dependency(%q<bacon>, [">= 0"])
|
72
|
-
s.add_dependency(%q<muack>, [">= 0"])
|
73
|
-
end
|
74
|
-
else
|
75
|
-
s.add_dependency(%q<rack>, [">= 0"])
|
76
|
-
s.add_dependency(%q<bacon>, [">= 0"])
|
77
|
-
s.add_dependency(%q<muack>, [">= 0"])
|
78
|
-
end
|
79
76
|
end
|
data/lib/jellyfish.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
module Jellyfish
|
3
3
|
autoload :VERSION , 'jellyfish/version'
|
4
4
|
autoload :Sinatra , 'jellyfish/sinatra'
|
5
|
+
autoload :Swagger , 'jellyfish/swagger'
|
5
6
|
autoload :NewRelic, 'jellyfish/newrelic'
|
6
7
|
|
7
8
|
autoload :MultiActions , 'jellyfish/multi_actions'
|
@@ -10,8 +11,8 @@ module Jellyfish
|
|
10
11
|
|
11
12
|
autoload :ChunkedBody, 'jellyfish/chunked_body'
|
12
13
|
|
14
|
+
Cascade = Object.new
|
13
15
|
GetValue = Object.new
|
14
|
-
Identity = lambda{|_|_}
|
15
16
|
|
16
17
|
class Response < RuntimeError
|
17
18
|
def headers
|
@@ -49,16 +50,18 @@ module Jellyfish
|
|
49
50
|
|
50
51
|
def block_call argument, block
|
51
52
|
val = instance_exec(argument, &block)
|
52
|
-
[status || 200, headers || {}, body ||
|
53
|
+
[status || 200, headers || {}, body || rack_body(val || '')]
|
53
54
|
rescue LocalJumpError
|
54
|
-
|
55
|
-
" `break' from the block.", env['rack.errors'])
|
55
|
+
log("Use `next' if you're trying to `return' or `break' from a block.")
|
56
56
|
raise
|
57
57
|
end
|
58
58
|
|
59
|
+
def log message; jellyfish.log( message, env['rack.errors']); end
|
60
|
+
def log_error error; jellyfish.log_error(error, env['rack.errors']); end
|
59
61
|
def request ; @request ||= Rack::Request.new(env); end
|
60
62
|
def halt *args; throw(:halt, *args) ; end
|
61
|
-
def
|
63
|
+
def cascade ; halt(Jellyfish::Cascade) ; end
|
64
|
+
def not_found ; halt(Jellyfish::NotFound.new) ; end
|
62
65
|
def found url; halt(Jellyfish:: Found.new(url)); end
|
63
66
|
alias_method :redirect, :found
|
64
67
|
|
@@ -83,7 +86,7 @@ module Jellyfish
|
|
83
86
|
elsif value.nil?
|
84
87
|
@body = value
|
85
88
|
else
|
86
|
-
@body =
|
89
|
+
@body = rack_body(value)
|
87
90
|
end
|
88
91
|
end
|
89
92
|
|
@@ -97,7 +100,7 @@ module Jellyfish
|
|
97
100
|
|
98
101
|
private
|
99
102
|
def actions
|
100
|
-
routes[request_method.downcase] ||
|
103
|
+
routes[request_method.downcase] || action_missing
|
101
104
|
end
|
102
105
|
|
103
106
|
def dispatch
|
@@ -109,11 +112,15 @@ module Jellyfish
|
|
109
112
|
match = route.match(path_info)
|
110
113
|
break match, block if match
|
111
114
|
end
|
112
|
-
} ||
|
115
|
+
} || action_missing
|
113
116
|
end
|
114
117
|
|
115
|
-
def
|
116
|
-
if
|
118
|
+
def action_missing
|
119
|
+
if jellyfish.app then cascade else not_found end
|
120
|
+
end
|
121
|
+
|
122
|
+
def rack_body v
|
123
|
+
if v.respond_to?(:each) || v.respond_to?(:to_path) then v else [v] end
|
117
124
|
end
|
118
125
|
end
|
119
126
|
|
@@ -151,10 +158,10 @@ module Jellyfish
|
|
151
158
|
|
152
159
|
%w[options get head post put delete patch].each do |method|
|
153
160
|
module_eval <<-RUBY
|
154
|
-
def #{method} route=//, &block
|
161
|
+
def #{method} route=//, meta={}, &block
|
155
162
|
raise TypeError.new("Route \#{route} should respond to :match") \
|
156
163
|
unless route.respond_to?(:match)
|
157
|
-
(routes['#{method}'] ||= []) << [route, block]
|
164
|
+
(routes['#{method}'] ||= []) << [route, block || lambda{|_|}, meta]
|
158
165
|
end
|
159
166
|
RUBY
|
160
167
|
end
|
@@ -176,12 +183,12 @@ module Jellyfish
|
|
176
183
|
def call env
|
177
184
|
ctrl = self.class.controller.new(self.class.routes, self)
|
178
185
|
case res = catch(:halt){ ctrl.call(env) }
|
179
|
-
when
|
180
|
-
|
186
|
+
when Cascade
|
187
|
+
cascade(ctrl, env)
|
181
188
|
when Response
|
182
189
|
handle(ctrl, res, env['rack.errors'])
|
183
|
-
else
|
184
|
-
res || ctrl.block_call(nil,
|
190
|
+
else # make sure we return rack triple
|
191
|
+
res || ctrl.block_call(nil, lambda{|_|_})
|
185
192
|
end
|
186
193
|
rescue => e
|
187
194
|
handle(ctrl, e, env['rack.errors'])
|
@@ -198,7 +205,7 @@ module Jellyfish
|
|
198
205
|
end
|
199
206
|
|
200
207
|
private
|
201
|
-
def
|
208
|
+
def cascade ctrl, env
|
202
209
|
app.call(env)
|
203
210
|
rescue => e
|
204
211
|
handle(ctrl, e, env['rack.errors'])
|
@@ -0,0 +1,64 @@
|
|
1
|
+
|
2
|
+
module Jellyfish; end
|
3
|
+
module Jellyfish::Json
|
4
|
+
module MultiJson
|
5
|
+
def self.extended mod
|
6
|
+
mod.const_set(:ParseError, ::MultiJson::DecodeError)
|
7
|
+
end
|
8
|
+
def encode hash
|
9
|
+
::MultiJson.dump(hash)
|
10
|
+
end
|
11
|
+
def decode json
|
12
|
+
::MultiJson.load(json)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module YajlRuby
|
17
|
+
def self.extended mod
|
18
|
+
mod.const_set(:ParseError, Yajl::ParseError)
|
19
|
+
end
|
20
|
+
def encode hash
|
21
|
+
Yajl::Encoder.encode(hash)
|
22
|
+
end
|
23
|
+
def decode json
|
24
|
+
Yajl::Parser.parse(json)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module Json
|
29
|
+
def self.extended mod
|
30
|
+
mod.const_set(:ParseError, JSON::ParserError)
|
31
|
+
end
|
32
|
+
def encode hash
|
33
|
+
JSON.dump(hash)
|
34
|
+
end
|
35
|
+
def decode json
|
36
|
+
JSON.parse(json)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.select_json! mod, picked=false
|
41
|
+
if Object.const_defined?(:MultiJson)
|
42
|
+
mod.send(:extend, MultiJson)
|
43
|
+
elsif Object.const_defined?(:Yajl)
|
44
|
+
mod.send(:extend, YajlRuby)
|
45
|
+
elsif Object.const_defined?(:JSON)
|
46
|
+
mod.send(:extend, Json)
|
47
|
+
elsif picked
|
48
|
+
raise LoadError.new(
|
49
|
+
'No JSON library found. Tried: multi_json, yajl-ruby, json.')
|
50
|
+
else
|
51
|
+
# pick a json gem if available
|
52
|
+
%w[multi_json yajl json].each{ |json|
|
53
|
+
begin
|
54
|
+
require json
|
55
|
+
break
|
56
|
+
rescue LoadError
|
57
|
+
end
|
58
|
+
}
|
59
|
+
select_json!(mod, true)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
select_json!(self)
|
64
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
|
2
|
+
require 'jellyfish/json'
|
3
|
+
|
4
|
+
module Jellyfish
|
5
|
+
class Swagger
|
6
|
+
include Jellyfish
|
7
|
+
controller_include Jellyfish::NormalizedPath, Module.new{
|
8
|
+
def dispatch
|
9
|
+
headers_merge 'Content-Type' => 'application/json; charset=utf-8'
|
10
|
+
super
|
11
|
+
end
|
12
|
+
}
|
13
|
+
|
14
|
+
get '/' do
|
15
|
+
[Jellyfish::Json.encode(
|
16
|
+
:swaggerVersion => '1.2' ,
|
17
|
+
:info => jellyfish.swagger_info ,
|
18
|
+
:apiVersion => jellyfish.swagger_apiVersion,
|
19
|
+
:apis => jellyfish.swagger_apis )]
|
20
|
+
end
|
21
|
+
|
22
|
+
get %r{\A/(?<name>.+)\Z} do |match|
|
23
|
+
basePath =
|
24
|
+
"#{request.scheme}://#{request.host_with_port}#{jellyfish.path_prefix}"
|
25
|
+
name = "/#{match[:name]}"
|
26
|
+
apis = jellyfish.jellyfish_apis[name].map{ |path, operations|
|
27
|
+
{:path => path, :operations => operations}
|
28
|
+
}
|
29
|
+
|
30
|
+
[Jellyfish::Json.encode(
|
31
|
+
:swaggerVersion => '1.2' ,
|
32
|
+
:basePath => basePath ,
|
33
|
+
:resourcePath => name ,
|
34
|
+
:apiVersion => jellyfish.swagger_apiVersion,
|
35
|
+
:apis => apis )]
|
36
|
+
end
|
37
|
+
|
38
|
+
# The application should define the API description.
|
39
|
+
def swagger_info
|
40
|
+
if app.respond_to?(:info)
|
41
|
+
app.info
|
42
|
+
else
|
43
|
+
{}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# The application should define the API version.
|
48
|
+
def swagger_apiVersion
|
49
|
+
if app.respond_to?(:swagger_apiVersion)
|
50
|
+
app.swagger_apiVersion
|
51
|
+
else
|
52
|
+
'0.1.0'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def swagger_apis
|
57
|
+
@swagger_apis ||= jellyfish_apis.keys.map do |name|
|
58
|
+
{:path => name}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
attr_reader :jellys, :path_prefix
|
63
|
+
def initialize path_prefix='', *apps
|
64
|
+
super(apps.first)
|
65
|
+
@jellys = apps
|
66
|
+
@path_prefix = path_prefix
|
67
|
+
end
|
68
|
+
|
69
|
+
def jellyfish_apis
|
70
|
+
@jellyfish_apis ||= jellys.flat_map{ |j|
|
71
|
+
j.routes.flat_map{ |meth, routes|
|
72
|
+
routes.map{ |(path, _, meta)|
|
73
|
+
meta.merge(operation(meth, path, meta))
|
74
|
+
}
|
75
|
+
}
|
76
|
+
}.group_by{ |api| api[:path] }.
|
77
|
+
inject(Hash.new{[]}){ |r, (path, operations)|
|
78
|
+
r[path] = operations.group_by{ |op| op[:nickname] }
|
79
|
+
r
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def operation meth, path, meta
|
85
|
+
if path.respond_to?(:source)
|
86
|
+
nick = nickname(path)
|
87
|
+
{:path => swagger_path(nick) ,
|
88
|
+
:method => meth.to_s.upcase ,
|
89
|
+
:nickname => nick ,
|
90
|
+
:summary => meta[:summary] ,
|
91
|
+
:notes => notes(meta) ,
|
92
|
+
:parameters => path_parameters(path, meta) }
|
93
|
+
else
|
94
|
+
{:path => swagger_path(path) ,
|
95
|
+
:method => meth.to_s.upcase ,
|
96
|
+
:nickname => path ,
|
97
|
+
:summary => meta[:summary] ,
|
98
|
+
:notes => notes(meta) ,
|
99
|
+
:parameters => query_parameters(meta) }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def swagger_path nickname
|
104
|
+
nickname[%r{^/[^/]*}]
|
105
|
+
end
|
106
|
+
|
107
|
+
def nickname path
|
108
|
+
if path.respond_to?(:source)
|
109
|
+
path.source.gsub(param_pattern, '{\1}').gsub(/\\\w+|[\^\$]/, '')
|
110
|
+
else
|
111
|
+
path.to_s
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def notes meta
|
116
|
+
if meta[:notes]
|
117
|
+
"#{meta[:summary]}<br>#{meta[:notes]}"
|
118
|
+
else
|
119
|
+
meta[:summary]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def path_parameters path, meta
|
124
|
+
Hash[path.source.scan(param_pattern)].map{ |name, pattern|
|
125
|
+
path_param(name, pattern, meta)
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
def query_parameters meta
|
130
|
+
if meta[:parameters]
|
131
|
+
meta[:parameters].map{ |(name, param)|
|
132
|
+
param.merge(:name => name.to_s,
|
133
|
+
:type => param[:type] || 'string',
|
134
|
+
:required => !!param[:required],
|
135
|
+
:paramType => param[:paramType] || 'query')
|
136
|
+
}
|
137
|
+
else
|
138
|
+
[]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def path_param name, pattern, meta
|
143
|
+
param = (meta[:parameters] || {})[name.to_sym] || {}
|
144
|
+
param.merge(:name => name ,
|
145
|
+
:type => param[:type] || param_type(pattern),
|
146
|
+
:required => true ,
|
147
|
+
:paramType => 'path' )
|
148
|
+
end
|
149
|
+
|
150
|
+
def param_type pattern
|
151
|
+
if pattern.start_with?('\\d')
|
152
|
+
if pattern.include?('.')
|
153
|
+
'number'
|
154
|
+
else
|
155
|
+
'integer'
|
156
|
+
end
|
157
|
+
else
|
158
|
+
'string'
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def param_pattern
|
163
|
+
/\(\?<(\w+)>([^\)]+)\)/
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
data/lib/jellyfish/test.rb
CHANGED
@@ -13,7 +13,10 @@ shared :jellyfish do
|
|
13
13
|
app.call({'PATH_INFO' => path ,
|
14
14
|
'REQUEST_METHOD' => '#{method}'.upcase,
|
15
15
|
'SCRIPT_NAME' => '' ,
|
16
|
-
'rack.input' => input
|
16
|
+
'rack.input' => input ,
|
17
|
+
'rack.url_scheme'=> 'https' ,
|
18
|
+
'SERVER_NAME' => 'localhost' ,
|
19
|
+
'SERVER_PORT' => '8080'}.merge(env))
|
17
20
|
end
|
18
21
|
end
|
19
22
|
RUBY
|