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,8 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'singleton'
|
|
2
4
|
|
|
3
5
|
require 'praxis/extensions/field_selection'
|
|
4
6
|
|
|
5
|
-
module Praxis
|
|
7
|
+
module Praxis
|
|
6
8
|
module Plugins
|
|
7
9
|
module MapperPlugin
|
|
8
10
|
include Praxis::PluginConcern
|
|
@@ -15,7 +17,7 @@ module Praxis
|
|
|
15
17
|
#
|
|
16
18
|
# The plugin accepts only 1 configuration option thus far, which you can set inside the same block as:
|
|
17
19
|
# application.config.mapper.debug_queries = true
|
|
18
|
-
# when debug_queries is set to true, the system will output information about the expanded fields
|
|
20
|
+
# when debug_queries is set to true, the system will output information about the expanded fields
|
|
19
21
|
# and associations that the system ihas calculated necessary to pull from the DB, based on the requested
|
|
20
22
|
# API fields, API filters and `property` dependencies defined in the domain models (i.e., resources)
|
|
21
23
|
class Plugin < Praxis::Plugin
|
|
@@ -32,7 +34,7 @@ module Praxis
|
|
|
32
34
|
def prepare_config!(node)
|
|
33
35
|
node.attributes do
|
|
34
36
|
attribute :debug_queries, Attributor::Boolean, default: false,
|
|
35
|
-
|
|
37
|
+
description: 'Weather or not to log debug information about queries executed in the build_query automation module'
|
|
36
38
|
end
|
|
37
39
|
end
|
|
38
40
|
end
|
|
@@ -45,27 +47,27 @@ module Praxis
|
|
|
45
47
|
include Praxis::Extensions::FieldExpansion
|
|
46
48
|
end
|
|
47
49
|
|
|
48
|
-
def build_query(base_query)
|
|
49
|
-
domain_model =
|
|
50
|
-
raise "No domain model defined for #{
|
|
51
|
-
|
|
52
|
-
filters = request.params.filters if request.params
|
|
50
|
+
def build_query(base_query)
|
|
51
|
+
domain_model = media_type&.domain_model
|
|
52
|
+
raise "No domain model defined for #{name}. Cannot use the attribute filtering helpers without it" unless domain_model
|
|
53
|
+
|
|
54
|
+
filters = request.params.filters if request.params.respond_to?(:filters)
|
|
53
55
|
# Handle filters
|
|
54
|
-
base_query = domain_model.craft_filter_query(
|
|
56
|
+
base_query = domain_model.craft_filter_query(base_query, filters: filters)
|
|
55
57
|
# Handle field and nested field selection
|
|
56
58
|
base_query = domain_model.craft_field_selection_query(base_query, selectors: selector_generator.selectors)
|
|
57
59
|
# handle pagination and ordering if the pagination extention is included
|
|
58
|
-
base_query = domain_model.craft_pagination_query(base_query, pagination: _pagination)
|
|
60
|
+
base_query = domain_model.craft_pagination_query(base_query, pagination: _pagination) if respond_to?(:_pagination)
|
|
59
61
|
|
|
60
62
|
base_query
|
|
61
63
|
end
|
|
62
64
|
|
|
63
65
|
def selector_generator
|
|
64
|
-
return unless
|
|
65
|
-
|
|
66
|
+
return unless media_type.respond_to?(:domain_model) &&
|
|
67
|
+
media_type.domain_model < Praxis::Mapper::Resource
|
|
66
68
|
|
|
67
69
|
@selector_generator ||= \
|
|
68
|
-
Praxis::Mapper::SelectorGenerator.new.add(
|
|
70
|
+
Praxis::Mapper::SelectorGenerator.new.add(media_type.domain_model, expanded_fields)
|
|
69
71
|
end
|
|
70
72
|
end
|
|
71
73
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'singleton'
|
|
2
4
|
require 'praxis/extensions/pagination'
|
|
3
5
|
|
|
@@ -6,7 +8,7 @@ require 'praxis/extensions/pagination'
|
|
|
6
8
|
# When combined with the MapperPlugin, there is no extra configuration that needs to be done for
|
|
7
9
|
# the system to appropriately identify the pagination and order parameters in the API, and translate
|
|
8
10
|
# that in to the appropriate queries to fetch.
|
|
9
|
-
#
|
|
11
|
+
#
|
|
10
12
|
# To use this plugin without the MapperPlugin (probably a rare case), one can apply the appropriate
|
|
11
13
|
# clauses onto a query, by directly calling (in the controller) the `craft_pagination_query` method
|
|
12
14
|
# of the domain_model associated to the controller's mediatype.
|
|
@@ -17,7 +19,7 @@ require 'praxis/extensions/pagination'
|
|
|
17
19
|
# objs = domain_model.craft_pagination_query(base_query, pagination: _pagination)
|
|
18
20
|
# display(objs)
|
|
19
21
|
# end
|
|
20
|
-
#
|
|
22
|
+
#
|
|
21
23
|
# This plugin accepts configuration about the default behavior of pagination.
|
|
22
24
|
# Any of these configs can individually be overidden when defining each Pagination/Order parameters
|
|
23
25
|
# in any of the Endpoint actions.
|
|
@@ -33,12 +35,12 @@ require 'praxis/extensions/pagination'
|
|
|
33
35
|
# disallow_paging_by_default: true, # Default false
|
|
34
36
|
# # Disallows the use of the cursor type pagination mode when true (i.e., using 'by=' or 'from=' parameter)
|
|
35
37
|
# disallow_cursor_by_default: true, # Default false
|
|
36
|
-
# # The default mode params to use
|
|
38
|
+
# # The default mode params to use
|
|
37
39
|
# paging_default_mode: {by: :uuid}, # Default {by: :uid}
|
|
38
40
|
# # Weather or not to enforce that all requested sort fields are part of the media_type attributes
|
|
39
41
|
# # when false (not enforced) only the first field would be checked
|
|
40
42
|
# sorting: {
|
|
41
|
-
# enforce_all_fields: false # Default true
|
|
43
|
+
# enforce_all_fields: false # Default true
|
|
42
44
|
# }
|
|
43
45
|
# end
|
|
44
46
|
# end
|
|
@@ -77,7 +79,7 @@ module Praxis
|
|
|
77
79
|
end
|
|
78
80
|
|
|
79
81
|
def setup!
|
|
80
|
-
|
|
82
|
+
config.each do |name, val|
|
|
81
83
|
if name == :sorting
|
|
82
84
|
val.each do |ordername, orderval|
|
|
83
85
|
Praxis::Types::OrderingParams.send(ordername, orderval)
|
|
@@ -85,7 +87,7 @@ module Praxis
|
|
|
85
87
|
else
|
|
86
88
|
Praxis::Types::PaginationParams.send(name, val)
|
|
87
89
|
end
|
|
88
|
-
end
|
|
90
|
+
end
|
|
89
91
|
end
|
|
90
92
|
end
|
|
91
93
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'praxis/plugin'
|
|
2
4
|
require 'praxis/plugin_concern'
|
|
3
5
|
|
|
@@ -7,40 +9,42 @@ module Praxis
|
|
|
7
9
|
include Praxis::PluginConcern
|
|
8
10
|
|
|
9
11
|
class Plugin < Praxis::Plugin
|
|
10
|
-
|
|
11
12
|
def setup!
|
|
12
13
|
require 'praxis/dispatcher'
|
|
13
14
|
enable_action_controller_instrumentation
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
private
|
|
18
|
+
|
|
17
19
|
def enable_action_controller_instrumentation
|
|
18
20
|
Praxis::Dispatcher.class_eval do
|
|
19
21
|
# Wrap the original action dispatch with a method that instruments rails-expected bits...
|
|
20
22
|
alias_method :orig_instrumented_dispatch, :instrumented_dispatch
|
|
21
23
|
|
|
22
|
-
def instrumented_dispatch(
|
|
24
|
+
def instrumented_dispatch(praxis_payload)
|
|
23
25
|
rails_payload = {
|
|
24
|
-
:
|
|
25
|
-
:
|
|
26
|
-
:
|
|
27
|
-
:
|
|
28
|
-
:
|
|
26
|
+
controller: controller.class.name,
|
|
27
|
+
action: action.name,
|
|
28
|
+
params: (request.params ? request.params.dump : {}),
|
|
29
|
+
method: request.verb,
|
|
30
|
+
path: begin
|
|
31
|
+
request.fullpath
|
|
32
|
+
rescue StandardError
|
|
33
|
+
'unknown'
|
|
34
|
+
end
|
|
29
35
|
}
|
|
30
|
-
Praxis::Notifications.instrument(
|
|
36
|
+
Praxis::Notifications.instrument('start_processing.action_controller', rails_payload.dup)
|
|
31
37
|
|
|
32
38
|
Praxis::Notifications.instrument 'process_action.action_controller' do |data|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
#data[:view_runtime] = 123
|
|
43
|
-
end
|
|
39
|
+
res = orig_instrumented_dispatch(praxis_payload)
|
|
40
|
+
# TODO: also add the db_runtime and view_runtime values...
|
|
41
|
+
data[:status] = res[0]
|
|
42
|
+
res
|
|
43
|
+
|
|
44
|
+
# Append DB runtime to payload
|
|
45
|
+
# data[:db_runtime] = 999
|
|
46
|
+
# Append rendering time to payload
|
|
47
|
+
# data[:view_runtime] = 123
|
|
44
48
|
end
|
|
45
49
|
end
|
|
46
50
|
end
|
|
@@ -59,30 +63,33 @@ module Praxis
|
|
|
59
63
|
# Avoid using them explicitly in your controllers though. Use request.params object instead, as they are
|
|
60
64
|
# the Praxis ones that have been validated and coerced into the types you've defined.
|
|
61
65
|
def params
|
|
62
|
-
|
|
66
|
+
request.parameters
|
|
63
67
|
end
|
|
64
68
|
|
|
65
69
|
# Allow accessing the response headers from the controller
|
|
66
70
|
def headers
|
|
67
|
-
|
|
71
|
+
response.headers
|
|
68
72
|
end
|
|
69
73
|
|
|
70
74
|
def session
|
|
71
|
-
|
|
75
|
+
request.session
|
|
72
76
|
end
|
|
73
77
|
|
|
74
78
|
# Allow setting the status and body of the response from the controller itself.
|
|
75
79
|
def status=(code)
|
|
76
|
-
|
|
80
|
+
response.status = code
|
|
77
81
|
end
|
|
78
82
|
|
|
79
83
|
def response_body=(body)
|
|
80
|
-
#TODO: @_rendered = true # Necessary to know if to stop filter chain or not...
|
|
81
|
-
|
|
84
|
+
# TODO: @_rendered = true # Necessary to know if to stop filter chain or not...
|
|
85
|
+
response.body = body
|
|
82
86
|
end
|
|
83
87
|
|
|
84
88
|
def head(status, options = {})
|
|
85
|
-
|
|
89
|
+
if status.is_a?(Hash)
|
|
90
|
+
options = status
|
|
91
|
+
status = nil
|
|
92
|
+
end
|
|
86
93
|
status ||= options.delete(:status) || :ok
|
|
87
94
|
location = options.delete(:location)
|
|
88
95
|
content_type = options.delete(:content_type)
|
|
@@ -96,9 +103,7 @@ module Praxis
|
|
|
96
103
|
response.content_type = content_type if content_type
|
|
97
104
|
response
|
|
98
105
|
end
|
|
99
|
-
|
|
100
106
|
end
|
|
101
|
-
|
|
102
107
|
end
|
|
103
108
|
end
|
|
104
109
|
end
|
data/lib/praxis/renderer.rb
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Praxis
|
|
4
4
|
class Renderer
|
|
5
|
-
attr_reader :include_nil
|
|
6
|
-
attr_reader :cache
|
|
5
|
+
attr_reader :include_nil, :cache
|
|
7
6
|
|
|
8
7
|
class CircularRenderingError < StandardError
|
|
9
|
-
attr_reader :object
|
|
10
|
-
attr_reader :context
|
|
8
|
+
attr_reader :object, :context
|
|
11
9
|
|
|
12
10
|
def initialize(object, context)
|
|
13
11
|
@object = object
|
|
@@ -35,18 +33,16 @@ module Praxis
|
|
|
35
33
|
def render(object, fields, context: Attributor::DEFAULT_ROOT_CONTEXT)
|
|
36
34
|
if object.is_a? Praxis::Blueprint
|
|
37
35
|
@cache[object._cache_key][fields] ||= _render(object, fields, context: context)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
render(sub_object, fields, context: sub_context)
|
|
43
|
-
end
|
|
44
|
-
else
|
|
45
|
-
_render(object, fields, context: context)
|
|
36
|
+
elsif object.class < Attributor::Collection
|
|
37
|
+
object.each_with_index.collect do |sub_object, i|
|
|
38
|
+
sub_context = context + ["at(#{i})"]
|
|
39
|
+
render(sub_object, fields, context: sub_context)
|
|
46
40
|
end
|
|
41
|
+
else
|
|
42
|
+
_render(object, fields, context: context)
|
|
47
43
|
end
|
|
48
44
|
rescue SystemStackError
|
|
49
|
-
raise CircularRenderingError.new(object, context)
|
|
45
|
+
raise CircularRenderingError.new(object, context)
|
|
50
46
|
end
|
|
51
47
|
|
|
52
48
|
def _render(object, fields, context: Attributor::DEFAULT_ROOT_CONTEXT)
|
|
@@ -62,12 +58,12 @@ module Praxis
|
|
|
62
58
|
fields.each_with_object({}) do |(key, subfields), hash|
|
|
63
59
|
begin
|
|
64
60
|
value = object._get_attr(key)
|
|
65
|
-
rescue => e
|
|
61
|
+
rescue StandardError => e
|
|
66
62
|
raise Attributor::DumpError.new(context: context, name: key, type: object.class, original_exception: e)
|
|
67
63
|
end
|
|
68
64
|
|
|
69
65
|
if value.nil?
|
|
70
|
-
hash[key] = nil if
|
|
66
|
+
hash[key] = nil if include_nil
|
|
71
67
|
next
|
|
72
68
|
end
|
|
73
69
|
|
data/lib/praxis/request.rb
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
module Praxis
|
|
3
4
|
class Request < Praxis.request_superclass
|
|
4
5
|
attr_reader :env, :query
|
|
5
|
-
attr_accessor :route_params, :action
|
|
6
|
-
|
|
7
|
-
PATH_VERSION_PREFIX =
|
|
8
|
-
CONTENT_TYPE_NAME = 'CONTENT_TYPE'
|
|
9
|
-
PATH_INFO_NAME = 'PATH_INFO'
|
|
10
|
-
REQUEST_METHOD_NAME = 'REQUEST_METHOD'
|
|
11
|
-
QUERY_STRING_NAME = 'QUERY_STRING'
|
|
12
|
-
API_VERSION_HEADER_NAME =
|
|
13
|
-
API_VERSION_PARAM_NAME = 'api_version'
|
|
14
|
-
API_NO_VERSION_NAME = 'n/a'
|
|
15
|
-
VERSION_USING_DEFAULTS = [
|
|
6
|
+
attr_accessor :route_params, :action, :headers, :params, :payload
|
|
7
|
+
|
|
8
|
+
PATH_VERSION_PREFIX = '/v'
|
|
9
|
+
CONTENT_TYPE_NAME = 'CONTENT_TYPE'
|
|
10
|
+
PATH_INFO_NAME = 'PATH_INFO'
|
|
11
|
+
REQUEST_METHOD_NAME = 'REQUEST_METHOD'
|
|
12
|
+
QUERY_STRING_NAME = 'QUERY_STRING'
|
|
13
|
+
API_VERSION_HEADER_NAME = 'HTTP_X_API_VERSION'
|
|
14
|
+
API_VERSION_PARAM_NAME = 'api_version'
|
|
15
|
+
API_NO_VERSION_NAME = 'n/a'
|
|
16
|
+
VERSION_USING_DEFAULTS = %i[header params].freeze
|
|
16
17
|
|
|
17
18
|
def initialize(env)
|
|
18
19
|
@env = env
|
|
@@ -45,10 +46,9 @@ module Praxis
|
|
|
45
46
|
@env[PATH_INFO_NAME]
|
|
46
47
|
end
|
|
47
48
|
|
|
48
|
-
attr_accessor :headers, :params, :payload
|
|
49
|
-
|
|
50
49
|
def params_hash
|
|
51
50
|
return {} if params.nil?
|
|
51
|
+
|
|
52
52
|
params.attributes
|
|
53
53
|
end
|
|
54
54
|
|
|
@@ -65,17 +65,15 @@ module Praxis
|
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def raw_payload
|
|
68
|
-
@raw_payload ||=
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
end
|
|
73
|
-
end
|
|
68
|
+
@raw_payload ||= if (input = env['rack.input'].read)
|
|
69
|
+
env['rack.input'].rewind
|
|
70
|
+
input
|
|
71
|
+
end
|
|
74
72
|
end
|
|
75
73
|
|
|
76
74
|
def coalesce_inputs!
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
raw_params
|
|
76
|
+
raw_payload
|
|
79
77
|
end
|
|
80
78
|
|
|
81
79
|
def self.path_version_prefix
|
|
@@ -83,14 +81,14 @@ module Praxis
|
|
|
83
81
|
end
|
|
84
82
|
|
|
85
83
|
# DEPRECATED: remove with EndpointDefinition.version using: :path
|
|
86
|
-
PATH_VERSION_MATCHER = %r{^#{
|
|
84
|
+
PATH_VERSION_MATCHER = %r{^#{path_version_prefix}(?<version>[^/]+)/}.freeze
|
|
87
85
|
|
|
88
86
|
def path_version_matcher
|
|
89
87
|
if Application.instance.versioning_scheme == :path
|
|
90
|
-
matcher = Mustermann.new(ApiDefinition.instance.info.base_path
|
|
91
|
-
matcher.params(
|
|
88
|
+
matcher = Mustermann.new("#{ApiDefinition.instance.info.base_path}*")
|
|
89
|
+
matcher.params(path)[API_VERSION_PARAM_NAME]
|
|
92
90
|
else
|
|
93
|
-
PATH_VERSION_MATCHER.match(
|
|
91
|
+
PATH_VERSION_MATCHER.match(path)[:version]
|
|
94
92
|
end
|
|
95
93
|
end
|
|
96
94
|
|
|
@@ -99,30 +97,32 @@ module Praxis
|
|
|
99
97
|
|
|
100
98
|
Array(Application.instance.versioning_scheme).find do |mode|
|
|
101
99
|
case mode
|
|
102
|
-
when :header
|
|
100
|
+
when :header
|
|
103
101
|
result = env[API_VERSION_HEADER_NAME]
|
|
104
|
-
when :params
|
|
102
|
+
when :params
|
|
105
103
|
result = @query[API_VERSION_PARAM_NAME]
|
|
106
|
-
when :path
|
|
107
|
-
result =
|
|
104
|
+
when :path
|
|
105
|
+
result = path_version_matcher
|
|
108
106
|
else
|
|
109
107
|
raise "Unknown method for retrieving the API version: #{mode}"
|
|
110
108
|
end
|
|
111
109
|
end
|
|
112
|
-
|
|
110
|
+
result || API_NO_VERSION_NAME
|
|
113
111
|
end
|
|
114
112
|
|
|
115
113
|
def load_headers(context)
|
|
116
114
|
return unless action.headers
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
|
|
116
|
+
defined_headers = action.precomputed_header_keys_for_rack.each_with_object({}) do |(upper, original), hash|
|
|
117
|
+
hash[original] = env[upper] if env.key? upper
|
|
119
118
|
end
|
|
120
119
|
self.headers = action.headers.load(defined_headers, context)
|
|
121
120
|
end
|
|
122
121
|
|
|
123
122
|
def load_params(context)
|
|
124
123
|
return unless action.params
|
|
125
|
-
|
|
124
|
+
|
|
125
|
+
self.params = action.params.load(raw_params, context)
|
|
126
126
|
end
|
|
127
127
|
|
|
128
128
|
def load_payload(context)
|
|
@@ -130,11 +130,11 @@ module Praxis
|
|
|
130
130
|
return if content_type.nil?
|
|
131
131
|
|
|
132
132
|
raw = if (handler = Praxis::Application.instance.handlers[content_type.handler_name])
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
handler.parse(raw_payload)
|
|
134
|
+
else
|
|
135
|
+
# TODO: is this a good default?
|
|
136
|
+
raw_payload
|
|
137
|
+
end
|
|
138
138
|
|
|
139
139
|
self.payload = action.payload.load(raw, context, content_type: content_type.to_s)
|
|
140
140
|
end
|
|
@@ -142,19 +142,25 @@ module Praxis
|
|
|
142
142
|
def validate_headers(context)
|
|
143
143
|
return [] unless action.headers
|
|
144
144
|
|
|
145
|
-
action.headers.
|
|
145
|
+
return ["Attribute #{Attributor.humanize_context(context)} is required."] if action.headers.options[:required] == true && headers.nil?
|
|
146
|
+
|
|
147
|
+
action.headers.validate(headers, context)
|
|
146
148
|
end
|
|
147
149
|
|
|
148
150
|
def validate_params(context)
|
|
149
151
|
return [] unless action.params
|
|
150
152
|
|
|
151
|
-
action.params.
|
|
153
|
+
return ["Attribute #{Attributor.humanize_context(context)} is required."] if action.params.options[:required] == true && params.nil?
|
|
154
|
+
|
|
155
|
+
action.params.validate(params, context)
|
|
152
156
|
end
|
|
153
157
|
|
|
154
158
|
def validate_payload(context)
|
|
155
159
|
return [] unless action.payload
|
|
156
160
|
|
|
157
|
-
action.payload.
|
|
161
|
+
return ["Attribute #{Attributor.humanize_context(context)} is required."] if action.payload.options[:required] == true && payload.nil?
|
|
162
|
+
|
|
163
|
+
action.payload.validate(payload, context)
|
|
158
164
|
end
|
|
159
165
|
|
|
160
166
|
# versions that matched all the conditions of the request (except its version)
|
|
@@ -167,7 +173,5 @@ module Praxis
|
|
|
167
173
|
def inspect
|
|
168
174
|
"'@env' => #{@env.inspect},\n'@headers' => #{@headers.inspect},\n'@params' => #{@params.inspect},\n'@query' => #{@query.inspect}"
|
|
169
175
|
end
|
|
170
|
-
|
|
171
176
|
end
|
|
172
|
-
|
|
173
177
|
end
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Praxis
|
|
2
4
|
module RequestStages
|
|
3
|
-
|
|
4
5
|
class Action < RequestStage
|
|
5
|
-
|
|
6
6
|
def execute
|
|
7
|
-
response = Notifications.instrument 'praxis.request_stage.execute'
|
|
8
|
-
if controller.method(action.name).arity
|
|
7
|
+
response = Notifications.instrument 'praxis.request_stage.execute', controller: controller do
|
|
8
|
+
if controller.method(action.name).arity.zero?
|
|
9
9
|
controller.__send__(action.name)
|
|
10
10
|
else
|
|
11
11
|
controller.__send__(action.name, **request.params_hash)
|
|
@@ -23,8 +23,6 @@ module Praxis
|
|
|
23
23
|
controller.response.request = request
|
|
24
24
|
nil # Action cannot return its OK request, as it would indicate the end of the stage chain
|
|
25
25
|
end
|
|
26
|
-
|
|
27
26
|
end
|
|
28
|
-
|
|
29
27
|
end
|
|
30
28
|
end
|
|
@@ -1,25 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Praxis
|
|
2
4
|
module RequestStages
|
|
3
|
-
|
|
4
5
|
# Special Stage what will hijack the run and execute methods to:
|
|
5
6
|
# 1- Run specific controller callbacks (in addition to any normal callbacks)
|
|
6
7
|
# 2- Shortcut the controller callback chain if any returns a Response object
|
|
7
8
|
class RequestStage < Stage
|
|
8
|
-
|
|
9
|
-
alias :dispatcher :application # it's technically application in the base Stage
|
|
9
|
+
alias dispatcher application # it's technically application in the base Stage
|
|
10
10
|
|
|
11
11
|
def path
|
|
12
|
-
@
|
|
12
|
+
@path ||= [name].freeze
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def execute_controller_callbacks(callbacks)
|
|
16
16
|
if callbacks.key?(path)
|
|
17
17
|
callbacks[path].each do |(conditions, block)|
|
|
18
|
-
if conditions.key?(:actions)
|
|
19
|
-
|
|
20
|
-
end
|
|
18
|
+
next if conditions.key?(:actions) && !(conditions[:actions].include? action.name)
|
|
19
|
+
|
|
21
20
|
result = block.call(controller)
|
|
22
|
-
if result
|
|
21
|
+
if result.is_a?(Praxis::Response)
|
|
23
22
|
controller.response = result
|
|
24
23
|
return result
|
|
25
24
|
end
|
|
@@ -51,7 +50,7 @@ module Praxis
|
|
|
51
50
|
|
|
52
51
|
def run
|
|
53
52
|
# stage-level callbacks (typically empty) will never shortcut
|
|
54
|
-
execute_callbacks(
|
|
53
|
+
execute_callbacks(before_callbacks)
|
|
55
54
|
|
|
56
55
|
r = execute_controller_callbacks(controller.class.before_callbacks)
|
|
57
56
|
# Shortcut lifecycle if filters return non-nil value
|
|
@@ -61,7 +60,7 @@ module Praxis
|
|
|
61
60
|
result = execute_with_around
|
|
62
61
|
# Shortcut lifecycle if filters return a response
|
|
63
62
|
# (non-nil but non-response-class response is ignored)
|
|
64
|
-
if result
|
|
63
|
+
if result.is_a?(Praxis::Response)
|
|
65
64
|
controller.response = result
|
|
66
65
|
return result
|
|
67
66
|
end
|
|
@@ -72,52 +71,49 @@ module Praxis
|
|
|
72
71
|
return r if r
|
|
73
72
|
|
|
74
73
|
# stage-level callbacks (typically empty) will never shortcut
|
|
75
|
-
execute_callbacks(
|
|
74
|
+
execute_callbacks(after_callbacks)
|
|
76
75
|
|
|
77
76
|
result
|
|
78
77
|
end
|
|
79
78
|
|
|
80
79
|
def execute_with_around
|
|
81
|
-
cb = controller.class.around_callbacks[
|
|
82
|
-
if cb
|
|
80
|
+
cb = controller.class.around_callbacks[path]
|
|
81
|
+
if cb.nil? || cb.empty?
|
|
83
82
|
execute
|
|
84
83
|
else
|
|
85
84
|
inner_proc = proc { execute }
|
|
86
85
|
|
|
87
|
-
applicable = cb.select do |(conditions,
|
|
88
|
-
if conditions.
|
|
89
|
-
|
|
86
|
+
applicable = cb.select do |(conditions, _handler)|
|
|
87
|
+
if conditions.key?(:actions)
|
|
88
|
+
conditions[:actions].include?(action.name) ? true : false
|
|
90
89
|
else
|
|
91
90
|
true
|
|
92
91
|
end
|
|
93
92
|
end
|
|
94
93
|
|
|
95
|
-
chain = applicable.reverse.inject(inner_proc) do |blk, (
|
|
94
|
+
chain = applicable.reverse.inject(inner_proc) do |blk, (_conditions, handler)|
|
|
96
95
|
if blk
|
|
97
|
-
proc{ handler.call(controller,blk) }
|
|
96
|
+
proc { handler.call(controller, blk) }
|
|
98
97
|
else
|
|
99
|
-
proc{ handler.call }
|
|
98
|
+
proc { handler.call }
|
|
100
99
|
end
|
|
101
100
|
end
|
|
102
101
|
chain.call
|
|
103
102
|
end
|
|
104
103
|
end
|
|
105
104
|
|
|
106
|
-
|
|
107
105
|
def execute
|
|
108
106
|
raise NotImplementedError, 'Subclass must implement Stage#execute' unless @stages.any?
|
|
109
107
|
|
|
110
108
|
@stages.each do |stage|
|
|
111
109
|
shortcut = stage.run
|
|
112
|
-
if shortcut
|
|
110
|
+
if shortcut.is_a?(Praxis::Response)
|
|
113
111
|
controller.response = shortcut
|
|
114
112
|
return shortcut
|
|
115
113
|
end
|
|
116
114
|
end
|
|
117
115
|
nil
|
|
118
116
|
end
|
|
119
|
-
|
|
120
117
|
end
|
|
121
|
-
|
|
122
118
|
end
|
|
123
119
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Praxis
|
|
2
4
|
module RequestStages
|
|
3
|
-
|
|
4
5
|
class Response < RequestStage
|
|
5
|
-
|
|
6
6
|
def execute
|
|
7
7
|
response = controller.response
|
|
8
8
|
|
|
@@ -15,16 +15,14 @@ module Praxis
|
|
|
15
15
|
end
|
|
16
16
|
rescue Exceptions::Validation => e
|
|
17
17
|
controller.response = validation_handler.handle!(
|
|
18
|
-
summary:
|
|
18
|
+
summary: 'Error validating response',
|
|
19
19
|
exception: e,
|
|
20
20
|
request: request,
|
|
21
|
-
stage: name,
|
|
21
|
+
stage: name,
|
|
22
22
|
errors: e.errors
|
|
23
23
|
)
|
|
24
24
|
retry
|
|
25
25
|
end
|
|
26
|
-
|
|
27
26
|
end
|
|
28
|
-
|
|
29
27
|
end
|
|
30
28
|
end
|