rubyrest 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +9 -5
- data/README +18 -23
- data/Rakefile +4 -4
- data/lib/rubyrest/application.rb +221 -0
- data/lib/rubyrest/atom.rb +247 -223
- data/lib/rubyrest/client.rb +19 -29
- data/lib/rubyrest/engine.rb +34 -83
- data/lib/rubyrest/resource.rb +87 -0
- data/lib/rubyrest/webrick.rb +92 -0
- data/lib/rubyrest.rb +10 -15
- metadata +5 -9
- data/examples/hello.rb +0 -57
- data/lib/rubyrest/config.rb +0 -80
- data/lib/rubyrest/servlets.rb +0 -233
- data/lib/rubyrest/tools.rb +0 -72
data/lib/rubyrest/servlets.rb
DELETED
@@ -1,233 +0,0 @@
|
|
1
|
-
# WEBrick servlet that implements a simple mapping between
|
2
|
-
# HTTP methods and request paths into business methods, using some
|
3
|
-
# conventions.
|
4
|
-
#
|
5
|
-
# Developpers should only subclass this servlet, and implement
|
6
|
-
# standard methods that return a xml content.
|
7
|
-
#
|
8
|
-
# $Id:$
|
9
|
-
module RubyRest
|
10
|
-
|
11
|
-
# This is the servlet that actually exposes a REST API
|
12
|
-
# by translating HTTP requests into 'generic' or 'custom' service
|
13
|
-
# methods.
|
14
|
-
#
|
15
|
-
class RESTServlet < WEBrick::HTTPServlet::AbstractServlet
|
16
|
-
include RubyRest::Atom, RubyRest::Tools
|
17
|
-
|
18
|
-
# Injects some RubyRest configuration options
|
19
|
-
# from the server to the servlet
|
20
|
-
def initialize( server, *options )
|
21
|
-
super( server, options )
|
22
|
-
@servicemodule = server.rubyrest[ :servicemodule ]
|
23
|
-
|
24
|
-
if server.rubyrest.has( :authmodel )
|
25
|
-
@authmodel = server.rubyrest[ :authmodel ]
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# Inspects the request, and resolve the parameters
|
30
|
-
# The format of a request is the following:
|
31
|
-
#
|
32
|
-
# /:model/:id/:property
|
33
|
-
#
|
34
|
-
def resolve_params( request )
|
35
|
-
params = request.path.split( "/" )
|
36
|
-
@http_method = request.request_method
|
37
|
-
@token = request[ 'token']
|
38
|
-
@model = params[1]
|
39
|
-
@id = params[2]
|
40
|
-
@property = params[3]
|
41
|
-
begin
|
42
|
-
@body = REXML::Document.new( request.body ) if request.body != nil
|
43
|
-
rescue => e
|
44
|
-
puts "unable to parse request body: #{request.body}"
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def resolve_custom_method
|
49
|
-
if @property != nil
|
50
|
-
@custom_method = "#{@model}_#{@property}"
|
51
|
-
else
|
52
|
-
@custom_method = WORKSPACE_METHOD
|
53
|
-
@custom_method = "#{@model}_#{@generic_method}" if @model != nil
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def resolve_service_method
|
58
|
-
resolve_custom_method
|
59
|
-
return @custom_method if self.respond_to? @custom_method
|
60
|
-
return @generic_method
|
61
|
-
end
|
62
|
-
|
63
|
-
def dispatch( request, response )
|
64
|
-
|
65
|
-
@service_method = resolve_service_method
|
66
|
-
check_security( request ) if @authmodel != nil
|
67
|
-
@result = self.method( @service_method ).call( request )
|
68
|
-
|
69
|
-
if @result.respond_to? "unauthorized" and @result.unauthorized == true
|
70
|
-
raise WEBrick::HTTPStatus::Unauthorized
|
71
|
-
else
|
72
|
-
response.status = @success_code
|
73
|
-
format_response( request, response )
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
|
78
|
-
def resolve_get_method
|
79
|
-
if @property != nil
|
80
|
-
@generic_method = :retrieve_related
|
81
|
-
else
|
82
|
-
if @id != nil
|
83
|
-
@generic_method = :show
|
84
|
-
else @generic_method = :retrieve end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def do_GET( request, response )
|
89
|
-
resolve_params( request )
|
90
|
-
@success_code = 200
|
91
|
-
resolve_get_method
|
92
|
-
dispatch( request, response )
|
93
|
-
end
|
94
|
-
|
95
|
-
def do_POST( request, response )
|
96
|
-
resolve_params( request )
|
97
|
-
incompatible_path( request ) if @id != nil
|
98
|
-
@generic_method = :create
|
99
|
-
@success_code = 201
|
100
|
-
dispatch( request, response )
|
101
|
-
end
|
102
|
-
|
103
|
-
def do_PUT( request, response )
|
104
|
-
resolve_params( request )
|
105
|
-
incompatible_path( request ) if @id == nil
|
106
|
-
@generic_method = :update
|
107
|
-
@success_code = 200
|
108
|
-
dispatch( request, response )
|
109
|
-
end
|
110
|
-
|
111
|
-
def do_DELETE( request, response )
|
112
|
-
resolve_params( request )
|
113
|
-
incompatible_path( request ) if @id == nil
|
114
|
-
@generic_method = :delete
|
115
|
-
@success_code = 410
|
116
|
-
dispatch( request, response )
|
117
|
-
end
|
118
|
-
|
119
|
-
# Raises an error stating that the current
|
120
|
-
# http method is not compatible with the requested path.
|
121
|
-
def incompatible_path( request )
|
122
|
-
error( 100, @http_method, request.path )
|
123
|
-
end
|
124
|
-
|
125
|
-
# Custom service method that authenticates a username/password pair
|
126
|
-
# found in the request body. The authentication is left to the class defined by the
|
127
|
-
# :authmodel configuration option.
|
128
|
-
#
|
129
|
-
# Developpers can provide their own implementation, however it is recommended to
|
130
|
-
# subclass the class *Credentials*
|
131
|
-
def credentials_create( request )
|
132
|
-
auth_class = to_class( @servicemodule, @authmodel )
|
133
|
-
auth = auth_class.new
|
134
|
-
auth_class.rest_bind( auth, @body )
|
135
|
-
auth = auth_class.authenticate( auth )
|
136
|
-
raise WEBrick::HTTPStatus::Unauthorized if auth == nil
|
137
|
-
return auth
|
138
|
-
end
|
139
|
-
|
140
|
-
ANONYMOUS_ACCESS = "POST/credentials"
|
141
|
-
|
142
|
-
# Defines whether the request is allowed to be processed
|
143
|
-
# without the need of a security token
|
144
|
-
#
|
145
|
-
def anonymous_access
|
146
|
-
"#{@http_method}/#{@model}" == ANONYMOUS_ACCESS
|
147
|
-
end
|
148
|
-
|
149
|
-
# Checks that a token is present in the request
|
150
|
-
# exception if doing a POST on a credentials property
|
151
|
-
def check_security( request )
|
152
|
-
auth_class = to_class( @servicemodule , @authmodel )
|
153
|
-
if @token == nil and !anonymous_access
|
154
|
-
raise WEBrick::HTTPStatus::Unauthorized
|
155
|
-
end
|
156
|
-
if @token != nil
|
157
|
-
@principal = auth_class.validate( @token )
|
158
|
-
raise WEBrick::HTTPStatus::Unauthorized if @principal == nil
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
# Returns the model defined by the configuration
|
163
|
-
# option :dashboard
|
164
|
-
def dashboard( request )
|
165
|
-
clazz = to_class( @servicemodule, "dashboard" )
|
166
|
-
clazz.rest_retrieve( @principal )
|
167
|
-
end
|
168
|
-
|
169
|
-
end
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
# Generic servlet, that implements the Moodisland Grape API
|
174
|
-
# This layer returns domain objects that can be included
|
175
|
-
# in atom feeds as entries.
|
176
|
-
#
|
177
|
-
class CRUDServlet < RESTServlet
|
178
|
-
|
179
|
-
# Creates and saves a new object. The object is then
|
180
|
-
# returned
|
181
|
-
def create( request )
|
182
|
-
clazz = to_class( @servicemodule, @model )
|
183
|
-
object = clazz.rest_create( @principal )
|
184
|
-
clazz.rest_bind( object, @body )
|
185
|
-
clazz.rest_save( object, @principal )
|
186
|
-
end
|
187
|
-
|
188
|
-
# Retrieves a list of objects
|
189
|
-
#
|
190
|
-
def retrieve( request )
|
191
|
-
clazz = to_class( @servicemodule, @model )
|
192
|
-
clazz.rest_retrieve( @principal )
|
193
|
-
end
|
194
|
-
|
195
|
-
# Retrieve a list of related objects
|
196
|
-
#
|
197
|
-
def retrieve_related( request )
|
198
|
-
clazz = to_class( @servicemodule, @model )
|
199
|
-
service_method = "rest_#{@property}"
|
200
|
-
error( 500, clazz.name, service_method ) if !clazz.respond_to?( service_method )
|
201
|
-
clazz.method( service_method ).call( @id, @principal )
|
202
|
-
end
|
203
|
-
|
204
|
-
# Retrieves a single object
|
205
|
-
#
|
206
|
-
def show( request )
|
207
|
-
clazz = to_class( @servicemodule, @model )
|
208
|
-
single = clazz.rest_single( @id, @principal )
|
209
|
-
raise error( 200, @model, @id ) if single == nil
|
210
|
-
return single
|
211
|
-
end
|
212
|
-
|
213
|
-
# Deletes a single object
|
214
|
-
#
|
215
|
-
def delete( request )
|
216
|
-
clazz = to_class( @servicemodule, @model )
|
217
|
-
clazz.rest_delete( @id, @principal )
|
218
|
-
end
|
219
|
-
|
220
|
-
# Retrieves, updates and saves an existing object.
|
221
|
-
# The object is then returned
|
222
|
-
#
|
223
|
-
def update( request )
|
224
|
-
object = show( request )
|
225
|
-
clazz = object.class
|
226
|
-
clazz.rest_bind( object, @body )
|
227
|
-
clazz.rest_save( object, @principal )
|
228
|
-
end
|
229
|
-
|
230
|
-
end
|
231
|
-
|
232
|
-
|
233
|
-
end
|
data/lib/rubyrest/tools.rb
DELETED
@@ -1,72 +0,0 @@
|
|
1
|
-
# RubyRest: $Id:$
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
|
6
|
-
module RubyRest
|
7
|
-
|
8
|
-
# This module provides a catalog of errors
|
9
|
-
# the application is supposed to throw.
|
10
|
-
#
|
11
|
-
#
|
12
|
-
module Tools
|
13
|
-
|
14
|
-
|
15
|
-
ATOM_DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
16
|
-
|
17
|
-
def nvl( value, default )
|
18
|
-
return value if value != nil
|
19
|
-
return default
|
20
|
-
end
|
21
|
-
|
22
|
-
def format_atom_date( value )
|
23
|
-
nvl( value, Time.now ).strftime( ATOM_DATE_FORMAT )
|
24
|
-
end
|
25
|
-
|
26
|
-
def parse_atom_date( value )
|
27
|
-
Date.strptime( value, ATOM_DATE_FORMAT )
|
28
|
-
end
|
29
|
-
|
30
|
-
# Resolves the specified module name and model
|
31
|
-
# into a class, and returns it
|
32
|
-
def to_class( modulename, model )
|
33
|
-
Class.by_name( "#{modulename}::#{model.capitalize}" )
|
34
|
-
end
|
35
|
-
|
36
|
-
# Builds a gem name
|
37
|
-
def to_gem_name( moduleprefix, modulename )
|
38
|
-
return modulename if moduleprefix == nil
|
39
|
-
return "#{moduleprefix}-#{modulename}"
|
40
|
-
end
|
41
|
-
|
42
|
-
# Builds a fully qualified module name
|
43
|
-
def to_module_name( moduleprefix, modulename )
|
44
|
-
return modulename.capitalize if moduleprefix == nil
|
45
|
-
return "#{moduleprefix.capitalize}::#{modulename.capitalize}"
|
46
|
-
end
|
47
|
-
|
48
|
-
# Catalog of error messages, indexed by error
|
49
|
-
# number
|
50
|
-
ERRORS = {
|
51
|
-
000 => "Missing configuration option",
|
52
|
-
001 => "Unable to connect to database. Missing method",
|
53
|
-
002 => "Unable to load schema. Missing method",
|
54
|
-
003 => "Unable to create table",
|
55
|
-
004 => "Please subclass and override",
|
56
|
-
005 => "Sorry, configuration method did not return a valid database connection",
|
57
|
-
006 => "Class was not property configured with its database connection (Sequel)",
|
58
|
-
100 => "Request path and HTTP method are not compatible",
|
59
|
-
200 => "No resource found for model and id",
|
60
|
-
500 => "No service method found in model class"
|
61
|
-
}
|
62
|
-
|
63
|
-
# Raises a new error. Resolves the specified
|
64
|
-
# number into a human readable message
|
65
|
-
def error( number, *params )
|
66
|
-
raise "\##{number}: #{ERRORS[number]}: #{params.join( ', ') }"
|
67
|
-
end
|
68
|
-
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
end
|