wabur 0.6.2 → 0.7.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.
Files changed (41) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +106 -15
  3. data/bin/wabur +6 -0
  4. data/export/assets/js/ui.js +18 -0
  5. data/lib/wab.rb +7 -1
  6. data/lib/wab/client.rb +145 -0
  7. data/lib/wab/controller.rb +1 -1
  8. data/lib/wab/errors.rb +6 -0
  9. data/lib/wab/impl.rb +1 -1
  10. data/lib/wab/impl/agoo.rb +18 -0
  11. data/lib/wab/impl/agoo/export_proxy.rb +55 -0
  12. data/lib/wab/impl/agoo/handler.rb +51 -0
  13. data/lib/wab/impl/agoo/sender.rb +50 -0
  14. data/lib/wab/impl/agoo/server.rb +59 -0
  15. data/lib/wab/impl/agoo/tql_handler.rb +35 -0
  16. data/lib/wab/impl/model.rb +8 -1
  17. data/lib/wab/impl/rack_error.rb +27 -0
  18. data/lib/wab/impl/rack_handler.rb +69 -0
  19. data/lib/wab/impl/shell.rb +56 -51
  20. data/lib/wab/impl/sinatra.rb +18 -0
  21. data/lib/wab/impl/sinatra/export_proxy.rb +57 -0
  22. data/lib/wab/impl/sinatra/handler.rb +50 -0
  23. data/lib/wab/impl/sinatra/sender.rb +53 -0
  24. data/lib/wab/impl/sinatra/server.rb +66 -0
  25. data/lib/wab/impl/sinatra/tql_handler.rb +35 -0
  26. data/lib/wab/impl/templates/wabur.conf.template +1 -1
  27. data/lib/wab/impl/webrick.rb +18 -0
  28. data/lib/wab/impl/webrick/export_proxy.rb +41 -0
  29. data/lib/wab/impl/webrick/handler.rb +116 -0
  30. data/lib/wab/impl/webrick/sender.rb +34 -0
  31. data/lib/wab/impl/webrick/server.rb +39 -0
  32. data/lib/wab/impl/webrick/tql_handler.rb +58 -0
  33. data/lib/wab/racker.rb +25 -0
  34. data/lib/wab/version.rb +1 -1
  35. data/pages/Architecture.md +15 -6
  36. data/test/test_client.rb +282 -0
  37. data/test/test_impl.rb +2 -0
  38. data/test/test_runner.rb +267 -91
  39. metadata +27 -5
  40. data/lib/wab/impl/export_proxy.rb +0 -39
  41. data/lib/wab/impl/handler.rb +0 -98
