rubyrest 0.0.5 → 0.1.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.
- 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
|