restapi 0.0.1

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.
Files changed (78) hide show
  1. data/.autotest +3 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +2 -0
  4. data/.rvmrc +1 -0
  5. data/Gemfile +15 -0
  6. data/Gemfile.lock +118 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.rdoc +0 -0
  9. data/Rakefile +13 -0
  10. data/app/controllers/restapis_controller.rb +11 -0
  11. data/app/public/javascripts/backbone.js +1431 -0
  12. data/app/public/javascripts/bootstrap-collapse.js +138 -0
  13. data/app/public/javascripts/bootstrap.js +1726 -0
  14. data/app/public/javascripts/jquery-1.7.2.js +9404 -0
  15. data/app/public/javascripts/json2.js +487 -0
  16. data/app/public/javascripts/restapi/jst.js +108 -0
  17. data/app/public/javascripts/restapi/restapi.js +14 -0
  18. data/app/public/javascripts/restapi/routers/documentation_router.js +47 -0
  19. data/app/public/javascripts/underscore.js +999 -0
  20. data/app/public/stylesheets/bootstrap-responsive.css +686 -0
  21. data/app/public/stylesheets/bootstrap-responsive.min.css +12 -0
  22. data/app/public/stylesheets/bootstrap.css +3990 -0
  23. data/app/public/stylesheets/bootstrap.min.css +689 -0
  24. data/app/public/stylesheets/restapi/application.css +7 -0
  25. data/app/views/restapis/index.html.erb +47 -0
  26. data/lib/restapi.rb +22 -0
  27. data/lib/restapi/application.rb +155 -0
  28. data/lib/restapi/dsl_definition.rb +119 -0
  29. data/lib/restapi/error_description.rb +15 -0
  30. data/lib/restapi/method_description.rb +38 -0
  31. data/lib/restapi/param_description.rb +48 -0
  32. data/lib/restapi/railtie.rb +9 -0
  33. data/lib/restapi/resource_description.rb +75 -0
  34. data/lib/restapi/restapi_module.rb +54 -0
  35. data/lib/restapi/routing.rb +15 -0
  36. data/lib/restapi/validator.rb +193 -0
  37. data/lib/restapi/version.rb +4 -0
  38. data/restapi.gemspec +23 -0
  39. data/spec/controllers/restapis_controller_spec.rb +13 -0
  40. data/spec/controllers/users_controller_spec.rb +175 -0
  41. data/spec/dummy/Rakefile +7 -0
  42. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  43. data/spec/dummy/app/controllers/dogs_controller.rb +22 -0
  44. data/spec/dummy/app/controllers/twitter_example_controller.rb +306 -0
  45. data/spec/dummy/app/controllers/users_controller.rb +211 -0
  46. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  47. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  48. data/spec/dummy/app/views/users/doc.html.erb +24 -0
  49. data/spec/dummy/config.ru +4 -0
  50. data/spec/dummy/config/application.rb +45 -0
  51. data/spec/dummy/config/boot.rb +10 -0
  52. data/spec/dummy/config/database.yml +22 -0
  53. data/spec/dummy/config/environment.rb +5 -0
  54. data/spec/dummy/config/environments/development.rb +25 -0
  55. data/spec/dummy/config/environments/production.rb +49 -0
  56. data/spec/dummy/config/environments/test.rb +35 -0
  57. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  58. data/spec/dummy/config/initializers/inflections.rb +10 -0
  59. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  60. data/spec/dummy/config/initializers/restapi.rb +30 -0
  61. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  62. data/spec/dummy/config/initializers/session_store.rb +8 -0
  63. data/spec/dummy/config/locales/en.yml +5 -0
  64. data/spec/dummy/config/routes.rb +69 -0
  65. data/spec/dummy/public/404.html +26 -0
  66. data/spec/dummy/public/422.html +26 -0
  67. data/spec/dummy/public/500.html +26 -0
  68. data/spec/dummy/public/favicon.ico +0 -0
  69. data/spec/dummy/public/javascripts/application.js +2 -0
  70. data/spec/dummy/public/javascripts/controls.js +965 -0
  71. data/spec/dummy/public/javascripts/dragdrop.js +974 -0
  72. data/spec/dummy/public/javascripts/effects.js +1123 -0
  73. data/spec/dummy/public/javascripts/prototype.js +6001 -0
  74. data/spec/dummy/public/javascripts/rails.js +202 -0
  75. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  76. data/spec/dummy/script/rails +6 -0
  77. data/spec/spec_helper.rb +32 -0
  78. metadata +207 -0