@@ -0,0 +1,34 @@
1
+
2
+ require 'webrick'
3
+
4
+ module WAB
5
+ module Impl
6
+ module WEBrick
7
+
8
+ # The Sender module adds support for sending results and errors.
9
+ module Sender
10
+
11
+ # Sends the results from a controller request.
12
+ def send_result(result, res, path, query)
13
+ result = @shell.data(result) unless result.is_a?(WAB::Data)
14
+ response_body = result.json(@shell.indent)
15
+ res.status = 200
16
+ res['Content-Type'] = 'application/json'
17
+ @shell.logger.debug("reply to #{path.join('/')}#{query}: #{response_body}") if @shell.logger.debug?
18
+ res.body = response_body
19
+ end
20
+
21
+ # Sends an error from a rescued call.
22
+ def send_error(e, res)
23
+ res.status = 500
24
+ res['Content-Type'] = 'application/json'
25
+ body = { code: -1, error: "#{e.class}: #{e.message}" }
26
+ body[:backtrace] = e.backtrace
27
+ res.body = @shell.data(body).json(@shell.indent)
28
+ @shell.logger.warn(Impl.format_error(e))
29
+ end
30
+
31
+ end # Sender
32
+ end # WEBrick
33
+ end # Impl
34
+ end # WAB
@@ -0,0 +1,39 @@
1
+
2
+ require 'webrick'
3
+
4
+ module WAB
5
+ module Impl
6
+ module WEBrick
7
+
8
+ # The Server module provides a server start method.
9
+ module Server
10
+
11
+ # Start the WEBrick server and set the mount points.
12
+ def self.start(shell)
13
+ mime_types = ::WEBrick::HTTPUtils::DefaultMimeTypes
14
+ mime_types['es6'] = 'application/javascript'
15
+ server = ::WEBrick::HTTPServer.new(Port: shell.http_port,
16
+ DocumentRoot: shell.http_dir,
17
+ MimeTypes: mime_types)
18
+ server.logger.level = 5 - shell.logger.level unless shell.logger.nil?
19
+
20
+ shell.mounts.each { |hh|
21
+ if hh.has_key?(:type)
22
+ server.mount("#{shell.pre_path}/#{hh[:type]}", WAB::Impl::WEBrick::Handler, shell, shell.create_controller(hh[:handler]), false)
23
+ elsif hh.has_key?(:path)
24
+ server.mount(hh[:path], WAB::Impl::WEBrick::Handler, shell, shell.create_controller(hh[:handler]), true)
25
+ else
26
+ raise WAB::Error.new("Invalid handle configuration. Missing path or type.")
27
+ end
28
+ }
29
+ server.mount(shell.tql_path, TqlHandler, shell) unless (shell.tql_path.nil? || shell.tql_path.empty?)
30
+ server.mount('/', ExportProxy, shell.http_dir) if shell.export_proxy
31
+
32
+ trap 'INT' do server.shutdown end
33
+ server.start
34
+ end
35
+
36
+ end # Server
37
+ end # WEBrick
38
+ end # Impl
39
+ end # WAB
@@ -0,0 +1,58 @@
1
+
2
+ require 'webrick'
3
+
4
+ module WAB
5
+ module Impl
6
+ module WEBrick
7
+
8
+ # Handler for requests that fall under the path assigned to the
9
+ # Controller. This is used only with the WAB::Impl::Shell.
10
+ class TqlHandler < ::WEBrick::HTTPServlet::AbstractServlet
11
+
12
+ def initialize(server, shell)
13
+ super(server)
14
+ @shell = shell
15
+ end
16
+
17
+ def do_POST(req, res)
18
+ path = req.path.split('/')[1..-1]
19
+ query = {}
20
+ req.query.each { |k,v| query[k.to_sym] = v }
21
+ tql = Oj.load(req.body, mode: :wab)
22
+ log_request_with_body('TQL', path, query, tql) if @shell.logger.info?
23
+ send_result(@shell.query(tql), res, path, query)
24
+ rescue StandardError => e
25
+ send_error(e, res)
26
+ end
27
+
28
+ private
29
+
30
+ def log_request_with_body(caller, path, query, body)
31
+ body = Data.new(body) unless body.is_a?(WAB::Data)
32
+ @shell.logger.info("#{caller} #{path.join('/')}#{query}\n#{body.json(@shell.indent)}")
33
+ end
34
+
35
+ # Sends the results from a controller request.
36
+ def send_result(result, res, path, query)
37
+ result = @shell.data(result) unless result.is_a?(WAB::Data)
38
+ response_body = result.json(@shell.indent)
39
+ res.status = 200
40
+ res['Content-Type'] = 'application/json'
41
+ @shell.logger.debug("reply to #{path.join('/')}#{query}: #{response_body}") if @shell.logger.debug?
42
+ res.body = response_body
43
+ end
44
+
45
+ # Sends an error from a rescued call.
46
+ def send_error(e, res)
47
+ res.status = 500
48
+ res['Content-Type'] = 'application/json'
49
+ body = { code: -1, error: "#{e.class}: #{e.message}" }
50
+ body[:backtrace] = e.backtrace
51
+ res.body = @shell.data(body).json(@shell.indent)
52
+ @shell.logger.warn(Impl.format_error(e))
53
+ end
54
+
55
+ end # TqlHandler
56
+ end # WEBrick
57
+ end # Impl
58
+ end # WAB
@@ -0,0 +1,25 @@
1
+ module WAB
2
+
3
+ # A Racker or a duck-typed alternative should be created and registered with
4
+ # a Shell for paths that expected to follow the Ruby rack API. The shell is
5
+ # included so that queries can be made when responding to requests.
6
+ class Racker # :doc: all
7
+ attr_accessor :shell
8
+
9
+ # Create a instance.
10
+ def initialize(shell)
11
+ @shell = shell
12
+ end
13
+
14
+ # Rack handler for processing rack requests. Implemenation should follow
15
+ # the rack API described at https://rack.github.io.
16
+ #
17
+ # env:: data to be processed
18
+ #
19
+ # return:: a rack compliant response.
20
+ def call(env)
21
+ [200, {}, ['A WABuR Rack Application']]
22
+ end
23
+
24
+ end
25
+ end
@@ -1,5 +1,5 @@
1
1
 
