culpa 1.2.5 → 1.3
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 +8 -8
- data/bin/culpa +22 -23
- data/lib/culpa.rb +54 -41
- data/lib/culpa/action.rb +4 -6
- data/lib/culpa/brickchain_helpers.rb +13 -16
- data/lib/culpa/coliseum_dsl.rb +23 -31
- data/lib/culpa/envelope.rb +9 -15
- data/lib/culpa/path_parser.rb +18 -22
- data/lib/culpa/renderer_describer.rb +55 -57
- data/lib/culpa/renderers/coliseum.rb +4 -6
- data/lib/culpa/renderers/file.rb +10 -11
- data/lib/culpa/renderers/json.rb +8 -10
- data/lib/culpa/renderers/status.rb +5 -5
- data/lib/culpa/routes_builder.rb +5 -8
- data/lib/culpa/test_helpers.rb +6 -10
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ZWZkYmJjYzczZWEyZDQyNDM2OTEyYTQ1NjljNjZhZmRjZThiZjRjMQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
M2I4Njg2NmUwZjk1MGExMTNmMjIxYmY2NmIzMTgwMTZkM2UwZTgyYQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZjUxMjFiZTdkZTg0ZjQ2YjEyYjAyNTZiMzNiNTM1NmFlMDI0YjEyMGQ4NjBl
|
10
|
+
YTcxYThkYTEyYWM2N2YyNTRhZjA5Nzg3OTBiMjFiZTViNWQ2YTA2MjJkMzEz
|
11
|
+
NDM5NzdkYTNiMjc2NDMyY2NmMTZmOWUyZDMzNTdkNzIwYmUzNDE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NGFiN2Y0ODc5ZmU4OTcwM2MwY2Y3NDM1MTY0NGMxYzZjZWU2MGQzYmEyYmQ5
|
14
|
+
OWMxOGI2MmU1OGI0OTZiNTA0OTY5OTNiMGMzMmEwMDE5MTliYzI0NTRmNjNj
|
15
|
+
YTlhOGZlYmNjZGNkZjk5OGZlMzkzYjNiOGJkZTVkMWJkNmVhM2M=
|
data/bin/culpa
CHANGED
@@ -27,27 +27,27 @@ def create_project(project_path)
|
|
27
27
|
FileUtils.touch "#{project_path}/config/initializers/.keep"
|
28
28
|
|
29
29
|
puts '==> Copying standard gemfile'
|
30
|
-
gemfile_path = File.join(
|
30
|
+
gemfile_path = File.join(File.dirname(__FILE__), '../templates/culpa/Gemfile')
|
31
31
|
FileUtils.cp gemfile_path, "#{project_path}/Gemfile"
|
32
32
|
|
33
33
|
puts '==> Copying standard config.ru'
|
34
|
-
config_rackup_path = File.join(
|
34
|
+
config_rackup_path = File.join(File.dirname(__FILE__), '../templates/culpa/config.ru')
|
35
35
|
FileUtils.cp config_rackup_path, "#{project_path}/config.ru"
|
36
36
|
|
37
37
|
puts '==> Copying Dockerfile'
|
38
|
-
dockerfile_rackup_path = File.join(
|
38
|
+
dockerfile_rackup_path = File.join(File.dirname(__FILE__), '../templates/culpa/Dockerfile')
|
39
39
|
FileUtils.cp dockerfile_rackup_path, "#{project_path}/Dockerfile"
|
40
40
|
|
41
41
|
puts '==> Copying Docker dependencies'
|
42
|
-
nginx_conf_path = File.join(
|
43
|
-
supervisord_path = File.join(
|
42
|
+
nginx_conf_path = File.join(File.dirname(__FILE__), '../templates/culpa/nginx.conf')
|
43
|
+
supervisord_path = File.join(File.dirname(__FILE__), '../templates/culpa/supervisord.conf')
|
44
44
|
FileUtils.cp nginx_conf_path, "#{project_path}/config/dockerized/nginx.conf"
|
45
45
|
FileUtils.cp supervisord_path, "#{project_path}/config/dockerized/supervisord.conf"
|
46
46
|
|
47
47
|
puts '==> Copying default public folder content'
|
48
|
-
index_html_rackup_path = File.join(
|
48
|
+
index_html_rackup_path = File.join(File.dirname(__FILE__), '../templates/culpa/index.html')
|
49
49
|
FileUtils.cp index_html_rackup_path, "#{project_path}/public/index.html"
|
50
|
-
logo_png_rackup_path = File.join(
|
50
|
+
logo_png_rackup_path = File.join(File.dirname(__FILE__), '../templates/culpa/logo.png')
|
51
51
|
FileUtils.cp logo_png_rackup_path, "#{project_path}/public/logo.png"
|
52
52
|
|
53
53
|
puts '==> Switching to project directory'
|
@@ -67,7 +67,7 @@ def create_project(project_path)
|
|
67
67
|
fo.puts "require 'culpa/test_helpers'"
|
68
68
|
File.foreach(spec_helper_path) do |li|
|
69
69
|
fo.puts li
|
70
|
-
fo.puts
|
70
|
+
fo.puts ' config.include CulpaHelpers' if li == "=end\n"
|
71
71
|
end
|
72
72
|
end
|
73
73
|
FileUtils.rm(spec_helper_path)
|
@@ -97,7 +97,7 @@ def generate_action
|
|
97
97
|
)
|
98
98
|
)
|
99
99
|
@snake_name = ARGV[2]
|
100
|
-
@name = ARGV[2].split('_').map
|
100
|
+
@name = ARGV[2].split('_').map(&:capitalize).join
|
101
101
|
@bricks = ARGV.drop(3)
|
102
102
|
rendered_action = ERB.new(erb_template).result
|
103
103
|
action_file = "./actions/#{@snake_name}.rb"
|
@@ -116,19 +116,18 @@ def start_server
|
|
116
116
|
end
|
117
117
|
|
118
118
|
case ARGV[0]
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
generate_action
|
129
|
-
else
|
130
|
-
puts 'Unknown generator'
|
131
|
-
end
|
119
|
+
when 'new', 'n'
|
120
|
+
create_project ARGV[1]
|
121
|
+
when 'console', 'c'
|
122
|
+
when 'server', 's'
|
123
|
+
start_server
|
124
|
+
when 'generate', 'g'
|
125
|
+
case ARGV[1]
|
126
|
+
when 'action'
|
127
|
+
generate_action
|
132
128
|
else
|
133
|
-
|
129
|
+
puts 'Unknown generator'
|
130
|
+
end
|
131
|
+
else
|
132
|
+
display_help
|
134
133
|
end
|
data/lib/culpa.rb
CHANGED
@@ -8,11 +8,11 @@ require_relative 'culpa/path_parser'
|
|
8
8
|
require_relative 'culpa/routes_builder'
|
9
9
|
require_relative 'culpa/brickchain_helpers'
|
10
10
|
|
11
|
-
CULPA_VERSION='1.2.
|
11
|
+
CULPA_VERSION = '1.2.3'.freeze
|
12
12
|
|
13
|
-
ACTIONS_PATH ||= './actions/*.rb'
|
14
|
-
MODELS_PATH ||= './models/*.rb'
|
15
|
-
INITIALIZERS_PATH ||= './config/initializers/*.rb'
|
13
|
+
ACTIONS_PATH ||= './actions/*.rb'.freeze
|
14
|
+
MODELS_PATH ||= './models/*.rb'.freeze
|
15
|
+
INITIALIZERS_PATH ||= './config/initializers/*.rb'.freeze
|
16
16
|
|
17
17
|
# Require the initializers
|
18
18
|
Dir[INITIALIZERS_PATH].each do |file|
|
@@ -30,7 +30,7 @@ end
|
|
30
30
|
|
31
31
|
# The main module of the application
|
32
32
|
module Culpa
|
33
|
-
|
33
|
+
|
34
34
|
# Error called when a sub_call is not defined and it can't be inferred
|
35
35
|
class UnpredictableSubCallError < StandardError; end
|
36
36
|
# Error raised when a brickchain didn't called a render at all
|
@@ -58,17 +58,8 @@ module Culpa
|
|
58
58
|
@public_folder = options[:public] || route_builder.public_folder || './public'
|
59
59
|
# Loading renderers
|
60
60
|
Action.load_renderers
|
61
|
-
#
|
62
|
-
|
63
|
-
@logger.level = case ENV['RACK_ENV']
|
64
|
-
when 'development', 'test'
|
65
|
-
Logger::DEBUG
|
66
|
-
else
|
67
|
-
# :nocov:
|
68
|
-
Logger::WARN
|
69
|
-
# :nocov:
|
70
|
-
end
|
71
|
-
@logger.info 'Culpa fully initialized'
|
61
|
+
# Loading logger
|
62
|
+
logger_initialize(options[:log_output] || STDOUT)
|
72
63
|
# If in dev or test env, serve the static assets
|
73
64
|
if %w(development test).include? ENV['RACK_ENV']
|
74
65
|
@rack_file = Rack::File.new(@public_folder)
|
@@ -79,32 +70,29 @@ module Culpa
|
|
79
70
|
# Rack entrypoint
|
80
71
|
def call(env)
|
81
72
|
# Checking if it is a static file before calling the router
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
return @rack_file.call(env)
|
87
|
-
end
|
88
|
-
@logger.info "Received request : #{path}"
|
73
|
+
static_asset = try_static_asset(env)
|
74
|
+
return static_asset if static_asset
|
75
|
+
# Calling the router
|
76
|
+
@logger.info "Received request : #{env['PATH_INFO']}"
|
89
77
|
# Request preparation
|
90
78
|
request = {
|
91
|
-
|
92
|
-
|
79
|
+
verb: env['REQUEST_METHOD'].downcase.to_sym,
|
80
|
+
params: Rack::Utils.parse_nested_query(env['QUERY_STRING'])
|
93
81
|
}
|
94
82
|
# Parse body if in JSON, otherwise pass the rack.input directly
|
95
83
|
if env['CONTENT_TYPE'] == 'application/json'
|
96
84
|
body = MultiJson.load(env['rack.input'].read)
|
97
|
-
request[:input] = if body.
|
85
|
+
request[:input] = if body.key?('data')
|
98
86
|
body['data']
|
99
87
|
else
|
100
88
|
body
|
101
89
|
end
|
102
|
-
request[:params]['sub_call'] = body['sub_call'] if body.
|
90
|
+
request[:params]['sub_call'] = body['sub_call'] if body.key? 'sub_call'
|
103
91
|
else
|
104
92
|
request[:input] = env['rack.input']
|
105
93
|
end
|
106
94
|
# Extract vars from path, take route decision and go !
|
107
|
-
method_name, f_request = PathParser.extract_vars(
|
95
|
+
method_name, f_request = PathParser.extract_vars(env['PATH_INFO'], request)
|
108
96
|
call_brickchain method_name, f_request
|
109
97
|
rescue UnpredictableSubCallError, MultiJson::ParseError
|
110
98
|
# The sub_call wasn't predictacle, or the body wans't correct JSON.
|
@@ -118,17 +106,30 @@ module Culpa
|
|
118
106
|
# Something went wrong executing the chain !
|
119
107
|
if ENV['RACK_ENV'] != 'test'
|
120
108
|
# :nocov:
|
121
|
-
@logger.error "#{err
|
109
|
+
@logger.error "#{err}\n#{err.backtrace.join("\n")}"
|
122
110
|
# :nocov:
|
123
111
|
end
|
124
112
|
rack_error 500
|
125
113
|
end
|
126
114
|
|
115
|
+
private
|
116
|
+
|
117
|
+
##
|
118
|
+
# Serving static assets
|
119
|
+
def try_static_asset(env)
|
120
|
+
path = env['PATH_INFO']
|
121
|
+
if @rack_file && (File.exist?("#{@public_folder}#{path}") || path == '/')
|
122
|
+
path = '/index.html' if path == '/'
|
123
|
+
@logger.info "Serving static file : #{path}"
|
124
|
+
return @rack_file.call(env)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
127
128
|
##
|
128
129
|
# Call a brickchain using the method_name associated to it
|
129
130
|
def call_brickchain(router_method_name, options)
|
130
131
|
# Loading the router method
|
131
|
-
raise RouteNotFoundError
|
132
|
+
raise RouteNotFoundError unless @router.key? router_method_name
|
132
133
|
route = @router[router_method_name]
|
133
134
|
@logger.info "Executing brickchain : #{router_method_name}"
|
134
135
|
# Preparing brickchain scopped variables
|
@@ -137,9 +138,9 @@ module Culpa
|
|
137
138
|
# Execute before blocks in the brickchain
|
138
139
|
route[:before].each do |before_block|
|
139
140
|
BrickchainExecutor.new(envelope, request).instance_eval(&before_block)
|
140
|
-
end if route.
|
141
|
+
end if route.key? :before
|
141
142
|
# Execute the brickchain itself
|
142
|
-
if route.
|
143
|
+
if route.key? :block
|
143
144
|
BrickchainExecutor.new(envelope, request).instance_eval(&route[:block])
|
144
145
|
else
|
145
146
|
ch = CallHolder.new(route[:brick_name], envelope, request)
|
@@ -148,7 +149,7 @@ module Culpa
|
|
148
149
|
# If we come to this point, the brickchain haven't raised the RenderNow.
|
149
150
|
# It means there is a problem with the brickchain since it has a path
|
150
151
|
# that didn't render.
|
151
|
-
raise NoRenderCalled
|
152
|
+
raise NoRenderCalled
|
152
153
|
rescue Action::RenderNow => renderer
|
153
154
|
return do_render(renderer.to_render)
|
154
155
|
end
|
@@ -157,14 +158,14 @@ module Culpa
|
|
157
158
|
# Method called after a correct Action::RenderNow have been raised
|
158
159
|
def do_render(to_render)
|
159
160
|
body = case to_render[:format]
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
161
|
+
when :json
|
162
|
+
[::MultiJson.dump(to_render[:object], pretty: ENV['RACK_ENV'] == 'development')]
|
163
|
+
when :file_from_path, :file_from_io
|
164
|
+
to_render[:object]
|
165
|
+
when :status
|
166
|
+
[]
|
166
167
|
end
|
167
|
-
[
|
168
|
+
[to_render[:status], to_render[:headers], body]
|
168
169
|
end
|
169
170
|
|
170
171
|
##
|
@@ -173,6 +174,18 @@ module Culpa
|
|
173
174
|
[code.to_s, {}, []]
|
174
175
|
end
|
175
176
|
|
177
|
+
def logger_initialize(log_output)
|
178
|
+
# Logging helper
|
179
|
+
@logger = Logger.new(log_output)
|
180
|
+
@logger.level = case ENV['RACK_ENV']
|
181
|
+
when 'development', 'test'
|
182
|
+
Logger::DEBUG
|
183
|
+
else
|
184
|
+
# :nocov:
|
185
|
+
Logger::WARN
|
186
|
+
# :nocov:
|
187
|
+
end
|
188
|
+
@logger.info 'Culpa fully initialized'
|
189
|
+
end
|
176
190
|
end
|
177
|
-
|
178
191
|
end
|
data/lib/culpa/action.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
class Action
|
2
|
-
|
3
2
|
require_relative 'renderer_describer'
|
4
3
|
|
5
4
|
@@renderers = {}
|
6
5
|
|
7
6
|
def self.load_renderers
|
8
|
-
Dir[File.join(
|
7
|
+
Dir[File.join(File.dirname(__FILE__), 'renderers/*.rb')].each do |renderer|
|
9
8
|
@@renderers.merge!(RendererDescriber.new(renderer).result)
|
10
9
|
end
|
11
10
|
end
|
@@ -28,12 +27,11 @@ class Action
|
|
28
27
|
@r.send(sym)
|
29
28
|
end
|
30
29
|
|
31
|
-
def render(options={})
|
30
|
+
def render(options = {})
|
32
31
|
keyword = options.keys.first
|
33
32
|
options[:headers] ||= {}
|
34
|
-
raise Culpa::UnknownRenderCall
|
33
|
+
raise Culpa::UnknownRenderCall unless @@renderers.include?(keyword)
|
35
34
|
to_render = @@renderers[keyword].call(options, @e)
|
36
|
-
raise RenderNow
|
35
|
+
raise RenderNow, to_render
|
37
36
|
end
|
38
|
-
|
39
37
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
module Culpa
|
2
2
|
|
3
3
|
class BrickchainExecutor
|
4
|
-
|
5
4
|
attr_reader :envelope
|
6
5
|
|
7
6
|
def initialize(envelope, request)
|
@@ -15,24 +14,24 @@ module Culpa
|
|
15
14
|
asb.chs.each { |call_holder| call_holder.thread.join }
|
16
15
|
end
|
17
16
|
|
17
|
+
def e
|
18
|
+
@envelope
|
19
|
+
end
|
20
|
+
|
18
21
|
def render_coliseum(file)
|
19
22
|
coliseum = ColiseumDSL.new("./coliseums/#{file}.rb", envelope)
|
20
|
-
raise Action::RenderNow
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
object: coliseum.dsl_attributes
|
25
|
-
})
|
23
|
+
raise Action::RenderNow, format: :json,
|
24
|
+
status: RendererDescriber::RETURN_CODES[coliseum.culpa_status] || 200,
|
25
|
+
headers: { 'Content-Type' => 'application/json' }.merge(coliseum.culpa_headers),
|
26
|
+
object: coliseum.dsl_attributes
|
26
27
|
end
|
27
28
|
|
28
29
|
def method_missing(sym)
|
29
|
-
|
30
|
+
CallHolder.new(sym.to_s, @envelope, @request)
|
30
31
|
end
|
31
|
-
|
32
32
|
end
|
33
33
|
|
34
34
|
class AsyncBrickchain
|
35
|
-
|
36
35
|
attr_reader :envelope
|
37
36
|
attr_reader :chs
|
38
37
|
|
@@ -46,14 +45,12 @@ module Culpa
|
|
46
45
|
@chs << CallHolder.new(sym.to_s, @envelope, @request, true)
|
47
46
|
@chs.last
|
48
47
|
end
|
49
|
-
|
50
48
|
end
|
51
49
|
|
52
50
|
class CallHolder
|
53
|
-
|
54
51
|
attr_reader :thread
|
55
52
|
|
56
|
-
def initialize(brickname, envelope, request, async=false)
|
53
|
+
def initialize(brickname, envelope, request, async = false)
|
57
54
|
@brickname = brickname
|
58
55
|
@envelope = envelope
|
59
56
|
@request = request
|
@@ -71,12 +68,12 @@ module Culpa
|
|
71
68
|
def sub_from(action_name)
|
72
69
|
action = get_action(action_name.to_s)
|
73
70
|
action.send @brickname
|
74
|
-
|
71
|
+
false
|
75
72
|
end
|
76
73
|
|
77
74
|
def get_action(name)
|
78
|
-
action_name = name.split('_').map
|
79
|
-
|
75
|
+
action_name = name.split('_').map(&:capitalize).join
|
76
|
+
Actions.const_get(action_name).new(@envelope, @request)
|
80
77
|
end
|
81
78
|
|
82
79
|
end
|
data/lib/culpa/coliseum_dsl.rb
CHANGED
@@ -1,17 +1,14 @@
|
|
1
|
-
require 'multi_json'
|
2
|
-
|
3
1
|
class ColiseumDSL
|
4
|
-
|
5
2
|
attr_reader :dsl_attributes, :culpa_status, :culpa_headers
|
6
3
|
|
7
|
-
def initialize(to_eval, envelope, eval_args=nil)
|
4
|
+
def initialize(to_eval, envelope, eval_args = nil)
|
8
5
|
@dsl_attributes = {}
|
9
6
|
@culpa_headers = {}
|
10
7
|
@culpa_envelope = envelope
|
11
|
-
if to_eval.is_a?
|
12
|
-
|
8
|
+
if to_eval.is_a? String
|
9
|
+
instance_eval File.read(to_eval), to_eval
|
13
10
|
else
|
14
|
-
|
11
|
+
instance_exec eval_args, &to_eval
|
15
12
|
end
|
16
13
|
end
|
17
14
|
|
@@ -23,7 +20,7 @@ class ColiseumDSL
|
|
23
20
|
@culpa_status = code
|
24
21
|
end
|
25
22
|
|
26
|
-
def add_http_headers!(hdrs={})
|
23
|
+
def add_http_headers!(hdrs = {})
|
27
24
|
@culpa_headers.merge!(hdrs)
|
28
25
|
end
|
29
26
|
|
@@ -45,33 +42,28 @@ class ColiseumDSL
|
|
45
42
|
def extract!(target, *attrs)
|
46
43
|
attrs.each do |att|
|
47
44
|
@dsl_attributes[att] = if target.is_a? Hash
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
rescue
|
57
|
-
nil
|
58
|
-
end
|
59
|
-
end
|
45
|
+
target[att] if target.key? att
|
46
|
+
else
|
47
|
+
begin
|
48
|
+
target.send(att)
|
49
|
+
rescue
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
60
53
|
end
|
61
54
|
end
|
62
55
|
|
63
56
|
def method_missing(sym, *args, &blk)
|
64
57
|
@dsl_attributes[sym] = if ::Kernel.block_given?
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
58
|
+
ColiseumDSL.new(blk, @culpa_envelope).dsl_attributes
|
59
|
+
else
|
60
|
+
if args.count.zero?
|
61
|
+
nil
|
62
|
+
elsif args.count == 1
|
63
|
+
args[0]
|
64
|
+
else
|
65
|
+
args
|
66
|
+
end
|
67
|
+
end
|
75
68
|
end
|
76
|
-
|
77
69
|
end
|
data/lib/culpa/envelope.rb
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
module Culpa
|
2
|
-
|
3
2
|
class MustHaveFailed < StandardError; end
|
4
3
|
|
5
4
|
class Envelope
|
6
|
-
|
7
5
|
def must_have!(*args)
|
8
6
|
args.each do |arg|
|
9
|
-
raise MustHaveFailed
|
7
|
+
raise MustHaveFailed, "Envelope must_have! failed on #{arg}" unless instance_variable_defined? "@#{arg}"
|
10
8
|
end
|
11
9
|
end
|
12
10
|
|
@@ -19,30 +17,28 @@ module Culpa
|
|
19
17
|
|
20
18
|
def method_missing(sym, *args)
|
21
19
|
sym = sym.to_s
|
22
|
-
@semaphore.synchronize
|
20
|
+
@semaphore.synchronize do
|
23
21
|
if sym.end_with? '='
|
24
|
-
instance_variable_set "@#{sym.
|
22
|
+
instance_variable_set "@#{sym.delete('=')}", args[0]
|
25
23
|
else
|
26
|
-
return instance_variable_get "@#{sym.
|
24
|
+
return instance_variable_get "@#{sym.delete('=')}"
|
27
25
|
end
|
28
|
-
|
26
|
+
end
|
29
27
|
end
|
30
28
|
|
31
29
|
def ==(other_envelope)
|
32
30
|
return false unless other_envelope.is_a? Envelope
|
33
31
|
return false unless other_envelope.instance_variables.count == instance_variables.count
|
34
|
-
instance_variables.select
|
32
|
+
instance_variables.select do |iv|
|
35
33
|
iv != :@semaphore
|
36
|
-
|
34
|
+
end.each do |iv|
|
37
35
|
return false unless instance_variable_get(iv) == other_envelope.instance_variable_get(iv)
|
38
|
-
|
39
|
-
|
36
|
+
end
|
37
|
+
true
|
40
38
|
end
|
41
|
-
|
42
39
|
end
|
43
40
|
|
44
41
|
class EnvelopeRequest
|
45
|
-
|
46
42
|
attr_accessor :verb
|
47
43
|
attr_accessor :path
|
48
44
|
attr_accessor :input
|
@@ -53,7 +49,5 @@ module Culpa
|
|
53
49
|
instance_variable_set "@#{name}", value
|
54
50
|
end
|
55
51
|
end
|
56
|
-
|
57
52
|
end
|
58
|
-
|
59
53
|
end
|
data/lib/culpa/path_parser.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
module Culpa
|
2
|
-
|
3
2
|
class PathParser
|
4
|
-
|
5
3
|
def self.route_patterns
|
6
4
|
@@route_patterns
|
7
5
|
end
|
@@ -11,20 +9,20 @@ module Culpa
|
|
11
9
|
def self.parse(pattern, path)
|
12
10
|
result = path.match(pattern[:regex])
|
13
11
|
return unless result
|
14
|
-
|
12
|
+
Hash[result.names.map { |name| name }.zip(result.captures)]
|
15
13
|
end
|
16
14
|
|
17
15
|
##
|
18
16
|
# Use the brickchains to build the cache of route
|
19
17
|
def self.create_route_cache(brickchains, global_prefix)
|
20
18
|
custom_routes = []
|
21
|
-
brickchains.each do |name,data|
|
22
|
-
next unless data.
|
19
|
+
brickchains.each do |name, data|
|
20
|
+
next unless data.key?(:options) && data[:options] && data[:options].key?(:url)
|
23
21
|
item = {
|
24
|
-
regex:
|
22
|
+
regex: compose_regex(global_prefix + data[:options][:url]),
|
25
23
|
router_method_name: name
|
26
24
|
}
|
27
|
-
item[:verb] = data[:options][:verb] if data[:options].
|
25
|
+
item[:verb] = data[:options][:verb] if data[:options].key? :verb
|
28
26
|
custom_routes << item
|
29
27
|
end
|
30
28
|
fixed_routes = [
|
@@ -33,7 +31,7 @@ module Culpa
|
|
33
31
|
get: 'list',
|
34
32
|
post: 'create'
|
35
33
|
},
|
36
|
-
regex:
|
34
|
+
regex: compose_regex(global_prefix + '/:res_name')
|
37
35
|
}, {
|
38
36
|
infer: {
|
39
37
|
get: 'get',
|
@@ -41,9 +39,9 @@ module Culpa
|
|
41
39
|
patch: 'update',
|
42
40
|
delete: 'delete'
|
43
41
|
},
|
44
|
-
regex:
|
42
|
+
regex: compose_regex(global_prefix + '/:res_name/:id')
|
45
43
|
}, {
|
46
|
-
regex:
|
44
|
+
regex: compose_regex(global_prefix + '/:res_name/:id/:sub_call')
|
47
45
|
}
|
48
46
|
]
|
49
47
|
@@route_patterns = custom_routes + fixed_routes
|
@@ -53,13 +51,13 @@ module Culpa
|
|
53
51
|
# Transform a string to its equivalent for the compose_regex
|
54
52
|
def self.dir_to_regex(dir)
|
55
53
|
return dir unless dir.start_with? ':'
|
56
|
-
"(?<#{dir[1..dir.length-1]}>\\w+)"
|
54
|
+
"(?<#{dir[1..dir.length - 1]}>\\w+)"
|
57
55
|
end
|
58
56
|
|
59
57
|
##
|
60
58
|
# Compose a regex from a human-readable http path pattern
|
61
59
|
def self.compose_regex(url)
|
62
|
-
regex = url.split('/').map{ |dir|
|
60
|
+
regex = url.split('/').map { |dir| dir_to_regex(dir) }.join('/')
|
63
61
|
Regexp.new("^#{regex}$")
|
64
62
|
end
|
65
63
|
|
@@ -68,13 +66,13 @@ module Culpa
|
|
68
66
|
# the router method name and the request informations
|
69
67
|
def self.extract_vars(path, call_options)
|
70
68
|
@@route_patterns.each do |data|
|
71
|
-
next unless params =
|
69
|
+
next unless (params = parse data, path)
|
72
70
|
call_options[:params].merge!(params)
|
73
|
-
if data.
|
74
|
-
raise RouteNotFoundError
|
71
|
+
if data.key?(:router_method_name)
|
72
|
+
raise RouteNotFoundError if data.key?(:verb) && call_options[:verb] != data[:verb]
|
75
73
|
return data[:router_method_name], call_options
|
76
74
|
else
|
77
|
-
return
|
75
|
+
return infer_method_name(data, call_options[:params], call_options[:verb]), call_options
|
78
76
|
end
|
79
77
|
end
|
80
78
|
end
|
@@ -82,17 +80,15 @@ module Culpa
|
|
82
80
|
##
|
83
81
|
# Try to return the router method name or infer it automatically
|
84
82
|
def self.infer_method_name(pattern, params, verb)
|
85
|
-
if params.
|
83
|
+
if params.key?('sub_call')
|
86
84
|
return "#{params['sub_call']}_#{params['res_name']}".to_sym
|
87
85
|
end
|
88
86
|
|
89
|
-
unless pattern[:infer].is_a?(Hash)
|
90
|
-
raise UnpredictableSubCallError
|
87
|
+
unless pattern[:infer].is_a?(Hash) && pattern[:infer].key?(verb)
|
88
|
+
raise UnpredictableSubCallError
|
91
89
|
end
|
92
90
|
|
93
|
-
|
91
|
+
"#{pattern[:infer][verb]}_#{params['res_name']}".to_sym
|
94
92
|
end
|
95
|
-
|
96
93
|
end
|
97
|
-
|
98
94
|
end
|
@@ -1,68 +1,66 @@
|
|
1
1
|
class RendererDescriber
|
2
|
-
|
3
2
|
RETURN_CODES = {
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
}
|
3
|
+
# Information codes
|
4
|
+
continue: 100,
|
5
|
+
switching_procotols: 101,
|
6
|
+
# Success codes
|
7
|
+
ok: 200,
|
8
|
+
created: 201,
|
9
|
+
accepted: 202,
|
10
|
+
non_authoritative_information: 203,
|
11
|
+
no_content: 204,
|
12
|
+
reset_content: 205,
|
13
|
+
partial_content: 206,
|
14
|
+
# Redirection codes
|
15
|
+
multiple_choices: 300,
|
16
|
+
moved_permanently: 301,
|
17
|
+
moved_temporarily: 302,
|
18
|
+
see_other: 303,
|
19
|
+
not_modified: 304,
|
20
|
+
use_proxy: 305,
|
21
|
+
temporary_redirect: 307,
|
22
|
+
permanent_redirect: 308,
|
23
|
+
too_many_redirects: 310,
|
24
|
+
# Clients error codes
|
25
|
+
bad_request: 400,
|
26
|
+
unauthorized: 401,
|
27
|
+
payment_required: 402,
|
28
|
+
forbidden: 403,
|
29
|
+
not_found: 404,
|
30
|
+
method_not_allowed: 405,
|
31
|
+
not_acceptable: 406,
|
32
|
+
proxy_authentication_required: 407,
|
33
|
+
request_time_out: 408,
|
34
|
+
conflict: 409,
|
35
|
+
gone: 410,
|
36
|
+
length_required: 411,
|
37
|
+
precondition_failed: 412,
|
38
|
+
request_entity_too_large: 413,
|
39
|
+
request_uri_too_long: 414,
|
40
|
+
unsupported_media_type: 415,
|
41
|
+
request_range_unsatisfiable: 416,
|
42
|
+
expectation_failed: 417,
|
43
|
+
im_a_tea_pot: 418,
|
44
|
+
bad_mapping: 412,
|
45
|
+
unavailable_for_legal_reason: 451,
|
46
|
+
# Server error codes
|
47
|
+
internal_server_error: 500,
|
48
|
+
not_implemented: 501,
|
49
|
+
bad_gateway: 502,
|
50
|
+
proxy_error: 502,
|
51
|
+
service_unavailable: 503,
|
52
|
+
gateway_time_out: 504,
|
53
|
+
bandwidth_limit_exceeded: 509,
|
54
|
+
unknown_error: 520
|
55
|
+
}.freeze
|
57
56
|
|
58
57
|
attr_reader :result
|
59
58
|
|
60
59
|
def initialize(file)
|
61
|
-
|
60
|
+
instance_eval(File.read(file), file)
|
62
61
|
end
|
63
62
|
|
64
63
|
def describe_renderer(keyword, &blk)
|
65
|
-
@result = {keyword => blk}
|
64
|
+
@result = { keyword => blk }
|
66
65
|
end
|
67
|
-
|
68
66
|
end
|
@@ -6,16 +6,14 @@
|
|
6
6
|
require_relative '../coliseum_dsl'
|
7
7
|
|
8
8
|
describe_renderer :coliseum do |options, envelope|
|
9
|
-
|
10
9
|
coliseum = ColiseumDSL.new("./coliseums/#{options[:coliseum]}.rb", envelope)
|
11
10
|
|
12
11
|
sent_headers = { 'Content-Type' => 'application/json' }.merge(options[:headers]).merge(coliseum.culpa_headers)
|
13
12
|
|
14
13
|
{
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
format: :json,
|
15
|
+
status: RETURN_CODES[options[:status]] || RETURN_CODES[coliseum.culpa_status] || 200,
|
16
|
+
headers: sent_headers,
|
17
|
+
object: coliseum.dsl_attributes
|
19
18
|
}
|
20
|
-
|
21
19
|
end
|
data/lib/culpa/renderers/file.rb
CHANGED
@@ -7,6 +7,7 @@ class FileStreamer
|
|
7
7
|
def initialize(element)
|
8
8
|
@element = element
|
9
9
|
end
|
10
|
+
|
10
11
|
def each(&blk)
|
11
12
|
@element.each(&blk)
|
12
13
|
ensure
|
@@ -14,20 +15,18 @@ class FileStreamer
|
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
17
|
-
describe_renderer :file do |options,
|
18
|
-
|
18
|
+
describe_renderer :file do |options, _envelope|
|
19
19
|
to_render = {
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
20
|
+
format: :file_from_path,
|
21
|
+
headers: {
|
22
|
+
'Content-Type' => options[:content_type] || 'application/octet-stream',
|
23
|
+
'Content-Disposition' => "#{options[:disposition].to_s || 'attachment'}; filename=#{options[:filename] || 'unknown'}"
|
24
|
+
}.merge(options[:headers]),
|
25
|
+
status: RETURN_CODES[options[:status]] || 200,
|
26
|
+
object: FileStreamer.new(options[:file])
|
27
27
|
}
|
28
28
|
|
29
|
-
to_render[:headers]['Content-Length'] = options[:size] if options.
|
29
|
+
to_render[:headers]['Content-Length'] = options[:size] if options.key? :size
|
30
30
|
|
31
31
|
to_render
|
32
|
-
|
33
32
|
end
|
data/lib/culpa/renderers/json.rb
CHANGED
@@ -4,21 +4,19 @@
|
|
4
4
|
# to render a status code if the passed object to render is nil.
|
5
5
|
##
|
6
6
|
|
7
|
-
describe_renderer :json do |options,
|
8
|
-
|
7
|
+
describe_renderer :json do |options, _envelope|
|
9
8
|
if options[:json]
|
10
9
|
{
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
format: :json,
|
11
|
+
status: RETURN_CODES[options[:status]] || 200,
|
12
|
+
headers: { 'Content-Type' => 'application/json' }.merge(options[:headers]),
|
13
|
+
object: options[:json]
|
15
14
|
}
|
16
15
|
else
|
17
16
|
{
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
format: :status,
|
18
|
+
headers: options[:headers],
|
19
|
+
status: RETURN_CODES[options[:or_status]]
|
21
20
|
}
|
22
21
|
end
|
23
|
-
|
24
22
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
##
|
2
2
|
# This renderer helps to return a simple status code the client
|
3
|
-
##
|
3
|
+
##
|
4
4
|
|
5
|
-
describe_renderer :status do |options,
|
5
|
+
describe_renderer :status do |options, _envelope|
|
6
6
|
{
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
format: :status,
|
8
|
+
headers: options[:headers],
|
9
|
+
status: RETURN_CODES[options[:status]]
|
10
10
|
}
|
11
11
|
end
|
data/lib/culpa/routes_builder.rb
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
module Culpa
|
2
|
-
|
3
2
|
##
|
4
3
|
# This class helps to build the routes.
|
5
4
|
# This class is used to understand and parse the DSL of the brickchains.rb file.
|
6
5
|
##
|
7
6
|
|
8
7
|
class RoutesBuilder
|
9
|
-
|
10
8
|
attr_reader :result
|
11
9
|
attr_reader :prefix
|
12
10
|
attr_reader :public_folder
|
@@ -28,7 +26,7 @@ module Culpa
|
|
28
26
|
@public_folder = folder
|
29
27
|
end
|
30
28
|
|
31
|
-
def brickchain(name
|
29
|
+
def brickchain(name, *opts, &blk)
|
32
30
|
@result[name] = {
|
33
31
|
options: opts[0],
|
34
32
|
block: blk
|
@@ -37,11 +35,11 @@ module Culpa
|
|
37
35
|
end
|
38
36
|
|
39
37
|
def before(&blk)
|
40
|
-
@before=[] unless @before
|
38
|
+
@before = [] unless @before
|
41
39
|
@before << blk
|
42
40
|
end
|
43
41
|
|
44
|
-
def brickchain_context(
|
42
|
+
def brickchain_context(_name, &blk)
|
45
43
|
@result.merge!(RoutesBuilder.new(blk).result)
|
46
44
|
end
|
47
45
|
|
@@ -54,10 +52,9 @@ module Culpa
|
|
54
52
|
class_name: name,
|
55
53
|
brick_name: item
|
56
54
|
}
|
57
|
-
|
55
|
+
@result["#{item}_#{name}".to_sym][:before] = @before if @before
|
58
56
|
end
|
59
57
|
end
|
60
|
-
|
61
58
|
end
|
62
59
|
|
63
60
|
class BlockToArray
|
@@ -65,9 +62,9 @@ module Culpa
|
|
65
62
|
def initialize
|
66
63
|
@culpa_bta_result = []
|
67
64
|
end
|
65
|
+
|
68
66
|
def method_missing(sym)
|
69
67
|
@culpa_bta_result << sym.to_s
|
70
68
|
end
|
71
69
|
end
|
72
|
-
|
73
70
|
end
|
data/lib/culpa/test_helpers.rb
CHANGED
@@ -3,7 +3,6 @@ require_relative 'action'
|
|
3
3
|
require 'rspec/expectations'
|
4
4
|
|
5
5
|
module CulpaHelpers
|
6
|
-
|
7
6
|
##
|
8
7
|
# Helper to access the result of the last call
|
9
8
|
def last_call
|
@@ -19,7 +18,6 @@ module CulpaHelpers
|
|
19
18
|
##
|
20
19
|
# Class used to mock a brick call
|
21
20
|
class BrickCall
|
22
|
-
|
23
21
|
attr_reader :from_done
|
24
22
|
|
25
23
|
def initialize(brick_name)
|
@@ -52,14 +50,13 @@ module CulpaHelpers
|
|
52
50
|
rescue Action::RenderNow => renderer
|
53
51
|
return @call_cache = { to_render: renderer.to_render, envelope: @envelope }
|
54
52
|
end
|
55
|
-
|
56
53
|
end
|
57
54
|
|
58
55
|
##
|
59
56
|
# expect(last_call).to have_status(:i_am_a_teapot)
|
60
57
|
RSpec::Matchers.define :have_status do |expected|
|
61
58
|
match do |actual|
|
62
|
-
actual.from(described_class) unless actual.from_done
|
59
|
+
actual.from(described_class) unless actual.from_done || described_class.nil?
|
63
60
|
bc = actual.call
|
64
61
|
bc[:to_render][:status] == RendererDescriber::RETURN_CODES[expected]
|
65
62
|
end
|
@@ -69,10 +66,10 @@ module CulpaHelpers
|
|
69
66
|
# expect(last_call).to have_headers({'X-Header-1' => 'ASD' .... })
|
70
67
|
RSpec::Matchers.define :have_headers do |expected|
|
71
68
|
match do |actual|
|
72
|
-
expected.each do |k,
|
73
|
-
actual.from(described_class) unless actual.from_done
|
69
|
+
expected.each do |k, _v|
|
70
|
+
actual.from(described_class) unless actual.from_done || described_class.nil?
|
74
71
|
bc = actual.call
|
75
|
-
return false unless bc[:to_render][:headers].
|
72
|
+
return false unless bc[:to_render][:headers].key?(k) &&
|
76
73
|
bc[:to_render][:headers][k] == expected[k]
|
77
74
|
end
|
78
75
|
true
|
@@ -83,7 +80,7 @@ module CulpaHelpers
|
|
83
80
|
# expect(last_call).to have_body({a: 'a' ... }) (Can be a JSON or anything else)
|
84
81
|
RSpec::Matchers.define :have_body do |expected|
|
85
82
|
match do |actual|
|
86
|
-
actual.from(described_class) unless actual.from_done
|
83
|
+
actual.from(described_class) unless actual.from_done || described_class.nil?
|
87
84
|
bc = actual.call
|
88
85
|
bc[:to_render][:object] == expected
|
89
86
|
end
|
@@ -93,10 +90,9 @@ module CulpaHelpers
|
|
93
90
|
# expect(last_call).to have_envelope({a: 'a' ... })
|
94
91
|
RSpec::Matchers.define :have_envelope do |expected|
|
95
92
|
match do |actual|
|
96
|
-
actual.from(described_class) unless actual.from_done
|
93
|
+
actual.from(described_class) unless actual.from_done || described_class.nil?
|
97
94
|
bc = actual.call
|
98
95
|
bc[:envelope] == Culpa::Envelope.new(expected)
|
99
96
|
end
|
100
97
|
end
|
101
|
-
|
102
98
|
end
|