@@ -0,0 +1,9 @@
1
+ module Restapi
2
+ class Railtie < Rails::Railtie
3
+ initializer 'restapi.controller_additions' do
4
+ ActiveSupport.on_load :action_controller do
5
+ extend Restapi::DSL
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,75 @@
1
+ module Restapi
2
+
3
+ # Resource description
4
+ #
5
+ # version - api version (1)
6
+ # description
7
+ # path - relative path (/api/articles)
8
+ # methods - array of keys to Restapi.method_descriptions (array of Restapi::MethodDescription)
9
+ # name - human readable alias of resource (Articles)
10
+ # id - resouce name
11
+ class ResourceDescription
12
+
13
+ attr_reader :_short_description, :_full_description, :_methods, :_id, :_path, :_version, :_name, :_params
14
+
15
+ def initialize(resource_name, &block)
16
+ @_methods = []
17
+ @_params = Hash.new
18
+
19
+ @_id = resource_name
20
+ @_version = "1"
21
+ @_name = @_id.humanize
22
+ @_full_description = ""
23
+ @_short_description = ""
24
+ @_path = ""
25
+
26
+ block.arity < 1 ? instance_eval(&block) : block.call(self) if block_given?
27
+ end
28
+
29
+ def param(param_name, *args, &block)
30
+ @_params[param_name] = Restapi::ParamDescription.new(param_name, *args, &block)
31
+ end
32
+
33
+ def path(path); @_path = path; end
34
+ def version(version); @_version = version; end
35
+ def name(name); @_name = name; end
36
+ def short(short); @_short_description = short; end
37
+ alias :short_description :short
38
+ def desc(description)
39
+ description ||= ''
40
+ @_full_description = Restapi.rdoc.convert(description.strip_heredoc)
41
+ end
42
+ alias :description :desc
43
+ alias :full_description :desc
44
+
45
+ # add description of resource method
46
+ def add_method(mapi_key)
47
+ @_methods << mapi_key
48
+ @_methods.uniq!
49
+ end
50
+
51
+ def doc_url; "#{Restapi.configuration.doc_base_url}/#{@_id}"; end
52
+ def api_url; "#{Restapi.configuration.api_base_url}#{@_path}"; end
53
+
54
+ def to_json(method_name = nil)
55
+
56
+ _methods = if method_name.blank?
57
+ @_methods.collect { |key| Restapi.method_descriptions[key].to_json }
58
+ else
59
+ [Restapi.method_descriptions[[@_id, method_name].join('#')].to_json]
60
+ end
61
+
62
+ {
63
+ :doc_url => doc_url,
64
+ :api_url => api_url,
65
+ :name => @_name,
66
+ :short_description => @_short_description,
67
+ :full_description => @_full_description,
68
+ :version => @_version,
69
+ :methods => _methods
70
+ }
71
+ end
72
+
73
+ end
74
+
75
+ end
@@ -0,0 +1,54 @@
1
+ require "restapi/application"
2
+ require "ostruct"
3
+ require "erb"
4
+
5
+ module Restapi
6
+
7
+ def self.rdoc
8
+ @rdoc ||= RDoc::Markup::ToHtml.new
9
+ end
10
+
11
+ def self.convert_markup(text)
12
+ Restapi.rdoc.convert(text.strip_heredoc)
13
+ end
14
+
15
+ def self.app
16
+ @application ||= Restapi::Application.new
17
+ end
18
+
19
+ def self.to_json(resource_name = nil, method_name = nil)
20
+ app.to_json(resource_name, method_name)
21
+ end
22
+
23
+ # all calls delegated to Restapi::Application instance
24
+ def self.method_missing(method, *args, &block)
25
+ app.respond_to?(method) ? app.send(method, *args, &block) : super
26
+ end
27
+
28
+ def self.configure
29
+ yield configuration
30
+ end
31
+
32
+ def self.configuration
33
+ @configuration ||= Configuration.new
34
+ end
35
+
36
+ class Configuration
37
+ attr_accessor :app_name, :app_info, :copyright, :markup_language, :validate, :api_base_url, :doc_base_url
38
+
39
+ def app_info=(text)
40
+ @app_info = Restapi.convert_markup(text)
41
+ end
42
+
43
+ def initialize
44
+ @markup_language = :rdoc
45
+ @app_name = "Another API"
46
+ @app_info = "Another API description"
47
+ @copyright = nil
48
+ @validate = true
49
+ @api_base_url = ""
50
+ @doc_base_url = "/apidoc"
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,15 @@
1
+ module Restapi
2
+ module Routing
3
+ module MapperExtensions
4
+ def restapi(route = "/apidoc")
5
+
6
+ Restapi.configuration.doc_base_url = route
7
+
8
+ self.get("#{route}/(:resource)/(:method)" => "restapis#index")
9
+
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ ActionDispatch::Routing::Mapper.send :include, Restapi::Routing::MapperExtensions
@@ -0,0 +1,193 @@
1
+ module Restapi
2
+
3
+ module Validator
4
+
5
+ # to create new validator, inherit from Restapi::Validator::Base
6
+ # and implement class method build and instance method validate
7
+ class BaseValidator
8
+
9
+ attr_accessor :param_name
10
+
11
+ # find the right validator for given options
12
+ def self.find(argument, options, block)
13
+ self.subclasses.each do |validator_type|
14
+ validator = validator_type.build(argument, options, block)
15
+ return validator if validator
16
+ end
17
+ return nil
18
+ end
19
+
20
+ # check if value is valid
21
+ def valid?(value)
22
+ if self.validate(value)
23
+ @error_value = nil
24
+ true
25
+ else
26
+ @error_value = value
27
+ false
28
+ end
29
+ end
30
+
31
+ # validator description
32
+ def description
33
+ "TODO: validator description"
34
+ end
35
+
36
+ def to_s
37
+ self.description
38
+ end
39
+
40
+ def to_json
41
+ self.description
42
+ end
43
+
44
+ end
45
+
46
+ # validate arguments type
47
+ class TypeValidator < BaseValidator
48
+
49
+ def initialize(argument)
50
+ @type = argument
51
+ @type = Integer if @type == Fixnum
52
+ end
53
+
54
+ def validate(value)
55
+ return false if value.nil?
56
+ begin
57
+ Kernel.send(@type.to_s, value)
58
+ rescue ArgumentError
59
+ return false
60
+ end
61
+ end
62
+
63
+ def self.build(argument, options, block)
64
+ self.new(argument) if argument.is_a?(Class) && argument != Hash
65
+ end
66
+
67
+ def error
68
+ "Parameter #{@param_name} expecting to be #{@type.name}, got: #{@error_value.class.name}"
69
+ end
70
+
71
+ def description
72
+ "Parameter has to be #{@type}."
73
+ end
74
+ end
75
+
76
+ # validate arguments value with regular expression
77
+ class RegexpValidator < BaseValidator
78
+
79
+ def initialize(argument)
80
+ @regexp = argument
81
+ end
82
+
83
+ def validate(value)
84
+ value =~ @regexp
85
+ end
86
+
87
+ def self.build(argument, options, proc)
88
+ self.new(argument) if argument.is_a? Regexp
89
+ end
90
+
91
+ def error
92
+ "Parameter #{@param_name} expecting to match /#{@regexp.source}/, got '#{@error_value}'"
93
+ end
94
+
95
+ def description
96
+ "Parameter has to match regular expression /#{@regexp.source}/."
97
+ end
98
+ end
99
+
100
+ # arguments value must be one of given in array
101
+ class ArrayValidator < BaseValidator
102
+
103
+ def initialize(argument)
104
+ @array = argument
105
+ end
106
+
107
+ 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
+ end
120
+
121
+ def self.build(argument, options, proc)
122
+ self.new(argument) if argument.is_a?(Array)
123
+ end
124
+
125
+ def error
126
+ "Parameter #{@param_name} has bad value (#{@error_value.inspect}). Expecting one of: #{@array.join(',')}."
127
+ end
128
+
129
+ def description
130
+ "Parameter has to be one of: #{@array.join(', ')}."
131
+ end
132
+ end
133
+
134
+ class ProcValidator < BaseValidator
135
+
136
+ def initialize(argument)
137
+ @proc = argument
138
+ end
139
+
140
+ def validate(value)
141
+ (@help = @proc.call(value)) === true
142
+ end
143
+
144
+ def self.build(argument, options, proc)
145
+ self.new(argument) if argument.is_a?(Proc) && argument.arity == 1
146
+ end
147
+
148
+ def error
149
+ "Parameter #{@param_name} has bad value (\"#{@error_value}\"). #{@help}"
150
+ end
151
+
152
+ def description
153
+ ""
154
+ end
155
+ end
156
+
157
+ class HashValidator < BaseValidator
158
+
159
+ def self.build(argument, options, block)
160
+ self.new(block) if block.is_a?(Proc) && block.arity <= 0 && argument == Hash
161
+ end
162
+
163
+ def initialize(argument)
164
+ @proc = argument
165
+ @hash_params = {}
166
+
167
+ self.instance_exec(&@proc)
168
+ end
169
+
170
+ def validate(value)
171
+ if @hash_params
172
+ @hash_params.each do |k, p|
173
+ p.validate(value[k]) if value.has_key?(k) || p.required
174
+ end
175
+ end
176
+ return true
177
+ end
178
+
179
+ def error
180
+ "TODO"
181
+ end
182
+
183
+ def description
184
+ "TODO"
185
+ end
186
+
187
+ def param(param_name, *args, &block)
188
+ @hash_params[param_name.to_sym] = Restapi::ParamDescription.new(param_name, *args, &block)
189
+ end
190
+ end
191
+
192
+ end
193
+ end
@@ -0,0 +1,4 @@
1
+ module Restapi
2
+ # gem version
3
+ VERSION = "0.0.1"
4
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "restapi/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "restapi"
7
+ s.version = Restapi::VERSION
8
+ s.authors = ["Pavel Pokorny"]
9
+ s.email = ["pajkycz@gmail.com"]
10
+ s.homepage = "http://github.com/Pajk/restapi"
11
+ s.summary = %q{REST API documentation tool}
12
+ s.description = %q{Maintain your API documentation up to date!}
13
+
14
+ s.rubyforge_project = "restapi"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_development_dependency "rspec-rails"
21
+ s.add_development_dependency "rcov"
22
+ # s.add_runtime_dependency "rest-client"
23
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe RestapisController do
4
+
5
+ describe "GET index" do
6
+
7
+ it "test if route exists" do
8
+ get :index
9
+
10
+ assert_response :success
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,175 @@
1
+ require 'spec_helper'
2
+
3
+ describe UsersController do
4
+
5
+ describe "resource" do
6
+ it "should be described" do
7
+ a = Restapi.get_resource_description(UsersController)
8
+
9
+ a._short_description.should eq('Site members')
10
+ # a._full_description.should eq("\n<h2 id=\"label-Long+description\">Long description</h2>\n\n<p>Example resource for rest api documentation</p>\n")
11
+ a._methods.should eq(["users#show", "users#create", "users#index"])
12
+ md = a._params[:id]
13
+ md.should_not be(nil)
14
+ md.name.should eq(:id)
15
+ md.desc.should eq("\n<p>User ID</p>\n")
16
+ md.required.should eq(true)
17
+ md.validator.class.should eq(Restapi::Validator::TypeValidator)
18
+ a._id.should eq('users')
19
+ a._path.should eq('/users')
20
+ a._version.should eq('1.0 - 3.4.2012')
21
+ a._name.should eq('Members')
22
+ end
23
+ end
24
+
25
+ describe "GET show" do
26
+
27
+ it "find a user with id" do
28
+ get :show, :id => 5, :session => "secret_hash"
29
+
30
+ assert_response :success
31
+ end
32
+
33
+ it "throw ArgumentError if some required parameter missing" do
34
+ lambda { get :show, :id => 5 }.should raise_error(ArgumentError)
35
+ end
36
+
37
+ it "responds with status 401 if session is wrong" do
38
+ get :show, :id => 5, :session => "bad_hash"
39
+ assert_response 401
40
+ end
41
+
42
+ it "should be annotated" do
43
+
44
+ a = Restapi.get_method_description(UsersController, :show)
45
+ b = Restapi[UsersController, :show]
46
+ a.should eq(b)
47
+
48
+ a.method.should eq(:show)
49
+ a.resource.should eq('users')
50
+ a.errors[0].code.should eq(401)
51
+ a.errors[0].description.should eq("Unauthorized")
52
+ a.errors[1].code.should eq(404)
53
+ a.errors[1].description.should eq("Not Found")
54
+
55
+ a.short_description.should eq("Show user profile")
56
+ a.path.should eq("/users/:id")
57
+ a.http.should eq("GET")
58
+
59
+ param = a.params[:session]
60
+ param.required.should eq(true)
61
+ param.desc.should eq("\n<p>user is logged in</p>\n")
62
+ param.validator.class.should be(Restapi::Validator::TypeValidator)
63
+ param.validator.instance_variable_get("@type").should eq(String)
64
+
65
+ param = a.params[:float_param]
66
+ param.required.should eq(false)
67
+ param.desc.should eq("\n<p>float param</p>\n")
68
+ param.validator.class.should be(Restapi::Validator::TypeValidator)
69
+ param.validator.instance_variable_get("@type").should eq(Float)
70
+
71
+ param = a.params[:id]
72
+ param.required.should eq(true)
73
+ param.desc.should eq("\n<p>user id</p>\n")
74
+ param.validator.class.should be(Restapi::Validator::TypeValidator)
75
+ param.validator.instance_variable_get("@type").should eq(Integer)
76
+
77
+ param = a.params[:regexp_param]
78
+ param.desc.should eq("\n<p>regexp param</p>\n")
79
+ param.required.should eq(false)
80
+ param.validator.class.should be(Restapi::Validator::RegexpValidator)
81
+ param.validator.instance_variable_get("@regexp").should eq(/^[0-9]* years/)
82
+
83
+ param = a.params[:array_param]
84
+ param.desc.should eq("\n<p>array validator</p>\n")
85
+ param.validator.class.should be(Restapi::Validator::ArrayValidator)
86
+ param.validator.instance_variable_get("@array").should eq([100, "one", "two", 1, 2])
87
+
88
+ param = a.params[:proc_param]
89
+ param.desc.should eq("\n<p>proc validator</p>\n")
90
+ param.validator.class.should be(Restapi::Validator::ProcValidator)
91
+
92
+ a.full_description.length.should be > 400
93
+ end
94
+
95
+ it "should yell ArgumentError if id is not a number" do
96
+ lambda { get :show, :id => "not a number", :session => "secret_hash" }.should raise_error(ArgumentError)
97
+ end
98
+
99
+ it "should yell ArgumentError if float_param is not a float" do
100
+ lambda { get :show, :id => 5, :session => "secret_hash", :float_param => "234.2.34"}.should raise_error(ArgumentError)
101
+ lambda { get :show, :id => 5, :session => "secret_hash", :float_param => "no float here"}.should raise_error(ArgumentError)
102
+ end
103
+
104
+ it "should understand float validator" do
105
+ get :show, :id => 5, :session => "secret_hash", :float_param => "234.34"
106
+ assert_response :success
107
+ get :show, :id => 5, :session => "secret_hash", :float_param => "234"
108
+ assert_response :success
109
+ end
110
+
111
+ it "should understand regexp validator" do
112
+ get :show, :id => 5, :session => "secret_hash", :regexp_param => "24 years"
113
+ assert_response :success
114
+ end
115
+
116
+ it "should validate with regexp validator" do
117
+ lambda {
118
+ get :show, :id => 5, :session => "secret_hash", :regexp_param => "ten years"
119
+ }.should raise_error(ArgumentError)
120
+ end
121
+
122
+ it "should validate with array validator" do
123
+ get :show, :id => 5, :session => "secret_hash", :array_param => "one"
124
+ assert_response :success
125
+ get :show, :id => 5, :session => "secret_hash", :array_param => "two"
126
+ assert_response :success
127
+ get :show, :id => 5, :session => "secret_hash", :array_param => 1
128
+ assert_response :success
129
+ get :show, :id => 5, :session => "secret_hash", :array_param => "2"
130
+ assert_response :success
131
+ end
132
+
133
+ it "should raise ArgumentError with array validator" do
134
+ lambda { get :show, :id => 5, :session => "secret_hash", :array_param => "blabla" }.should
135
+ raise_error(ArgumentError)
136
+
137
+ lambda { get :show, :id => 5, :session => "secret_hash", :array_param => 3 }.should
138
+ raise_error(ArgumentError)
139
+ end
140
+
141
+ it "should validate with Proc validator" do
142
+ lambda { get :show, :id => 5, :session => "secret_hash", :proc_param => "asdgsag" }.should
143
+ raise_error(ArgumentError)
144
+
145
+ get :show, :id => 5, :session => "secret_hash", :proc_param => "param value"
146
+ assert_response :success
147
+ end
148
+
149
+ end
150
+
151
+ describe "POST create" do
152
+
153
+ it "should understand hash validator" do
154
+ post :create, :user => { :username => "root", :password => "12345", :membership => "standard" }
155
+ assert_response :success
156
+
157
+ a = Restapi[UsersController, :create]
158
+ a.short_description.should eq("Create user")
159
+ a.path.should eq("/users")
160
+ a.http.should eq("POST")
161
+
162
+ lambda { post :create, :user => { :username => "root", :password => "12345", :membership => "____" } }.should
163
+ raise_error(ArgumentError)
164
+
165
+ lambda { post :create, :user => { :username => "root" } }.should
166
+ raise_error(ArgumentError)
167
+
168
+ post :create, :user => { :username => "root", :password => "pwd" }
169
+ assert_response :success
170
+
171
+ end
172
+
173
+ end
174
+
175
+ end