jellyfish 0.9.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/jellyfish.gemspec CHANGED
@@ -1,13 +1,14 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: jellyfish 0.9.2 ruby lib
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.9.2"
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 = "2013-09-26"
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
- "task/.gitignore",
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.require_paths = ["lib"]
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 || with_each(val || '')]
53
+ [status || 200, headers || {}, body || rack_body(val || '')]
53
54
  rescue LocalJumpError
54
- jellyfish.log("Use `next' if you're trying to `return' or" \
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 forward ; halt(Jellyfish::NotFound.new) ; end
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 = with_each(value)
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] || halt(Jellyfish::NotFound.new)
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
- } || halt(Jellyfish::NotFound.new)
115
+ } || action_missing
113
116
  end
114
117
 
115
- def with_each value
116
- if value.respond_to?(:each) then value else [value] end
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 NotFound
180
- app && forward(ctrl, env) || handle(ctrl, res)
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, Identity)
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 forward ctrl, env
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
@@ -22,7 +22,7 @@ module Jellyfish
22
22
  }.compact
23
23
 
24
24
  if acts.empty?
25
- halt(Jellyfish::NotFound.new)
25
+ action_missing
26
26
  else
27
27
  acts
28
28
  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
@@ -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 }.merge(env))
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