praxis 2.0.pre.17 → 2.0.pre.21
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/.rubocop.yml +54 -0
- data/.simplecov +3 -1
- data/.travis.yml +2 -1
- data/CHANGELOG.md +19 -0
- data/CONTRIBUTING.md +2 -79
- data/Gemfile +5 -1
- data/Guardfile +6 -4
- data/LICENSE +0 -2
- data/MAINTAINERS.md +1 -0
- data/README.md +15 -22
- data/Rakefile +4 -2
- data/bin/praxis +55 -58
- data/lib/praxis/action_definition/headers_dsl_compiler.rb +5 -6
- data/lib/praxis/action_definition.rb +65 -95
- data/lib/praxis/api_definition.rb +21 -29
- data/lib/praxis/api_general_info.rb +55 -66
- data/lib/praxis/application.rb +15 -32
- data/lib/praxis/blueprint.rb +80 -73
- data/lib/praxis/bootloader.rb +24 -33
- data/lib/praxis/bootloader_stages/environment.rb +5 -10
- data/lib/praxis/bootloader_stages/file_loader.rb +3 -6
- data/lib/praxis/bootloader_stages/plugin_config_load.rb +4 -6
- data/lib/praxis/bootloader_stages/plugin_config_prepare.rb +2 -2
- data/lib/praxis/bootloader_stages/plugin_loader.rb +3 -7
- data/lib/praxis/bootloader_stages/plugin_setup.rb +3 -3
- data/lib/praxis/bootloader_stages/routing.rb +5 -8
- data/lib/praxis/bootloader_stages/subgroup_loader.rb +2 -10
- data/lib/praxis/bootloader_stages/warn_unloaded_files.rb +15 -19
- data/lib/praxis/callbacks.rb +12 -11
- data/lib/praxis/collection.rb +11 -14
- data/lib/praxis/config.rb +17 -28
- data/lib/praxis/config_hash.rb +2 -1
- data/lib/praxis/controller.rb +7 -6
- data/lib/praxis/dispatcher.rb +34 -42
- data/lib/praxis/docs/open_api/info_object.rb +11 -8
- data/lib/praxis/docs/open_api/media_type_object.rb +18 -17
- data/lib/praxis/docs/open_api/operation_object.rb +7 -4
- data/lib/praxis/docs/open_api/parameter_object.rb +17 -14
- data/lib/praxis/docs/open_api/paths_object.rb +11 -9
- data/lib/praxis/docs/open_api/request_body_object.rb +14 -13
- data/lib/praxis/docs/open_api/response_object.rb +24 -18
- data/lib/praxis/docs/open_api/responses_object.rb +3 -1
- data/lib/praxis/docs/open_api/schema_object.rb +61 -29
- data/lib/praxis/docs/open_api/server_object.rb +5 -2
- data/lib/praxis/docs/open_api/tag_object.rb +9 -6
- data/lib/praxis/docs/open_api_generator.rb +114 -150
- data/lib/praxis/endpoint_definition.rb +60 -77
- data/lib/praxis/error_handler.rb +2 -2
- data/lib/praxis/exception.rb +2 -0
- data/lib/praxis/exceptions/config.rb +3 -1
- data/lib/praxis/exceptions/config_load.rb +2 -0
- data/lib/praxis/exceptions/config_validation.rb +3 -1
- data/lib/praxis/exceptions/invalid_configuration.rb +3 -1
- data/lib/praxis/exceptions/invalid_response.rb +3 -1
- data/lib/praxis/exceptions/invalid_trait.rb +3 -1
- data/lib/praxis/exceptions/stage_not_found.rb +3 -1
- data/lib/praxis/exceptions/validation.rb +4 -3
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +163 -149
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/5x.rb +18 -13
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_0.rb +13 -9
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_1_plus.rb +14 -11
- data/lib/praxis/extensions/attribute_filtering/active_record_patches.rb +12 -9
- data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +8 -5
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +89 -65
- data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +68 -62
- data/lib/praxis/extensions/attribute_filtering.rb +3 -1
- data/lib/praxis/extensions/field_expansion.rb +6 -4
- data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +10 -8
- data/lib/praxis/extensions/field_selection/field_selector.rb +91 -92
- data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +12 -12
- data/lib/praxis/extensions/field_selection.rb +3 -1
- data/lib/praxis/extensions/pagination/active_record_pagination_handler.rb +6 -4
- data/lib/praxis/extensions/pagination/header_generator.rb +16 -11
- data/lib/praxis/extensions/pagination/ordering_params.rb +29 -28
- data/lib/praxis/extensions/pagination/pagination_handler.rb +44 -42
- data/lib/praxis/extensions/pagination/pagination_params.rb +29 -48
- data/lib/praxis/extensions/pagination/sequel_pagination_handler.rb +8 -7
- data/lib/praxis/extensions/pagination.rb +10 -15
- data/lib/praxis/extensions/rails_compat/request_methods.rb +3 -4
- data/lib/praxis/extensions/rails_compat.rb +2 -0
- data/lib/praxis/extensions/rendering.rb +12 -12
- data/lib/praxis/field_expander.rb +8 -9
- data/lib/praxis/file_group.rb +8 -12
- data/lib/praxis/finalizable.rb +1 -0
- data/lib/praxis/handlers/json.rb +5 -2
- data/lib/praxis/handlers/plain.rb +2 -1
- data/lib/praxis/handlers/www_form.rb +6 -3
- data/lib/praxis/handlers/{xml-sample.rb → xml_sample.rb} +26 -22
- data/lib/praxis/mapper/active_model_compat.rb +13 -10
- data/lib/praxis/mapper/resource.rb +196 -181
- data/lib/praxis/mapper/selector_generator.rb +106 -112
- data/lib/praxis/mapper/sequel_compat.rb +70 -67
- data/lib/praxis/media_type.rb +2 -2
- data/lib/praxis/media_type_identifier.rb +26 -22
- data/lib/praxis/middleware_app.rb +18 -15
- data/lib/praxis/multipart/parser.rb +46 -51
- data/lib/praxis/multipart/part.rb +78 -110
- data/lib/praxis/notifications.rb +2 -4
- data/lib/praxis/plugin.rb +11 -18
- data/lib/praxis/plugin_concern.rb +12 -15
- data/lib/praxis/plugins/mapper_plugin.rb +15 -13
- data/lib/praxis/plugins/pagination_plugin.rb +8 -6
- data/lib/praxis/plugins/rails_plugin.rb +33 -28
- data/lib/praxis/renderer.rb +11 -15
- data/lib/praxis/request.rb +48 -44
- data/lib/praxis/request_stages/action.rb +4 -6
- data/lib/praxis/request_stages/load_request.rb +2 -4
- data/lib/praxis/request_stages/request_stage.rb +19 -23
- data/lib/praxis/request_stages/response.rb +4 -6
- data/lib/praxis/request_stages/validate.rb +3 -5
- data/lib/praxis/request_stages/validate_params_and_headers.rb +15 -22
- data/lib/praxis/request_stages/validate_payload.rb +25 -28
- data/lib/praxis/request_superclassing.rb +3 -3
- data/lib/praxis/resource_definition.rb +1 -0
- data/lib/praxis/response.rb +24 -26
- data/lib/praxis/response_definition.rb +77 -122
- data/lib/praxis/response_template.rb +11 -15
- data/lib/praxis/responses/http.rb +23 -44
- data/lib/praxis/responses/internal_server_error.rb +18 -21
- data/lib/praxis/responses/multipart_ok.rb +4 -9
- data/lib/praxis/responses/validation_error.rb +8 -15
- data/lib/praxis/route.rb +8 -10
- data/lib/praxis/router/rack.rb +13 -7
- data/lib/praxis/router/simple.rb +10 -5
- data/lib/praxis/router.rb +27 -34
- data/lib/praxis/routing_config.rb +52 -29
- data/lib/praxis/simple_media_type.rb +5 -8
- data/lib/praxis/stage.rb +17 -25
- data/lib/praxis/tasks/api_docs.rb +17 -16
- data/lib/praxis/tasks/console.rb +3 -1
- data/lib/praxis/tasks/environment.rb +2 -0
- data/lib/praxis/tasks/routes.rb +26 -24
- data/lib/praxis/tasks.rb +3 -1
- data/lib/praxis/trait.rb +37 -46
- data/lib/praxis/types/fuzzy_hash.rb +13 -14
- data/lib/praxis/types/media_type_common.rb +11 -10
- data/lib/praxis/types/multipart_array/part_definition.rb +14 -17
- data/lib/praxis/types/multipart_array.rb +100 -115
- data/lib/praxis/validation_handler.rb +5 -3
- data/lib/praxis/version.rb +3 -1
- data/lib/praxis.rb +4 -5
- data/praxis.gemspec +22 -21
- data/spec/functional_spec.rb +44 -56
- data/spec/praxis/action_definition_spec.rb +39 -48
- data/spec/praxis/api_definition_spec.rb +45 -47
- data/spec/praxis/api_general_info_spec.rb +28 -29
- data/spec/praxis/application_spec.rb +18 -14
- data/spec/praxis/blueprint_spec.rb +33 -34
- data/spec/praxis/bootloader_spec.rb +32 -30
- data/spec/praxis/callbacks_spec.rb +37 -37
- data/spec/praxis/collection_spec.rb +18 -25
- data/spec/praxis/config_hash_spec.rb +5 -4
- data/spec/praxis/config_spec.rb +27 -26
- data/spec/praxis/controller_spec.rb +8 -9
- data/spec/praxis/endpoint_definition_spec.rb +25 -32
- data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +171 -114
- data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +22 -21
- data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +112 -60
- data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +37 -38
- data/spec/praxis/extensions/field_expansion_spec.rb +8 -10
- data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +14 -13
- data/spec/praxis/extensions/field_selection/field_selector_spec.rb +9 -16
- data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +50 -49
- data/spec/praxis/extensions/pagination/active_record_pagination_handler_spec.rb +32 -31
- data/spec/praxis/extensions/rendering_spec.rb +9 -9
- data/spec/praxis/extensions/support/spec_resources_active_model.rb +32 -49
- data/spec/praxis/extensions/support/spec_resources_sequel.rb +48 -48
- data/spec/praxis/field_expander_spec.rb +6 -5
- data/spec/praxis/file_group_spec.rb +3 -1
- data/spec/praxis/handlers/json_spec.rb +6 -5
- data/spec/praxis/mapper/resource_spec.rb +39 -29
- data/spec/praxis/mapper/selector_generator_spec.rb +80 -46
- data/spec/praxis/media_type_identifier_spec.rb +13 -10
- data/spec/praxis/media_type_spec.rb +12 -12
- data/spec/praxis/middleware_app_spec.rb +23 -22
- data/spec/praxis/multipart/parser_spec.rb +7 -9
- data/spec/praxis/notifications_spec.rb +4 -4
- data/spec/praxis/plugin_concern_spec.rb +5 -6
- data/spec/praxis/renderer_spec.rb +10 -9
- data/spec/praxis/request_spec.rb +38 -41
- data/spec/praxis/request_stages/action_spec.rb +14 -15
- data/spec/praxis/request_stages/request_stage_spec.rb +30 -41
- data/spec/praxis/request_stages/validate_spec.rb +3 -1
- data/spec/praxis/response_definition_spec.rb +79 -92
- data/spec/praxis/response_spec.rb +35 -40
- data/spec/praxis/responses/internal_server_error_spec.rb +6 -9
- data/spec/praxis/responses/validation_error_spec.rb +17 -18
- data/spec/praxis/route_spec.rb +4 -7
- data/spec/praxis/router_spec.rb +69 -79
- data/spec/praxis/routing_config_spec.rb +15 -14
- data/spec/praxis/stage_spec.rb +56 -53
- data/spec/praxis/trait_spec.rb +17 -17
- data/spec/praxis/types/fuzzy_hash_spec.rb +11 -9
- data/spec/praxis/types/multipart_array/part_definition_spec.rb +3 -2
- data/spec/praxis/types/multipart_array_spec.rb +33 -48
- data/spec/spec_app/app/concerns/authenticated.rb +5 -5
- data/spec/spec_app/app/concerns/basic_api.rb +3 -1
- data/spec/spec_app/app/concerns/log_wrapper.rb +5 -3
- data/spec/spec_app/app/controllers/base_class.rb +6 -5
- data/spec/spec_app/app/controllers/instances.rb +31 -34
- data/spec/spec_app/app/controllers/volumes.rb +6 -6
- data/spec/spec_app/app/responses/multipart.rb +1 -2
- data/spec/spec_app/app/responses/other_response.rb +2 -2
- data/spec/spec_app/config/environment.rb +19 -6
- data/spec/spec_app/config.ru +4 -3
- data/spec/spec_app/design/api.rb +13 -15
- data/spec/spec_app/design/media_types/instance.rb +6 -6
- data/spec/spec_app/design/media_types/volume.rb +2 -1
- data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -1
- data/spec/spec_app/design/resources/instances.rb +11 -17
- data/spec/spec_app/design/resources/volume_snapshots.rb +4 -5
- data/spec/spec_app/design/resources/volumes.rb +4 -5
- data/spec/spec_helper.rb +11 -13
- data/spec/support/be_deep_equal_matcher.rb +5 -0
- data/spec/support/spec_authorization_plugin.rb +7 -12
- data/spec/support/spec_blueprints.rb +5 -4
- data/spec/support/spec_complex_authentication_plugin.rb +17 -34
- data/spec/support/spec_endpoint_definitions.rb +2 -3
- data/spec/support/spec_media_types.rb +28 -35
- data/spec/support/spec_resources.rb +22 -16
- data/spec/support/spec_simple_authentication_plugin.rb +5 -9
- data/tasks/loader.thor +4 -2
- data/tasks/thor/app.rb +7 -5
- data/tasks/thor/example.rb +23 -22
- data/tasks/thor/model.rb +7 -7
- data/tasks/thor/scaffold.rb +23 -23
- data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +0 -8
- data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +1 -2
- metadata +72 -84
- data/MAINTAINERS +0 -2
- data/TODO.md +0 -25
- data/spec/praxis/api_resource_spec.rb +0 -0
- data/spec/praxis/dispatcher_spec.rb +0 -0
- data/spec/spec_app/app/responses/bulk_response.rb +0 -0
|
@@ -1,32 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'rack/utils'
|
|
2
4
|
|
|
3
5
|
# stolen from Rack::Multipart::Parser
|
|
4
6
|
|
|
5
7
|
module Praxis
|
|
6
8
|
class MultipartParser
|
|
7
|
-
EOL = "\r\n"
|
|
8
|
-
MULTIPART_BOUNDARY =
|
|
9
|
-
MULTIPART = %r
|
|
10
|
-
TOKEN =
|
|
9
|
+
EOL = "\r\n"
|
|
10
|
+
MULTIPART_BOUNDARY = 'AaB03x'
|
|
11
|
+
MULTIPART = %r{\Amultipart/.*boundary="?([^";,]+)"?}n.freeze
|
|
12
|
+
TOKEN = %r{[^\s()<>,;:\\"/\[\]?=]+}.freeze
|
|
11
13
|
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i.freeze
|
|
12
14
|
DISPPARM = /;\s*(#{TOKEN})=("(?:\\"|[^"])*"|#{TOKEN})/.freeze
|
|
13
15
|
RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i.freeze
|
|
14
16
|
BROKEN_QUOTED = /^#{CONDISP}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i.freeze
|
|
15
17
|
BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i.freeze
|
|
16
18
|
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni.freeze
|
|
17
|
-
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*\s+name="?([
|
|
19
|
+
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*\s+name="?([^";]*)"?/ni.freeze
|
|
18
20
|
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni.freeze
|
|
19
21
|
|
|
20
22
|
HEADER_KV = /(?<k>[^:]+): (?<v>.*)/.freeze
|
|
21
23
|
UNTIL_NEWLINE = /\A([^\n]*\n)/.freeze
|
|
22
24
|
TERMINAL_CRLF = /\r\n$/.freeze
|
|
23
25
|
|
|
26
|
+
PARAMS_BUF_SIZE = 65_536 # Same as implicitly in rack 1.x
|
|
27
|
+
BUFSIZE = 16_384
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def self.parse(headers,body)
|
|
29
|
-
self.new(headers,body).parse
|
|
29
|
+
def self.parse(headers, body)
|
|
30
|
+
new(headers, body).parse
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
def initialize(headers, body)
|
|
@@ -36,7 +37,7 @@ module Praxis
|
|
|
36
37
|
@io << chunk
|
|
37
38
|
end
|
|
38
39
|
@io.rewind
|
|
39
|
-
@parts =
|
|
40
|
+
@parts = []
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
def parse
|
|
@@ -46,19 +47,19 @@ module Praxis
|
|
|
46
47
|
|
|
47
48
|
loop do
|
|
48
49
|
head, filename, content_type, name, body =
|
|
49
|
-
|
|
50
|
+
current_head_and_filename_and_content_type_and_name_and_body
|
|
50
51
|
|
|
51
52
|
# Save the rest.
|
|
52
|
-
if i = @buf.index(rx)
|
|
53
|
+
if (i = @buf.index(rx))
|
|
53
54
|
body << @buf.slice!(0, i)
|
|
54
|
-
@buf.slice!(0, @boundary_size+2)
|
|
55
|
+
@buf.slice!(0, @boundary_size + 2)
|
|
55
56
|
|
|
56
|
-
@content_length = -1
|
|
57
|
+
@content_length = -1 if Regexp.last_match(1) == '--'
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
filename, data = get_data(filename, body, content_type, name, head)
|
|
60
61
|
|
|
61
|
-
parsed_headers = head.split(EOL).each_with_object(
|
|
62
|
+
parsed_headers = head.split(EOL).each_with_object({}) do |line, hash|
|
|
62
63
|
match = HEADER_KV.match(line)
|
|
63
64
|
k = match[:k]
|
|
64
65
|
v = match[:v]
|
|
@@ -70,7 +71,7 @@ module Praxis
|
|
|
70
71
|
@parts << part
|
|
71
72
|
|
|
72
73
|
# break if we're at the end of a buffer, but not if it is the end of a field
|
|
73
|
-
break if (@buf.empty? &&
|
|
74
|
+
break if (@buf.empty? && Regexp.last_match(1) != EOL) || @content_length == -1
|
|
74
75
|
end
|
|
75
76
|
|
|
76
77
|
@io.rewind
|
|
@@ -79,6 +80,7 @@ module Praxis
|
|
|
79
80
|
end
|
|
80
81
|
|
|
81
82
|
private
|
|
83
|
+
|
|
82
84
|
def setup_parse
|
|
83
85
|
unless (match = MULTIPART.match @headers['Content-Type'])
|
|
84
86
|
return false
|
|
@@ -86,13 +88,13 @@ module Praxis
|
|
|
86
88
|
|
|
87
89
|
@boundary = "--#{match[1]}"
|
|
88
90
|
|
|
89
|
-
@buf =
|
|
91
|
+
@buf = String.new
|
|
90
92
|
|
|
91
93
|
@params = new_params
|
|
92
94
|
|
|
93
95
|
@boundary_size = @boundary.bytesize + EOL.size
|
|
94
96
|
|
|
95
|
-
if @content_length = @headers['Content-Length']
|
|
97
|
+
if (@content_length = @headers['Content-Length'])
|
|
96
98
|
@content_length = @content_length.to_i
|
|
97
99
|
@content_length -= @boundary_size
|
|
98
100
|
end
|
|
@@ -119,36 +121,35 @@ module Praxis
|
|
|
119
121
|
end
|
|
120
122
|
|
|
121
123
|
def fast_forward_to_first_boundary
|
|
122
|
-
preamble =
|
|
124
|
+
preamble = String.new
|
|
123
125
|
loop do
|
|
124
126
|
content = @io.read(BUFSIZE)
|
|
125
|
-
raise EOFError,
|
|
127
|
+
raise EOFError, 'bad content body' unless content
|
|
128
|
+
|
|
126
129
|
@buf << content
|
|
127
130
|
|
|
128
131
|
while @buf.gsub!(UNTIL_NEWLINE, '')
|
|
129
|
-
read_buffer =
|
|
130
|
-
if read_buffer == full_boundary
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
preamble << read_buffer
|
|
134
|
-
end
|
|
132
|
+
read_buffer = Regexp.last_match(1)
|
|
133
|
+
return preamble.gsub!(TERMINAL_CRLF, '') if read_buffer == full_boundary
|
|
134
|
+
|
|
135
|
+
preamble << read_buffer
|
|
135
136
|
end
|
|
136
137
|
|
|
137
|
-
raise EOFError,
|
|
138
|
+
raise EOFError, 'bad content body' if Rack::Utils.bytesize(@buf) >= BUFSIZE
|
|
138
139
|
end
|
|
139
140
|
end
|
|
140
141
|
|
|
141
|
-
def
|
|
142
|
+
def current_head_and_filename_and_content_type_and_name_and_body
|
|
142
143
|
head = nil
|
|
143
|
-
body =
|
|
144
|
+
body = String.new
|
|
144
145
|
filename = content_type = name = nil
|
|
145
146
|
content = nil
|
|
146
147
|
|
|
147
148
|
until head && @buf =~ rx
|
|
148
|
-
if !head && i = @buf.index(EOL+EOL)
|
|
149
|
-
head = @buf.slice!(0, i+2) # First \r\n
|
|
149
|
+
if !head && (i = @buf.index(EOL + EOL))
|
|
150
|
+
head = @buf.slice!(0, i + 2) # First \r\n
|
|
150
151
|
|
|
151
|
-
@buf.slice!(0, 2)
|
|
152
|
+
@buf.slice!(0, 2) # Second \r\n
|
|
152
153
|
|
|
153
154
|
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
|
154
155
|
name = head[MULTIPART_CONTENT_DISPOSITION, 1] || head[MULTIPART_CONTENT_ID, 1]
|
|
@@ -157,7 +158,7 @@ module Praxis
|
|
|
157
158
|
filename = get_filename(head)
|
|
158
159
|
|
|
159
160
|
if filename
|
|
160
|
-
body = Tempfile.new(
|
|
161
|
+
body = Tempfile.new('RackMultipart')
|
|
161
162
|
body.binmode if body.respond_to?(:binmode)
|
|
162
163
|
end
|
|
163
164
|
|
|
@@ -165,12 +166,10 @@ module Praxis
|
|
|
165
166
|
end
|
|
166
167
|
|
|
167
168
|
# Save the read body part.
|
|
168
|
-
if head && (@boundary_size+4 < @buf.size)
|
|
169
|
-
body << @buf.slice!(0, @buf.size - (@boundary_size+4))
|
|
170
|
-
end
|
|
169
|
+
body << @buf.slice!(0, @buf.size - (@boundary_size + 4)) if head && (@boundary_size + 4 < @buf.size)
|
|
171
170
|
|
|
172
171
|
content = @io.read(@content_length && BUFSIZE >= @content_length ? @content_length : BUFSIZE)
|
|
173
|
-
raise EOFError,
|
|
172
|
+
raise EOFError, 'bad content body' if content.nil? || content.empty?
|
|
174
173
|
|
|
175
174
|
@buf << content
|
|
176
175
|
@content_length -= content.size if @content_length
|
|
@@ -183,34 +182,30 @@ module Praxis
|
|
|
183
182
|
filename = nil
|
|
184
183
|
if head =~ RFC2183
|
|
185
184
|
filename = Hash[head.scan(DISPPARM)]['filename']
|
|
186
|
-
filename =
|
|
185
|
+
filename = Regexp.last_match(1) if filename && filename =~ (/^"(.*)"$/)
|
|
187
186
|
elsif head =~ BROKEN_QUOTED
|
|
188
|
-
filename =
|
|
187
|
+
filename = Regexp.last_match(1)
|
|
189
188
|
elsif head =~ BROKEN_UNQUOTED
|
|
190
|
-
filename =
|
|
189
|
+
filename = Regexp.last_match(1)
|
|
191
190
|
end
|
|
192
191
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
end
|
|
196
|
-
if filename && filename !~ /\\[^\\"]/
|
|
197
|
-
filename = filename.gsub(/\\(.)/, '\1')
|
|
198
|
-
end
|
|
192
|
+
filename = Rack::Utils.unescape(filename) if filename&.scan(/%.?.?/)&.all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
|
|
193
|
+
filename = filename.gsub(/\\(.)/, '\1') if filename && filename !~ /\\[^\\"]/
|
|
199
194
|
filename
|
|
200
195
|
end
|
|
201
196
|
|
|
202
|
-
def get_data(filename, body,
|
|
197
|
+
def get_data(filename, body, _content_type, _name, _head)
|
|
203
198
|
# filename is blank which means no file has been selected
|
|
204
|
-
return nil if filename ==
|
|
199
|
+
return nil if filename == ''
|
|
205
200
|
|
|
206
201
|
# Take the basename of the upload's original filename.
|
|
207
202
|
# This handles the full Windows paths given by Internet Explorer
|
|
208
203
|
# (and perhaps other broken user agents) without affecting
|
|
209
204
|
# those which give the lone filename.
|
|
210
|
-
filename = filename.split(
|
|
205
|
+
filename = filename.split(%r{[/\\]}).last if filename
|
|
211
206
|
|
|
212
207
|
# Rewind any IO bodies so the app can read them at its leisure
|
|
213
|
-
body.rewind if
|
|
208
|
+
body.rewind if body.is_a?(IO)
|
|
214
209
|
|
|
215
210
|
[filename, body]
|
|
216
211
|
end
|
|
@@ -1,22 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Praxis
|
|
2
4
|
class MultipartPart
|
|
3
5
|
include Attributor::Type
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
attr_accessor :headers
|
|
7
|
-
attr_accessor :filename
|
|
8
|
-
attr_accessor :name
|
|
9
|
-
attr_accessor :payload_attribute
|
|
10
|
-
attr_accessor :headers_attribute
|
|
11
|
-
attr_accessor :filename_attribute
|
|
12
|
-
attr_accessor :default_handler
|
|
7
|
+
attr_reader :name, :filename
|
|
8
|
+
attr_accessor :body, :headers, :payload_attribute, :headers_attribute, :filename_attribute, :default_handler
|
|
13
9
|
|
|
14
10
|
def self.check_option!(name, definition)
|
|
15
11
|
case name
|
|
16
12
|
when :payload_attribute, :headers_attribute, :filename_attribute
|
|
17
|
-
unless definition.nil? || definition.
|
|
18
|
-
raise Attributor::AttributorException.new("Value for option #{name.inspect} must be an Attribute. Got #{definition.class.name}")
|
|
19
|
-
end
|
|
13
|
+
raise Attributor::AttributorException, "Value for option #{name.inspect} must be an Attribute. Got #{definition.class.name}" unless definition.nil? || definition.is_a?(Attributor::Attribute)
|
|
20
14
|
else
|
|
21
15
|
return :unknown
|
|
22
16
|
end
|
|
@@ -32,32 +26,30 @@ module Praxis
|
|
|
32
26
|
:object
|
|
33
27
|
end
|
|
34
28
|
|
|
35
|
-
def self.example(context=nil, options:{})
|
|
29
|
+
def self.example(context = nil, options: {})
|
|
36
30
|
if (payload_attribute = options[:payload_attribute])
|
|
37
31
|
payload = payload_attribute.example(context + ['payload'])
|
|
38
32
|
end
|
|
39
33
|
|
|
40
34
|
headers = if (headers_attribute = options[:headers_attribute])
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
headers_attribute.example(context + ['headers'])
|
|
36
|
+
else
|
|
37
|
+
{}
|
|
38
|
+
end
|
|
45
39
|
|
|
46
40
|
name = options[:name]
|
|
47
41
|
|
|
48
42
|
filename = if (filename_attribute = options[:filename_attribute])
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
nil
|
|
52
|
-
end
|
|
43
|
+
filename_attribute.example(context + ['filename'])
|
|
44
|
+
end
|
|
53
45
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
46
|
+
new(payload, headers, name: name, filename: filename,
|
|
47
|
+
payload_attribute: payload_attribute,
|
|
48
|
+
headers_attribute: headers_attribute,
|
|
49
|
+
filename_attribute: filename_attribute)
|
|
58
50
|
end
|
|
59
|
-
|
|
60
|
-
def self.describe(shallow=true, example: nil, options:{})
|
|
51
|
+
|
|
52
|
+
def self.describe(shallow = true, example: nil, options: {})
|
|
61
53
|
hash = super(shallow, example: example)
|
|
62
54
|
|
|
63
55
|
if (payload_attribute = options[:payload_attribute])
|
|
@@ -76,63 +68,49 @@ module Praxis
|
|
|
76
68
|
hash
|
|
77
69
|
end
|
|
78
70
|
|
|
79
|
-
def initialize(body, headers={},
|
|
80
|
-
@name = name
|
|
71
|
+
def initialize(body, headers = {}, **args)
|
|
72
|
+
@name = args[:name]
|
|
81
73
|
@body = body
|
|
82
74
|
@headers = headers
|
|
83
75
|
@default_handler = Praxis::Application.instance.handlers['json']
|
|
84
76
|
|
|
85
|
-
if content_type.nil?
|
|
86
|
-
self.content_type = 'text/plain'
|
|
87
|
-
end
|
|
77
|
+
self.content_type = 'text/plain' if content_type.nil?
|
|
88
78
|
|
|
89
|
-
@filename = filename
|
|
79
|
+
@filename = args[:filename]
|
|
90
80
|
|
|
91
|
-
@payload_attribute = payload_attribute
|
|
92
|
-
@headers_attribute = headers_attribute
|
|
93
|
-
@filename_attribute = filename_attribute
|
|
81
|
+
@payload_attribute = args[:payload_attribute]
|
|
82
|
+
@headers_attribute = args[:headers_attribute]
|
|
83
|
+
@filename_attribute = args[:filename_attribute]
|
|
94
84
|
|
|
95
85
|
reset_content_disposition
|
|
96
86
|
end
|
|
97
87
|
|
|
98
|
-
|
|
99
|
-
|
|
88
|
+
alias payload body
|
|
89
|
+
alias payload= body=
|
|
100
90
|
|
|
101
91
|
def attribute=(attribute)
|
|
102
|
-
unless
|
|
103
|
-
raise ArgumentError, "invalid attribute type #{attribute.type}"
|
|
104
|
-
end
|
|
92
|
+
raise ArgumentError, "invalid attribute type #{attribute.type}" unless is_a?(attribute.type)
|
|
105
93
|
|
|
106
|
-
if attribute.options.key? :payload_attribute
|
|
107
|
-
@payload_attribute = attribute.options[:payload_attribute]
|
|
108
|
-
end
|
|
94
|
+
@payload_attribute = attribute.options[:payload_attribute] if attribute.options.key? :payload_attribute
|
|
109
95
|
|
|
110
|
-
if attribute.options.key? :headers_attribute
|
|
111
|
-
@headers_attribute = attribute.options[:headers_attribute]
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
if attribute.options.key? :filename_attribute
|
|
115
|
-
@filename_attribute = attribute.options[:filename_attribute]
|
|
116
|
-
end
|
|
96
|
+
@headers_attribute = attribute.options[:headers_attribute] if attribute.options.key? :headers_attribute
|
|
117
97
|
|
|
98
|
+
@filename_attribute = attribute.options[:filename_attribute] if attribute.options.key? :filename_attribute
|
|
118
99
|
end
|
|
119
100
|
|
|
120
|
-
def load_payload(
|
|
121
|
-
|
|
122
|
-
value = if self.payload.kind_of?(String)
|
|
123
|
-
handler.parse(self.payload)
|
|
124
|
-
else
|
|
125
|
-
self.payload
|
|
126
|
-
end
|
|
101
|
+
def load_payload(_context = Attributor::DEFAULT_ROOT_CONTEXT)
|
|
102
|
+
return unless payload_attribute
|
|
127
103
|
|
|
128
|
-
|
|
129
|
-
|
|
104
|
+
value = if payload.is_a?(String)
|
|
105
|
+
handler.parse(payload)
|
|
106
|
+
else
|
|
107
|
+
payload
|
|
108
|
+
end
|
|
109
|
+
self.payload = payload_attribute.load(value)
|
|
130
110
|
end
|
|
131
111
|
|
|
132
|
-
def load_headers(
|
|
133
|
-
|
|
134
|
-
self.headers = self.headers_attribute.load(self.headers)
|
|
135
|
-
end
|
|
112
|
+
def load_headers(_context = Attributor::DEFAULT_ROOT_CONTEXT)
|
|
113
|
+
self.headers = headers_attribute.load(headers) if headers_attribute
|
|
136
114
|
end
|
|
137
115
|
|
|
138
116
|
# Determine the content type of this response.
|
|
@@ -166,42 +144,39 @@ module Praxis
|
|
|
166
144
|
end
|
|
167
145
|
end
|
|
168
146
|
|
|
169
|
-
def validate_headers(context=Attributor::DEFAULT_ROOT_CONTEXT)
|
|
170
|
-
return [] unless
|
|
147
|
+
def validate_headers(context = Attributor::DEFAULT_ROOT_CONTEXT)
|
|
148
|
+
return [] unless headers_attribute
|
|
171
149
|
|
|
172
|
-
|
|
150
|
+
headers_attribute.validate(headers, context + ['headers'])
|
|
173
151
|
end
|
|
174
152
|
|
|
175
|
-
def validate_payload(context=Attributor::DEFAULT_ROOT_CONTEXT)
|
|
176
|
-
return [] unless
|
|
153
|
+
def validate_payload(context = Attributor::DEFAULT_ROOT_CONTEXT)
|
|
154
|
+
return [] unless payload_attribute
|
|
177
155
|
|
|
178
|
-
|
|
156
|
+
payload_attribute.validate(payload, context + ['payload'])
|
|
179
157
|
end
|
|
180
158
|
|
|
181
|
-
def validate_filename(context=Attributor::DEFAULT_ROOT_CONTEXT)
|
|
182
|
-
return [] unless
|
|
159
|
+
def validate_filename(context = Attributor::DEFAULT_ROOT_CONTEXT)
|
|
160
|
+
return [] unless filename_attribute
|
|
183
161
|
|
|
184
|
-
|
|
162
|
+
filename_attribute.validate(filename, context + ['filename'])
|
|
185
163
|
end
|
|
186
164
|
|
|
187
|
-
def validate(context=Attributor::DEFAULT_ROOT_CONTEXT)
|
|
165
|
+
def validate(context = Attributor::DEFAULT_ROOT_CONTEXT)
|
|
188
166
|
errors = validate_headers(context)
|
|
189
|
-
errors.push
|
|
190
|
-
errors.push
|
|
167
|
+
errors.push(*validate_payload(context))
|
|
168
|
+
errors.push(*validate_filename(context))
|
|
191
169
|
end
|
|
192
170
|
|
|
193
171
|
def reset_content_disposition
|
|
194
|
-
|
|
172
|
+
headers['Content-Disposition'] = begin
|
|
195
173
|
disposition = "form-data; name=#{name}"
|
|
196
|
-
if filename
|
|
197
|
-
disposition += "; filename=#{filename}"
|
|
198
|
-
end
|
|
174
|
+
disposition += "; filename=#{filename}" if filename
|
|
199
175
|
|
|
200
176
|
disposition
|
|
201
177
|
end
|
|
202
178
|
end
|
|
203
179
|
|
|
204
|
-
|
|
205
180
|
def name=(name)
|
|
206
181
|
@name = name
|
|
207
182
|
reset_content_disposition
|
|
@@ -221,25 +196,23 @@ module Praxis
|
|
|
221
196
|
|
|
222
197
|
# Determine an appropriate default content_type for this part given
|
|
223
198
|
# the preferred handler_name, if possible.
|
|
224
|
-
#
|
|
199
|
+
#
|
|
225
200
|
# Considers any pre-defined set of values on the content_type attributge
|
|
226
201
|
# of the headers.
|
|
227
202
|
def derive_content_type(handler_name)
|
|
228
|
-
possible_values = if
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
203
|
+
possible_values = if content_type.match 'text/plain'
|
|
204
|
+
_, content_type_attribute = headers_attribute&.attributes&.find { |k, _v| k.to_s =~ /^content[-_]{1}type$/i }
|
|
205
|
+
if content_type_attribute&.options&.key?(:values)
|
|
206
|
+
content_type_attribute.options[:values]
|
|
207
|
+
else
|
|
208
|
+
[]
|
|
209
|
+
end
|
|
210
|
+
else
|
|
211
|
+
[content_type]
|
|
212
|
+
end
|
|
238
213
|
|
|
239
214
|
# generic default encoding is the best we can do
|
|
240
|
-
if possible_values.empty?
|
|
241
|
-
return MediaTypeIdentifier.load("application/#{handler_name}")
|
|
242
|
-
end
|
|
215
|
+
return MediaTypeIdentifier.load("application/#{handler_name}") if possible_values.empty?
|
|
243
216
|
|
|
244
217
|
# if any defined value match the preferred handler_name, return it
|
|
245
218
|
possible_values.each do |ct|
|
|
@@ -253,38 +226,33 @@ module Praxis
|
|
|
253
226
|
# and return that one if it already corresponds to a registered handler
|
|
254
227
|
# otherwise, add the encoding
|
|
255
228
|
if Praxis::Application.instance.handlers.include?(pick.handler_name)
|
|
256
|
-
|
|
229
|
+
pick
|
|
257
230
|
else
|
|
258
|
-
|
|
231
|
+
pick + handler_name
|
|
259
232
|
end
|
|
260
|
-
|
|
261
233
|
end
|
|
262
234
|
|
|
263
235
|
def dump(default_format: nil, **opts)
|
|
264
|
-
original_content_type =
|
|
236
|
+
original_content_type = content_type
|
|
265
237
|
|
|
266
|
-
body =
|
|
238
|
+
body = payload_attribute.dump(payload, **opts)
|
|
267
239
|
|
|
268
240
|
body_string = case body
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
self.content_type = derive_content_type(default_format)
|
|
272
|
-
end
|
|
241
|
+
when Hash, Array
|
|
242
|
+
self.content_type = derive_content_type(default_format) if default_format
|
|
273
243
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
244
|
+
handler.generate(body)
|
|
245
|
+
else
|
|
246
|
+
body
|
|
247
|
+
end
|
|
278
248
|
|
|
279
|
-
header_string =
|
|
249
|
+
header_string = headers.collect do |name, value|
|
|
280
250
|
"#{name}: #{value}"
|
|
281
251
|
end.join("\r\n")
|
|
282
252
|
|
|
283
|
-
|
|
284
253
|
"#{header_string}\r\n\r\n#{body_string}"
|
|
285
254
|
ensure
|
|
286
255
|
self.content_type = original_content_type
|
|
287
256
|
end
|
|
288
|
-
|
|
289
257
|
end
|
|
290
258
|
end
|
data/lib/praxis/notifications.rb
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_support/notifications'
|
|
2
4
|
|
|
3
5
|
require 'singleton'
|
|
4
6
|
|
|
5
7
|
module Praxis
|
|
6
|
-
|
|
7
8
|
module Notifications
|
|
8
9
|
include Praxis::PluginConcern
|
|
9
10
|
|
|
@@ -13,7 +14,6 @@ module Praxis
|
|
|
13
14
|
def config_key
|
|
14
15
|
:notifications # 'praxis.notifications'
|
|
15
16
|
end
|
|
16
|
-
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def self.publish(name, *args)
|
|
@@ -35,7 +35,5 @@ module Praxis
|
|
|
35
35
|
def self.unsubscribe(subscriber_or_name)
|
|
36
36
|
ActiveSupport::Notifications.unsubscribe(subscriber_or_name)
|
|
37
37
|
end
|
|
38
|
-
|
|
39
|
-
|
|
40
38
|
end
|
|
41
39
|
end
|
data/lib/praxis/plugin.rb
CHANGED
|
@@ -1,44 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
module Praxis
|
|
3
4
|
# one instance is created per use.
|
|
4
5
|
class Plugin
|
|
5
|
-
|
|
6
|
-
attr_accessor :application
|
|
7
|
-
attr_accessor :block
|
|
8
|
-
attr_accessor :config
|
|
9
|
-
attr_accessor :config_attribute
|
|
6
|
+
attr_accessor :application, :block, :config, :config_attribute
|
|
10
7
|
|
|
11
8
|
def options
|
|
12
9
|
@options ||= {}
|
|
13
10
|
end
|
|
14
11
|
|
|
15
|
-
def config_key
|
|
16
|
-
end
|
|
12
|
+
def config_key; end
|
|
17
13
|
|
|
18
|
-
def prepare_config!(node)
|
|
19
|
-
end
|
|
14
|
+
def prepare_config!(node); end
|
|
20
15
|
|
|
21
16
|
def load_config!
|
|
22
|
-
return unless options.
|
|
17
|
+
return unless options.key?(:config_file)
|
|
23
18
|
return {} unless (application.root + options[:config_file]).exist?
|
|
24
19
|
|
|
25
20
|
YAML.load_file(application.root + options[:config_file])
|
|
26
21
|
end
|
|
27
22
|
|
|
28
|
-
def setup
|
|
29
|
-
end
|
|
23
|
+
def setup!; end
|
|
30
24
|
|
|
31
25
|
def register_doc_browser_plugin(path)
|
|
32
26
|
application.doc_browser_plugin_paths << File.expand_path(path)
|
|
33
27
|
end
|
|
34
28
|
|
|
35
|
-
def after(stage
|
|
36
|
-
application.bootloader.after(stage
|
|
29
|
+
def after(stage, &block)
|
|
30
|
+
application.bootloader.after(stage, &block)
|
|
37
31
|
end
|
|
38
32
|
|
|
39
|
-
def before(stage
|
|
40
|
-
application.bootloader.before(stage
|
|
33
|
+
def before(stage, &block)
|
|
34
|
+
application.bootloader.before(stage, &block)
|
|
41
35
|
end
|
|
42
|
-
|
|
43
36
|
end
|
|
44
37
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
module Praxis
|
|
3
4
|
module PluginConcern
|
|
4
5
|
extend ::ActiveSupport::Concern
|
|
5
6
|
|
|
@@ -8,35 +9,31 @@ module Praxis
|
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
module ClassMethods
|
|
11
|
-
PLUGIN_CLASSES = [
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
]
|
|
12
|
+
PLUGIN_CLASSES = %i[
|
|
13
|
+
Request
|
|
14
|
+
Controller
|
|
15
|
+
EndpointDefinition
|
|
16
|
+
ActionDefinition
|
|
17
|
+
Response
|
|
18
|
+
ApiGeneralInfo
|
|
19
|
+
].freeze
|
|
19
20
|
|
|
20
21
|
def setup!
|
|
21
22
|
return if @setup
|
|
22
23
|
|
|
23
24
|
PLUGIN_CLASSES.each do |name|
|
|
24
|
-
if
|
|
25
|
-
inject!(name)
|
|
26
|
-
end
|
|
25
|
+
inject!(name) if constants.include?(name)
|
|
27
26
|
end
|
|
28
27
|
|
|
29
28
|
@setup = true
|
|
30
29
|
end
|
|
31
30
|
|
|
32
31
|
def inject!(name)
|
|
33
|
-
plugin =
|
|
32
|
+
plugin = const_get(name)
|
|
34
33
|
praxis = Praxis.const_get(name)
|
|
35
34
|
|
|
36
35
|
praxis.include(plugin)
|
|
37
36
|
end
|
|
38
|
-
|
|
39
37
|
end
|
|
40
38
|
end
|
|
41
|
-
|
|
42
39
|
end
|