praxis 2.0.pre.16 → 2.0.pre.20
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 +22 -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 +187 -131
- 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 +221 -106
- 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 -47
- 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 +12 -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,17 +1,17 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
3
|
+
module Praxis
|
4
4
|
module BootloaderStages
|
5
|
-
|
6
5
|
class Routing < Stage
|
7
6
|
class Target
|
8
7
|
attr_reader :action
|
8
|
+
|
9
9
|
def initialize(application, controller, action)
|
10
10
|
@application = application
|
11
11
|
@controller = controller
|
12
12
|
@action = action
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def call(request)
|
16
16
|
request.action = @action
|
17
17
|
dispatcher = Dispatcher.current(application: @application)
|
@@ -19,7 +19,7 @@ module Praxis
|
|
19
19
|
dispatcher.dispatch(@controller, @action, request)
|
20
20
|
end
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
def execute
|
24
24
|
application.controllers.each do |controller|
|
25
25
|
controller.definition.actions.each do |action_name, action|
|
@@ -29,14 +29,11 @@ module Praxis
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
|
33
32
|
def target_factory(controller, action_name)
|
34
33
|
action = controller.definition.actions.fetch(action_name)
|
35
34
|
|
36
35
|
Target.new(application, controller, action)
|
37
36
|
end
|
38
|
-
|
39
37
|
end
|
40
|
-
|
41
38
|
end
|
42
39
|
end
|
@@ -1,17 +1,10 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module Praxis
|
3
4
|
module BootloaderStages
|
4
|
-
|
5
5
|
class SubgroupLoader < Stage
|
6
|
-
|
7
6
|
attr_writer :order
|
8
7
|
|
9
|
-
def initialize(name, application)
|
10
|
-
super
|
11
|
-
# always finalize Taylor after loading app code.
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
8
|
def order
|
16
9
|
@order ||= application.file_layout[name] == [] ? [] : application.file_layout[name].groups.keys
|
17
10
|
end
|
@@ -23,6 +16,5 @@ module Praxis
|
|
23
16
|
setup_deferred_callbacks!
|
24
17
|
end
|
25
18
|
end
|
26
|
-
|
27
19
|
end
|
28
20
|
end
|
@@ -1,28 +1,26 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
3
|
+
module Praxis
|
4
4
|
module BootloaderStages
|
5
|
-
|
6
5
|
class WarnUnloadedFiles < Stage
|
7
6
|
@enabled = true
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
class << self
|
9
|
+
attr_writer :enabled
|
11
10
|
end
|
12
11
|
|
13
|
-
|
14
|
-
|
12
|
+
class << self
|
13
|
+
attr_reader :enabled
|
15
14
|
end
|
16
15
|
|
17
16
|
def execute
|
18
17
|
return unless self.class.enabled
|
19
18
|
|
20
|
-
if application.file_layout[:app] == []
|
21
|
-
return
|
22
|
-
end
|
19
|
+
return if application.file_layout[:app] == []
|
23
20
|
|
24
21
|
base = application.file_layout[:app].base
|
25
22
|
return unless base.exist?
|
23
|
+
|
26
24
|
file_enum = base.find.to_a
|
27
25
|
files = file_enum.select do |file|
|
28
26
|
path = file.relative_path_from(base)
|
@@ -30,17 +28,15 @@ module Praxis
|
|
30
28
|
end
|
31
29
|
|
32
30
|
missing = Set.new(files) - application.loaded_files
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
warn msg
|
31
|
+
return unless missing.any?
|
32
|
+
|
33
|
+
msg = "The following application files under #{base} were not loaded:\n"
|
34
|
+
missing.each do |file|
|
35
|
+
path = file.relative_path_from(base)
|
36
|
+
msg << " * #{path}\n"
|
40
37
|
end
|
38
|
+
warn msg
|
41
39
|
end
|
42
|
-
|
43
|
-
|
44
40
|
end
|
45
41
|
end
|
46
42
|
end
|
data/lib/praxis/callbacks.rb
CHANGED
@@ -1,33 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Praxis
|
2
4
|
module Callbacks
|
3
5
|
extend ::ActiveSupport::Concern
|
4
6
|
|
5
7
|
included do
|
6
8
|
class_attribute :before_callbacks, :after_callbacks, :around_callbacks
|
7
|
-
self.before_callbacks =
|
8
|
-
self.after_callbacks =
|
9
|
-
self.around_callbacks =
|
9
|
+
self.before_callbacks = ({})
|
10
|
+
self.after_callbacks = ({})
|
11
|
+
self.around_callbacks = ({})
|
10
12
|
end
|
11
|
-
|
13
|
+
|
12
14
|
module ClassMethods
|
13
|
-
|
14
15
|
def before(*stage_path, **conditions, &block)
|
15
16
|
stage_path = [:action] if stage_path.empty?
|
16
|
-
before_callbacks[stage_path] ||=
|
17
|
+
before_callbacks[stage_path] ||= []
|
17
18
|
before_callbacks[stage_path] << [conditions, block]
|
18
19
|
end
|
19
|
-
|
20
|
+
|
20
21
|
def after(*stage_path, **conditions, &block)
|
21
22
|
stage_path = [:action] if stage_path.empty?
|
22
|
-
after_callbacks[stage_path] ||=
|
23
|
+
after_callbacks[stage_path] ||= []
|
23
24
|
after_callbacks[stage_path] << [conditions, block]
|
24
25
|
end
|
25
|
-
|
26
|
+
|
26
27
|
def around(*stage_path, **conditions, &block)
|
27
28
|
stage_path = [:action] if stage_path.empty?
|
28
|
-
around_callbacks[stage_path] ||=
|
29
|
+
around_callbacks[stage_path] ||= []
|
29
30
|
around_callbacks[stage_path] << [conditions, block]
|
30
31
|
end
|
31
32
|
end
|
32
33
|
end
|
33
|
-
end
|
34
|
+
end
|
data/lib/praxis/collection.rb
CHANGED
@@ -1,28 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Praxis
|
2
4
|
class Collection < Attributor::Collection
|
3
5
|
include Types::MediaTypeCommon
|
4
6
|
|
5
7
|
def self.of(type)
|
6
|
-
if defined?(type::Collection)
|
7
|
-
return type::Collection
|
8
|
-
end
|
8
|
+
return type::Collection if defined?(type::Collection)
|
9
9
|
|
10
10
|
klass = super
|
11
11
|
klass.anonymous_type
|
12
12
|
|
13
|
-
|
14
|
-
klass.member_type type
|
15
|
-
type.const_set :Collection, klass
|
16
|
-
else
|
17
|
-
raise "Praxis::Collection.of() for non-MediaTypes is unsupported. Use Attributor::Collection.of() instead."
|
18
|
-
end
|
13
|
+
raise 'Praxis::Collection.of() for non-MediaTypes is unsupported. Use Attributor::Collection.of() instead.' unless type < Praxis::Types::MediaTypeCommon
|
19
14
|
|
15
|
+
klass.member_type type
|
16
|
+
type.const_set :Collection, klass
|
20
17
|
end
|
21
18
|
|
22
|
-
def self.member_type(type=nil)
|
19
|
+
def self.member_type(type = nil)
|
23
20
|
unless type.nil?
|
24
21
|
@member_type = type
|
25
|
-
|
22
|
+
identifier("#{type.identifier};type=collection") unless type.identifier.nil?
|
26
23
|
end
|
27
24
|
|
28
25
|
@member_type
|
@@ -36,11 +33,11 @@ module Praxis
|
|
36
33
|
:array
|
37
34
|
end
|
38
35
|
|
39
|
-
def self.as_json_schema(**
|
40
|
-
the_type = @attribute
|
36
|
+
def self.as_json_schema(**_args)
|
37
|
+
the_type = @attribute&.type || member_type
|
41
38
|
{
|
42
39
|
type: json_schema_type,
|
43
|
-
items:
|
40
|
+
items: the_type.as_json_schema
|
44
41
|
}
|
45
42
|
end
|
46
43
|
end
|
data/lib/praxis/config.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Praxis
|
2
4
|
class Config
|
3
5
|
include Attributor::Type
|
@@ -10,7 +12,7 @@ module Praxis
|
|
10
12
|
end
|
11
13
|
|
12
14
|
# You can define the configuration in different ways
|
13
|
-
#
|
15
|
+
#
|
14
16
|
# Add a key to the top struct
|
15
17
|
# define do
|
16
18
|
# attribute :added_to_top, String
|
@@ -28,46 +30,36 @@ module Praxis
|
|
28
30
|
# attribute :two String
|
29
31
|
# end
|
30
32
|
# end
|
31
|
-
|
33
|
+
|
32
34
|
# ...or using this way too (equivalent)
|
33
35
|
# define(:app) do
|
34
36
|
# attribute :two, String
|
35
37
|
# end
|
36
38
|
# You can also define a key to be a non-Struct type
|
37
39
|
# define :app, Attributor::Hash
|
38
|
-
|
39
|
-
def define(key=nil, type=Attributor::Struct, **opts, &block)
|
40
|
-
if key.nil? && type != Attributor::Struct
|
41
|
-
|
42
|
-
"You cannot define the top level configuration with a non-Struct type. Got: #{type.inspect}"
|
43
|
-
)
|
44
|
-
end
|
45
|
-
|
40
|
+
|
41
|
+
def define(key = nil, type = Attributor::Struct, **opts, &block)
|
42
|
+
raise Exceptions::InvalidConfiguration, "You cannot define the top level configuration with a non-Struct type. Got: #{type.inspect}" if key.nil? && type != Attributor::Struct
|
43
|
+
|
46
44
|
case key
|
47
45
|
when String, Symbol, NilClass
|
48
46
|
|
49
47
|
top = key.nil? ? @attribute : @attribute.attributes[key]
|
50
|
-
if top #key defined...redefine
|
51
|
-
unless
|
52
|
-
|
53
|
-
"Incompatible type received for extending configuration key [#{key}]. Got type #{type.name}"
|
54
|
-
)
|
55
|
-
end
|
48
|
+
if top # key defined...redefine
|
49
|
+
raise Exceptions::InvalidConfiguration, "Incompatible type received for extending configuration key [#{key}]. Got type #{type.name}" unless type == Attributor::Struct && top.type < Attributor::Struct
|
50
|
+
|
56
51
|
top.options.merge!(opts)
|
57
52
|
top.type.attributes(**opts, &block)
|
58
53
|
else
|
59
54
|
@attribute.attributes[key] = Attributor::Attribute.new(type, opts, &block)
|
60
55
|
end
|
61
56
|
else
|
62
|
-
raise Exceptions::InvalidConfiguration.
|
63
|
-
"Defining a configuration key requires a String or a Symbol key. Got: #{key.inspect}"
|
64
|
-
)
|
57
|
+
raise Exceptions::InvalidConfiguration, "Defining a configuration key requires a String or a Symbol key. Got: #{key.inspect}"
|
65
58
|
end
|
66
|
-
|
67
59
|
end
|
68
60
|
|
69
61
|
def set(config)
|
70
|
-
context = [
|
62
|
+
context = %w[Application config]
|
71
63
|
|
72
64
|
begin
|
73
65
|
@value = @attribute.load(config, context, recurse: true)
|
@@ -77,17 +69,14 @@ module Praxis
|
|
77
69
|
|
78
70
|
errors = @attribute.validate(@value, context)
|
79
71
|
|
80
|
-
unless errors.empty?
|
81
|
-
raise Exceptions::ConfigValidation.new(errors: errors)
|
82
|
-
end
|
72
|
+
raise Exceptions::ConfigValidation.new(errors: errors) unless errors.empty?
|
83
73
|
end
|
84
74
|
|
85
75
|
def get
|
86
|
-
@value ||= begin
|
87
|
-
context = [
|
88
|
-
@attribute.load({},context, recurse: true)
|
76
|
+
@value ||= begin # rubocop:disable Naming/MemoizedInstanceVariableName
|
77
|
+
context = %w[Application config].freeze
|
78
|
+
@attribute.load({}, context, recurse: true)
|
89
79
|
end
|
90
80
|
end
|
91
|
-
|
92
81
|
end
|
93
82
|
end
|
data/lib/praxis/config_hash.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# TODO: move to Praxis proper...it's not used here
|
3
4
|
module Praxis
|
4
5
|
class ConfigHash < BasicObject
|
@@ -22,7 +23,7 @@ module Praxis
|
|
22
23
|
true
|
23
24
|
end
|
24
25
|
|
25
|
-
def method_missing(name, value, *rest, &block)
|
26
|
+
def method_missing(name, value, *rest, &block)
|
26
27
|
if (existing = @hash[name])
|
27
28
|
if block
|
28
29
|
existing << [value, block]
|
data/lib/praxis/controller.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/concern'
|
2
4
|
require 'active_support/all'
|
3
5
|
|
@@ -24,11 +26,11 @@ module Praxis
|
|
24
26
|
end
|
25
27
|
|
26
28
|
def id
|
27
|
-
|
29
|
+
name.gsub('::', '-')
|
28
30
|
end
|
29
31
|
end
|
30
32
|
|
31
|
-
def initialize(request, response=Responses::Ok.new)
|
33
|
+
def initialize(request, response = Responses::Ok.new)
|
32
34
|
@request = request
|
33
35
|
@response = response
|
34
36
|
end
|
@@ -38,12 +40,11 @@ module Praxis
|
|
38
40
|
end
|
39
41
|
|
40
42
|
def media_type
|
41
|
-
if (response_definition =
|
42
|
-
|
43
|
+
if (response_definition = request.action.responses[response.name])
|
44
|
+
response_definition.media_type
|
43
45
|
else
|
44
|
-
|
46
|
+
definition.media_type
|
45
47
|
end
|
46
48
|
end
|
47
|
-
|
48
49
|
end
|
49
50
|
end
|
data/lib/praxis/dispatcher.rb
CHANGED
@@ -1,35 +1,33 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module Praxis
|
3
4
|
CONTEXT_FOR = {
|
4
|
-
params: [Attributor::
|
5
|
-
headers: [Attributor::
|
6
|
-
payload: [Attributor::
|
5
|
+
params: [Attributor::ROOT_PREFIX, 'params'],
|
6
|
+
headers: [Attributor::ROOT_PREFIX, 'headers'],
|
7
|
+
payload: [Attributor::ROOT_PREFIX, 'payload']
|
7
8
|
}.freeze
|
8
9
|
|
9
10
|
class Dispatcher
|
10
|
-
attr_reader :controller
|
11
|
-
attr_reader :action
|
12
|
-
attr_reader :request
|
13
|
-
attr_reader :application
|
11
|
+
attr_reader :controller, :action, :request, :application
|
14
12
|
|
15
|
-
@deferred_callbacks = Hash.new do |hash,stage|
|
16
|
-
hash[stage] = {before: [], after:[]}
|
13
|
+
@deferred_callbacks = Hash.new do |hash, stage|
|
14
|
+
hash[stage] = { before: [], after: [] }
|
17
15
|
end
|
18
16
|
|
19
17
|
class << self
|
20
18
|
attr_reader :deferred_callbacks
|
21
19
|
end
|
22
20
|
|
23
|
-
def self.before(*
|
21
|
+
def self.before(*_stage_path, **conditions, &block)
|
24
22
|
@deferred_callbacks[:before] << [conditions, block]
|
25
23
|
end
|
26
24
|
|
27
|
-
def self.after(*
|
25
|
+
def self.after(*_stage_path, **conditions, &block)
|
28
26
|
@deferred_callbacks[:after] << [conditions, block]
|
29
27
|
end
|
30
28
|
|
31
29
|
def self.current(thread: Thread.current, application: Application.instance)
|
32
|
-
thread[:praxis_dispatcher] ||=
|
30
|
+
thread[:praxis_dispatcher] ||= new(application: application)
|
33
31
|
end
|
34
32
|
|
35
33
|
def initialize(application: Application.instance)
|
@@ -43,20 +41,18 @@ module Praxis
|
|
43
41
|
@stages << RequestStages::Validate.new(:validate, self)
|
44
42
|
@stages << RequestStages::Action.new(:action, self)
|
45
43
|
@stages << RequestStages::Response.new(:response, self)
|
46
|
-
@stages.each
|
47
|
-
s.setup!
|
48
|
-
end
|
44
|
+
@stages.each(&:setup!)
|
49
45
|
setup_deferred_callbacks!
|
50
46
|
end
|
51
47
|
|
52
48
|
def setup_deferred_callbacks!
|
53
49
|
self.class.deferred_callbacks.each do |stage_name, callbacks|
|
54
50
|
callbacks[:before].each do |(*stage_path, block)|
|
55
|
-
|
51
|
+
before(stage_name, *stage_path, &block)
|
56
52
|
end
|
57
53
|
|
58
54
|
callbacks[:after].each do |(*stage_path, block)|
|
59
|
-
|
55
|
+
after(stage_name, *stage_path, &block)
|
60
56
|
end
|
61
57
|
end
|
62
58
|
end
|
@@ -76,38 +72,35 @@ module Praxis
|
|
76
72
|
@action = action
|
77
73
|
@request = request
|
78
74
|
|
79
|
-
payload = {request: request, response: nil, controller: @controller}
|
80
|
-
|
81
|
-
instrumented_dispatch( payload )
|
75
|
+
payload = { request: request, response: nil, controller: @controller }
|
82
76
|
|
77
|
+
instrumented_dispatch(payload)
|
83
78
|
ensure
|
84
79
|
@controller = nil
|
85
80
|
@action = nil
|
86
81
|
@request = nil
|
87
82
|
end
|
88
83
|
|
89
|
-
def instrumented_dispatch(
|
90
|
-
Notifications.instrument 'praxis.request.all'
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
break
|
101
|
-
end
|
84
|
+
def instrumented_dispatch(payload)
|
85
|
+
Notifications.instrument 'praxis.request.all', payload do
|
86
|
+
# the response stage must be the final stage in the list
|
87
|
+
*stages, response_stage = @stages
|
88
|
+
|
89
|
+
stages.each do |stage|
|
90
|
+
result = stage.run
|
91
|
+
case result
|
92
|
+
when Response
|
93
|
+
controller.response = result
|
94
|
+
break
|
102
95
|
end
|
96
|
+
end
|
103
97
|
|
104
|
-
|
98
|
+
response_stage.run
|
105
99
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
end
|
100
|
+
payload[:response] = controller.response
|
101
|
+
controller.response.finish
|
102
|
+
rescue StandardError => e
|
103
|
+
@application.error_handler.handle!(request, e)
|
111
104
|
end
|
112
105
|
end
|
113
106
|
|
@@ -116,9 +109,8 @@ module Praxis
|
|
116
109
|
return unless Praxis::Blueprint.caching_enabled?
|
117
110
|
|
118
111
|
Praxis::Blueprint.cache = Hash.new do |hash, key|
|
119
|
-
hash[key] =
|
112
|
+
hash[key] = {}
|
120
113
|
end
|
121
114
|
end
|
122
|
-
|
123
115
|
end
|
124
116
|
end
|
@@ -1,9 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Praxis
|
2
4
|
module Docs
|
3
5
|
module OpenApi
|
4
6
|
class InfoObject
|
5
7
|
attr_reader :info, :version
|
6
|
-
|
8
|
+
|
9
|
+
def initialize(version:, api_definition_info:)
|
7
10
|
@version = version
|
8
11
|
@info = api_definition_info
|
9
12
|
raise "OpenApi docs require a 'Title' for your API." unless info.title
|
@@ -11,12 +14,12 @@ module Praxis
|
|
11
14
|
|
12
15
|
def dump
|
13
16
|
data = { version: version }
|
14
|
-
[
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
%i[
|
18
|
+
title
|
19
|
+
description
|
20
|
+
termsOfService
|
21
|
+
contact
|
22
|
+
license
|
20
23
|
].each do |attr|
|
21
24
|
val = info.send(attr)
|
22
25
|
data[attr] = val if val
|
@@ -27,7 +30,7 @@ module Praxis
|
|
27
30
|
if info.logo_url
|
28
31
|
data[:'x-logo'] = {
|
29
32
|
url: info.logo_url,
|
30
|
-
backgroundColor:
|
33
|
+
backgroundColor: '#FFFFFF',
|
31
34
|
altText: info.title
|
32
35
|
}
|
33
36
|
end
|
@@ -1,8 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Praxis
|
2
4
|
module Docs
|
3
5
|
module OpenApi
|
4
6
|
class MediaTypeObject
|
5
7
|
attr_reader :schema, :example
|
8
|
+
|
6
9
|
def initialize(schema:, example:)
|
7
10
|
@schema = schema
|
8
11
|
@example = example
|
@@ -10,26 +13,24 @@ module Praxis
|
|
10
13
|
|
11
14
|
def dump
|
12
15
|
{
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
+
schema: schema,
|
17
|
+
example: example
|
18
|
+
# encoding: TODO SUPPORT IT maybe be great/necessary for multipart
|
16
19
|
}
|
17
20
|
end
|
18
21
|
|
19
22
|
# Helper to create the typical content attribute for responses and request bodies
|
20
|
-
def self.create_content_attribute_helper(type
|
23
|
+
def self.create_content_attribute_helper(type:, example_payload:, example_handlers: nil)
|
21
24
|
# Will produce 1 example encoded with a given handler (and marking it with the given content type)
|
22
|
-
|
23
|
-
example_handlers = [ {'application/json' => 'json' } ]
|
24
|
-
end
|
25
|
+
example_handlers ||= [{ 'application/json' => 'json' }]
|
25
26
|
# NOTE2: we should just create a $ref here unless it's an anon mediatype...
|
26
|
-
return {} if type.is_a? SimpleMediaType
|
27
|
+
return {} if type.is_a? SimpleMediaType # NOTE: skip if it's a SimpleMediaType?? ... is that correct?
|
27
28
|
|
28
|
-
the_schema = if type.anonymous? || !
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
the_schema = if type.anonymous? || !(type < Praxis::MediaType) # Avoid referencing custom/simple Types? (i.e., just MTs)
|
30
|
+
SchemaObject.new(info: type).dump_schema(shallow: false, allow_ref: false)
|
31
|
+
else
|
32
|
+
SchemaObject.new(info: type).dump_schema(shallow: true, allow_ref: true)
|
33
|
+
end
|
33
34
|
|
34
35
|
if example_payload
|
35
36
|
examples_by_content_type = {}
|
@@ -40,16 +41,16 @@ module Praxis
|
|
40
41
|
handler = Praxis::Application.instance.handlers[handler_name]
|
41
42
|
# ReDoc is not happy to display json generated outputs when served as JSON...wtf?
|
42
43
|
generated = handler.generate(rendered_payload)
|
43
|
-
final =
|
44
|
+
final = handler_name == 'json' ? JSON.parse(generated) : generated
|
44
45
|
examples_by_content_type[content_type] = final
|
45
46
|
end
|
46
47
|
end
|
47
48
|
|
48
49
|
# Key string (of MT) , value MTObject
|
49
|
-
|
50
|
-
|
50
|
+
examples_by_content_type.transform_values do |example_hash|
|
51
|
+
MediaTypeObject.new(
|
51
52
|
schema: the_schema, # Every MT will have the same exact type..oh well .. maybe a REF?
|
52
|
-
example: example_hash
|
53
|
+
example: example_hash
|
53
54
|
).dump
|
54
55
|
end
|
55
56
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'parameter_object'
|
2
4
|
require_relative 'request_body_object'
|
3
5
|
require_relative 'responses_object'
|
@@ -8,6 +10,7 @@ module Praxis
|
|
8
10
|
class OperationObject
|
9
11
|
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operation-object
|
10
12
|
attr_reader :id, :url, :action, :tags
|
13
|
+
|
11
14
|
def initialize(id:, url:, action:, tags:)
|
12
15
|
@id = id
|
13
16
|
@url = url
|
@@ -20,18 +23,18 @@ module Praxis
|
|
20
23
|
all_tags = tags + action.traits
|
21
24
|
h = {
|
22
25
|
summary: action.name.to_s,
|
23
|
-
|
24
|
-
#externalDocs: {}, # TODO/FIXME
|
26
|
+
# externalDocs: {}, # TODO/FIXME
|
25
27
|
operationId: id,
|
26
|
-
responses: ResponsesObject.new(responses: action.responses).dump
|
28
|
+
responses: ResponsesObject.new(responses: action.responses).dump
|
27
29
|
# callbacks
|
28
30
|
# deprecated: false
|
29
31
|
# security: [{}]
|
30
32
|
# servers: [{}]
|
31
33
|
}
|
34
|
+
h[:description] = action.description if action.description
|
32
35
|
h[:tags] = all_tags.uniq unless all_tags.empty?
|
33
36
|
h[:parameters] = all_parameters unless all_parameters.empty?
|
34
|
-
h[:requestBody] = RequestBodyObject.new(attribute: action.payload
|
37
|
+
h[:requestBody] = RequestBodyObject.new(attribute: action.payload).dump if action.payload
|
35
38
|
h
|
36
39
|
end
|
37
40
|
end
|