praxis 2.0.pre.17 → 2.0.pre.21
Sign up to get free protection for your applications and to get access to all the features.
- 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,9 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Praxis
|
2
4
|
module RequestStages
|
3
|
-
|
4
5
|
class Validate < RequestStage
|
5
|
-
|
6
|
-
def initialize(name, context,**opts)
|
6
|
+
def initialize(name, context, **opts)
|
7
7
|
super
|
8
8
|
# Add our sub-stages
|
9
9
|
@stages = [
|
@@ -11,8 +11,6 @@ module Praxis
|
|
11
11
|
RequestStages::ValidatePayload.new(:payload, context, parent: self)
|
12
12
|
]
|
13
13
|
end
|
14
|
-
|
15
14
|
end
|
16
|
-
|
17
15
|
end
|
18
16
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Praxis
|
2
4
|
module RequestStages
|
3
|
-
|
4
5
|
class ValidateParamsAndHeaders < RequestStage
|
5
6
|
attr_reader :parent
|
6
7
|
|
@@ -10,14 +11,14 @@ module Praxis
|
|
10
11
|
end
|
11
12
|
|
12
13
|
def path
|
13
|
-
@
|
14
|
+
@path ||= (@parent.path + [name])
|
14
15
|
end
|
15
16
|
|
16
17
|
def execute
|
17
18
|
begin
|
18
19
|
request.load_headers(CONTEXT_FOR[:headers])
|
19
|
-
rescue => e
|
20
|
-
message =
|
20
|
+
rescue StandardError => e
|
21
|
+
message = 'Error loading headers.'
|
21
22
|
return validation_handler.handle!(
|
22
23
|
exception: e,
|
23
24
|
summary: message,
|
@@ -29,7 +30,7 @@ module Praxis
|
|
29
30
|
begin
|
30
31
|
request.load_params(CONTEXT_FOR[:params])
|
31
32
|
rescue Attributor::AttributorException => e
|
32
|
-
message =
|
33
|
+
message = 'Error loading params.'
|
33
34
|
return validation_handler.handle!(
|
34
35
|
exception: e,
|
35
36
|
summary: message,
|
@@ -38,26 +39,18 @@ module Praxis
|
|
38
39
|
)
|
39
40
|
end
|
40
41
|
|
41
|
-
attribute_resolver = Attributor::AttributeResolver.new
|
42
|
-
Attributor::AttributeResolver.current = attribute_resolver
|
43
|
-
|
44
|
-
attribute_resolver.register("headers",request.headers)
|
45
|
-
attribute_resolver.register("params",request.params)
|
46
|
-
|
47
42
|
errors = request.validate_headers(CONTEXT_FOR[:headers])
|
48
43
|
errors += request.validate_params(CONTEXT_FOR[:params])
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
44
|
+
return unless errors.any?
|
45
|
+
|
46
|
+
message = 'Error validating request data.'
|
47
|
+
validation_handler.handle!(
|
48
|
+
summary: message,
|
49
|
+
errors: errors,
|
50
|
+
request: request,
|
51
|
+
stage: name
|
52
|
+
)
|
58
53
|
end
|
59
|
-
|
60
54
|
end
|
61
|
-
|
62
55
|
end
|
63
56
|
end
|
@@ -1,8 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Praxis
|
2
4
|
module RequestStages
|
3
|
-
|
4
5
|
class ValidatePayload < RequestStage
|
5
|
-
|
6
6
|
attr_reader :parent
|
7
7
|
|
8
8
|
def initialize(name, context, parent:)
|
@@ -11,37 +11,34 @@ module Praxis
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def path
|
14
|
-
@
|
14
|
+
@path ||= (@parent.path + [name])
|
15
15
|
end
|
16
16
|
|
17
17
|
def execute
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
Attributor::AttributeResolver.current.register("payload",request.payload)
|
31
|
-
|
32
|
-
errors = request.validate_payload(CONTEXT_FOR[:payload])
|
33
|
-
if errors.any?
|
34
|
-
return validation_handler.handle!(
|
35
|
-
summary: "Errors validating payload data",
|
36
|
-
errors: errors,
|
37
|
-
request: request,
|
38
|
-
stage: name
|
39
|
-
)
|
40
|
-
end
|
18
|
+
return unless request.action.payload
|
19
|
+
|
20
|
+
begin
|
21
|
+
request.load_payload(CONTEXT_FOR[:payload])
|
22
|
+
rescue StandardError => e
|
23
|
+
message = "Error loading payload. Used Content-Type: '#{request.content_type}'"
|
24
|
+
return validation_handler.handle!(
|
25
|
+
exception: e,
|
26
|
+
summary: message,
|
27
|
+
request: request,
|
28
|
+
stage: name
|
29
|
+
)
|
41
30
|
end
|
42
|
-
end
|
43
31
|
|
44
|
-
|
32
|
+
errors = request.validate_payload(CONTEXT_FOR[:payload])
|
33
|
+
return unless errors.any?
|
45
34
|
|
35
|
+
validation_handler.handle!(
|
36
|
+
summary: 'Errors validating payload data',
|
37
|
+
errors: errors,
|
38
|
+
request: request,
|
39
|
+
stage: name
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
46
43
|
end
|
47
44
|
end
|
data/lib/praxis/response.rb
CHANGED
@@ -1,31 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Praxis
|
2
4
|
class Response
|
3
|
-
attr_reader :name
|
4
|
-
attr_reader :parts
|
5
|
+
attr_reader :name, :parts
|
5
6
|
|
6
|
-
attr_accessor :status
|
7
|
-
attr_accessor :headers
|
8
|
-
attr_accessor :body
|
9
|
-
attr_accessor :request
|
7
|
+
attr_accessor :status, :headers, :body, :request
|
10
8
|
|
11
9
|
class << self
|
12
|
-
attr_accessor :response_name
|
13
|
-
attr_accessor :status
|
10
|
+
attr_accessor :response_name, :status
|
14
11
|
end
|
15
12
|
|
16
13
|
def self.inherited(klass)
|
17
14
|
klass.response_name = klass.name.demodulize.underscore.to_sym
|
18
|
-
klass.status =
|
15
|
+
klass.status = status if status
|
19
16
|
end
|
20
17
|
|
21
|
-
def initialize(status:self.class.status, headers:{}, body: nil, location: nil)
|
18
|
+
def initialize(status: self.class.status, headers: {}, body: nil, location: nil)
|
22
19
|
@name = response_name
|
23
20
|
@status = status
|
24
21
|
@headers = headers
|
25
22
|
@body = body
|
26
23
|
@headers['Location'] = location if location
|
27
24
|
@form_data = nil
|
28
|
-
@parts =
|
25
|
+
@parts = {}
|
29
26
|
end
|
30
27
|
|
31
28
|
# Determine the content type of this response.
|
@@ -44,10 +41,9 @@ module Praxis
|
|
44
41
|
headers['Content-Type'] = MediaTypeIdentifier.load(identifier).to_s
|
45
42
|
end
|
46
43
|
|
47
|
-
def handle
|
48
|
-
end
|
44
|
+
def handle; end
|
49
45
|
|
50
|
-
def add_part(name=nil
|
46
|
+
def add_part(part, name = nil)
|
51
47
|
@form_data ||= begin
|
52
48
|
form = MIME::Multipart::FormData.new
|
53
49
|
@headers.merge! form.headers.headers
|
@@ -63,8 +59,7 @@ module Praxis
|
|
63
59
|
self.class.response_name
|
64
60
|
end
|
65
61
|
|
66
|
-
def format
|
67
|
-
end
|
62
|
+
def format!; end
|
68
63
|
|
69
64
|
def encode!
|
70
65
|
case @body
|
@@ -85,11 +80,7 @@ module Praxis
|
|
85
80
|
@body = Array(@body)
|
86
81
|
|
87
82
|
if @form_data
|
88
|
-
if @body.any?
|
89
|
-
unless @body.last =~ /\n$/
|
90
|
-
@body << "\r\n"
|
91
|
-
end
|
92
|
-
end
|
83
|
+
@body << "\r\n" if @body.any? && @body.last !~ /\n$/
|
93
84
|
|
94
85
|
@parts.each do |name, part|
|
95
86
|
part.encode!
|
@@ -108,7 +99,6 @@ module Praxis
|
|
108
99
|
[@status, @headers, @body]
|
109
100
|
end
|
110
101
|
|
111
|
-
|
112
102
|
# Validates the response
|
113
103
|
#
|
114
104
|
# @param [Object] action
|
@@ -118,12 +108,20 @@ module Praxis
|
|
118
108
|
|
119
109
|
unless (response_definition = action.responses[response_name])
|
120
110
|
raise Exceptions::Validation, "Attempting to return a response with name #{response_name} " \
|
121
|
-
|
111
|
+
'but no response definition with that name can be found'
|
122
112
|
end
|
123
|
-
|
124
113
|
response_definition.validate(self, validate_body: validate_body)
|
114
|
+
rescue Exceptions::Validation => e
|
115
|
+
ve = Application.instance.validation_handler.handle!(
|
116
|
+
summary: 'Error validating response',
|
117
|
+
exception: e,
|
118
|
+
request: request,
|
119
|
+
stage: 'response',
|
120
|
+
errors: e.errors
|
121
|
+
)
|
122
|
+
body = ve.format!
|
123
|
+
|
124
|
+
Responses::InternalServerError.new(body: body)
|
125
125
|
end
|
126
|
-
|
127
|
-
|
128
126
|
end
|
129
127
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
module Praxis
|
3
4
|
# Response spec DSL container
|
@@ -6,57 +7,46 @@ module Praxis
|
|
6
7
|
attr_reader :name
|
7
8
|
|
8
9
|
def initialize(response_name, **spec, &block)
|
9
|
-
unless response_name
|
10
|
-
|
11
|
-
|
12
|
-
)
|
13
|
-
end
|
14
|
-
@spec = { headers:{} }
|
10
|
+
raise Exceptions::InvalidConfiguration, 'Response name is required for a response specification' unless response_name
|
11
|
+
|
12
|
+
@spec = { headers: {} }
|
15
13
|
@name = response_name
|
16
|
-
|
14
|
+
instance_exec(**spec, &block) if block_given?
|
17
15
|
|
18
|
-
if
|
19
|
-
raise Exceptions::InvalidConfiguration.new(
|
20
|
-
"Status code is required for a response specification"
|
21
|
-
)
|
22
|
-
end
|
16
|
+
raise Exceptions::InvalidConfiguration, 'Status code is required for a response specification' if status.nil?
|
23
17
|
end
|
24
18
|
|
25
|
-
def description(text=nil)
|
19
|
+
def description(text = nil)
|
26
20
|
return @spec[:description] if text.nil?
|
21
|
+
|
27
22
|
@spec[:description] = text
|
28
23
|
end
|
29
24
|
|
30
|
-
def status(code=nil)
|
25
|
+
def status(code = nil)
|
31
26
|
return @spec[:status] if code.nil?
|
27
|
+
|
32
28
|
@spec[:status] = code
|
33
29
|
end
|
34
30
|
|
35
|
-
def media_type(media_type=nil)
|
31
|
+
def media_type(media_type = nil)
|
36
32
|
return @spec[:media_type] if media_type.nil?
|
37
33
|
|
38
34
|
@spec[:media_type] = case media_type
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
media_type
|
51
|
-
else
|
52
|
-
raise Exceptions::InvalidConfiguration.new(
|
53
|
-
'Invalid media_type specification. media_type must be a String, MediaType or SimpleMediaType'
|
54
|
-
)
|
55
|
-
end
|
35
|
+
when String
|
36
|
+
SimpleMediaType.new(media_type)
|
37
|
+
when Class
|
38
|
+
raise Exceptions::InvalidConfiguration, 'Invalid media_type specification. media_type must be a Praxis::MediaType' unless media_type < Praxis::Types::MediaTypeCommon
|
39
|
+
|
40
|
+
media_type
|
41
|
+
when SimpleMediaType
|
42
|
+
media_type
|
43
|
+
else
|
44
|
+
raise Exceptions::InvalidConfiguration, 'Invalid media_type specification. media_type must be a String, MediaType or SimpleMediaType'
|
45
|
+
end
|
56
46
|
end
|
57
47
|
|
58
|
-
def location(loc=nil, description: nil)
|
59
|
-
return
|
48
|
+
def location(loc = nil, description: nil)
|
49
|
+
return headers.dig('Location', :value) if loc.nil?
|
60
50
|
|
61
51
|
header('Location', loc, description: description)
|
62
52
|
end
|
@@ -67,101 +57,88 @@ module Praxis
|
|
67
57
|
|
68
58
|
def header(name, value, description: nil)
|
69
59
|
the_type, args = case value
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
info = {
|
83
|
-
value: value,
|
60
|
+
when nil, String
|
61
|
+
[String, {}]
|
62
|
+
when Regexp
|
63
|
+
# A regexp means it's gonna be a String typed, attached to a regexp
|
64
|
+
[String, { regexp: value }]
|
65
|
+
else
|
66
|
+
raise Exceptions::InvalidConfiguration, 'A header definition for a response can only take String, Regexp or nil values (to match anything).' \
|
67
|
+
"Received the following value for header name #{name}: #{value}"
|
68
|
+
end
|
69
|
+
|
70
|
+
info = {
|
71
|
+
value: value,
|
84
72
|
attribute: Attributor::Attribute.new(the_type, **args)
|
85
73
|
}
|
86
74
|
info[:description] = description if description
|
87
75
|
@spec[:headers][name] = info
|
88
76
|
end
|
89
77
|
|
90
|
-
def example(context=nil)
|
91
|
-
return nil if
|
92
|
-
return nil if
|
78
|
+
def example(context = nil)
|
79
|
+
return nil if media_type.nil?
|
80
|
+
return nil if media_type.is_a?(SimpleMediaType)
|
93
81
|
|
94
|
-
if context.nil?
|
95
|
-
context = "#{self.media_type.name}-#{self.name}"
|
96
|
-
end
|
82
|
+
context = "#{media_type.name}-#{name}" if context.nil?
|
97
83
|
|
98
|
-
|
84
|
+
media_type.example(context)
|
99
85
|
end
|
100
86
|
|
101
|
-
|
102
87
|
def describe(context: nil)
|
103
|
-
location_type = location.is_a?(Regexp) ? :regexp : :string
|
104
|
-
location_value = location.is_a?(Regexp) ? location.inspect : location
|
105
88
|
content = {
|
106
|
-
:
|
107
|
-
:
|
108
|
-
:
|
89
|
+
description: description,
|
90
|
+
status: status,
|
91
|
+
headers: {}
|
109
92
|
}
|
110
93
|
|
111
|
-
|
112
|
-
headers
|
113
|
-
content[:headers][name] = _describe_header(value)
|
114
|
-
end
|
94
|
+
headers&.each do |name, value|
|
95
|
+
content[:headers][name] = _describe_header(value)
|
115
96
|
end
|
116
97
|
content[:location] = content[:headers]['Location']
|
117
98
|
|
118
|
-
|
119
|
-
if self.media_type
|
99
|
+
if media_type
|
120
100
|
payload = media_type.describe(true)
|
121
101
|
|
122
|
-
if (example_payload =
|
102
|
+
if (example_payload = example(context))
|
123
103
|
payload[:examples] = {}
|
124
104
|
rendered_payload = example_payload.dump
|
125
105
|
|
126
106
|
# FIXME: remove load when when MediaTypeCommon.identifier returns a MediaTypeIdentifier
|
127
|
-
identifier = MediaTypeIdentifier.load(
|
107
|
+
identifier = MediaTypeIdentifier.load(media_type.identifier)
|
128
108
|
|
129
109
|
default_handlers = ApiDefinition.instance.info.produces
|
130
110
|
|
131
|
-
handlers = Praxis::Application.instance.handlers.select do |k,
|
111
|
+
handlers = Praxis::Application.instance.handlers.select do |k, _v|
|
132
112
|
default_handlers.include?(k)
|
133
113
|
end
|
134
114
|
|
135
|
-
if
|
115
|
+
if identifier && (handler = handlers[identifier.handler_name])
|
136
116
|
payload[:examples][identifier.handler_name] = {
|
137
117
|
content_type: identifier.to_s,
|
138
118
|
body: handler.generate(rendered_payload)
|
139
119
|
}
|
140
120
|
else
|
141
|
-
handlers.each do |name,
|
142
|
-
content_type =
|
121
|
+
handlers.each do |name, handler_class|
|
122
|
+
content_type = identifier ? identifier + name : "application/#{name}"
|
143
123
|
payload[:examples][name] = {
|
144
124
|
content_type: content_type.to_s,
|
145
|
-
body:
|
125
|
+
body: handler_class.generate(rendered_payload)
|
146
126
|
}
|
147
127
|
end
|
148
128
|
end
|
149
129
|
end
|
150
130
|
|
151
|
-
content[:payload] = {type: payload}
|
131
|
+
content[:payload] = { type: payload }
|
152
132
|
end
|
153
133
|
|
154
|
-
|
155
|
-
content[:parts_like] = parts.describe
|
156
|
-
end
|
134
|
+
content[:parts_like] = parts.describe unless parts.nil?
|
157
135
|
content
|
158
136
|
end
|
159
137
|
|
160
138
|
def _describe_header(data)
|
161
|
-
|
162
139
|
data_type = data[:value].is_a?(Regexp) ? :regexp : :string
|
163
140
|
data_value = data[:value].is_a?(Regexp) ? data[:value].inspect : data[:value]
|
164
|
-
{ :
|
141
|
+
{ value: data_value, type: data_type }
|
165
142
|
end
|
166
143
|
|
167
144
|
def validate(response, validate_body: false)
|
@@ -170,20 +147,18 @@ module Praxis
|
|
170
147
|
validate_content_type!(response)
|
171
148
|
validate_parts!(response)
|
172
149
|
|
173
|
-
if validate_body
|
174
|
-
validate_body!(response)
|
175
|
-
end
|
150
|
+
validate_body!(response) if validate_body
|
176
151
|
end
|
177
152
|
|
178
|
-
def parts(proc=nil, like: nil,
|
153
|
+
def parts(proc = nil, like: nil, **args, &block)
|
179
154
|
a_proc = proc || block
|
180
155
|
if like.nil? && !a_proc
|
181
|
-
raise ArgumentError, "Parts definition for response #{name} needs a :like argument or a block/proc"
|
156
|
+
raise ArgumentError, "Parts definition for response #{name} needs a :like argument or a block/proc" unless args.empty?
|
157
|
+
|
182
158
|
return @parts
|
183
159
|
end
|
184
|
-
if like && a_proc
|
185
|
-
|
186
|
-
end
|
160
|
+
raise ArgumentError, "Parts definition for response #{name} does not allow :like and a block simultaneously" if like && a_proc
|
161
|
+
|
187
162
|
if like
|
188
163
|
template = ApiDefinition.instance.response(like)
|
189
164
|
@parts = template.compile(nil, **args)
|
@@ -198,13 +173,9 @@ module Praxis
|
|
198
173
|
#
|
199
174
|
def validate_status!(response)
|
200
175
|
return unless status
|
176
|
+
|
201
177
|
# Validate status code if defined in the spec
|
202
|
-
if response.status != status
|
203
|
-
raise Exceptions::Validation.new(
|
204
|
-
"Invalid response code detected. Response %s dictates status of %s but this response is returning %s." %
|
205
|
-
[name, status.inspect, response.status.inspect]
|
206
|
-
)
|
207
|
-
end
|
178
|
+
raise Exceptions::Validation, format('Invalid response code detected. Response %<name>s dictates status of %<status>s but this response is returning %<response>s.', name: name, status: status.inspect, response: response.status.inspect) if response.status != status
|
208
179
|
end
|
209
180
|
|
210
181
|
# Validates Headers
|
@@ -215,23 +186,13 @@ module Praxis
|
|
215
186
|
return unless headers
|
216
187
|
|
217
188
|
headers.each do |name, value|
|
218
|
-
if name.is_a? Symbol
|
219
|
-
raise Exceptions::Validation.new(
|
220
|
-
"Symbols are not supported in headers"
|
221
|
-
)
|
222
|
-
end
|
189
|
+
raise Exceptions::Validation, 'Symbols are not supported in headers' if name.is_a? Symbol
|
223
190
|
|
224
|
-
unless response.headers.
|
225
|
-
raise Exceptions::Validation.new(
|
226
|
-
"Header #{name.inspect} was required but is missing"
|
227
|
-
)
|
228
|
-
end
|
191
|
+
raise Exceptions::Validation, "Header #{name.inspect} was required but is missing" unless response.headers.key?(name)
|
229
192
|
|
230
193
|
errors = value[:attribute].validate(response.headers[name])
|
231
194
|
|
232
|
-
unless errors.empty?
|
233
|
-
raise Exceptions::Validation.new("Header #{name.inspect}, with value #{value.inspect} does not match #{response.headers[name]}.")
|
234
|
-
end
|
195
|
+
raise Exceptions::Validation, "Header #{name.inspect}, with value #{value.inspect} does not match #{response.headers[name]}." unless errors.empty?
|
235
196
|
# case value
|
236
197
|
# when String
|
237
198
|
# if response.headers[name] != value
|
@@ -249,7 +210,6 @@ module Praxis
|
|
249
210
|
end
|
250
211
|
end
|
251
212
|
|
252
|
-
|
253
213
|
# Validates Content-Type header and response media type
|
254
214
|
#
|
255
215
|
# @param [Object] response
|
@@ -261,13 +221,10 @@ module Praxis
|
|
261
221
|
|
262
222
|
response_content_type = response.content_type
|
263
223
|
expected_content_type = Praxis::MediaTypeIdentifier.load(media_type.identifier)
|
224
|
+
return if expected_content_type.match(response_content_type)
|
264
225
|
|
265
|
-
|
266
|
-
|
267
|
-
"Bad Content-Type header. #{response_content_type}" +
|
268
|
-
" is incompatible with #{expected_content_type} as described in response: #{self.name}"
|
269
|
-
)
|
270
|
-
end
|
226
|
+
raise Exceptions::Validation, "Bad Content-Type header. #{response_content_type}" \
|
227
|
+
" is incompatible with #{expected_content_type} as described in response: #{name}"
|
271
228
|
end
|
272
229
|
|
273
230
|
# Validates response body
|
@@ -277,15 +234,14 @@ module Praxis
|
|
277
234
|
# @raise [Exceptions::Validation] When there is a missing required header..
|
278
235
|
def validate_body!(response)
|
279
236
|
return unless media_type
|
280
|
-
return if media_type.
|
237
|
+
return if media_type.is_a? SimpleMediaType
|
281
238
|
|
282
|
-
errors =
|
283
|
-
|
284
|
-
message = "Invalid response body for #{media_type.identifier}." +
|
285
|
-
"Errors: #{errors.inspect}"
|
286
|
-
raise Exceptions::Validation.new(message, errors: errors)
|
287
|
-
end
|
239
|
+
errors = media_type.validate(media_type.load(response.body))
|
240
|
+
return unless errors.any?
|
288
241
|
|
242
|
+
message = "Invalid response body for #{media_type.identifier}." \
|
243
|
+
"Errors: #{errors.inspect}"
|
244
|
+
raise Exceptions::Validation.new(message, errors: errors)
|
289
245
|
end
|
290
246
|
|
291
247
|
def validate_parts!(response)
|
@@ -295,6 +251,5 @@ module Praxis
|
|
295
251
|
parts.validate(part)
|
296
252
|
end
|
297
253
|
end
|
298
|
-
|
299
254
|
end
|
300
255
|
end
|
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module Praxis
|
3
4
|
class ResponseTemplate
|
4
5
|
attr_reader :name, :block
|
5
6
|
|
@@ -8,27 +9,22 @@ module Praxis
|
|
8
9
|
@block = block
|
9
10
|
end
|
10
11
|
|
11
|
-
def compile(action=nil, **args)
|
12
|
+
def compile(action = nil, **args)
|
12
13
|
# Default media_type to the endpoint_definition one, if the block has it in
|
13
14
|
# its required args but no value is passed (funky, but can help in the common case)
|
14
|
-
if block.parameters.any? { |(type, name)| name == :media_type && type == :keyreq } && action
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
"Could not default :media_type argument for response template #{@name}." +
|
20
|
-
" Endpoint #{action.endpoint_definition} does not have an associated mediatype and none was passed"
|
21
|
-
)
|
22
|
-
end
|
23
|
-
args[:media_type] = media_type
|
15
|
+
if block.parameters.any? { |(type, name)| name == :media_type && type == :keyreq } && action && !(args.key? :media_type)
|
16
|
+
media_type = action.endpoint_definition.media_type
|
17
|
+
unless media_type
|
18
|
+
raise Exceptions::InvalidConfiguration, "Could not default :media_type argument for response template #{@name}." \
|
19
|
+
" Endpoint #{action.endpoint_definition} does not have an associated mediatype and none was passed"
|
24
20
|
end
|
21
|
+
args[:media_type] = media_type
|
25
22
|
end
|
26
|
-
Praxis::ResponseDefinition.new(name, **args, &block)
|
23
|
+
Praxis::ResponseDefinition.new(name, **args, &block)
|
27
24
|
end
|
28
25
|
|
29
26
|
def describe
|
30
|
-
puts
|
27
|
+
puts 'TODO!!!!!!'
|
31
28
|
end
|
32
29
|
end
|
33
|
-
|
34
30
|
end
|