2
2
  module WAB
3
3
  # Current version of the module.
4
- VERSION = '0.6.2'
4
+ VERSION = '0.7.0'
5
5
  end
@@ -158,12 +158,21 @@ API. From the view perspective a REST over HTTP is used.
158
158
 
159
159
  #### Just Data
160
160
 
161
- - just data and data helpers (get, set, inspect, to_s)
162
- - nothing like to_json
163
- - reasoning it that different uses require different behavior
164
- - different stores, view, processing, etc
165
- - use of delgate is encouraged
166
-
161
+ WABuR keeps data and behavior separate. The data portion of a system is
162
+ represented by `WAB::Data` class. Behavior is implemented by support
163
+ classes. This alows extensions to be added without conflict as the `Data`
164
+ class is not modified by adding new features. This avoids the conflicts
165
+ encountered with Rails when it monkey patches core classes to add `to_json`
166
+ which conflicts with the JSON gem and locks out other better performing
167
+ extensions.
168
+
169
+ The reasoning behind the separation of data and behavior to improve
170
+ extendability is that different features often need similar but different
171
+ features. As an example data might need to be stored in multiple data stores
172
+ or be encoded for a view, external processing, or writing to a file. If the
173
+ behavior is encoded in the data object then care must be taken to avoid using
174
+ the same method names which is difficult if separate authors are contributing
175
+ or open source gems are used.
167
176
 
168
177
  ### View/Controller
