restapi 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +5 -0
- data/Gemfile +4 -7
- data/Gemfile.lock +5 -25
- data/README.rdoc +3 -0
- data/app/controllers/restapi/restapis_controller.rb +3 -12
- data/app/public/restapi/javascripts/jst.js +41 -22
- data/app/public/restapi/javascripts/restapi.js +4 -5
- data/app/public/restapi/javascripts/routers/documentation_router.js +13 -8
- data/lib/restapi.rb +1 -2
- data/lib/restapi/application.rb +28 -23
- data/lib/restapi/dsl_definition.rb +8 -12
- data/lib/restapi/helpers.rb +2 -2
- data/lib/restapi/markup.rb +45 -0
- data/lib/restapi/method_description.rb +82 -16
- data/lib/restapi/param_description.rb +36 -11
- data/lib/restapi/resource_description.rb +18 -10
- data/lib/restapi/restapi_module.rb +6 -7
- data/lib/restapi/static_dispatcher.rb +2 -0
- data/lib/restapi/validator.rb +40 -31
- data/lib/restapi/version.rb +1 -1
- data/restapi.gemspec +1 -1
- data/spec/controllers/users_controller_spec.rb +156 -32
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/dogs_controller.rb +0 -2
- data/spec/dummy/app/controllers/users_controller.rb +20 -5
- data/spec/dummy/config/database.yml +2 -3
- data/spec/dummy/config/environment.rb +3 -0
- data/spec/dummy/config/initializers/restapi.rb +6 -2
- data/spec/dummy/config/routes.rb +11 -64
- metadata +7 -21
- data/spec/dummy/app/views/users/doc.html.erb +0 -24
data/lib/restapi/helpers.rb
CHANGED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Restapi
|
2
|
+
|
3
|
+
module Markup
|
4
|
+
|
5
|
+
class RDoc
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
require 'rdoc'
|
9
|
+
require 'rdoc/markup/to_html'
|
10
|
+
@rdoc ||= ::RDoc::Markup::ToHtml.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_html(text)
|
14
|
+
@rdoc.convert(text)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class Markdown
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
require 'redcarpet'
|
23
|
+
@redcarpet ||= ::Redcarpet::Markdown.new(::Redcarpet::Render::HTML.new)
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_html(text)
|
27
|
+
@redcarpet.render(text)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
class Textile
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
require 'RedCloth'
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_html(text)
|
39
|
+
RedCloth.new(text).to_html
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -1,38 +1,104 @@
|
|
1
|
+
require 'set'
|
1
2
|
module Restapi
|
2
|
-
|
3
|
+
|
3
4
|
class MethodDescription
|
4
|
-
|
5
|
-
|
5
|
+
|
6
|
+
class Api
|
7
|
+
|
8
|
+
attr_accessor :short_description, :api_url, :http_method
|
9
|
+
|
10
|
+
def initialize(args)
|
11
|
+
@http_method = args[:method] || args[:http_method] || args[:http]
|
12
|
+
@short_description = args[:desc] || args[:short] || args[:description]
|
13
|
+
@api_url = create_api_url(args[:path] || args[:url])
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def create_api_url(path)
|
19
|
+
"#{Restapi.configuration.api_base_url}#{path}"
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :errors, :full_description, :method, :resource, :apis
|
6
25
|
|
7
26
|
def initialize(method, resource, app)
|
8
27
|
@method = method
|
9
28
|
@resource = resource
|
10
|
-
|
11
|
-
@
|
12
|
-
|
13
|
-
@http = args[:method]
|
29
|
+
|
30
|
+
@apis = app.get_api_args
|
31
|
+
|
14
32
|
desc = app.get_description || ''
|
15
|
-
@full_description = Restapi.
|
33
|
+
@full_description = Restapi.markup_to_html(desc)
|
16
34
|
@errors = app.get_errors
|
17
|
-
@
|
35
|
+
@params_ordered = app.get_params
|
36
|
+
|
37
|
+
parent = @resource.controller.superclass
|
38
|
+
if parent != ActionController::Base
|
39
|
+
@parent_resource = parent.controller_name
|
40
|
+
end
|
41
|
+
@resource.add_method("#{resource._id}##{method}")
|
42
|
+
end
|
43
|
+
|
44
|
+
def params
|
45
|
+
params_ordered.reduce({}) { |h,p| h[p.name] = p; h }
|
46
|
+
end
|
47
|
+
|
48
|
+
def params_ordered
|
49
|
+
all_params = []
|
50
|
+
# get params from parent resource description
|
51
|
+
if @parent_resource
|
52
|
+
parent = Restapi.get_resource_description(@parent_resource)
|
53
|
+
merge_params(all_params, parent._params_ordered) if parent
|
54
|
+
end
|
55
|
+
|
56
|
+
# get params from actual resource description
|
57
|
+
if @resource
|
58
|
+
merge_params(all_params, resource._params_ordered)
|
59
|
+
end
|
60
|
+
|
61
|
+
merge_params(all_params, @params_ordered)
|
62
|
+
all_params.find_all(&:validator)
|
18
63
|
end
|
19
64
|
|
20
|
-
def doc_url
|
21
|
-
|
65
|
+
def doc_url
|
66
|
+
[
|
67
|
+
ENV["RAILS_RELATIVE_URL_ROOT"],
|
68
|
+
Restapi.configuration.doc_base_url,
|
69
|
+
"##{@resource._id}/#{@method}"
|
70
|
+
].join
|
71
|
+
end
|
72
|
+
|
73
|
+
def method_apis_to_json
|
74
|
+
@apis.each.collect do |api|
|
75
|
+
{
|
76
|
+
:api_url => api.api_url,
|
77
|
+
:http_method => api.http_method,
|
78
|
+
:short_description => api.short_description
|
79
|
+
}
|
80
|
+
end
|
81
|
+
end
|
22
82
|
|
23
83
|
def to_json
|
24
84
|
{
|
25
85
|
:doc_url => doc_url,
|
26
|
-
:api_url => api_url,
|
27
86
|
:name => @method,
|
28
|
-
:
|
29
|
-
:short_description => @short_description,
|
87
|
+
:apis => method_apis_to_json,
|
30
88
|
:full_description => @full_description,
|
31
89
|
:errors => @errors,
|
32
|
-
:params =>
|
90
|
+
:params => params_ordered.map(&:to_json).flatten
|
33
91
|
}
|
34
92
|
end
|
35
93
|
|
94
|
+
private
|
95
|
+
|
96
|
+
def merge_params(params, new_params)
|
97
|
+
new_param_names = Set.new(new_params.map(&:name))
|
98
|
+
params.delete_if { |p| new_param_names.include?(p.name) }
|
99
|
+
params.concat(new_params)
|
100
|
+
end
|
101
|
+
|
36
102
|
end
|
37
103
|
|
38
|
-
end
|
104
|
+
end
|
@@ -9,6 +9,8 @@ module Restapi
|
|
9
9
|
class ParamDescription
|
10
10
|
|
11
11
|
attr_reader :name, :desc, :required, :validator
|
12
|
+
|
13
|
+
attr_accessor :parent
|
12
14
|
|
13
15
|
def initialize(name, *args, &block)
|
14
16
|
|
@@ -20,12 +22,15 @@ module Restapi
|
|
20
22
|
options = args.pop || {}
|
21
23
|
|
22
24
|
@name = name
|
23
|
-
@desc = Restapi.
|
25
|
+
@desc = Restapi.markup_to_html(options[:desc] || '')
|
24
26
|
@required = options[:required] || false
|
25
27
|
|
26
|
-
@validator =
|
27
|
-
|
28
|
-
|
28
|
+
@validator = nil
|
29
|
+
unless validator_type.nil?
|
30
|
+
@validator =
|
31
|
+
Validator::BaseValidator.find(self, validator_type, options, block)
|
32
|
+
raise "Validator not found." unless validator
|
33
|
+
end
|
29
34
|
end
|
30
35
|
|
31
36
|
def validate(value)
|
@@ -34,15 +39,35 @@ module Restapi
|
|
34
39
|
end
|
35
40
|
end
|
36
41
|
|
42
|
+
def full_name
|
43
|
+
name_parts = parents_and_self.map(&:name)
|
44
|
+
return ([name_parts.first] + name_parts[1..-1].map { |n| "[#{n}]" }).join("")
|
45
|
+
end
|
46
|
+
|
47
|
+
# returns an array of all the parents: starting with the root parent
|
48
|
+
# ending with itself
|
49
|
+
def parents_and_self
|
50
|
+
ret = []
|
51
|
+
if self.parent
|
52
|
+
ret.concat(self.parent.parents_and_self)
|
53
|
+
end
|
54
|
+
ret << self
|
55
|
+
ret
|
56
|
+
end
|
57
|
+
|
37
58
|
def to_json
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
59
|
+
if validator.is_a? Restapi::Validator::HashValidator
|
60
|
+
validator.hash_params_ordered.map(&:to_json)
|
61
|
+
else
|
62
|
+
{
|
63
|
+
:name => full_name,
|
64
|
+
:description => desc,
|
65
|
+
:required => required,
|
66
|
+
:validator => validator.to_s
|
67
|
+
}
|
68
|
+
end
|
44
69
|
end
|
45
70
|
|
46
71
|
end
|
47
72
|
|
48
|
-
end
|
73
|
+
end
|
@@ -10,12 +10,14 @@ module Restapi
|
|
10
10
|
# id - resouce name
|
11
11
|
class ResourceDescription
|
12
12
|
|
13
|
-
attr_reader :_short_description, :_full_description, :_methods, :_id,
|
13
|
+
attr_reader :controller, :_short_description, :_full_description, :_methods, :_id,
|
14
|
+
:_path, :_version, :_name, :_params_ordered
|
14
15
|
|
15
|
-
def initialize(resource_name, &block)
|
16
|
+
def initialize(controller, resource_name, &block)
|
16
17
|
@_methods = []
|
17
|
-
@
|
18
|
+
@_params_ordered = []
|
18
19
|
|
20
|
+
@controller = controller
|
19
21
|
@_id = resource_name
|
20
22
|
@_version = "1"
|
21
23
|
@_name = @_id.humanize
|
@@ -25,19 +27,24 @@ module Restapi
|
|
25
27
|
|
26
28
|
block.arity < 1 ? instance_eval(&block) : block.call(self) if block_given?
|
27
29
|
end
|
28
|
-
|
30
|
+
|
29
31
|
def param(param_name, *args, &block)
|
30
|
-
|
32
|
+
param_description = Restapi::ParamDescription.new(param_name, *args, &block)
|
33
|
+
@_params_ordered << param_description
|
31
34
|
end
|
32
35
|
|
33
36
|
def path(path); @_path = path; end
|
37
|
+
|
34
38
|
def version(version); @_version = version; end
|
39
|
+
|
35
40
|
def name(name); @_name = name; end
|
41
|
+
|
36
42
|
def short(short); @_short_description = short; end
|
37
43
|
alias :short_description :short
|
44
|
+
|
38
45
|
def desc(description)
|
39
46
|
description ||= ''
|
40
|
-
@_full_description = Restapi.
|
47
|
+
@_full_description = Restapi.markup_to_html(description)
|
41
48
|
end
|
42
49
|
alias :description :desc
|
43
50
|
alias :full_description :desc
|
@@ -48,7 +55,10 @@ module Restapi
|
|
48
55
|
@_methods.uniq!
|
49
56
|
end
|
50
57
|
|
51
|
-
def doc_url
|
58
|
+
def doc_url
|
59
|
+
"#{ENV["RAILS_RELATIVE_URL_ROOT"]}#{Restapi.configuration.doc_base_url}##{@_id}"
|
60
|
+
end
|
61
|
+
|
52
62
|
def api_url; "#{Restapi.configuration.api_base_url}#{@_path}"; end
|
53
63
|
|
54
64
|
def to_json(method_name = nil)
|
@@ -69,7 +79,5 @@ module Restapi
|
|
69
79
|
:methods => _methods
|
70
80
|
}
|
71
81
|
end
|
72
|
-
|
73
82
|
end
|
74
|
-
|
75
|
-
end
|
83
|
+
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
require "restapi/helpers"
|
2
2
|
require "restapi/application"
|
3
|
-
require "ostruct"
|
4
|
-
require "erb"
|
5
3
|
|
6
4
|
module Restapi
|
7
5
|
extend Restapi::Helpers
|
@@ -28,14 +26,15 @@ module Restapi
|
|
28
26
|
end
|
29
27
|
|
30
28
|
class Configuration
|
31
|
-
attr_accessor :app_name, :app_info, :copyright, :
|
32
|
-
|
33
|
-
|
34
|
-
|
29
|
+
attr_accessor :app_name, :app_info, :copyright, :markup,
|
30
|
+
:validate, :api_base_url, :doc_base_url
|
31
|
+
|
32
|
+
def app_info
|
33
|
+
Restapi.markup_to_html(@app_info)
|
35
34
|
end
|
36
35
|
|
37
36
|
def initialize
|
38
|
-
@
|
37
|
+
@markup = Restapi::Markup::RDoc.new
|
39
38
|
@app_name = "Another API"
|
40
39
|
@app_info = "Another API description"
|
41
40
|
@copyright = nil
|
@@ -46,6 +46,8 @@ module Restapi
|
|
46
46
|
case env['REQUEST_METHOD']
|
47
47
|
when 'GET', 'HEAD'
|
48
48
|
path = env['PATH_INFO'].sub("#{@baseurl}/","/restapi/").chomp('/')
|
49
|
+
path.sub!("#{ENV["RAILS_RELATIVE_URL_ROOT"]}",'')
|
50
|
+
|
49
51
|
if match = @file_handler.match?(path)
|
50
52
|
env["PATH_INFO"] = match
|
51
53
|
return @file_handler.call(env)
|
data/lib/restapi/validator.rb
CHANGED
@@ -6,12 +6,16 @@ module Restapi
|
|
6
6
|
# and implement class method build and instance method validate
|
7
7
|
class BaseValidator
|
8
8
|
|
9
|
-
attr_accessor :
|
9
|
+
attr_accessor :param_description
|
10
|
+
|
11
|
+
def initialize(param_description)
|
12
|
+
@param_description = param_description
|
13
|
+
end
|
10
14
|
|
11
15
|
# find the right validator for given options
|
12
|
-
def self.find(argument, options, block)
|
16
|
+
def self.find(param_description, argument, options, block)
|
13
17
|
self.subclasses.each do |validator_type|
|
14
|
-
validator = validator_type.build(argument, options, block)
|
18
|
+
validator = validator_type.build(param_description, argument, options, block)
|
15
19
|
return validator if validator
|
16
20
|
end
|
17
21
|
return nil
|
@@ -28,6 +32,10 @@ module Restapi
|
|
28
32
|
end
|
29
33
|
end
|
30
34
|
|
35
|
+
def param_name
|
36
|
+
@param_description.name
|
37
|
+
end
|
38
|
+
|
31
39
|
# validator description
|
32
40
|
def description
|
33
41
|
"TODO: validator description"
|
@@ -46,7 +54,8 @@ module Restapi
|
|
46
54
|
# validate arguments type
|
47
55
|
class TypeValidator < BaseValidator
|
48
56
|
|
49
|
-
def initialize(argument)
|
57
|
+
def initialize(param_description, argument)
|
58
|
+
super(param_description)
|
50
59
|
@type = argument
|
51
60
|
@type = Integer if @type == Fixnum
|
52
61
|
end
|
@@ -60,8 +69,8 @@ module Restapi
|
|
60
69
|
end
|
61
70
|
end
|
62
71
|
|
63
|
-
def self.build(argument, options, block)
|
64
|
-
self.new(argument) if argument.is_a?(Class) && argument != Hash
|
72
|
+
def self.build(param_description, argument, options, block)
|
73
|
+
self.new(param_description, argument) if argument.is_a?(Class) && (argument != Hash || block.nil?)
|
65
74
|
end
|
66
75
|
|
67
76
|
def error
|
@@ -76,7 +85,8 @@ module Restapi
|
|
76
85
|
# validate arguments value with regular expression
|
77
86
|
class RegexpValidator < BaseValidator
|
78
87
|
|
79
|
-
def initialize(argument)
|
88
|
+
def initialize(param_description, argument)
|
89
|
+
super(param_description)
|
80
90
|
@regexp = argument
|
81
91
|
end
|
82
92
|
|
@@ -84,8 +94,8 @@ module Restapi
|
|
84
94
|
value =~ @regexp
|
85
95
|
end
|
86
96
|
|
87
|
-
def self.build(argument, options, proc)
|
88
|
-
self.new(argument) if argument.is_a? Regexp
|
97
|
+
def self.build(param_description, argument, options, proc)
|
98
|
+
self.new(param_description, argument) if argument.is_a? Regexp
|
89
99
|
end
|
90
100
|
|
91
101
|
def error
|
@@ -100,26 +110,17 @@ module Restapi
|
|
100
110
|
# arguments value must be one of given in array
|
101
111
|
class ArrayValidator < BaseValidator
|
102
112
|
|
103
|
-
def initialize(argument)
|
113
|
+
def initialize(param_description, argument)
|
114
|
+
super(param_description)
|
104
115
|
@array = argument
|
105
116
|
end
|
106
117
|
|
107
118
|
def validate(value)
|
108
|
-
|
109
|
-
@array.find do |expected|
|
110
|
-
expected_class = expected.class
|
111
|
-
expected_class = Integer if expected_class == Fixnum
|
112
|
-
begin
|
113
|
-
converted_value = Kernel.send(expected_class.to_s, value)
|
114
|
-
rescue ArgumentError
|
115
|
-
false
|
116
|
-
end
|
117
|
-
converted_value === expected
|
118
|
-
end
|
119
|
+
@array.include?(value)
|
119
120
|
end
|
120
121
|
|
121
|
-
def self.build(argument, options, proc)
|
122
|
-
self.new(argument) if argument.is_a?(Array)
|
122
|
+
def self.build(param_description, argument, options, proc)
|
123
|
+
self.new(param_description, argument) if argument.is_a?(Array)
|
123
124
|
end
|
124
125
|
|
125
126
|
def error
|
@@ -133,7 +134,8 @@ module Restapi
|
|
133
134
|
|
134
135
|
class ProcValidator < BaseValidator
|
135
136
|
|
136
|
-
def initialize(argument)
|
137
|
+
def initialize(param_description, argument)
|
138
|
+
super(param_description)
|
137
139
|
@proc = argument
|
138
140
|
end
|
139
141
|
|
@@ -141,8 +143,8 @@ module Restapi
|
|
141
143
|
(@help = @proc.call(value)) === true
|
142
144
|
end
|
143
145
|
|
144
|
-
def self.build(argument, options, proc)
|
145
|
-
self.new(argument) if argument.is_a?(Proc) && argument.arity == 1
|
146
|
+
def self.build(param_description, argument, options, proc)
|
147
|
+
self.new(param_description, argument) if argument.is_a?(Proc) && argument.arity == 1
|
146
148
|
end
|
147
149
|
|
148
150
|
def error
|
@@ -156,12 +158,16 @@ module Restapi
|
|
156
158
|
|
157
159
|
class HashValidator < BaseValidator
|
158
160
|
|
159
|
-
|
160
|
-
|
161
|
+
attr_reader :hash_params_ordered
|
162
|
+
|
163
|
+
def self.build(param_description, argument, options, block)
|
164
|
+
self.new(param_description, block) if block.is_a?(Proc) && block.arity <= 0 && argument == Hash
|
161
165
|
end
|
162
166
|
|
163
|
-
def initialize(argument)
|
167
|
+
def initialize(param_description, argument)
|
168
|
+
super(param_description)
|
164
169
|
@proc = argument
|
170
|
+
@hash_params_ordered = []
|
165
171
|
@hash_params = {}
|
166
172
|
|
167
173
|
self.instance_exec(&@proc)
|
@@ -185,9 +191,12 @@ module Restapi
|
|
185
191
|
end
|
186
192
|
|
187
193
|
def param(param_name, *args, &block)
|
188
|
-
|
194
|
+
param_description = Restapi::ParamDescription.new(param_name, *args, &block)
|
195
|
+
param_description.parent = self.param_description
|
196
|
+
@hash_params_ordered << param_description
|
197
|
+
@hash_params[param_name.to_sym] = param_description
|
189
198
|
end
|
190
199
|
end
|
191
200
|
|
192
201
|
end
|
193
|
-
end
|
202
|
+
end
|