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,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
|