169
178
 
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << __dir__
4
+ $LOAD_PATH << File.expand_path('../lib', __dir__)
5
+
6
+ require 'minitest'
7
+ require 'minitest/autorun'
8
+ require 'fileutils'
9
+ require 'open3'
10
+ require 'uri'
11
+
12
+ require 'wab'
13
+
14
+ class TestClient < Minitest::Test
15
+
16
+ # All tests must leave the DB state of the server unchanged.
17
+
18
+ def run_wabur(port)
19
+ store_dir = "#{__dir__}/client_test/data#{port}"
20
+ FileUtils.remove_dir(store_dir, true)
21
+ _, @out, wt = Open3.popen2e("ruby", "#{__dir__}/../bin/wabur", '-I', "#{__dir__}/../lib", '-c', "#{__dir__}/client_test/wabur.conf", '--http.port', port.to_s, '--store.dir', store_dir)
22
+ 20.times {
23
+ begin
24
+ Net::HTTP.get_response(URI("http://localhost:#{port}"))
25
+ break
26
+ rescue Exception => e
27
+ sleep(0.1)
28
+ end
29
+ }
30
+ wt.pid
31
+ end
32
+
33
+ def shutdown_wabur(pid)
34
+ Process.kill('HUP', pid)
35
+ # Uncomment to get verbose output from the server.
36
+ #puts @out.read
37
+ Process.wait
38
+ rescue
39
+ # ignore
40
+ end
41
+
42
+ def test_simple
43
+ port = 6371
44
+ pid = run_wabur(port)
45
+ client = WAB::Client.new('localhost', port)
46
+ result = client.create({kind: 'Test', value: 123})
47
+ assert_equal(0, result[:code], 'create result code mismatch')
48
+ ref = result[:ref]
49
+
50
+ result = client.read('Test', ref)
51
+ assert_equal(0, result[:code], 'read result code mismatch')
52
+ assert_equal(1, result[:results].length, 'read result wrong length')
53
+ rec = result[:results][0]
54
+ assert_equal('Test', rec[:data][:kind], 'read kind mismatch')
55
+ assert_equal(123, rec[:data][:value], 'read value mismatch')
56
+
57
+ result = client.delete('Test', ref)
58
+ assert_equal(0, result[:code], 'delete result code mismatch')
59
+ assert_equal([ref], result[:deleted], 'delete list mismatch')
60
+
61
+ result = client.read('Test', ref)
62
+ assert_equal(0, result[:code], 'read after delete result code mismatch')
63
+ assert_equal(0, result[:results].length, 'read after delete result wrong length')
64
+
65
+ ensure
66
+ shutdown_wabur(pid)
67
+ end
68
+
69
+ def test_types
70
+ port = 6372
71
+ pid = run_wabur(port)
72
+ client = WAB::Client.new('localhost', port)
73
+ t = Time.now
74
+ uuid = WAB::UUID.new('b0ca922d-372e-41f4-8fea-47d880188ba3')
75
+ uri = URI("http://wab.systems/sample")
76
+ obj = {
77
+ kind: 'Test',
78
+ num: 123,
79
+ bool: true,
80
+ null: nil,
81
+ str: "string",
82
+ dub: 1.23,
83
+ time: t,
84
+ uuid: uuid,
85
+ uri: uri
86
+ }
87
+ result = client.create(obj)
88
+ assert_equal(0, result[:code], 'create result code mismatch')
89
+ ref = result[:ref]
90
+
91
+ result = client.read('Test', ref)
92
+ assert_equal(0, result[:code], 'read result code mismatch')
93
+ assert_equal(1, result[:results].length, 'read result wrong length')
94
+ rec = result[:results][0]
95
+ assert_equal('Test', rec[:data][:kind], 'read kind mismatch')
96
+ assert_equal(obj, rec[:data], 'read data mismatch')
97
+
98
+ result = client.delete('Test', ref)
99
+ assert_equal(0, result[:code], 'delete result code mismatch')
100
+ assert_equal([ref], result[:deleted], 'delete list mismatch')
101
+
102
+ ensure
103
+ shutdown_wabur(pid)
104
+ end
105
+
106
+ def test_create_query
107
+ port = 6373
108
+ pid = run_wabur(port)
109
+ client = WAB::Client.new('localhost', port)
110
+
111
+ result = client.create({kind: 'Test', value: 123}, 'Test', {value: 123})
112
+ assert_equal(0, result[:code], 'create result code mismatch')
113
+ ref = result[:ref]
114
+
115
+ result = client.read('Test', ref)
116
+ assert_equal(0, result[:code], 'read result code mismatch')
117
+ assert_equal(1, result[:results].length, 'read result wrong length')
118
+
119
+ # Same again should fail.
120
+ assert_raises('should fail to create a duplicate') {
121
+ result = client.create({kind: 'Test', value: 123}, 'Test', {value: 123})
122
+ }
123
+
124
+ result = client.delete('Test', ref)
125
+ assert_equal(0, result[:code], 'delete result code mismatch')
126
+ assert_equal([ref], result[:deleted], 'delete list mismatch')
127
+
128
+ ensure
129
+ shutdown_wabur(pid)
130
+ end
131
+
132
+ def test_delete_all
133
+ port = 6374
134
+ pid = run_wabur(port)
135
+ client = WAB::Client.new('localhost', port)
136
+ refs = []
137
+ 3.times { |i|
138
+ result = client.create({kind: 'Test', value: i})
139
+ assert_equal(0, result[:code], "create #{i} result code mismatch")
140
+ refs << result[:ref]
141
+ }
142
+
143
+ result = client.read('Test')
144
+ assert_equal(0, result[:code], 'read result code mismatch')
145
+ assert_equal(3, result[:results].length, 'read result wrong length')
146
+
147
+ result = client.delete('Test')
148
+ assert_equal(0, result[:code], 'delete result code mismatch')
149
+ assert_equal(3, result[:deleted].length, 'delete list length mismatch')
150
+
151
+ ensure
152
+ shutdown_wabur(pid)
153
+ end
154
+
155
+ def test_update_ref
156
+ port = 6375
157
+ pid = run_wabur(port)
158
+ client = WAB::Client.new('localhost', port)
159
+
160
+ result = client.create({kind: 'Test', value: 123})
161
+ assert_equal(0, result[:code], 'create result code mismatch')
162
+ ref = result[:ref]
163
+
164
+ result = client.update('Test', {kind: 'Test', value: 321}, ref)
165
+ assert_equal(0, result[:code], 'create result code mismatch')
166
+
167
+ result = client.read('Test', ref)
168
+ assert_equal(0, result[:code], 'read result code mismatch')
169
+ assert_equal(1, result[:results].length, 'read result wrong length')
170
+ rec = result[:results][0]
171
+ assert_equal('Test', rec[:data][:kind], 'read kind mismatch')
172
+ assert_equal(321, rec[:data][:value], 'read value mismatch')
173
+
174
+ result = client.delete('Test', ref)
175
+ assert_equal(0, result[:code], 'delete result code mismatch')
176
+ assert_equal([ref], result[:deleted], 'delete list mismatch')
177
+
178
+ ensure
179
+ shutdown_wabur(pid)
180
+ end
181
+
182
+ def test_update_query
183
+ port = 6376
184
+ pid = run_wabur(port)
185
+ client = WAB::Client.new('localhost', port)
186
+ refs = []
187
+ 3.times { |i|
188
+ result = client.create({kind: 'Test', value: i})
189
+ assert_equal(0, result[:code], "create #{i} result code mismatch")
190
+ refs << result[:ref]
191
+ }
192
+
193
+ result = client.update('Test', {kind: 'Test', value: 111}, {value: 1})
194
+ assert_equal(0, result[:code], 'create result code mismatch')
195
+ assert_equal(1, result[:updated].length, 'updated wrong length')
196
+
197
+ result = client.read('Test', refs[1])
198
+ assert_equal(0, result[:code], 'read result code mismatch')
199
+ assert_equal(1, result[:results].length, 'read result wrong length')
200
+ rec = result[:results][0]
201
+ assert_equal('Test', rec[:data][:kind], 'read kind mismatch')
202
+ assert_equal(111, rec[:data][:value], 'read value mismatch')
203
+
204
+ result = client.delete('Test')
205
+ assert_equal(0, result[:code], 'delete result code mismatch')
206
+
207
+ ensure
208
+ shutdown_wabur(pid)
209
+ end
210
+
211
+ def test_delete_query
212
+ port = 6377
213
+ pid = run_wabur(port)
214
+ client = WAB::Client.new('localhost', port)
215
+ refs = []
216
+ 3.times { |i|
217
+ result = client.create({kind: 'Test', value: i})
218
+ assert_equal(0, result[:code], "create #{i} result code mismatch")
219
+ refs << result[:ref]
220
+ }
221
+
222
+ result = client.delete('Test', {value: 1})
223
+ assert_equal(0, result[:code], 'create result code mismatch')
224
+ assert_equal(1, result[:deleted].length, 'updated wrong length')
225
+
226
+ result = client.read('Test')
227
+ assert_equal(0, result[:code], 'read result code mismatch')
228
+ assert_equal(2, result[:results].length, 'read result wrong length')
229
+
230
+ result = client.delete('Test')
231
+ assert_equal(0, result[:code], 'delete result code mismatch')
232
+
233
+ ensure
234
+ shutdown_wabur(pid)
235
+ end
236
+
237
+ def test_read_query
238
+ port = 6378
239
+ pid = run_wabur(port)
240
+ client = WAB::Client.new('localhost', port)
241
+ 3.times { |i|
242
+ result = client.create({kind: 'Test', value: i})
243
+ assert_equal(0, result[:code], "create #{i} result code mismatch")
244
+ }
245
+
246
+ result = client.read('Test', {value: 1})
247
+ assert_equal(0, result[:code], 'read result code mismatch')
248
+ assert_equal(1, result[:results].length, 'read wrong length')
249
+
250
+ result = client.delete('Test')
251
+ assert_equal(0, result[:code], 'delete result code mismatch')
252
+
253
+ ensure
254
+ shutdown_wabur(pid)
255
+ end
256
+
257
+ def test_tql
258
+ port = 6379
259
+ pid = run_wabur(port)
260
+ client = WAB::Client.new('localhost', port)
261
+ 3.times { |i|
262
+ result = client.create({kind: 'Test', value: i})
263
+ assert_equal(0, result[:code], "create #{i} result code mismatch")
264
+ }
265
+
266
+ result = client.find({
267
+ where: ['LT', 'value', 2],
268
+ select: '$ref'
269
+ })
270
+
271
+ assert_equal(0, result[:code], 'read result code mismatch')
272
+ assert_equal(2, result[:results].length, 'results wrong length')
273
+
274
+ result = client.delete('Test')
275
+ assert_equal(0, result[:code], 'delete result code mismatch')
276
+
277
+ ensure
278
+ shutdown_wabur(pid)
279
+ end
280
+
281
+ end
282
+