syntropy 0.26 → 0.27.1
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/CHANGELOG.md +13 -0
- data/lib/syntropy/applets/builtin/default_error_handler.rb +11 -8
- data/lib/syntropy/errors.rb +5 -5
- data/lib/syntropy/module.rb +25 -21
- data/lib/syntropy/request_extensions.rb +38 -6
- data/lib/syntropy/version.rb +1 -1
- data/test/app/api+.rb +1 -1
- data/test/test_app.rb +4 -4
- data/test/test_errors.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8fd5a77712915e9472ed58a8786dce7369e9d24852533c19ce25d9f4959815bf
|
|
4
|
+
data.tar.gz: d6420486dc63bb1227b723f14e28e170e6e772f28970a2ce62e2d8283bdcd2b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5041a3820ed58393b643c7ed298fbdfedf255b9116156a1909d39bf0dfbb842004b38a651c85419d28c5dc2cae9b1c0d5fb13a9e24bbb1ec5340508ffbe7591c
|
|
7
|
+
data.tar.gz: d239554ae9e5b8c1b3159cbc0bb13cc828d2b00a4cf376197aaeb7740c57ae49d6419ccb957893bc6a1026d2db273a77f25a2960e6c21d84dad68528bf2ebf97
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
# 0.27.1 2025-10-21
|
|
2
|
+
|
|
3
|
+
- Fix error instantiation and error testing
|
|
4
|
+
|
|
5
|
+
# 0.27 2025-10-21
|
|
6
|
+
|
|
7
|
+
- Use accept header (instead of user-agent) for rendering error page
|
|
8
|
+
- Add `Request#accept?` method
|
|
9
|
+
- Fix import path normalization, module method visibility
|
|
10
|
+
- Fix instantiation of Syntropy::Error
|
|
11
|
+
- Improve default error handler response
|
|
12
|
+
- Fix and enhance `Request#html_response`, `Request#json_response` methods
|
|
13
|
+
|
|
1
14
|
# 0.26 2025-10-21
|
|
2
15
|
|
|
3
16
|
- Add /.syntropy/req route for testing request headers
|
|
@@ -35,21 +35,24 @@ def transform_backtrace(backtrace)
|
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
def
|
|
38
|
+
def error_response_html(req, error)
|
|
39
39
|
status = Syntropy::Error.http_status(error)
|
|
40
40
|
backtrace = transform_backtrace(error.backtrace)
|
|
41
41
|
html = Papercraft.html(ErrorPage, error:, status:, backtrace:)
|
|
42
|
-
req.
|
|
42
|
+
req.html_response(html, ':status' => status)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def error_response_raw(req, error)
|
|
46
|
-
status =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
status = Syntropy::Error.http_status(error)
|
|
47
|
+
response = {
|
|
48
|
+
class: error.class.to_s,
|
|
49
|
+
message: error.message,
|
|
50
|
+
backtrace: error.backtrace
|
|
51
|
+
}
|
|
52
|
+
req.json_pretty_response(response, ':status' => status)
|
|
50
53
|
end
|
|
51
54
|
|
|
52
55
|
export ->(req, error) {
|
|
53
|
-
req.
|
|
54
|
-
|
|
56
|
+
req.accept?('text/html') ?
|
|
57
|
+
error_response_html(req, error) : error_response_raw(req, error)
|
|
55
58
|
}
|
data/lib/syntropy/errors.rb
CHANGED
|
@@ -21,17 +21,17 @@ module Syntropy
|
|
|
21
21
|
# Creates an error with status 404 Not Found
|
|
22
22
|
#
|
|
23
23
|
# @return [Syntropy::Error]
|
|
24
|
-
def self.not_found(msg = '') = new(Status::NOT_FOUND
|
|
24
|
+
def self.not_found(msg = 'Not found') = new(msg, Status::NOT_FOUND)
|
|
25
25
|
|
|
26
26
|
# Creates an error with status 405 Method Not Allowed
|
|
27
27
|
#
|
|
28
28
|
# @return [Syntropy::Error]
|
|
29
|
-
def self.method_not_allowed(msg = '') = new(Status::METHOD_NOT_ALLOWED
|
|
29
|
+
def self.method_not_allowed(msg = 'Method not allowed') = new(msg, Status::METHOD_NOT_ALLOWED)
|
|
30
30
|
|
|
31
31
|
# Creates an error with status 418 I'm a teapot
|
|
32
32
|
#
|
|
33
33
|
# @return [Syntropy::Error]
|
|
34
|
-
def self.teapot(msg = '') = new(Status::TEAPOT
|
|
34
|
+
def self.teapot(msg = 'I\'m a teapot') = new(msg, Status::TEAPOT)
|
|
35
35
|
|
|
36
36
|
attr_reader :http_status
|
|
37
37
|
|
|
@@ -40,7 +40,7 @@ module Syntropy
|
|
|
40
40
|
# @param http_status [Integer, String] HTTP status
|
|
41
41
|
# @param msg [String] error message
|
|
42
42
|
# @return [void]
|
|
43
|
-
def initialize(
|
|
43
|
+
def initialize(msg = 'Internal server error', http_status = DEFAULT_STATUS)
|
|
44
44
|
super(msg)
|
|
45
45
|
@http_status = http_status
|
|
46
46
|
end
|
|
@@ -56,7 +56,7 @@ module Syntropy
|
|
|
56
56
|
# ValidationError is raised when a validation has failed.
|
|
57
57
|
class ValidationError < Error
|
|
58
58
|
def initialize(msg)
|
|
59
|
-
super(Qeweney::Status::BAD_REQUEST
|
|
59
|
+
super(msg, Qeweney::Status::BAD_REQUEST)
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
end
|
data/lib/syntropy/module.rb
CHANGED
|
@@ -116,7 +116,8 @@ module Syntropy
|
|
|
116
116
|
def clean_ref(ref)
|
|
117
117
|
return '/' if ref =~ /^index(\+)?$/
|
|
118
118
|
|
|
119
|
-
ref.gsub(/\/index(?:\+)?$/, '')
|
|
119
|
+
clean = ref.gsub(/\/index(?:\+)?$/, '')
|
|
120
|
+
clean == '' ? '/' : clean
|
|
120
121
|
end
|
|
121
122
|
|
|
122
123
|
# Transforms the given export value. If the value is nil, an exception is
|
|
@@ -180,10 +181,21 @@ module Syntropy
|
|
|
180
181
|
@app = env[:app]
|
|
181
182
|
@ref = env[:ref]
|
|
182
183
|
@logger = env[:logger]
|
|
184
|
+
@__dependencies__ = []
|
|
183
185
|
singleton_class.const_set(:MODULE, self)
|
|
184
186
|
end
|
|
185
187
|
|
|
186
|
-
attr_reader :__export_value__
|
|
188
|
+
attr_reader :__export_value__, :__dependencies__
|
|
189
|
+
|
|
190
|
+
# Returns a list of pages found at the given ref.
|
|
191
|
+
#
|
|
192
|
+
# @param ref [String] directory reference
|
|
193
|
+
# @return [Array] array of pages found in directory
|
|
194
|
+
def page_list(ref)
|
|
195
|
+
Syntropy.page_list(@env, ref)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
private
|
|
187
199
|
|
|
188
200
|
# Exports the given value. This value will be used as the module's
|
|
189
201
|
# entrypoint. It can be any Ruby value, but for a route module would
|
|
@@ -195,23 +207,23 @@ module Syntropy
|
|
|
195
207
|
@__export_value__ = v
|
|
196
208
|
end
|
|
197
209
|
|
|
198
|
-
# Returns the list of module references imported by the module.
|
|
199
|
-
#
|
|
200
|
-
# @return [Array] array of module references
|
|
201
|
-
def __dependencies__
|
|
202
|
-
@__dependencies__ ||= []
|
|
203
|
-
end
|
|
204
|
-
|
|
205
210
|
# Imports the module corresponding to the given reference. The return value
|
|
206
211
|
# is the module's export value.
|
|
207
212
|
#
|
|
208
213
|
# @param ref [String] module reference
|
|
209
214
|
# @return [any] loaded dependency's export value
|
|
210
215
|
def import(ref)
|
|
211
|
-
|
|
212
|
-
@module_loader.load(
|
|
213
|
-
|
|
214
|
-
|
|
216
|
+
ref = normalize_import_ref(ref)
|
|
217
|
+
@module_loader.load(ref).tap { __dependencies__ << ref }
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def normalize_import_ref(ref)
|
|
221
|
+
base = @ref == '' ? '/' : @ref
|
|
222
|
+
if ref =~ /^\//
|
|
223
|
+
ref
|
|
224
|
+
else
|
|
225
|
+
File.expand_path(File.join(File.dirname(base), ref))
|
|
226
|
+
end
|
|
215
227
|
end
|
|
216
228
|
|
|
217
229
|
# Creates and returns a Papercraft template created with the given block.
|
|
@@ -242,14 +254,6 @@ module Syntropy
|
|
|
242
254
|
raise
|
|
243
255
|
end
|
|
244
256
|
|
|
245
|
-
# Returns a list of pages found at the given ref.
|
|
246
|
-
#
|
|
247
|
-
# @param ref [String] directory reference
|
|
248
|
-
# @return [Array] array of pages found in directory
|
|
249
|
-
def page_list(ref)
|
|
250
|
-
Syntropy.page_list(@env, ref)
|
|
251
|
-
end
|
|
252
|
-
|
|
253
257
|
# Creates and returns a Syntropy app for the given environment. The app's
|
|
254
258
|
# environment is based on the module's env merged with the given parameters.
|
|
255
259
|
#
|
|
@@ -177,20 +177,36 @@ module Syntropy
|
|
|
177
177
|
def get_form_data
|
|
178
178
|
body = read
|
|
179
179
|
if !body || body.empty?
|
|
180
|
-
raise Syntropy::Error.new(
|
|
180
|
+
raise Syntropy::Error.new('Missing form data', Qeweney::Status::BAD_REQUEST)
|
|
181
181
|
end
|
|
182
182
|
|
|
183
183
|
Qeweney::Request.parse_form_data(body, headers)
|
|
184
184
|
rescue Qeweney::BadRequestError
|
|
185
|
-
raise Syntropy::Error.new(
|
|
185
|
+
raise Syntropy::Error.new('Invalid form data', Qeweney::Status::BAD_REQUEST)
|
|
186
186
|
end
|
|
187
187
|
|
|
188
|
-
def
|
|
189
|
-
respond(
|
|
188
|
+
def html_response(html, **headers)
|
|
189
|
+
respond(
|
|
190
|
+
html,
|
|
191
|
+
'Content-Type' => 'text/html; charset=utf-8',
|
|
192
|
+
**headers
|
|
193
|
+
)
|
|
190
194
|
end
|
|
191
195
|
|
|
192
|
-
def json_response(obj)
|
|
193
|
-
respond(
|
|
196
|
+
def json_response(obj, **headers)
|
|
197
|
+
respond(
|
|
198
|
+
JSON.dump(obj),
|
|
199
|
+
'Content-Type' => 'application/json; charset=utf-8',
|
|
200
|
+
**headers
|
|
201
|
+
)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def json_pretty_response(obj, **headers)
|
|
205
|
+
respond(
|
|
206
|
+
JSON.pretty_generate(obj),
|
|
207
|
+
'Content-Type' => 'application/json; charset=utf-8',
|
|
208
|
+
**headers
|
|
209
|
+
)
|
|
194
210
|
end
|
|
195
211
|
|
|
196
212
|
def browser?
|
|
@@ -198,8 +214,24 @@ module Syntropy
|
|
|
198
214
|
user_agent && user_agent =~ /^Mozilla\//
|
|
199
215
|
end
|
|
200
216
|
|
|
217
|
+
# Returns true if the accept header includes the given MIME type
|
|
218
|
+
#
|
|
219
|
+
# @param mime_type [String] MIME type
|
|
220
|
+
# @return [bool]
|
|
221
|
+
def accept?(mime_type)
|
|
222
|
+
accept = headers['accept']
|
|
223
|
+
return nil if !accept
|
|
224
|
+
|
|
225
|
+
@accept_parts ||= parse_accept_parts(accept)
|
|
226
|
+
@accept_parts.include?(mime_type)
|
|
227
|
+
end
|
|
228
|
+
|
|
201
229
|
private
|
|
202
230
|
|
|
231
|
+
def parse_accept_parts(accept)
|
|
232
|
+
accept.split(',').map { it.match(/^\s*([^\s;]+)/)[1] }
|
|
233
|
+
end
|
|
234
|
+
|
|
203
235
|
BOOL_REGEXP = /^(t|f|true|false|on|off|1|0|yes|no)$/
|
|
204
236
|
BOOL_TRUE_REGEXP = /^(t|true|on|1|yes)$/
|
|
205
237
|
INTEGER_REGEXP = /^[+-]?[0-9]+$/
|
data/lib/syntropy/version.rb
CHANGED
data/test/app/api+.rb
CHANGED
data/test/test_app.rb
CHANGED
|
@@ -50,7 +50,7 @@ class AppTest < Minitest::Test
|
|
|
50
50
|
|
|
51
51
|
req = make_request(':method' => 'POST', ':path' => '/test')
|
|
52
52
|
assert_equal Status::METHOD_NOT_ALLOWED, req.response_status
|
|
53
|
-
|
|
53
|
+
assert_equal "Method not allowed", req.response_body
|
|
54
54
|
|
|
55
55
|
req = make_request(':method' => 'GET', ':path' => '/test/assets/style.css')
|
|
56
56
|
assert_equal '* { color: beige }', req.response_body
|
|
@@ -64,7 +64,7 @@ class AppTest < Minitest::Test
|
|
|
64
64
|
|
|
65
65
|
req = make_request(':method' => 'POST', ':path' => '/test/api?q=get')
|
|
66
66
|
assert_equal Status::METHOD_NOT_ALLOWED, req.response_status
|
|
67
|
-
assert_equal({ status: 'Error', message: '' }, req.response_json)
|
|
67
|
+
assert_equal({ status: 'Error', message: 'Method not allowed' }, req.response_json)
|
|
68
68
|
|
|
69
69
|
req = make_request(':method' => 'GET', ':path' => '/test/api/foo?q=get')
|
|
70
70
|
assert_equal({ status: 'OK', response: 0 }, req.response_json)
|
|
@@ -74,7 +74,7 @@ class AppTest < Minitest::Test
|
|
|
74
74
|
|
|
75
75
|
req = make_request(':method' => 'GET', ':path' => '/test/api?q=incr')
|
|
76
76
|
assert_equal Status::METHOD_NOT_ALLOWED, req.response_status
|
|
77
|
-
assert_equal({ status: 'Error', message: '' }, req.response_json)
|
|
77
|
+
assert_equal({ status: 'Error', message: 'Method not allowed' }, req.response_json)
|
|
78
78
|
|
|
79
79
|
req = make_request(':method' => 'POST', ':path' => '/test/api/foo?q=incr')
|
|
80
80
|
assert_equal({ status: 'Error', message: 'Teapot' }, req.response_json)
|
|
@@ -97,7 +97,7 @@ class AppTest < Minitest::Test
|
|
|
97
97
|
assert_equal Status::OK, req.response_status
|
|
98
98
|
|
|
99
99
|
req = make_request(':method' => 'POST', ':path' => '/test/baz')
|
|
100
|
-
|
|
100
|
+
assert_equal 'Method not allowed', req.response_body
|
|
101
101
|
assert_equal Status::METHOD_NOT_ALLOWED, req.response_status
|
|
102
102
|
|
|
103
103
|
req = make_request(':method' => 'GET', ':path' => '/test/about')
|
data/test/test_errors.rb
CHANGED
|
@@ -12,7 +12,7 @@ class ErrorsTest < Minitest::Test
|
|
|
12
12
|
e = Syntropy::Error.new
|
|
13
13
|
assert_equal ISE, Syntropy::Error.http_status(e)
|
|
14
14
|
|
|
15
|
-
e = Syntropy::Error.new(Qeweney::Status::UNAUTHORIZED)
|
|
15
|
+
e = Syntropy::Error.new("", Qeweney::Status::UNAUTHORIZED)
|
|
16
16
|
assert_equal Qeweney::Status::UNAUTHORIZED, Syntropy::Error.http_status(e)
|
|
17
17
|
end
|
|
18
18
|
|