startback-websocket 0.14.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 +7 -0
- data/Gemfile +3 -0
- data/README.md +13 -0
- data/Rakefile +18 -0
- data/lib/startback/audit/prometheus.rb +87 -0
- data/lib/startback/audit/shared.rb +17 -0
- data/lib/startback/audit/trailer.rb +129 -0
- data/lib/startback/audit.rb +3 -0
- data/lib/startback/caching/entity_cache.rb +157 -0
- data/lib/startback/caching/no_store.rb +28 -0
- data/lib/startback/caching/store.rb +34 -0
- data/lib/startback/context/h_factory.rb +43 -0
- data/lib/startback/context/middleware.rb +53 -0
- data/lib/startback/context.rb +122 -0
- data/lib/startback/errors.rb +197 -0
- data/lib/startback/event/agent.rb +84 -0
- data/lib/startback/event/bus/bunny/async.rb +162 -0
- data/lib/startback/event/bus/bunny.rb +1 -0
- data/lib/startback/event/bus/memory/async.rb +45 -0
- data/lib/startback/event/bus/memory/sync.rb +35 -0
- data/lib/startback/event/bus/memory.rb +2 -0
- data/lib/startback/event/bus.rb +100 -0
- data/lib/startback/event/engine.rb +94 -0
- data/lib/startback/event/ext/context.rb +5 -0
- data/lib/startback/event/ext/operation.rb +13 -0
- data/lib/startback/event.rb +47 -0
- data/lib/startback/ext/date_time.rb +9 -0
- data/lib/startback/ext/time.rb +9 -0
- data/lib/startback/ext.rb +2 -0
- data/lib/startback/model.rb +6 -0
- data/lib/startback/operation/error_operation.rb +19 -0
- data/lib/startback/operation/multi_operation.rb +28 -0
- data/lib/startback/operation.rb +78 -0
- data/lib/startback/services.rb +11 -0
- data/lib/startback/support/data_object.rb +71 -0
- data/lib/startback/support/env.rb +41 -0
- data/lib/startback/support/fake_logger.rb +18 -0
- data/lib/startback/support/hooks.rb +48 -0
- data/lib/startback/support/log_formatter.rb +34 -0
- data/lib/startback/support/logger.rb +34 -0
- data/lib/startback/support/operation_runner.rb +150 -0
- data/lib/startback/support/robustness.rb +157 -0
- data/lib/startback/support/transaction_manager.rb +25 -0
- data/lib/startback/support/transaction_policy.rb +33 -0
- data/lib/startback/support/world.rb +54 -0
- data/lib/startback/support.rb +26 -0
- data/lib/startback/version.rb +8 -0
- data/lib/startback/web/api.rb +99 -0
- data/lib/startback/web/auto_caching.rb +85 -0
- data/lib/startback/web/catch_all.rb +52 -0
- data/lib/startback/web/cors_headers.rb +80 -0
- data/lib/startback/web/health_check.rb +49 -0
- data/lib/startback/web/magic_assets/ng_html_transformer.rb +80 -0
- data/lib/startback/web/magic_assets/rake_tasks.rb +64 -0
- data/lib/startback/web/magic_assets.rb +98 -0
- data/lib/startback/web/middleware.rb +13 -0
- data/lib/startback/web/prometheus.rb +16 -0
- data/lib/startback/web/shield.rb +58 -0
- data/lib/startback.rb +43 -0
- data/spec/spec_helper.rb +49 -0
- data/spec/unit/audit/test_prometheus.rb +72 -0
- data/spec/unit/audit/test_trailer.rb +105 -0
- data/spec/unit/caching/test_entity_cache.rb +136 -0
- data/spec/unit/context/test_abstraction_factory.rb +64 -0
- data/spec/unit/context/test_dup.rb +42 -0
- data/spec/unit/context/test_fork.rb +37 -0
- data/spec/unit/context/test_h_factory.rb +31 -0
- data/spec/unit/context/test_middleware.rb +45 -0
- data/spec/unit/context/test_with_world.rb +20 -0
- data/spec/unit/context/test_world.rb +17 -0
- data/spec/unit/event/bus/memory/test_async.rb +43 -0
- data/spec/unit/event/bus/memory/test_sync.rb +43 -0
- data/spec/unit/support/hooks/test_after_hook.rb +54 -0
- data/spec/unit/support/hooks/test_before_hook.rb +54 -0
- data/spec/unit/support/operation_runner/test_around_run.rb +156 -0
- data/spec/unit/support/operation_runner/test_before_after_call.rb +48 -0
- data/spec/unit/support/test_data_object.rb +156 -0
- data/spec/unit/support/test_env.rb +75 -0
- data/spec/unit/support/test_robusteness.rb +229 -0
- data/spec/unit/support/test_transaction_manager.rb +64 -0
- data/spec/unit/support/test_world.rb +72 -0
- data/spec/unit/test_event.rb +62 -0
- data/spec/unit/test_operation.rb +55 -0
- data/spec/unit/test_support.rb +40 -0
- data/spec/unit/web/fixtures/assets/app/hello.es6 +4 -0
- data/spec/unit/web/fixtures/assets/app/hello.html +1 -0
- data/spec/unit/web/fixtures/assets/index.es6 +1 -0
- data/spec/unit/web/test_api.rb +82 -0
- data/spec/unit/web/test_auto_caching.rb +81 -0
- data/spec/unit/web/test_catch_all.rb +77 -0
- data/spec/unit/web/test_cors_headers.rb +88 -0
- data/spec/unit/web/test_healthcheck.rb +59 -0
- data/spec/unit/web/test_magic_assets.rb +82 -0
- data/tasks/test.rake +14 -0
- metadata +237 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
module Startback
|
2
|
+
module Web
|
3
|
+
#
|
4
|
+
# Sets Cross-Origin Response Headers on requests specifying an Origin
|
5
|
+
# HTTP header, according configuration passed at construction and/or
|
6
|
+
# environment variables.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# # Default configuration, using environment variables when set
|
11
|
+
# use CorsHeaders
|
12
|
+
#
|
13
|
+
# # Force a bouncing of the origin, using the Origin request header
|
14
|
+
# # as Access-Control-Allow-Origin response header
|
15
|
+
# use CorsHeaders, bounce: true
|
16
|
+
#
|
17
|
+
# # Overrides a specific header
|
18
|
+
# use CorsHeaders, headers: { 'Access-Control-Allow-Methods' => 'POST' }
|
19
|
+
#
|
20
|
+
class CorsHeaders
|
21
|
+
|
22
|
+
ALLOW_ORIGIN = ENV['STARTBACK_CORS_ALLOW_ORIGIN'] || '*'
|
23
|
+
|
24
|
+
ALLOW_METHODS = ENV['STARTBACK_CORS_ALLOW_METHODS'] || 'OPTIONS, HEAD, GET, POST, PUT, PATCH, DELETE'
|
25
|
+
|
26
|
+
ALLOW_CREDENTIALS = ENV['STARTBACK_CORS_ALLOW_CREDENTIALS'] || 'true'
|
27
|
+
|
28
|
+
MAX_AGE = ENV['STARTBACK_CORS_MAX_AGE'] || '1728000'
|
29
|
+
|
30
|
+
ALLOW_HEADERS = ENV['STARTBACK_CORS_ALLOW_HEADERS'] || 'Authorization, Content-Type, Origin, Accept, If-Modified-Since, If-Match, If-None-Match'
|
31
|
+
|
32
|
+
EXPOSE_HEADERS = ENV['STARTBACK_CORS_EXPOSE_HEADERS'] || 'Location, ETag, Last-Modified, Content-Type'
|
33
|
+
|
34
|
+
DEFAULT_CORS_HEADERS = {
|
35
|
+
'Access-Control-Allow-Origin' => ALLOW_ORIGIN,
|
36
|
+
'Access-Control-Allow-Methods' => ALLOW_METHODS,
|
37
|
+
'Access-Control-Allow-Credentials' => ALLOW_CREDENTIALS,
|
38
|
+
'Access-Control-Max-Age' => MAX_AGE,
|
39
|
+
'Access-Control-Allow-Headers' => ALLOW_HEADERS,
|
40
|
+
'Access-Control-Expose-Headers' => EXPOSE_HEADERS
|
41
|
+
}
|
42
|
+
|
43
|
+
DEFAULT_OPTIONS = {
|
44
|
+
:headers => DEFAULT_CORS_HEADERS
|
45
|
+
}
|
46
|
+
|
47
|
+
def initialize(app, options = {})
|
48
|
+
@app = app
|
49
|
+
@options = Startback::Support.deep_merge(DEFAULT_OPTIONS, options)
|
50
|
+
end
|
51
|
+
|
52
|
+
def call(env)
|
53
|
+
status, headers, body = @app.call(env)
|
54
|
+
if origin = env['HTTP_ORIGIN']
|
55
|
+
headers = cors_headers(origin).merge(headers)
|
56
|
+
end
|
57
|
+
if env['REQUEST_METHOD'] == 'OPTIONS'
|
58
|
+
headers['Content-Length'] = '0'
|
59
|
+
status, headers, body = [204, headers, []]
|
60
|
+
end
|
61
|
+
[status, headers, body]
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def cors_headers(origin)
|
67
|
+
headers = @options[:headers].dup
|
68
|
+
if bounce?
|
69
|
+
headers['Access-Control-Allow-Origin'] = origin
|
70
|
+
end
|
71
|
+
headers
|
72
|
+
end
|
73
|
+
|
74
|
+
def bounce?
|
75
|
+
@options[:bounce]
|
76
|
+
end
|
77
|
+
|
78
|
+
end # class AllowCors
|
79
|
+
end # class CorsHeaders
|
80
|
+
end # module Samback
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Startback
|
2
|
+
module Web
|
3
|
+
#
|
4
|
+
# Can be used to easily implement a HealthCheck web service inside a Startback
|
5
|
+
# application.
|
6
|
+
#
|
7
|
+
# Examples:
|
8
|
+
#
|
9
|
+
# # Returns a 204 with no body
|
10
|
+
# run Startback::Web::HealthCheck.new
|
11
|
+
#
|
12
|
+
# # Returns a 204 with no body
|
13
|
+
# run Startback::Web::HealthCheck.new { nil }
|
14
|
+
#
|
15
|
+
# # Returns a 200 with Ok in plain text
|
16
|
+
# run Startback::Web::HealthCheck.new { "Ok" }
|
17
|
+
#
|
18
|
+
# # Re-raises the exception
|
19
|
+
# run Startback::Web::HealthCheck.new { raise "Something bad" }
|
20
|
+
#
|
21
|
+
# Please note that this rack app is not 100% Rack compliant, since it raises
|
22
|
+
# any error that the block itself raises. This class aims at being backed up
|
23
|
+
# by a Shield and/or CatchAll middleware.
|
24
|
+
#
|
25
|
+
# This class is not aimed at being subclassed.
|
26
|
+
#
|
27
|
+
class HealthCheck
|
28
|
+
|
29
|
+
def initialize(&bl)
|
30
|
+
@checker = bl
|
31
|
+
end
|
32
|
+
|
33
|
+
def call(env)
|
34
|
+
if debug_msg = check!(env)
|
35
|
+
[ 200, { "Content-Type" => "text/plain" }, Array(debug_msg) ]
|
36
|
+
else
|
37
|
+
[ 204, {}, [] ]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def check!(env)
|
44
|
+
@checker.call if @checker
|
45
|
+
end
|
46
|
+
|
47
|
+
end # class HealthCheck
|
48
|
+
end # module Web
|
49
|
+
end # module Startback
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Startback
|
2
|
+
module Web
|
3
|
+
class MagicAssets
|
4
|
+
#
|
5
|
+
# Plugin for MagicAssets that compiles .html angular templates in the
|
6
|
+
# assets structure to javascript files filling angular's template cache.
|
7
|
+
#
|
8
|
+
# Heavily inspired, yet over-simplified version, of angular-rails-templates
|
9
|
+
# See https://github.com/pitr/angular-rails-templates, licensed under MIT
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# use Startback::Web::MagicAssets, {
|
14
|
+
# plugins: [Startback::Web::MagicAssets::NgHtmlTransfomer.new]
|
15
|
+
# }
|
16
|
+
#
|
17
|
+
class NgHtmlTransformer
|
18
|
+
|
19
|
+
DEFAULT_OPTIONS = {
|
20
|
+
:path => '/assets',
|
21
|
+
:ng_module => 'templates',
|
22
|
+
:mime_type => 'text/ng-html',
|
23
|
+
:extensions => [".html"]
|
24
|
+
}
|
25
|
+
|
26
|
+
def initialize(options = {})
|
27
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
28
|
+
end
|
29
|
+
attr_reader :options
|
30
|
+
|
31
|
+
def install(sprockets)
|
32
|
+
sprockets.register_mime_type options[:mime_type], extensions: options[:extensions]
|
33
|
+
sprockets.register_transformer options[:mime_type], 'application/javascript', self
|
34
|
+
end
|
35
|
+
|
36
|
+
TPL = <<-EOF
|
37
|
+
angular.module("<%= ng_module %>").run(["$templateCache", function($templateCache) {
|
38
|
+
$templateCache.put("<%= angular_template_name %>", <%= html %>)
|
39
|
+
}]);
|
40
|
+
EOF
|
41
|
+
|
42
|
+
# inspired by Rails' action_view/helpers/javascript_helper.rb
|
43
|
+
JS_ESCAPE_MAP = {
|
44
|
+
'\\' => '\\\\',
|
45
|
+
"\r\n" => '\n',
|
46
|
+
"\n" => '\n',
|
47
|
+
"\r" => '\n',
|
48
|
+
'"' => '\\"',
|
49
|
+
"'" => "\\'"
|
50
|
+
}
|
51
|
+
|
52
|
+
# We want to deliver the shortist valid javascript escaped string
|
53
|
+
# Count the number of " vs '
|
54
|
+
# If more ', escape "
|
55
|
+
# If more ", escape '
|
56
|
+
# If equal, prefer to escape "
|
57
|
+
|
58
|
+
def escape_javascript(raw)
|
59
|
+
if raw
|
60
|
+
quote = raw.count(%{'}) >= raw.count(%{"}) ? %{"} : %{'}
|
61
|
+
escaped = raw.gsub(/(\\|\r\n|[\n\r#{quote}])/u) {|match| JS_ESCAPE_MAP[match] }
|
62
|
+
"#{quote}#{escaped}#{quote}"
|
63
|
+
else
|
64
|
+
'""'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def call(input)
|
69
|
+
file_path = input[:filename]
|
70
|
+
angular_template_name = "#{options[:path]}/#{input[:name]}.html"
|
71
|
+
source_file = file_path
|
72
|
+
ng_module = options[:ng_module]
|
73
|
+
html = escape_javascript(input[:data].chomp)
|
74
|
+
ERB.new(TPL).result(binding)
|
75
|
+
end
|
76
|
+
|
77
|
+
end # class NgHtmlTransformer
|
78
|
+
end # class MagicAssets
|
79
|
+
end # module Web
|
80
|
+
end # module Startback
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Startback
|
2
|
+
module Web
|
3
|
+
class MagicAssets
|
4
|
+
class RakeTasks
|
5
|
+
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
:namespace => :assets
|
8
|
+
}
|
9
|
+
|
10
|
+
def initialize(rake, options)
|
11
|
+
@rake = rake
|
12
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
13
|
+
install
|
14
|
+
end
|
15
|
+
attr_reader :rake, :options
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def install
|
20
|
+
require 'securerandom'
|
21
|
+
|
22
|
+
ns = options[:namespace]
|
23
|
+
target_folder = options[:target]
|
24
|
+
assets = options[:assets]
|
25
|
+
assets = MagicAssets.new(assets) if assets.is_a?(Hash)
|
26
|
+
version = SecureRandom.urlsafe_base64
|
27
|
+
|
28
|
+
rake.instance_exec do
|
29
|
+
namespace(ns) do
|
30
|
+
|
31
|
+
desc 'Cleans generated assets'
|
32
|
+
task :clean do
|
33
|
+
FileUtils.rm_rf target_folder
|
34
|
+
end
|
35
|
+
|
36
|
+
task :prepare do
|
37
|
+
FileUtils.mkdir_p target_folder
|
38
|
+
(target_folder/"VERSION").write(version)
|
39
|
+
end
|
40
|
+
|
41
|
+
desc 'compile javascript assets'
|
42
|
+
task :compile_js do
|
43
|
+
assets['vendor.js'].write_to(target_folder/"vendor-#{version}.min.js")
|
44
|
+
assets['app.js'].write_to(target_folder/"app-#{version}.min.js")
|
45
|
+
puts "successfully compiled js assets"
|
46
|
+
end
|
47
|
+
|
48
|
+
desc 'compile css assets'
|
49
|
+
task :compile_css do
|
50
|
+
assets['vendor.css'].write_to(target_folder/"vendor-#{version}.min.css")
|
51
|
+
assets['app.css'].write_to(target_folder/"app-#{version}.min.css")
|
52
|
+
puts "successfully compiled css assets"
|
53
|
+
end
|
54
|
+
|
55
|
+
desc 'compile assets'
|
56
|
+
task :compile => [:clean, :prepare, :compile_js, :compile_css]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end # class MagicAssets
|
63
|
+
end # module Web
|
64
|
+
end # module Startback
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'sprockets'
|
2
|
+
module Startback
|
3
|
+
module Web
|
4
|
+
#
|
5
|
+
# Rack application & middleware that can be used to simplify javascript
|
6
|
+
# and css assets management, using Sprockets.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# # Used as rack app, typically under a path
|
11
|
+
# Rack::Builder.new do
|
12
|
+
# map '/assets' do
|
13
|
+
# run Startback::Web::MagicAssets.new({
|
14
|
+
# folder: "/path/to/assets/src"
|
15
|
+
# })
|
16
|
+
# end
|
17
|
+
# run MyApp
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # Used as a rack middleware, e.g. in a Sinatra application
|
21
|
+
# use Startback::Web::MagicAssets, {
|
22
|
+
# folder: "/path/to/assets/src",
|
23
|
+
# path: "/assets"
|
24
|
+
# }
|
25
|
+
#
|
26
|
+
# Sprocket configuration can be done through the `:sprocket` option:
|
27
|
+
#
|
28
|
+
# use Startback::Web::MagicAssets, {
|
29
|
+
# sprockets: {
|
30
|
+
# :css_compressor => :scss
|
31
|
+
# }
|
32
|
+
# }
|
33
|
+
#
|
34
|
+
class MagicAssets
|
35
|
+
|
36
|
+
DEFAULT_OPTIONS = {
|
37
|
+
sprockets: {},
|
38
|
+
plugins: {}
|
39
|
+
}
|
40
|
+
|
41
|
+
def initialize(app, options = {})
|
42
|
+
app, options = nil, app if app.is_a?(Hash)
|
43
|
+
@app = app
|
44
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
45
|
+
@sprockets = build_sprockets
|
46
|
+
end
|
47
|
+
attr_reader :sprockets
|
48
|
+
|
49
|
+
def call(env)
|
50
|
+
if new_env = is_match?(env)
|
51
|
+
@sprockets.call(new_env)
|
52
|
+
else
|
53
|
+
@app.call(env)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def [](*args, &bl)
|
58
|
+
@sprockets.[](*args, &bl)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def path
|
64
|
+
@options[:path]
|
65
|
+
end
|
66
|
+
|
67
|
+
def is_match?(env)
|
68
|
+
if @app.nil?
|
69
|
+
# Not used as a middleware, use this env and match
|
70
|
+
env
|
71
|
+
elsif env['PATH_INFO'].start_with?(path)
|
72
|
+
# Used as a middleware, and PATH_INFO starts with the
|
73
|
+
# assets path => strip it for sprockets
|
74
|
+
env.merge("PATH_INFO" => env["PATH_INFO"].sub(path, ""))
|
75
|
+
else
|
76
|
+
# No match, let @app execute with the untouched environment
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_sprockets
|
82
|
+
Sprockets::Environment.new.tap{|s|
|
83
|
+
Array(@options[:folder]).each do |folder|
|
84
|
+
s.append_path(folder)
|
85
|
+
end
|
86
|
+
@options[:sprockets].each_pair do |k,v|
|
87
|
+
s.public_send(:"#{k}=", v)
|
88
|
+
end
|
89
|
+
@options[:plugins].each do |p|
|
90
|
+
p.install(s)
|
91
|
+
end
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
end # class MagicAssets
|
96
|
+
end # module Web
|
97
|
+
end # module Startback
|
98
|
+
require_relative 'magic_assets/rake_tasks'
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Startback
|
2
|
+
module Web
|
3
|
+
module Middleware
|
4
|
+
|
5
|
+
protected
|
6
|
+
|
7
|
+
def context(env = @env)
|
8
|
+
::Startback::Context::Middleware.context(env) || Errors.server_error!("Unable to find context!!")
|
9
|
+
end
|
10
|
+
|
11
|
+
end # module Middleware
|
12
|
+
end # module Web
|
13
|
+
end # module Startback
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'prometheus/middleware/exporter'
|
2
|
+
module Startback
|
3
|
+
module Web
|
4
|
+
#
|
5
|
+
# Can be used to expose the prometheus metrics inside a Startback
|
6
|
+
# application.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# use Startback::Web::Prometheus
|
11
|
+
#
|
12
|
+
class Prometheus < Prometheus::Middleware::Exporter
|
13
|
+
|
14
|
+
end # class Prometheus
|
15
|
+
end # module Web
|
16
|
+
end # module Startback
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Startback
|
2
|
+
module Web
|
3
|
+
#
|
4
|
+
# This Rack middleware catches all known exceptions raised by sublayers
|
5
|
+
# in the Rack chain. Those exceptions are converted to proper HTTP error
|
6
|
+
# codes and friendly error messages encoded in json.
|
7
|
+
#
|
8
|
+
# Please check the Errors module about status codes used for each Startback
|
9
|
+
# error.
|
10
|
+
#
|
11
|
+
# This class aims at being used as top level of a Rack chain.
|
12
|
+
#
|
13
|
+
# Examples:
|
14
|
+
#
|
15
|
+
# Rack::Builder.new do
|
16
|
+
# use Startback::Web::Shield
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
class Shield < Rack::Robustness
|
20
|
+
include Errors
|
21
|
+
|
22
|
+
self.no_catch_all
|
23
|
+
self.content_type 'application/json'
|
24
|
+
|
25
|
+
# Decoding errors from json and csv are considered user's fault
|
26
|
+
self.on(Finitio::TypeError){ 400 }
|
27
|
+
self.on(::NotImplementedError){ 501 }
|
28
|
+
|
29
|
+
# Various other codes for the framework specific error classes
|
30
|
+
self.on(Startback::Errors::Error) {|ex|
|
31
|
+
ex.class.status
|
32
|
+
}
|
33
|
+
|
34
|
+
# A bit of logic to choose the best error message for the user
|
35
|
+
# according to the error class
|
36
|
+
self.body{|ex|
|
37
|
+
body_for(ex).to_json
|
38
|
+
}
|
39
|
+
|
40
|
+
def body_for(ex)
|
41
|
+
ex = ex.root_cause if ex.is_a?(Finitio::TypeError)
|
42
|
+
body = { code: ex.class.name, description: ex.message }
|
43
|
+
return body unless ex.is_a?(Startback::Errors::Error)
|
44
|
+
return body unless ex.has_causes?
|
45
|
+
|
46
|
+
body[:causes] = ex.causes
|
47
|
+
.filter{|cause|
|
48
|
+
cause.is_a?(Startback::Errors::Error)
|
49
|
+
}
|
50
|
+
.map{|cause|
|
51
|
+
body_for(cause)
|
52
|
+
}
|
53
|
+
body
|
54
|
+
end
|
55
|
+
|
56
|
+
end # class Shield
|
57
|
+
end # module Web
|
58
|
+
end # module Startback
|
data/lib/startback.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'rack/robustness'
|
3
|
+
require 'finitio'
|
4
|
+
require 'logger'
|
5
|
+
require 'path'
|
6
|
+
require 'ostruct'
|
7
|
+
require 'benchmark'
|
8
|
+
|
9
|
+
# Provides a reusable backend framework for backend components written
|
10
|
+
# in ruby.
|
11
|
+
#
|
12
|
+
# The framework installs conventions regarding:
|
13
|
+
#
|
14
|
+
# - The exposition of web service APIs (Framework::Api, on top of Sinatra)
|
15
|
+
# - Operations (Framework::Operation)
|
16
|
+
# - Error handling (Framework::Errors) and their handling in web APIs
|
17
|
+
# (based on Rack::Robustness)
|
18
|
+
# - General code support (Framework::Support modules & classes).
|
19
|
+
#
|
20
|
+
# Please refer to the documentation of those main abstractions for details.
|
21
|
+
#
|
22
|
+
module Startback
|
23
|
+
|
24
|
+
# Simply checks that a path exists of raise an error
|
25
|
+
def self._!(path)
|
26
|
+
Path(path).tap do |p|
|
27
|
+
raise "Missing #{p.basename}." unless p.exists?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
require_relative 'startback/version'
|
32
|
+
require_relative 'startback/ext'
|
33
|
+
require_relative 'startback/errors'
|
34
|
+
require_relative 'startback/support'
|
35
|
+
require_relative 'startback/model'
|
36
|
+
require_relative 'startback/context'
|
37
|
+
require_relative 'startback/operation'
|
38
|
+
require_relative 'startback/services'
|
39
|
+
|
40
|
+
# Logger instance to use for the application
|
41
|
+
LOGGER = ::Startback::Support::Logger.new
|
42
|
+
|
43
|
+
end # module Startback
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'startback'
|
2
|
+
require 'startback/event'
|
3
|
+
require 'startback/support/fake_logger'
|
4
|
+
require 'rack/test'
|
5
|
+
require 'ostruct'
|
6
|
+
|
7
|
+
module SpecHelpers
|
8
|
+
end
|
9
|
+
|
10
|
+
RSpec.configure do |c|
|
11
|
+
c.include SpecHelpers
|
12
|
+
end
|
13
|
+
|
14
|
+
class SubContext < Startback::Context
|
15
|
+
|
16
|
+
attr_accessor :foo
|
17
|
+
|
18
|
+
h_factory do |c,h|
|
19
|
+
c.foo = h["foo"]
|
20
|
+
end
|
21
|
+
|
22
|
+
h_dump do |h|
|
23
|
+
h.merge!("foo" => foo)
|
24
|
+
end
|
25
|
+
|
26
|
+
world(:partner) do
|
27
|
+
Object.new
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
class SubContext
|
33
|
+
|
34
|
+
attr_accessor :bar
|
35
|
+
|
36
|
+
h_factory do |c,h|
|
37
|
+
c.bar = h["bar"]
|
38
|
+
end
|
39
|
+
|
40
|
+
h_dump do |h|
|
41
|
+
h.merge!("bar" => bar)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
class User
|
47
|
+
class Changed < Startback::Event
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'startback/audit'
|
3
|
+
module Startback
|
4
|
+
module Audit
|
5
|
+
describe Prometheus do
|
6
|
+
|
7
|
+
EXPORTER = Prometheus.new({
|
8
|
+
prefix: "hello",
|
9
|
+
labels: {
|
10
|
+
app_version: "1.0"
|
11
|
+
}
|
12
|
+
})
|
13
|
+
|
14
|
+
class Runner
|
15
|
+
include Startback::Support::OperationRunner
|
16
|
+
|
17
|
+
class IdealOp < Startback::Operation
|
18
|
+
def call
|
19
|
+
42
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class ExceptionalOp < Startback::Operation
|
24
|
+
def call
|
25
|
+
raise "Oops"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
around_run(EXPORTER)
|
30
|
+
def test
|
31
|
+
run IdealOp.new
|
32
|
+
end
|
33
|
+
def test_exp
|
34
|
+
run ExceptionalOp.new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'The ideal case' do
|
39
|
+
before do
|
40
|
+
expect(EXPORTER.calls).to receive(:observe).with(
|
41
|
+
kind_of(Numeric),
|
42
|
+
hash_including(labels: {
|
43
|
+
operation: "Startback::Audit::Runner::IdealOp",
|
44
|
+
startback_version: Startback::VERSION,
|
45
|
+
app_version: "1.0"
|
46
|
+
}))
|
47
|
+
expect(EXPORTER.errors).not_to receive(:increment)
|
48
|
+
end
|
49
|
+
it 'runs the operation' do
|
50
|
+
expect(Runner.new.test).to eql(42)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe 'The exceptional case' do
|
55
|
+
before do
|
56
|
+
expect(EXPORTER.errors).to receive(:increment).with(
|
57
|
+
hash_including(labels: {
|
58
|
+
operation: "Startback::Audit::Runner::ExceptionalOp",
|
59
|
+
startback_version: Startback::VERSION,
|
60
|
+
app_version: "1.0"
|
61
|
+
})
|
62
|
+
)
|
63
|
+
expect(EXPORTER.calls).not_to receive(:observe)
|
64
|
+
end
|
65
|
+
it 'let errors bubble up' do
|
66
|
+
expect { Runner.new.test_exp }.to raise_error(/Oops/)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|