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