webservice 0.5.0 → 0.6.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.
- checksums.yaml +4 -4
- data/Manifest.txt +2 -0
- data/assets/webservice-32x32.png +0 -0
- data/lib/webservice.rb +2 -0
- data/lib/webservice/base.rb +146 -132
- data/lib/webservice/response_handler.rb +248 -0
- data/lib/webservice/version.rb +1 -1
- data/test/test_app.rb +8 -14
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1cbf8b69b97842dcc85d69c5941b1df7533183d3
|
4
|
+
data.tar.gz: 21a647b38f0143f1075be6f1171b22a8f333e10b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a55ee94ae9ad07ddfe3dd1428c5e503ceaca7b9180c965f99c3891ca3952caf008b53aaf51dd18ce71f228ca5aca716377d227f112c1cbcf99c9b8caaa4fcc8
|
7
|
+
data.tar.gz: e3d677611c7240886b37380b9cd36ae546abbf561cb2ee9dbc2501eee04e3b0f888d0dd6192949d43c2fb5c3d207da2534903372c813dbe8607e82590b8ee8a0
|
data/Manifest.txt
CHANGED
Binary file
|
data/lib/webservice.rb
CHANGED
data/lib/webservice/base.rb
CHANGED
@@ -16,8 +16,9 @@ module Webservice
|
|
16
16
|
## HTTP headers
|
17
17
|
CONTENT_LENGTH = Rack::CONTENT_LENGTH
|
18
18
|
CONTENT_TYPE = Rack::CONTENT_TYPE
|
19
|
-
|
20
|
-
LOCATION
|
19
|
+
# -- more HTTP headers - not available from Rack
|
20
|
+
LOCATION = 'Location'.freeze
|
21
|
+
LAST_MODIFIED = 'Last-Modified'.freeze
|
21
22
|
|
22
23
|
|
23
24
|
module Helpers
|
@@ -71,6 +72,56 @@ module Helpers
|
|
71
72
|
### unknown type; do nothing - sorry; issue warning - why? why not??
|
72
73
|
end
|
73
74
|
end ## method content_type
|
75
|
+
|
76
|
+
|
77
|
+
## simple send file (e.g. for images/binary blobs, etc.) helper
|
78
|
+
def send_file( path )
|
79
|
+
## puts "send_file path=>#{path}<"
|
80
|
+
|
81
|
+
## puts "HTTP_IF_MODIFIED_SINCE:"
|
82
|
+
## puts request.get_header('HTTP_IF_MODIFIED_SINCE')
|
83
|
+
|
84
|
+
last_modified = File.mtime(path).httpdate
|
85
|
+
## puts "last_modified:"
|
86
|
+
## puts last_modified
|
87
|
+
|
88
|
+
## HTTP 304 => Not Modified
|
89
|
+
halt 304 if request.get_header('HTTP_IF_MODIFIED_SINCE') == last_modified
|
90
|
+
|
91
|
+
headers[ LAST_MODIFIED ] = last_modified
|
92
|
+
|
93
|
+
bytes = File.open( path, 'rb' ) { |f| f.read }
|
94
|
+
|
95
|
+
## puts "encoding:"
|
96
|
+
## puts bytes.encoding
|
97
|
+
|
98
|
+
## puts "size:"
|
99
|
+
## puts bytes.size
|
100
|
+
|
101
|
+
extname = File.extname( path )
|
102
|
+
## puts "extname:"
|
103
|
+
## puts extname
|
104
|
+
|
105
|
+
## puts "headers (before):"
|
106
|
+
## pp headers
|
107
|
+
|
108
|
+
if extname == '.png'
|
109
|
+
headers[ CONTENT_TYPE ] = 'image/png'
|
110
|
+
else
|
111
|
+
## fallback to application/octet-stream
|
112
|
+
headers[ CONTENT_TYPE ] = 'application/octet-stream'
|
113
|
+
end
|
114
|
+
|
115
|
+
headers[ CONTENT_LENGTH ] = bytes.size.to_s ## note: do NOT forget to use to_s (requires string!)
|
116
|
+
|
117
|
+
## puts "headers (after):"
|
118
|
+
## pp headers
|
119
|
+
|
120
|
+
halt 200, bytes
|
121
|
+
end # method send_file
|
122
|
+
|
123
|
+
|
124
|
+
|
74
125
|
end ## module Helpers
|
75
126
|
|
76
127
|
|
@@ -123,6 +174,27 @@ class Base
|
|
123
174
|
end
|
124
175
|
|
125
176
|
|
177
|
+
##########################
|
178
|
+
## support for "builtin" fallback routes
|
179
|
+
##
|
180
|
+
## e.g. use like
|
181
|
+
## fallback_route GET, '/' do
|
182
|
+
## "Hello, World!"
|
183
|
+
## end
|
184
|
+
def fallback_route( method, pattern, &block )
|
185
|
+
puts "[debug] Webservice::Base.#{method.downcase} - add (fallback) route #{method} '#{pattern}' to #<#{self.name}:#{self.object_id}> : #{self.class.name}"
|
186
|
+
|
187
|
+
## note: for now use the sintatra-style patterns (with mustermann)
|
188
|
+
fallback_routes[method] << [Mustermann::Sinatra.new(pattern), block]
|
189
|
+
end
|
190
|
+
|
191
|
+
def fallback_routes
|
192
|
+
## note: !!! use @@ NOT just @ e.g.
|
193
|
+
## routes get shared/used by all classes/subclasses
|
194
|
+
@@fallback_routes ||= Hash.new { |hash, key| hash[key]=[] }
|
195
|
+
end
|
196
|
+
|
197
|
+
|
126
198
|
def environment
|
127
199
|
## include APP_ENV why? why not?
|
128
200
|
## todo -- cache value? why why not? (see/follow sinatara set machinery ??)
|
@@ -152,6 +224,9 @@ class Base
|
|
152
224
|
attr_reader :params
|
153
225
|
attr_reader :env
|
154
226
|
|
227
|
+
attr_reader :handler # default response_handler/respond_with handler magic
|
228
|
+
|
229
|
+
|
155
230
|
def call( env )
|
156
231
|
dup.call!( env )
|
157
232
|
end
|
@@ -164,7 +239,9 @@ class Base
|
|
164
239
|
@params = request.params
|
165
240
|
@env = env
|
166
241
|
|
242
|
+
@handler = ResponseHandler.new( self ) ## for now "hard-coded"; make it a setting later - why? why not?
|
167
243
|
|
244
|
+
### move cors headers to responseHandler to initialize!!!! - why? why not??
|
168
245
|
## (auto-)add (merge in) cors headers
|
169
246
|
## todo: move into a before filter ?? lets you overwrite headers - needed - why? why not??
|
170
247
|
headers 'Access-Control-Allow-Origin' => '*',
|
@@ -187,7 +264,10 @@ class Base
|
|
187
264
|
private
|
188
265
|
|
189
266
|
def route_eval
|
267
|
+
puts " [#{self.class.name}] try matching route >#{request.request_method} #{request.path_info}<..."
|
268
|
+
|
190
269
|
catch(:halt) do
|
270
|
+
## pass 1
|
191
271
|
routes = self.class.routes[ request.request_method ]
|
192
272
|
routes.each do |pattern, block|
|
193
273
|
## puts "trying matching route >#{request.path_info}<..."
|
@@ -198,10 +278,24 @@ private
|
|
198
278
|
## todo/fix: check merge order - params overwrites url_params - why? why not??
|
199
279
|
@params = url_params.merge( @params )
|
200
280
|
end
|
201
|
-
handle_response( instance_eval( &block ))
|
281
|
+
handler.handle_response( instance_eval( &block ))
|
202
282
|
return
|
203
283
|
end
|
204
284
|
end
|
285
|
+
|
286
|
+
## pass 2 - (builtin) fallbacks
|
287
|
+
routes = self.class.fallback_routes[ request.request_method ]
|
288
|
+
routes.each do |pattern, block|
|
289
|
+
url_params = pattern.params( request.path_info )
|
290
|
+
if url_params ## note: params returns nil if no match
|
291
|
+
if !url_params.empty? ## url_params hash NOT empty (e.g. {}) merge with req params
|
292
|
+
@params = url_params.merge( @params )
|
293
|
+
end
|
294
|
+
handler.handle_response( instance_eval( &block ))
|
295
|
+
return
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
205
299
|
# no match found for route/request
|
206
300
|
halt 404
|
207
301
|
end
|
@@ -209,157 +303,77 @@ private
|
|
209
303
|
|
210
304
|
|
211
305
|
|
212
|
-
|
213
|
-
|
214
|
-
puts "[Webservice::Base#handle_response (#{request.path_info}) params: #{params.inspect}] - obj : #{obj.class.name}"
|
215
|
-
pp obj
|
216
|
-
|
217
|
-
## "magic" param format; default to json
|
218
|
-
format = params['format'] || 'json'
|
219
|
-
|
220
|
-
## note: response.body must be (expects) an array!!!
|
221
|
-
## thus, [json] etc.
|
222
|
-
|
223
|
-
if format == 'csv' || format == 'txt' ||
|
224
|
-
format == 'html' || format == 'htm'
|
225
|
-
|
226
|
-
data = as_tabular( obj )
|
306
|
+
##################################
|
307
|
+
### add some fallback (builtin) routes
|
227
308
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
response.body = [generate_csv( data )]
|
234
|
-
else
|
235
|
-
## asume html
|
236
|
-
content_type :html
|
237
|
-
response.body = [generate_html_table( data )]
|
238
|
-
end
|
239
|
-
else
|
240
|
-
## wrong format (expect array of hashes)
|
241
|
-
## todo: issue warning/notice about wrong format - how?
|
242
|
-
## use different http status code - why? why not??
|
243
|
-
content_type :txt
|
244
|
-
## todo/check: use just data.to_s for all - why? why not?
|
245
|
-
## for now return as is (convert to string with to_s or inspect)
|
246
|
-
response.body = [data.is_a?( String ) ? data.to_s : data.inspect]
|
247
|
-
end
|
248
|
-
else
|
249
|
-
data = as_json( obj )
|
250
|
-
|
251
|
-
## note: hash or array required!!! for now for json generation
|
252
|
-
# hash => single record
|
253
|
-
# array => multiple records (that is, array of hashes)
|
254
|
-
|
255
|
-
if data.is_a?( Hash ) || data.is_a?( Array )
|
256
|
-
json = JSON.pretty_generate( data ) ## use pretty printer
|
257
|
-
|
258
|
-
callback = params.delete( 'callback' )
|
309
|
+
fallback_route GET, '/favicon.ico' do
|
310
|
+
## use 302 to redirect
|
311
|
+
## note: use strg+F5 to refresh page (clear cache for favicon.ico)
|
312
|
+
redirect_to '/webservice-32x32.png'
|
313
|
+
end
|
259
314
|
|
260
|
-
|
261
|
-
|
262
|
-
response.body = ["#{callback}(#{json})"]
|
263
|
-
else
|
264
|
-
content_type :json
|
265
|
-
response.body = [json]
|
266
|
-
end
|
267
|
-
else
|
268
|
-
## todo/fix/check: change http status to unprocessable entity
|
269
|
-
## or something -- why ??? why not??
|
270
|
-
##
|
271
|
-
## allow "standalone" number, nils, strings - why? why not?
|
272
|
-
## for now valid json must be wrapped in array [] or hash {}
|
273
|
-
content_type :txt
|
274
|
-
## todo/check: use just data.to_s for all - why? why not?
|
275
|
-
## for now return as is (convert to string with to_s or inspect)
|
276
|
-
response.body = [data.is_a?( String ) ? data.to_s : data.inspect]
|
277
|
-
end
|
315
|
+
fallback_route GET, '/webservice-32x32.png' do
|
316
|
+
send_file "#{Webservice.root}/assets/webservice-32x32.png"
|
278
317
|
end
|
279
|
-
end # method handle_response
|
280
318
|
|
319
|
+
fallback_route GET, '/routes' do
|
320
|
+
msg =<<TXT
|
321
|
+
#{dump_routes}
|
281
322
|
|
323
|
+
#{dump_version}
|
324
|
+
TXT
|
325
|
+
end
|
282
326
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
## { key: 'mx', name: 'Mexico', ...},
|
288
|
-
## ...
|
289
|
-
## ]
|
327
|
+
## catch all (404 not found)
|
328
|
+
fallback_route GET, '/*' do
|
329
|
+
pp env
|
330
|
+
pp self.class.routes
|
290
331
|
|
291
|
-
|
292
|
-
|
332
|
+
msg =<<TXT
|
333
|
+
404 Not Found
|
293
334
|
|
294
|
-
|
335
|
+
No route matched >#{request.request_method} #{request.path_info}<:
|
295
336
|
|
296
|
-
|
337
|
+
REQUEST_METHOD: >#{request.request_method}<
|
338
|
+
PATH_INFO: >#{request.path_info}<
|
339
|
+
QUERY_STRING: >#{request.query_string}<
|
297
340
|
|
298
|
-
|
299
|
-
|
300
|
-
csv << rec.values
|
301
|
-
end
|
302
|
-
end
|
303
|
-
end
|
341
|
+
SCRIPT_NAME: >#{request.script_name}<
|
342
|
+
REQUEST_URI: >#{env['REQUEST_URI']}<
|
304
343
|
|
305
344
|
|
306
|
-
|
307
|
-
## note: for now assumes (only works with) array of hash records e.g.:
|
308
|
-
## [
|
309
|
-
## { key: 'at', name: 'Austria', ...},
|
310
|
-
## { key: 'mx', name: 'Mexico', ...},
|
311
|
-
## ...
|
312
|
-
## ]
|
345
|
+
#{dump_routes}
|
313
346
|
|
314
|
-
|
347
|
+
#{dump_version}
|
348
|
+
TXT
|
315
349
|
|
316
|
-
|
317
|
-
buf << "<table>\n"
|
318
|
-
recs.each do |rec|
|
319
|
-
buf << " <tr>"
|
320
|
-
rec.values.each do |value|
|
321
|
-
buf << "<td>#{value}</td>"
|
322
|
-
end
|
323
|
-
buf << "</tr>\n"
|
324
|
-
end
|
325
|
-
buf << "</table>\n"
|
326
|
-
buf
|
350
|
+
halt 404, msg
|
327
351
|
end
|
328
352
|
|
353
|
+
############################
|
354
|
+
## fallback helpers
|
329
355
|
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
def as_tabular( obj, opts={} )
|
334
|
-
## for now allow
|
335
|
-
## as_tab, as_tabular - others too? e.g. as_table why? why not?
|
336
|
-
## like as_json will return a hash or array of hashes NOT a string!!!!
|
356
|
+
def dump_routes ## todo/check - rename to build_routes/show_routes/etc. - why? why not?
|
357
|
+
buf = ""
|
358
|
+
buf <<" Routes >#{self.class.name}<:\n\n"
|
337
359
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
else
|
343
|
-
## note: use as_json will return hash (for record) or array of hashes (for records)
|
344
|
-
if obj.respond_to? :as_json
|
345
|
-
obj.as_json
|
346
|
-
else
|
347
|
-
obj ## just try/use as is (assumes array of hashesd)
|
360
|
+
self.class.routes.each do |method,routes|
|
361
|
+
buf << " #{method}:\n"
|
362
|
+
routes.each do |pattern,block|
|
363
|
+
buf << " #{pattern.to_s}\n"
|
348
364
|
end
|
349
365
|
end
|
366
|
+
buf
|
350
367
|
end
|
351
368
|
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
else
|
361
|
-
obj ## just try/use as is
|
362
|
-
end
|
369
|
+
def dump_version
|
370
|
+
## single line version string
|
371
|
+
buf = " " # note: start with two leading spaces (indent)
|
372
|
+
buf << "webservice/#{VERSION} "
|
373
|
+
buf << "(#{self.class.environment}), "
|
374
|
+
buf << "rack/#{Rack::RELEASE} (#{Rack::VERSION.join('.')}) - "
|
375
|
+
buf << "ruby/#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}/#{RUBY_PLATFORM})"
|
376
|
+
buf
|
363
377
|
end
|
364
378
|
|
365
379
|
end # class Base
|
@@ -0,0 +1,248 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
## default (built-in) response handler
|
4
|
+
|
5
|
+
module Webservice
|
6
|
+
|
7
|
+
|
8
|
+
class ResponseHandler
|
9
|
+
|
10
|
+
|
11
|
+
def initialize( app )
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
|
15
|
+
## delegate request, response, params, env
|
16
|
+
## todo/fix: use def_delegate - why? why not???
|
17
|
+
def request() @app.request; end
|
18
|
+
def response() @app.response; end
|
19
|
+
def params() @app.params; end
|
20
|
+
def env() @app.env; end
|
21
|
+
|
22
|
+
## delegate some helpers too
|
23
|
+
def content_type( type=nil ) @app.content_type( type ); end
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
## todo: add as_json like opts={} why? why not?
|
28
|
+
def handle_response( obj, opts={} )
|
29
|
+
puts "[Webservice::Handler#handle_response (#{request.path_info}) params: #{params.inspect}] - obj : #{obj.class.name}"
|
30
|
+
pp obj
|
31
|
+
|
32
|
+
## "magic" param format; default to json
|
33
|
+
format = params['format'] || 'json'
|
34
|
+
|
35
|
+
## note: response.body must be (expects) an array!!!
|
36
|
+
## thus, [json] etc.
|
37
|
+
|
38
|
+
if format == 'csv' || format == 'txt' ||
|
39
|
+
format == 'html' || format == 'htm'
|
40
|
+
|
41
|
+
data = as_tabular( obj )
|
42
|
+
|
43
|
+
if data.is_a? Tabular
|
44
|
+
if format == 'csv' || format == 'txt'
|
45
|
+
content_type :txt ## use csv content type - why? why not??
|
46
|
+
response.body = [data.to_csv]
|
47
|
+
else
|
48
|
+
## assume html
|
49
|
+
content_type :html
|
50
|
+
response.body = [data.to_html_table]
|
51
|
+
end
|
52
|
+
else
|
53
|
+
## wrong format (expected Tabular from as_tabular - cannot convert)
|
54
|
+
## todo: issue warning/notice about wrong format - how?
|
55
|
+
## use different http status code - why? why not??
|
56
|
+
content_type :txt
|
57
|
+
## todo/check: use just data.to_s for all - why? why not?
|
58
|
+
## for now return as is (convert to string with to_s or inspect)
|
59
|
+
response.body = [data.is_a?( String ) ? data.to_s : data.inspect]
|
60
|
+
end
|
61
|
+
else ## default/assume json
|
62
|
+
data = as_json( obj )
|
63
|
+
|
64
|
+
## note: hash or array required!!! for now for json generation
|
65
|
+
# hash => single record
|
66
|
+
# array => multiple records (that is, array of hashes)
|
67
|
+
|
68
|
+
if data.is_a?( Hash ) || data.is_a?( Array )
|
69
|
+
json = JSON.pretty_generate( data ) ## use pretty printer
|
70
|
+
|
71
|
+
callback = params.delete( 'callback' )
|
72
|
+
|
73
|
+
if callback
|
74
|
+
content_type :js
|
75
|
+
response.body = ["#{callback}(#{json})"]
|
76
|
+
else
|
77
|
+
content_type :json
|
78
|
+
response.body = [json]
|
79
|
+
end
|
80
|
+
else
|
81
|
+
## todo/fix/check: change http status to unprocessable entity
|
82
|
+
## or something -- why ??? why not??
|
83
|
+
##
|
84
|
+
## allow "standalone" number, nils, strings - why? why not?
|
85
|
+
## for now valid json must be wrapped in array [] or hash {}
|
86
|
+
content_type :txt
|
87
|
+
## todo/check: use just data.to_s for all - why? why not?
|
88
|
+
## for now return as is (convert to string with to_s or inspect)
|
89
|
+
response.body = [data.is_a?( String ) ? data.to_s : data.inspect]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end # method handle_response
|
93
|
+
|
94
|
+
|
95
|
+
##########################################
|
96
|
+
## auto-generate/convert "magic"
|
97
|
+
|
98
|
+
def as_tabular( obj, opts={} )
|
99
|
+
headers = []
|
100
|
+
headers_clone = [] ## keep an unmodified (e.g. with symbols not string) headers/keys clone
|
101
|
+
rows = []
|
102
|
+
errors = []
|
103
|
+
|
104
|
+
if obj.respond_to? :to_a ### convert activerecord relation to array (of records)
|
105
|
+
recs = obj.to_a
|
106
|
+
elsif obj.is_a? Array
|
107
|
+
recs = obj
|
108
|
+
else
|
109
|
+
## return as is; cannot convert
|
110
|
+
## todo/fix: handle cannot convert different (e.g. except etc.) - why? why not??
|
111
|
+
puts "!!!! [as_tabular] sorry; can't convert <#{obj.class.name}> - Array or to_a method required"
|
112
|
+
return obj
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
recs.each do |rec|
|
117
|
+
puts "rec #{rec.class.name}"
|
118
|
+
if rec.respond_to? :as_row
|
119
|
+
row = rec.as_row
|
120
|
+
rows << row.values ## add rows as is 1:1
|
121
|
+
elsif rec.respond_to?( :as_json_v3 ) ||
|
122
|
+
rec.respond_to?( :as_json_v2 ) ||
|
123
|
+
rec.respond_to?( :as_json ) ||
|
124
|
+
rec.is_a?( Hash ) ## allow (plain) hash too - give it priority (try first?) - why? why not??
|
125
|
+
|
126
|
+
if rec.respond_to? :as_json_v3
|
127
|
+
row = rec.as_json_v3
|
128
|
+
elsif rec.respond_to? :as_json_v2
|
129
|
+
row = rec.as_json_v2
|
130
|
+
elsif rec.respond_to? :as_json
|
131
|
+
row = rec.as_json
|
132
|
+
else
|
133
|
+
row = rec ## assume it's already a hash (with key/value pairs)
|
134
|
+
end
|
135
|
+
|
136
|
+
## convert nested values e.g. array and hash to strings
|
137
|
+
values = row.values.map do |value|
|
138
|
+
if value.is_a? Hash
|
139
|
+
## todo: use our own "pretty printer" e.g. use unqouted strings - why? why not?
|
140
|
+
value.to_json ## use to_json "key": "value" instead of :key => "value"
|
141
|
+
elsif value.is_a? Array
|
142
|
+
## todo: use our own "pretty printer" e.g. use unqouted strings - why? why not?
|
143
|
+
## value = "[#{value.join('|')}]" ## use | for joins (instead of ,) - why? why not?? keep comma(,) - why? why not??
|
144
|
+
value.to_json
|
145
|
+
else
|
146
|
+
value
|
147
|
+
end
|
148
|
+
end
|
149
|
+
pp values
|
150
|
+
rows << values
|
151
|
+
else
|
152
|
+
## todo: add record index - why? why not?
|
153
|
+
puts "sorry; can't convert - as_row or as_json method or hash required"
|
154
|
+
errors << "sorry; can't convert <#{rec.class.name}> - as_row or as_json method or hash required"
|
155
|
+
next
|
156
|
+
end
|
157
|
+
|
158
|
+
## check headers - always must match!!!!!!!
|
159
|
+
if headers.empty?
|
160
|
+
headers_clone = row.keys
|
161
|
+
pp headers_clone
|
162
|
+
headers = headers_clone.map { |header| header.to_s }
|
163
|
+
pp headers
|
164
|
+
else
|
165
|
+
## todo: check if headers match!!!
|
166
|
+
end
|
167
|
+
end # recs.each
|
168
|
+
|
169
|
+
if errors.empty?
|
170
|
+
Tabular.new( headers, rows )
|
171
|
+
else ## return row of errors
|
172
|
+
## return errors as a (simple) multi-line string - why? why not??
|
173
|
+
errors.join( "\n" )
|
174
|
+
end
|
175
|
+
|
176
|
+
end # method as_tabular
|
177
|
+
|
178
|
+
|
179
|
+
def as_json( obj, opts={} )
|
180
|
+
if obj.respond_to? :as_json_v3 ## try (our own) serializer first
|
181
|
+
obj.as_json_v3
|
182
|
+
elsif obj.respond_to? :as_json_v2 ## try (our own) serializer first
|
183
|
+
obj.as_json_v2
|
184
|
+
elsif obj.respond_to? :as_json ## try (activerecord) serializer
|
185
|
+
obj.as_json
|
186
|
+
else
|
187
|
+
obj ## just try/use as is
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
######
|
193
|
+
# Tabular helper/support class
|
194
|
+
|
195
|
+
class Tabular
|
196
|
+
|
197
|
+
attr_reader :headers
|
198
|
+
attr_reader :rows
|
199
|
+
|
200
|
+
def initialize( headers, rows )
|
201
|
+
@headers = headers
|
202
|
+
@rows = rows
|
203
|
+
end
|
204
|
+
|
205
|
+
def to_csv( opts={} )
|
206
|
+
## allow changing of column/value separator (col_sep) - why? why not?
|
207
|
+
## :col_sep => "\t"
|
208
|
+
## :col_sep => ";"
|
209
|
+
|
210
|
+
pp self
|
211
|
+
|
212
|
+
CSV.generate( headers: true ) do |csv|
|
213
|
+
csv << headers
|
214
|
+
rows.each do |row|
|
215
|
+
csv << row
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end # method to_csv
|
219
|
+
|
220
|
+
|
221
|
+
def to_html_table( opts={} )
|
222
|
+
|
223
|
+
## todo/fix: html escape values - why? why not??
|
224
|
+
|
225
|
+
pp self
|
226
|
+
|
227
|
+
buf = ""
|
228
|
+
buf << "<table>\n"
|
229
|
+
buf << " <tr>"
|
230
|
+
headers.each do |header|
|
231
|
+
buf << "<th>#{header}</th>"
|
232
|
+
end
|
233
|
+
buf << "</tr>\n"
|
234
|
+
|
235
|
+
rows.each do |row|
|
236
|
+
buf << " <tr>"
|
237
|
+
row.each do |value|
|
238
|
+
buf << "<td>#{value}</td>"
|
239
|
+
end
|
240
|
+
buf << "</tr>\n"
|
241
|
+
end
|
242
|
+
buf << "</table>\n"
|
243
|
+
buf
|
244
|
+
end # method to_html_table
|
245
|
+
end # class Tabular
|
246
|
+
|
247
|
+
end # class ResponseHandler
|
248
|
+
end # module Webservice
|
data/lib/webservice/version.rb
CHANGED
data/test/test_app.rb
CHANGED
@@ -72,6 +72,7 @@ class TestApp < MiniTest::Test
|
|
72
72
|
get '/countries.csv'
|
73
73
|
assert last_response.ok?
|
74
74
|
assert_equal <<CSV, last_response.body
|
75
|
+
key,name
|
75
76
|
at,Austria
|
76
77
|
mx,Mexico
|
77
78
|
CSV
|
@@ -80,32 +81,25 @@ CSV
|
|
80
81
|
assert last_response.ok?
|
81
82
|
assert_equal <<HTML, last_response.body
|
82
83
|
<table>
|
84
|
+
<tr><th>key</th><th>name</th></tr>
|
83
85
|
<tr><td>at</td><td>Austria</td></tr>
|
84
86
|
<tr><td>mx</td><td>Mexico</td></tr>
|
85
87
|
</table>
|
86
88
|
HTML
|
87
89
|
|
88
90
|
|
89
|
-
countries_json =
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
"name": "Austria"
|
94
|
-
},
|
95
|
-
{
|
96
|
-
"key": "mx",
|
97
|
-
"name": "Mexico"
|
98
|
-
}
|
99
|
-
]
|
100
|
-
JSON
|
91
|
+
countries_json = [
|
92
|
+
{ 'key' => 'at', 'name' => 'Austria' },
|
93
|
+
{ 'key' => 'mx', 'name' => 'Mexico' },
|
94
|
+
]
|
101
95
|
|
102
96
|
get '/countries.json'
|
103
97
|
assert last_response.ok?
|
104
|
-
assert_equal countries_json, last_response.body
|
98
|
+
assert_equal countries_json, JSON.parse( last_response.body )
|
105
99
|
|
106
100
|
get '/countries'
|
107
101
|
assert last_response.ok?
|
108
|
-
assert_equal countries_json, last_response.body
|
102
|
+
assert_equal countries_json, JSON.parse( last_response.body )
|
109
103
|
end # method test_countries
|
110
104
|
|
111
105
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: webservice
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-08-
|
11
|
+
date: 2017-08-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: logutils
|
@@ -93,8 +93,10 @@ files:
|
|
93
93
|
- Manifest.txt
|
94
94
|
- README.md
|
95
95
|
- Rakefile
|
96
|
+
- assets/webservice-32x32.png
|
96
97
|
- lib/webservice.rb
|
97
98
|
- lib/webservice/base.rb
|
99
|
+
- lib/webservice/response_handler.rb
|
98
100
|
- lib/webservice/version.rb
|
99
101
|
- test/helper.rb
|
100
102
|
- test/service/app.rb
|