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,15 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Praxis
|
|
2
4
|
module Types
|
|
3
5
|
class MultipartArray < Attributor::Collection
|
|
4
6
|
class PartDefinition
|
|
5
|
-
|
|
6
|
-
attr_accessor :payload_attribute
|
|
7
|
-
attr_accessor :headers_attribute
|
|
8
|
-
attr_accessor :filename_attribute
|
|
9
|
-
|
|
7
|
+
attr_accessor :payload_attribute, :headers_attribute, :filename_attribute
|
|
10
8
|
|
|
11
9
|
def initialize(&block)
|
|
12
|
-
|
|
10
|
+
instance_eval(&block)
|
|
13
11
|
end
|
|
14
12
|
|
|
15
13
|
def update_attribute(attribute, options, block)
|
|
@@ -17,17 +15,17 @@ module Praxis
|
|
|
17
15
|
attribute.type.attributes(**options, &block)
|
|
18
16
|
end
|
|
19
17
|
|
|
20
|
-
def create_attribute(type=Attributor::Struct, **opts, &block)
|
|
18
|
+
def create_attribute(type = Attributor::Struct, **opts, &block)
|
|
21
19
|
# TODO: how do we want to handle any referenced types?
|
|
22
|
-
|
|
20
|
+
Attributor::Attribute.new(type, opts, &block)
|
|
23
21
|
end
|
|
24
22
|
|
|
25
|
-
def payload(type=Attributor::Struct, **opts, &block)
|
|
26
|
-
#return @payload if !block_given? && type == Attributor::Struct
|
|
23
|
+
def payload(type = Attributor::Struct, **opts, &block)
|
|
24
|
+
# return @payload if !block_given? && type == Attributor::Struct
|
|
27
25
|
@payload_attribute = create_attribute(type, **opts, &block)
|
|
28
26
|
end
|
|
29
27
|
|
|
30
|
-
def header(name, val=nil, **options)
|
|
28
|
+
def header(name, val = nil, **options)
|
|
31
29
|
block = proc { header(name, val, **options) }
|
|
32
30
|
|
|
33
31
|
if @headers_attribute
|
|
@@ -35,17 +33,16 @@ module Praxis
|
|
|
35
33
|
else
|
|
36
34
|
type = Attributor::Hash.of(key: String)
|
|
37
35
|
@headers_attribute = create_attribute(type,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
dsl_compiler: Praxis::ActionDefinition::HeadersDSLCompiler,
|
|
37
|
+
case_insensitive_load: false, # :(
|
|
38
|
+
allow_extra: true,
|
|
39
|
+
&block)
|
|
42
40
|
end
|
|
43
41
|
end
|
|
44
42
|
|
|
45
|
-
def filename(type=String, **opts)
|
|
43
|
+
def filename(type = String, **opts)
|
|
46
44
|
@filename_attribute = create_attribute(type, **opts)
|
|
47
45
|
end
|
|
48
|
-
|
|
49
46
|
end
|
|
50
47
|
end
|
|
51
48
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'praxis/types/multipart_array/part_definition'
|
|
2
4
|
|
|
3
5
|
module Praxis
|
|
@@ -12,6 +14,7 @@ module Praxis
|
|
|
12
14
|
@identifier = MediaTypeIdentifier.load('multipart/form-data').freeze
|
|
13
15
|
|
|
14
16
|
def self.inherited(klass)
|
|
17
|
+
super
|
|
15
18
|
klass.instance_eval do
|
|
16
19
|
@attributes = FuzzyHash.new
|
|
17
20
|
@saved_blocks = []
|
|
@@ -26,38 +29,32 @@ module Praxis
|
|
|
26
29
|
@part_attribute = nil
|
|
27
30
|
|
|
28
31
|
@identifier = MediaTypeIdentifier.load('multipart/form-data').freeze
|
|
29
|
-
|
|
30
32
|
end
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
class << self
|
|
34
|
-
attr_reader :attributes
|
|
35
|
-
attr_reader :multiple
|
|
36
|
-
attr_reader :options
|
|
37
|
-
attr_reader :identifier
|
|
36
|
+
attr_reader :attributes, :multiple, :options, :identifier
|
|
38
37
|
end
|
|
39
38
|
|
|
40
39
|
def self.constructable?
|
|
41
40
|
true
|
|
42
41
|
end
|
|
43
42
|
|
|
44
|
-
def self.construct(constructor_block, _options={})
|
|
43
|
+
def self.construct(constructor_block, _options = {})
|
|
45
44
|
Class.new(self, &constructor_block)
|
|
46
45
|
end
|
|
47
46
|
|
|
48
|
-
def self.name_type(type=nil)
|
|
47
|
+
def self.name_type(type = nil)
|
|
49
48
|
return @name_type if type.nil?
|
|
50
49
|
|
|
51
50
|
@name_type = Attributor.resolve_type type
|
|
52
51
|
end
|
|
53
52
|
|
|
54
|
-
def self.payload_type(type=nil, **opts, &block)
|
|
53
|
+
def self.payload_type(type = nil, **opts, &block)
|
|
55
54
|
if type.nil?
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return @payload_type
|
|
60
|
-
end
|
|
55
|
+
return @payload_type unless block_given?
|
|
56
|
+
|
|
57
|
+
type = Attributor::Struct
|
|
61
58
|
end
|
|
62
59
|
@payload_type = Attributor.resolve_type(type)
|
|
63
60
|
@payload_attribute = Attributor::Attribute.new(@payload_type, **opts, &block)
|
|
@@ -70,13 +67,13 @@ module Praxis
|
|
|
70
67
|
end
|
|
71
68
|
|
|
72
69
|
def self.part_attribute
|
|
73
|
-
@part_attribute ||= Attributor::Attribute.new(Praxis::MultipartPart, payload_attribute:
|
|
70
|
+
@part_attribute ||= Attributor::Attribute.new(Praxis::MultipartPart, payload_attribute: payload_attribute)
|
|
74
71
|
end
|
|
75
72
|
|
|
76
|
-
def self.part(name, payload_type=nil, multiple: false, filename: false, **opts, &block)
|
|
73
|
+
def self.part(name, payload_type = nil, multiple: false, filename: false, **opts, &block)
|
|
77
74
|
@attributes.default_proc = nil
|
|
78
75
|
|
|
79
|
-
if name.
|
|
76
|
+
if name.is_a?(Regexp)
|
|
80
77
|
raise 'part with regexp name may not take :multiple option' if multiple
|
|
81
78
|
raise 'part with regexp name may not be required' if opts[:required] == true
|
|
82
79
|
end
|
|
@@ -85,9 +82,7 @@ module Praxis
|
|
|
85
82
|
|
|
86
83
|
compiler = Attributor::DSLCompiler.new(self, **opts)
|
|
87
84
|
|
|
88
|
-
if filename
|
|
89
|
-
filename_attribute = compiler.define('filename', String, required: true)
|
|
90
|
-
end
|
|
85
|
+
filename_attribute = compiler.define('filename', String, required: true) if filename
|
|
91
86
|
|
|
92
87
|
if block_given?
|
|
93
88
|
definition = PartDefinition.new(&block)
|
|
@@ -95,35 +90,31 @@ module Praxis
|
|
|
95
90
|
header_attribute = definition.headers_attribute
|
|
96
91
|
filename_attribute = definition.filename_attribute || filename_attribute
|
|
97
92
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
)
|
|
93
|
+
attributes[name] = compiler.define(name, Praxis::MultipartPart,
|
|
94
|
+
payload_attribute: payload_attribute,
|
|
95
|
+
headers_attribute: header_attribute,
|
|
96
|
+
filename_attribute: filename_attribute)
|
|
103
97
|
else
|
|
104
98
|
payload_attribute = compiler.define(name, payload_type || self.payload_type, **opts)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
)
|
|
99
|
+
attributes[name] = compiler.define(name, Praxis::MultipartPart,
|
|
100
|
+
payload_attribute: payload_attribute,
|
|
101
|
+
filename_attribute: filename_attribute)
|
|
109
102
|
end
|
|
110
103
|
end
|
|
111
104
|
|
|
112
|
-
def self.file(name, payload_type=
|
|
113
|
-
|
|
105
|
+
def self.file(name, payload_type = nil, **opts, &block)
|
|
106
|
+
part(name, payload_type, filename: true, **opts, &block)
|
|
114
107
|
end
|
|
115
108
|
|
|
116
|
-
def self.load(value,
|
|
117
|
-
return value if value.
|
|
109
|
+
def self.load(value, _context = Attributor::DEFAULT_ROOT_CONTEXT, content_type: nil)
|
|
110
|
+
return value if value.is_a?(self) || value.nil?
|
|
118
111
|
|
|
119
|
-
if value.
|
|
120
|
-
raise ArgumentError, "content_type is required to load values of type String for #{Attributor.type_name(self)}"
|
|
121
|
-
end
|
|
112
|
+
raise ArgumentError, "content_type is required to load values of type String for #{Attributor.type_name(self)}" if value.is_a?(::String) && content_type.nil?
|
|
122
113
|
|
|
123
|
-
parser = Praxis::MultipartParser.new({'Content-Type' => content_type}, value)
|
|
114
|
+
parser = Praxis::MultipartParser.new({ 'Content-Type' => content_type }, value)
|
|
124
115
|
preamble, parts = parser.parse
|
|
125
116
|
|
|
126
|
-
instance =
|
|
117
|
+
instance = new
|
|
127
118
|
instance.push(*parts)
|
|
128
119
|
|
|
129
120
|
instance.preamble = preamble
|
|
@@ -132,22 +123,23 @@ module Praxis
|
|
|
132
123
|
instance
|
|
133
124
|
end
|
|
134
125
|
|
|
135
|
-
def self.example(context=Attributor::DEFAULT_ROOT_CONTEXT, **
|
|
136
|
-
example =
|
|
126
|
+
def self.example(context = Attributor::DEFAULT_ROOT_CONTEXT, **_options)
|
|
127
|
+
example = new
|
|
137
128
|
|
|
138
|
-
|
|
139
|
-
next if name.
|
|
140
|
-
|
|
129
|
+
attributes.each do |name, attribute|
|
|
130
|
+
next if name.is_a? Regexp
|
|
131
|
+
|
|
132
|
+
sub_context = generate_subcontext(context, name)
|
|
141
133
|
|
|
142
134
|
part = attribute.example(sub_context)
|
|
143
135
|
part.name = name
|
|
144
136
|
example.push part
|
|
145
137
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
138
|
+
next unless multiple.include? name
|
|
139
|
+
|
|
140
|
+
part = attribute.example(sub_context + ['2'])
|
|
141
|
+
part.name = name
|
|
142
|
+
example.push part
|
|
151
143
|
end
|
|
152
144
|
|
|
153
145
|
example
|
|
@@ -174,36 +166,35 @@ module Praxis
|
|
|
174
166
|
# profileImage: # Part 3 (an image)
|
|
175
167
|
# type: string
|
|
176
168
|
# format: binary
|
|
177
|
-
#
|
|
178
|
-
# NOTE: not sure if this
|
|
179
|
-
def self.as_openapi_request_body(
|
|
169
|
+
#
|
|
170
|
+
# NOTE: not sure if this
|
|
171
|
+
def self.as_openapi_request_body(attribute_options: {})
|
|
180
172
|
hash = { type: json_schema_type }
|
|
181
|
-
opts =
|
|
173
|
+
opts = options.merge(attribute_options)
|
|
182
174
|
hash[:description] = opts[:description] if opts[:description]
|
|
183
175
|
hash[:default] = opts[:default] if opts[:default]
|
|
184
176
|
|
|
185
|
-
unless
|
|
177
|
+
unless attributes.empty?
|
|
186
178
|
props = {}
|
|
187
179
|
encoding = {}
|
|
188
|
-
|
|
180
|
+
attributes.each do |part_name, part_attribute|
|
|
189
181
|
part_example = part_attribute.example
|
|
190
182
|
key_to_use = part_name.is_a?(Regexp) ? part_name.source : part_name
|
|
191
|
-
|
|
192
|
-
part_info = {}
|
|
183
|
+
|
|
193
184
|
if (payload_attribute = part_attribute.options[:payload_attribute])
|
|
194
185
|
props[key_to_use] = payload_attribute.as_json_schema(example: part_example.payload)
|
|
195
186
|
end
|
|
196
|
-
#{
|
|
187
|
+
# {
|
|
197
188
|
# contentType: 'fff',
|
|
198
189
|
# headers: {
|
|
199
190
|
# custom1: 'safd'
|
|
200
191
|
# }
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
192
|
+
next unless (headers_attribute = part_attribute.options[:headers_attribute])
|
|
193
|
+
|
|
194
|
+
# Does this 'Content-Type' string check work?...can it be a symbol? what does it mean anyway?
|
|
195
|
+
encoding[key_to_use][:contentType] = headers_attribute['Content-Type'] if headers_attribute['Content-Type']
|
|
196
|
+
# TODO?rethink? ...is this correct?: att a 'headers' key with some header schemas if this part have some
|
|
197
|
+
encoding[key_to_use]['headers'] = headers_attribute.as_json_schema(example: part_example.headers)
|
|
207
198
|
end
|
|
208
199
|
|
|
209
200
|
hash[:properties] = props
|
|
@@ -212,27 +203,27 @@ module Praxis
|
|
|
212
203
|
hash
|
|
213
204
|
end
|
|
214
205
|
|
|
215
|
-
def self.as_json_schema(
|
|
206
|
+
def self.as_json_schema(attribute_options: {}, **_other)
|
|
216
207
|
as_openapi_request_body(attribute_options: attribute_options)
|
|
217
208
|
end
|
|
218
209
|
|
|
219
|
-
def self.describe(shallow=true, example: nil)
|
|
210
|
+
def self.describe(shallow = true, example: nil)
|
|
220
211
|
type_name = Attributor.type_name(self)
|
|
221
212
|
hash = {
|
|
222
213
|
name: type_name.gsub(Attributor::MODULE_PREFIX_REGEX, ''),
|
|
223
|
-
family:
|
|
224
|
-
id:
|
|
214
|
+
family: family,
|
|
215
|
+
id: id
|
|
225
216
|
}
|
|
226
217
|
hash[:example] = example if example
|
|
227
218
|
|
|
228
|
-
hash[:part_name] = {type: name_type.describe(true)}
|
|
219
|
+
hash[:part_name] = { type: name_type.describe(true) }
|
|
229
220
|
|
|
230
221
|
unless shallow
|
|
231
|
-
hash[:attributes] = {} if
|
|
232
|
-
hash[:pattern_attributes] = {} if
|
|
222
|
+
hash[:attributes] = {} if attributes.keys.any? { |name| name.is_a? String }
|
|
223
|
+
hash[:pattern_attributes] = {} if attributes.keys.any? { |name| name.is_a? Regexp }
|
|
233
224
|
|
|
234
225
|
if hash.key?(:attributes) || hash.key?(:pattern_attributes)
|
|
235
|
-
|
|
226
|
+
describe_attributes(shallow, example: example).each do |name, sub_hash|
|
|
236
227
|
case name
|
|
237
228
|
when String
|
|
238
229
|
hash[:attributes][name] = sub_hash
|
|
@@ -241,38 +232,30 @@ module Praxis
|
|
|
241
232
|
end
|
|
242
233
|
end
|
|
243
234
|
else
|
|
244
|
-
hash[:part_payload] = {type: payload_type.describe(true)}
|
|
235
|
+
hash[:part_payload] = { type: payload_type.describe(true) }
|
|
245
236
|
end
|
|
246
237
|
end
|
|
247
238
|
hash
|
|
248
239
|
end
|
|
249
240
|
|
|
250
|
-
def self.describe_attributes(shallow=true, example: nil)
|
|
251
|
-
|
|
241
|
+
def self.describe_attributes(shallow = true, example: nil)
|
|
242
|
+
attributes.each_with_object({}) do |(part_name, part_attribute), parts|
|
|
252
243
|
sub_example = example.part(part_name) if example
|
|
253
|
-
if sub_example &&
|
|
254
|
-
sub_example = sub_example.first
|
|
255
|
-
end
|
|
244
|
+
sub_example = sub_example.first if sub_example && multiple.include?(part_name)
|
|
256
245
|
|
|
257
246
|
sub_hash = part_attribute.describe(shallow, example: sub_example)
|
|
258
247
|
|
|
259
|
-
|
|
260
248
|
if (options = sub_hash.delete(:options))
|
|
261
249
|
sub_hash[:options] = {}
|
|
262
|
-
if
|
|
263
|
-
sub_hash[:options][:multiple] = true
|
|
264
|
-
end
|
|
250
|
+
sub_hash[:options][:multiple] = true if multiple.include?(part_name)
|
|
265
251
|
|
|
266
|
-
if (payload_attribute = options.delete :payload_attribute)
|
|
267
|
-
|
|
268
|
-
sub_hash[:options][:required] = true
|
|
269
|
-
end
|
|
252
|
+
if (payload_attribute = options.delete :payload_attribute) && payload_attribute.options[:required]
|
|
253
|
+
sub_hash[:options][:required] = true
|
|
270
254
|
end
|
|
271
255
|
end
|
|
272
256
|
|
|
273
257
|
sub_hash[:type] = MultipartPart.describe(shallow, example: sub_example, options: part_attribute.options)
|
|
274
258
|
|
|
275
|
-
|
|
276
259
|
parts[part_name] = sub_hash
|
|
277
260
|
end
|
|
278
261
|
end
|
|
@@ -281,14 +264,13 @@ module Praxis
|
|
|
281
264
|
attr_reader :content_type
|
|
282
265
|
|
|
283
266
|
def initialize(content_type: self.class.identifier.to_s)
|
|
267
|
+
super()
|
|
284
268
|
self.content_type = content_type
|
|
285
269
|
end
|
|
286
270
|
|
|
287
271
|
def content_type=(content_type)
|
|
288
272
|
@content_type = MediaTypeIdentifier.load(content_type)
|
|
289
|
-
if @content_type.parameters.get('boundary').nil?
|
|
290
|
-
@content_type.parameters.set 'boundary', 'Boundary_puppies'
|
|
291
|
-
end
|
|
273
|
+
@content_type.parameters.set 'boundary', 'Boundary_puppies' if @content_type.parameters.get('boundary').nil?
|
|
292
274
|
@content_type
|
|
293
275
|
end
|
|
294
276
|
|
|
@@ -298,9 +280,7 @@ module Praxis
|
|
|
298
280
|
|
|
299
281
|
def push(*parts, context: Attributor::DEFAULT_ROOT_CONTEXT)
|
|
300
282
|
part, *rest = parts
|
|
301
|
-
if rest.any?
|
|
302
|
-
return self.push(part, context: context).push(*rest, context:context)
|
|
303
|
-
end
|
|
283
|
+
return push(part, context: context).push(*rest, context: context) if rest.any?
|
|
304
284
|
|
|
305
285
|
original_context = context
|
|
306
286
|
|
|
@@ -313,12 +293,10 @@ module Praxis
|
|
|
313
293
|
# payload_attribute, otherwise we constrain the parts
|
|
314
294
|
# to the defined names.
|
|
315
295
|
attribute = if self.class.attributes.empty?
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
nil
|
|
321
|
-
end
|
|
296
|
+
self.class.part_attribute
|
|
297
|
+
elsif (default_thingy = self.class.attributes[key])
|
|
298
|
+
default_thingy
|
|
299
|
+
end
|
|
322
300
|
|
|
323
301
|
if attribute
|
|
324
302
|
part.attribute = attribute
|
|
@@ -327,11 +305,11 @@ module Praxis
|
|
|
327
305
|
return self << part
|
|
328
306
|
elsif self.class.options[:case_insensitive_load]
|
|
329
307
|
name = self.class.attributes.keys.find do |k|
|
|
330
|
-
k.
|
|
308
|
+
k.is_a?(String) && key.downcase == k.downcase
|
|
331
309
|
end
|
|
332
310
|
if name
|
|
333
311
|
part.name = name
|
|
334
|
-
return
|
|
312
|
+
return push(part, context: original_context)
|
|
335
313
|
end
|
|
336
314
|
end
|
|
337
315
|
|
|
@@ -342,30 +320,39 @@ module Praxis
|
|
|
342
320
|
if self.class.multiple.include?(name)
|
|
343
321
|
self.select { |i| i.name == name }
|
|
344
322
|
else
|
|
345
|
-
|
|
323
|
+
find { |i| i.name == name }
|
|
346
324
|
end
|
|
347
325
|
end
|
|
348
326
|
|
|
349
|
-
def
|
|
350
|
-
|
|
327
|
+
def part?(name)
|
|
328
|
+
any? { |i| i.name == name }
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def validate(context = Attributor::DEFAULT_ROOT_CONTEXT)
|
|
332
|
+
errors = each_with_index.each_with_object([]) do |(part, idx), error_list|
|
|
351
333
|
sub_context = if part.name
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
334
|
+
self.class.generate_subcontext(context, part.name)
|
|
335
|
+
else
|
|
336
|
+
context + ["at(#{idx})"]
|
|
337
|
+
end
|
|
356
338
|
|
|
357
|
-
|
|
339
|
+
error_list.push(*part.validate(sub_context))
|
|
358
340
|
end
|
|
359
341
|
|
|
360
342
|
self.class.attributes.each do |name, attribute|
|
|
361
343
|
payload_attribute = attribute.options[:payload_attribute]
|
|
362
|
-
next unless payload_attribute.options[:required]
|
|
363
|
-
next if self.part(name)
|
|
364
344
|
|
|
365
|
-
|
|
366
|
-
|
|
345
|
+
if !part?(name)
|
|
346
|
+
if payload_attribute.options[:required]
|
|
347
|
+
sub_context = self.class.generate_subcontext(context, name)
|
|
348
|
+
errors.push "Attribute #{Attributor.humanize_context(sub_context)} is required"
|
|
349
|
+
end
|
|
350
|
+
# Return, don't bother checking nullability as it hasn't been provided
|
|
351
|
+
elsif !self.part(name) && !Attribute.nullable_attribute?(payload_attribute.options)
|
|
352
|
+
sub_context = self.class.generate_subcontext(context, name)
|
|
353
|
+
errors.push "Attribute #{Attributor.humanize_context(sub_context)} is not nullable"
|
|
354
|
+
end
|
|
367
355
|
end
|
|
368
|
-
|
|
369
356
|
errors
|
|
370
357
|
end
|
|
371
358
|
|
|
@@ -376,15 +363,13 @@ module Praxis
|
|
|
376
363
|
def dump(**opts)
|
|
377
364
|
boundary = content_type.parameters.get 'boundary'
|
|
378
365
|
|
|
379
|
-
parts =
|
|
366
|
+
parts = collect do |part|
|
|
380
367
|
part.dump(**opts)
|
|
381
368
|
end
|
|
382
369
|
|
|
383
370
|
all_entities = parts.join("\r\n--#{boundary}\r\n")
|
|
384
371
|
"--#{boundary}\r\n#{all_entities}\r\n--#{boundary}--\r\n"
|
|
385
372
|
end
|
|
386
|
-
|
|
387
|
-
|
|
388
373
|
end
|
|
389
374
|
end
|
|
390
375
|
end
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Praxis
|
|
2
4
|
class ValidationHandler
|
|
3
|
-
|
|
4
5
|
# Should return the Response to send back
|
|
5
|
-
def handle!(summary:,
|
|
6
|
+
def handle!(summary:, errors: nil, exception: nil, **opts)
|
|
7
|
+
opts.delete(:request)
|
|
8
|
+
opts.delete(:stage)
|
|
6
9
|
Responses::ValidationError.new(summary: summary, errors: errors, exception: exception, **opts)
|
|
7
10
|
end
|
|
8
|
-
|
|
9
11
|
end
|
|
10
12
|
end
|
data/lib/praxis/version.rb
CHANGED
data/lib/praxis.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'rack'
|
|
2
4
|
require 'attributor'
|
|
3
5
|
|
|
@@ -5,7 +7,7 @@ require 'active_support/concern'
|
|
|
5
7
|
require 'praxis/request_superclassing'
|
|
6
8
|
require 'active_support/inflector'
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
$LOAD_PATH.unshift File.dirname(__FILE__)
|
|
9
11
|
|
|
10
12
|
require 'mime'
|
|
11
13
|
module MIME
|
|
@@ -15,7 +17,6 @@ module MIME
|
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
module Praxis
|
|
18
|
-
|
|
19
20
|
autoload :ActionDefinition, 'praxis/action_definition'
|
|
20
21
|
autoload :ApiGeneralInfo, 'praxis/api_general_info'
|
|
21
22
|
autoload :ApiDefinition, 'praxis/api_definition'
|
|
@@ -28,7 +29,7 @@ module Praxis
|
|
|
28
29
|
autoload :ErrorHandler, 'praxis/error_handler'
|
|
29
30
|
autoload :ValidationHandler, 'praxis/validation_handler'
|
|
30
31
|
autoload :Exception, 'praxis/exception'
|
|
31
|
-
autoload :FileGroup,'praxis/file_group'
|
|
32
|
+
autoload :FileGroup, 'praxis/file_group'
|
|
32
33
|
autoload :Plugin, 'praxis/plugin'
|
|
33
34
|
autoload :PluginConcern, 'praxis/plugin_concern'
|
|
34
35
|
autoload :Request, 'praxis/request'
|
|
@@ -50,7 +51,6 @@ module Praxis
|
|
|
50
51
|
autoload :Blueprint, 'praxis/blueprint'
|
|
51
52
|
autoload :FieldExpander, 'praxis/field_expander'
|
|
52
53
|
autoload :Renderer, 'praxis/renderer'
|
|
53
|
-
|
|
54
54
|
|
|
55
55
|
autoload :Notifications, 'praxis/notifications'
|
|
56
56
|
autoload :MiddlewareApp, 'praxis/middleware_app'
|
|
@@ -143,5 +143,4 @@ module Praxis
|
|
|
143
143
|
require 'praxis/responses/internal_server_error'
|
|
144
144
|
require 'praxis/responses/validation_error'
|
|
145
145
|
require 'praxis/responses/multipart_ok'
|
|
146
|
-
|
|
147
146
|
end
|
data/praxis.gemspec
CHANGED
|
@@ -1,36 +1,38 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
5
|
|
|
4
6
|
require 'praxis/version'
|
|
5
7
|
|
|
6
8
|
Gem::Specification.new do |spec|
|
|
7
|
-
spec.name =
|
|
9
|
+
spec.name = 'praxis'
|
|
8
10
|
spec.version = Praxis::VERSION
|
|
9
|
-
spec.authors = [
|
|
11
|
+
spec.authors = ['Josep M. Blanquer', 'Dane Jensen']
|
|
10
12
|
spec.summary = 'Building APIs the way you want it.'
|
|
11
13
|
|
|
12
|
-
spec.email = [
|
|
14
|
+
spec.email = ['blanquer@gmail.com', 'dane.jensen@gmail.com']
|
|
13
15
|
|
|
14
|
-
spec.homepage =
|
|
15
|
-
spec.license =
|
|
16
|
-
spec.required_ruby_version =
|
|
16
|
+
spec.homepage = 'https://github.com/praxis/praxis'
|
|
17
|
+
spec.license = 'MIT'
|
|
18
|
+
spec.required_ruby_version = '>=2.5'
|
|
17
19
|
|
|
18
|
-
spec.require_paths = [
|
|
20
|
+
spec.require_paths = ['lib']
|
|
19
21
|
spec.files = `git ls-files -z`.split("\x0")
|
|
20
22
|
spec.bindir = 'bin'
|
|
21
23
|
spec.executables << 'praxis'
|
|
22
24
|
|
|
23
|
-
spec.add_dependency 'rack', '>= 1'
|
|
24
|
-
spec.add_dependency 'mustermann', '>=1.1', '<=2'
|
|
25
25
|
spec.add_dependency 'activesupport', '>= 3'
|
|
26
|
+
spec.add_dependency 'attributor', '>= 6.0'
|
|
26
27
|
spec.add_dependency 'mime', '~> 0'
|
|
27
|
-
spec.add_dependency '
|
|
28
|
-
spec.add_dependency '
|
|
28
|
+
spec.add_dependency 'mustermann', '>=1.1', '<=2'
|
|
29
|
+
spec.add_dependency 'rack', '>= 1'
|
|
29
30
|
spec.add_dependency 'terminal-table', '~> 1.4'
|
|
31
|
+
spec.add_dependency 'thor'
|
|
30
32
|
|
|
31
33
|
spec.add_development_dependency 'bundler'
|
|
32
34
|
spec.add_development_dependency 'rake', '>= 12.3.3'
|
|
33
|
-
|
|
35
|
+
|
|
34
36
|
if RUBY_PLATFORM !~ /java/
|
|
35
37
|
spec.add_development_dependency 'pry'
|
|
36
38
|
spec.add_development_dependency 'pry-byebug'
|
|
@@ -39,17 +41,16 @@ Gem::Specification.new do |spec|
|
|
|
39
41
|
else
|
|
40
42
|
spec.add_development_dependency 'jdbc-sqlite3'
|
|
41
43
|
end
|
|
42
|
-
spec.add_development_dependency '
|
|
43
|
-
spec.add_development_dependency '
|
|
44
|
-
spec.add_development_dependency 'rspec-collection_matchers', '~> 1'
|
|
44
|
+
spec.add_development_dependency 'coveralls'
|
|
45
|
+
spec.add_development_dependency 'fuubar', '~> 2'
|
|
45
46
|
spec.add_development_dependency 'guard', '~> 2'
|
|
46
|
-
spec.add_development_dependency 'guard-rspec', '~> 4'
|
|
47
47
|
spec.add_development_dependency 'guard-bundler', '~> 2'
|
|
48
|
+
spec.add_development_dependency 'guard-rspec', '~> 4'
|
|
48
49
|
spec.add_development_dependency 'rack-test', '~> 0'
|
|
49
|
-
spec.add_development_dependency '
|
|
50
|
-
spec.add_development_dependency '
|
|
51
|
-
spec.add_development_dependency '
|
|
50
|
+
spec.add_development_dependency 'rspec', '~> 3'
|
|
51
|
+
spec.add_development_dependency 'rspec-collection_matchers', '~> 1'
|
|
52
|
+
spec.add_development_dependency 'rspec-its', '~> 1'
|
|
52
53
|
# Just for the query selector extensions etc...
|
|
54
|
+
spec.add_development_dependency 'activerecord', '> 4', '< 7'
|
|
53
55
|
spec.add_development_dependency 'sequel', '~> 5'
|
|
54
|
-
spec.add_development_dependency 'activerecord', '> 4'
|
|
55
56
|
end
|