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,151 +1,145 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
module Praxis
|
4
|
+
module Mapper
|
5
|
+
class SelectorGeneratorNode
|
6
|
+
attr_reader :select, :model, :resource, :tracks
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
+
def initialize(resource)
|
9
|
+
@resource = resource
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
end
|
13
|
-
|
14
|
-
def add(fields)
|
15
|
-
fields.each do |name, field|
|
16
|
-
map_property(name, field)
|
11
|
+
@select = Set.new
|
12
|
+
@select_star = false
|
13
|
+
@tracks = {}
|
17
14
|
end
|
18
|
-
self
|
19
|
-
end
|
20
15
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
add_association(name, fields)
|
27
|
-
else
|
28
|
-
add_select(name)
|
16
|
+
def add(fields)
|
17
|
+
fields.each do |name, field|
|
18
|
+
map_property(name, field)
|
19
|
+
end
|
20
|
+
self
|
29
21
|
end
|
30
|
-
end
|
31
22
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
# Add the required columns in this model to make sure the association can be loaded
|
42
|
-
association[:local_key_columns].each {|col| add_select(col) }
|
43
|
-
|
44
|
-
node = SelectorGeneratorNode.new(associated_resource)
|
45
|
-
unless association[:remote_key_columns].empty?
|
46
|
-
# Make sure we add the required columns for this association to the remote model query
|
47
|
-
fields = {} if fields == true
|
48
|
-
new_fields_as_hash = association[:remote_key_columns].each_with_object({}) do|name, hash|
|
49
|
-
hash[name] = true
|
23
|
+
def map_property(name, fields)
|
24
|
+
praxis_compat_model = resource.model&.respond_to?(:_praxis_associations)
|
25
|
+
if resource.properties.key?(name)
|
26
|
+
add_property(name, fields)
|
27
|
+
elsif praxis_compat_model && resource.model._praxis_associations.key?(name)
|
28
|
+
add_association(name, fields)
|
29
|
+
else
|
30
|
+
add_select(name)
|
50
31
|
end
|
51
|
-
fields = fields.merge(new_fields_as_hash)
|
52
32
|
end
|
53
33
|
|
54
|
-
|
34
|
+
def add_association(name, fields)
|
35
|
+
association = resource.model._praxis_associations.fetch(name) do
|
36
|
+
raise "missing association for #{resource} with name #{name}"
|
37
|
+
end
|
38
|
+
associated_resource = resource.model_map[association[:model]]
|
39
|
+
raise "Whoops! could not find a resource associated with model #{association[:model]} (root resource #{resource})" unless associated_resource
|
40
|
+
|
41
|
+
# Add the required columns in this model to make sure the association can be loaded
|
42
|
+
association[:local_key_columns].each { |col| add_select(col) }
|
43
|
+
|
44
|
+
node = SelectorGeneratorNode.new(associated_resource)
|
45
|
+
unless association[:remote_key_columns].empty?
|
46
|
+
# Make sure we add the required columns for this association to the remote model query
|
47
|
+
fields = {} if fields == true
|
48
|
+
new_fields_as_hash = association[:remote_key_columns].each_with_object({}) do |key, hash|
|
49
|
+
hash[key] = true
|
50
|
+
end
|
51
|
+
fields = fields.merge(new_fields_as_hash)
|
52
|
+
end
|
53
|
+
|
54
|
+
node.add(fields) unless fields == true
|
55
55
|
|
56
|
-
|
57
|
-
|
56
|
+
merge_track(name, node)
|
57
|
+
end
|
58
58
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
@select.add name
|
63
|
-
end
|
59
|
+
def add_select(name)
|
60
|
+
return @select_star = true if name == :*
|
61
|
+
return if @select_star
|
64
62
|
|
65
|
-
|
66
|
-
dependencies = resource.properties[name][:dependencies]
|
67
|
-
# Always add the underlying association if we're overriding the name...
|
68
|
-
praxis_compat_model = resource.model && resource.model.respond_to?(:_praxis_associations)
|
69
|
-
if praxis_compat_model && resource.model._praxis_associations.key?(name)
|
70
|
-
add_association(name, fields)
|
63
|
+
@select.add name
|
71
64
|
end
|
72
|
-
|
73
|
-
|
65
|
+
|
66
|
+
def add_property(name, fields)
|
67
|
+
dependencies = resource.properties[name][:dependencies]
|
68
|
+
# Always add the underlying association if we're overriding the name...
|
69
|
+
praxis_compat_model = resource.model&.respond_to?(:_praxis_associations)
|
70
|
+
add_association(name, fields) if praxis_compat_model && resource.model._praxis_associations.key?(name)
|
71
|
+
dependencies&.each do |dependency|
|
74
72
|
# To detect recursion, let's allow mapping depending fields to the same name of the property
|
75
73
|
# but properly detecting if it's a real association...in which case we've already added it above
|
76
74
|
if dependency == name
|
77
|
-
unless praxis_compat_model && resource.model._praxis_associations.key?(name)
|
78
|
-
add_select(name)
|
79
|
-
end
|
75
|
+
add_select(name) unless praxis_compat_model && resource.model._praxis_associations.key?(name)
|
80
76
|
else
|
81
77
|
apply_dependency(dependency)
|
82
78
|
end
|
83
79
|
end
|
84
|
-
end
|
85
80
|
|
86
|
-
|
87
|
-
|
81
|
+
head, *tail = resource.properties[name][:through]
|
82
|
+
return if head.nil?
|
88
83
|
|
89
|
-
|
90
|
-
|
91
|
-
|
84
|
+
new_fields = tail.reverse.inject(fields) do |thing, step|
|
85
|
+
{ step => thing }
|
86
|
+
end
|
92
87
|
|
93
|
-
|
94
|
-
|
88
|
+
add_association(head, new_fields)
|
89
|
+
end
|
95
90
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
91
|
+
def apply_dependency(dependency)
|
92
|
+
case dependency
|
93
|
+
when Symbol
|
94
|
+
map_property(dependency, true)
|
95
|
+
when String
|
96
|
+
head, *tail = dependency.split('.').collect(&:to_sym)
|
97
|
+
raise 'String dependencies can not be singular' if tail.nil?
|
103
98
|
|
104
|
-
|
99
|
+
add_association(head, tail.reverse.inject({}) { |hash, dep| { dep => hash } })
|
100
|
+
end
|
105
101
|
end
|
106
|
-
end
|
107
102
|
|
108
|
-
|
109
|
-
|
103
|
+
def merge_track(track_name, node)
|
104
|
+
raise "Cannot merge another node for association #{track_name}: incompatible model" unless node.model == model
|
110
105
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
106
|
+
existing = tracks[track_name]
|
107
|
+
if existing
|
108
|
+
node.select.each do |col_name|
|
109
|
+
existing.add_select(col_name)
|
110
|
+
end
|
111
|
+
node.tracks.each do |name, n|
|
112
|
+
existing.merge_track(name, n)
|
113
|
+
end
|
114
|
+
else
|
115
|
+
tracks[track_name] = node
|
118
116
|
end
|
119
|
-
else
|
120
|
-
self.tracks[track_name] = node
|
121
117
|
end
|
122
|
-
end
|
123
118
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
hash
|
119
|
+
def dump
|
120
|
+
hash = {}
|
121
|
+
hash[:model] = resource.model
|
122
|
+
if !@select.empty? || @select_star
|
123
|
+
hash[:columns] = @select_star ? [:*] : @select.to_a
|
124
|
+
end
|
125
|
+
hash[:tracks] = @tracks.transform_values(&:dump) unless @tracks.empty?
|
126
|
+
hash
|
132
127
|
end
|
133
|
-
hash
|
134
128
|
end
|
135
|
-
end
|
136
129
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
130
|
+
# Generates a set of selectors given a resource and
|
131
|
+
# list of resource attributes.
|
132
|
+
class SelectorGenerator
|
133
|
+
# Entry point
|
134
|
+
def add(resource, fields)
|
135
|
+
@root = SelectorGeneratorNode.new(resource)
|
136
|
+
@root.add(fields)
|
137
|
+
self
|
138
|
+
end
|
146
139
|
|
147
|
-
|
148
|
-
|
140
|
+
def selectors
|
141
|
+
@root
|
142
|
+
end
|
149
143
|
end
|
150
144
|
end
|
151
145
|
end
|
@@ -1,84 +1,87 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'active_support/concern'
|
3
4
|
|
4
|
-
module Praxis
|
5
|
-
module
|
6
|
-
|
5
|
+
module Praxis
|
6
|
+
module Mapper
|
7
|
+
module SequelCompat
|
8
|
+
extend ActiveSupport::Concern
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
class <<self
|
11
|
-
alias_method :find_by, :find # Easy way to be method compatible with AR
|
12
|
-
end
|
13
|
-
end
|
10
|
+
included do
|
11
|
+
attr_accessor :_resource
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
Praxis::Extensions::SequelFilterQueryBuilder
|
13
|
+
class << self
|
14
|
+
alias_method :find_by, :find # Easy way to be method compatible with AR
|
15
|
+
end
|
19
16
|
end
|
20
17
|
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
module ClassMethods
|
19
|
+
def _filter_query_builder_class
|
20
|
+
# TODO: refactor the query builder, and add the explicit require in this file
|
21
|
+
Praxis::Extensions::SequelFilterQueryBuilder
|
22
|
+
end
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
def _field_selector_query_builder_class
|
25
|
+
Praxis::Extensions::FieldSelection::SequelQuerySelector
|
26
|
+
end
|
28
27
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
v[:
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
28
|
+
def _pagination_query_builder_class
|
29
|
+
Praxis::Extensions::Pagination::SequelPaginationHandler
|
30
|
+
end
|
31
|
+
|
32
|
+
def _praxis_associations
|
33
|
+
orig = association_reflections.clone
|
34
|
+
orig.each do |_k, v|
|
35
|
+
v[:model] = v.associated_class
|
36
|
+
v[:local_key_columns] = local_columns_used_for_the_association(v[:type], v)
|
37
|
+
v[:remote_key_columns] = remote_columns_used_for_the_association(v[:type], v)
|
38
|
+
v[:primary_key] = if v.respond_to?(:primary_key)
|
39
|
+
v.primary_key
|
40
|
+
else
|
41
|
+
# FIXME: figure out exactly what to do here.
|
42
|
+
# not super critical, as we can't track these associations
|
43
|
+
# directly, but it would be nice to traverse these
|
44
|
+
# properly.
|
45
|
+
:unsupported
|
46
|
+
end
|
43
47
|
end
|
48
|
+
orig
|
44
49
|
end
|
45
|
-
orig
|
46
|
-
end
|
47
50
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
51
|
+
private
|
52
|
+
|
53
|
+
def local_columns_used_for_the_association(type, assoc_reflection)
|
54
|
+
case type
|
55
|
+
when :one_to_many
|
56
|
+
# The associated table (or middle table if many to many) will point to us by PK
|
57
|
+
assoc_reflection[:primary_key_columns]
|
58
|
+
when :many_to_one
|
59
|
+
# We have the FKs to the associated model
|
60
|
+
assoc_reflection[:keys]
|
61
|
+
when :many_to_many
|
62
|
+
# The middle table if many to many) will point to us by key (usually the PK, but not always)
|
63
|
+
assoc_reflection[:left_primary_keys]
|
64
|
+
else
|
65
|
+
raise "association type #{type} not supported"
|
66
|
+
end
|
62
67
|
end
|
63
|
-
end
|
64
68
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
69
|
+
def remote_columns_used_for_the_association(type, assoc_reflection)
|
70
|
+
case type
|
71
|
+
when :one_to_many
|
72
|
+
# The columns in the associated table that will point back to the original association
|
73
|
+
assoc_reflection[:keys]
|
74
|
+
when :many_to_one
|
75
|
+
# The columns in the associated table that the children will point to (usually the PK, but not always) ??
|
76
|
+
[assoc_reflection.associated_class.primary_key]
|
77
|
+
when :many_to_many
|
78
|
+
# The middle table if many to many will point to us by key (usually the PK, but not always) ??
|
79
|
+
[assoc_reflection.associated_class.primary_key]
|
80
|
+
else
|
81
|
+
raise "association type #{type} not supported"
|
82
|
+
end
|
78
83
|
end
|
79
84
|
end
|
80
|
-
|
81
85
|
end
|
82
|
-
|
83
86
|
end
|
84
|
-
end
|
87
|
+
end
|
data/lib/praxis/media_type.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Praxis
|
2
4
|
# An Internet Media Type as defined in RFC 1590, as used in HTTP (see RFC 2616). As used in the
|
3
5
|
# Praxis framework, media types also define the structure and content of entities of that type:
|
@@ -43,8 +45,6 @@ module Praxis
|
|
43
45
|
# end
|
44
46
|
# end
|
45
47
|
class MediaType < Praxis::Blueprint
|
46
|
-
|
47
48
|
include Types::MediaTypeCommon
|
48
49
|
end
|
49
|
-
|
50
50
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'set'
|
2
4
|
require 'active_support/core_ext/object/blank'
|
3
5
|
|
@@ -10,7 +12,7 @@ module Praxis
|
|
10
12
|
# the syntax for type identifiers is substantially narrower than what we accept there.
|
11
13
|
#
|
12
14
|
# Note that this ONLY matches type, subtype and suffix; we handle options differently.
|
13
|
-
VALID_TYPE =
|
15
|
+
VALID_TYPE = %r{^\s*(?<type>[^/]+)/(?<subtype>[^+]+)(?<nothing>\+(?<suffix>[^; ]+))?\s*$}x.freeze
|
14
16
|
|
15
17
|
# Pattern that separates parameters of a media type from each other, and from the base identifier.
|
16
18
|
PARAMETER_SEPARATOR = /\s*;\s*/x.freeze
|
@@ -23,7 +25,7 @@ module Praxis
|
|
23
25
|
QUOTED_STRING = /^".*"$/.freeze
|
24
26
|
|
25
27
|
# Token that indicates a media-type component that matches anything.
|
26
|
-
WILDCARD = '*'
|
28
|
+
WILDCARD = '*'
|
27
29
|
|
28
30
|
# Inner type representing semicolon-delimited parameters.
|
29
31
|
Parameters = Attributor::Hash.of(key: String)
|
@@ -32,7 +34,7 @@ module Praxis
|
|
32
34
|
attribute :type, Attributor::String, default: 'application', description: 'RFC2046 media type'
|
33
35
|
attribute :subtype, Attributor::String, default: '*', description: 'RFC2046 media subtype', example: 'vnd.widget'
|
34
36
|
attribute :suffix, Attributor::String, default: '', description: 'RFC6838 structured-syntax suffix', example: 'json'
|
35
|
-
attribute :parameters, Parameters, default: {}, description:
|
37
|
+
attribute :parameters, Parameters, default: {}, description: 'Type-specific parameters', example: "{'type' => 'collection'}"
|
36
38
|
end
|
37
39
|
|
38
40
|
# Parse a media type identifier from a String, or load it from a Hash or Model. Assume malformed
|
@@ -41,10 +43,11 @@ module Praxis
|
|
41
43
|
# @param [String,Hash,Attributor::Model] value
|
42
44
|
# @return [MediaTypeIdentifier]
|
43
45
|
# @see Attributor::Model#load
|
44
|
-
def self.load(value, context=Attributor::DEFAULT_ROOT_CONTEXT, recurse: false, **options)
|
46
|
+
def self.load(value, context = Attributor::DEFAULT_ROOT_CONTEXT, recurse: false, **options)
|
45
47
|
case value
|
46
48
|
when String
|
47
49
|
return nil if value.blank?
|
50
|
+
|
48
51
|
base, *parameters = value.split(PARAMETER_SEPARATOR)
|
49
52
|
match = VALID_TYPE.match(base)
|
50
53
|
|
@@ -56,17 +59,19 @@ module Praxis
|
|
56
59
|
h[k] = v
|
57
60
|
end
|
58
61
|
|
59
|
-
obj.type
|
60
|
-
|
62
|
+
obj.type = match[:type]
|
63
|
+
obj.subtype = match[:subtype]
|
64
|
+
obj.suffix = match[:suffix]
|
65
|
+
obj.parameters = parameters
|
61
66
|
else
|
62
67
|
obj.type = 'application'
|
63
68
|
obj.subtype = base.split(WORD_SEPARATOR, 2).first
|
64
|
-
obj.suffix =
|
69
|
+
obj.suffix = String.new
|
65
70
|
obj.parameters = {}
|
66
71
|
end
|
67
72
|
obj
|
68
73
|
when nil
|
69
|
-
|
74
|
+
nil
|
70
75
|
else
|
71
76
|
super
|
72
77
|
end
|
@@ -97,6 +102,7 @@ module Praxis
|
|
97
102
|
return false unless type == other.type || type == WILDCARD
|
98
103
|
return false unless subtype == other.subtype || subtype == WILDCARD
|
99
104
|
return false unless suffix.empty? || suffix == other.suffix
|
105
|
+
|
100
106
|
parameters.each_pair do |k, v|
|
101
107
|
return false unless other.parameters[k] == v
|
102
108
|
end
|
@@ -120,7 +126,7 @@ module Praxis
|
|
120
126
|
# @return [String] canonicalized representation of the media type including all suffixes and parameters
|
121
127
|
def to_s
|
122
128
|
# Our handcrafted media types consist of a rich chocolatey center
|
123
|
-
s = "#{type}/#{subtype}"
|
129
|
+
s = String.new("#{type}/#{subtype}")
|
124
130
|
|
125
131
|
# coated in a hard candy shell
|
126
132
|
s << '+' << suffix unless suffix.empty?
|
@@ -135,18 +141,17 @@ module Praxis
|
|
135
141
|
s
|
136
142
|
end
|
137
143
|
|
138
|
-
|
139
|
-
|
144
|
+
alias to_str to_s
|
140
145
|
|
141
146
|
# If parameters are empty, return self; otherwise return a new object consisting of this type
|
142
147
|
# minus parameters.
|
143
148
|
#
|
144
149
|
# @return [MediaTypeIdentifier]
|
145
150
|
def without_parameters
|
146
|
-
if
|
151
|
+
if parameters.empty?
|
147
152
|
self
|
148
153
|
else
|
149
|
-
val = {type:
|
154
|
+
val = { type: type, subtype: subtype, suffix: suffix }
|
150
155
|
MediaTypeIdentifier.load(val)
|
151
156
|
end
|
152
157
|
end
|
@@ -176,12 +181,12 @@ module Praxis
|
|
176
181
|
#
|
177
182
|
# @example Indicate UTF8 encoding
|
178
183
|
# MediaTypeIdentifier.new('application/vnd.widget') + 'charset=UTF8' # => 'application/vnd.widget; charset="UTF8"'
|
179
|
-
def +(
|
180
|
-
parameters =
|
184
|
+
def +(other)
|
185
|
+
parameters = other.split(PARAMETER_SEPARATOR)
|
181
186
|
# remove useless initial '; '
|
182
187
|
parameters.shift if parameters.first && parameters.first.empty?
|
183
188
|
|
184
|
-
raise ArgumentError,
|
189
|
+
raise ArgumentError, 'Must pass a type identifier suffix and/or parameters' if parameters.empty?
|
185
190
|
|
186
191
|
suffix = parameters.shift unless parameters.first.include?('=')
|
187
192
|
# remove redundant '+'
|
@@ -196,10 +201,10 @@ module Praxis
|
|
196
201
|
parameters = Parameters.load(parameters)
|
197
202
|
|
198
203
|
obj = self.class.new
|
199
|
-
obj.type =
|
200
|
-
obj.subtype =
|
204
|
+
obj.type = type
|
205
|
+
obj.subtype = subtype
|
201
206
|
target_suffix = suffix || self.suffix
|
202
|
-
obj.suffix = redundant_suffix(target_suffix) ?
|
207
|
+
obj.suffix = redundant_suffix(target_suffix) ? String.new : target_suffix
|
203
208
|
obj.parameters = self.parameters.merge(parameters)
|
204
209
|
|
205
210
|
obj
|
@@ -208,9 +213,8 @@ module Praxis
|
|
208
213
|
def redundant_suffix(suffix)
|
209
214
|
# application/json does not need to be suffixed with +json (same for application/xml)
|
210
215
|
# we're supporting text/json and text/xml for older formats as well
|
211
|
-
if (
|
212
|
-
|
213
|
-
end
|
216
|
+
return true if (type == 'application' || type == 'text') && subtype == suffix
|
217
|
+
|
214
218
|
false
|
215
219
|
end
|
216
220
|
end
|
@@ -1,48 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Praxis
|
2
4
|
class MiddlewareApp
|
3
|
-
|
4
5
|
attr_reader :target
|
5
6
|
|
6
7
|
# Initialize the application instance with the desired args, and return the wrapping class.
|
7
|
-
def self.for(
|
8
|
+
def self.for(**args)
|
8
9
|
Class.new(self) do
|
9
10
|
@args = args
|
10
11
|
@setup_done = false
|
11
12
|
def self.name
|
12
13
|
'MiddlewareApp'
|
13
14
|
end
|
14
|
-
|
15
|
-
|
15
|
+
|
16
|
+
class << self
|
17
|
+
attr_reader :args
|
16
18
|
end
|
17
|
-
|
18
|
-
|
19
|
+
|
20
|
+
class << self
|
21
|
+
attr_reader :setup_done
|
19
22
|
end
|
23
|
+
|
20
24
|
def self.setup
|
21
25
|
@setup_done = true
|
22
26
|
Praxis::Application.instance.setup(**@args)
|
23
27
|
end
|
24
28
|
end
|
25
|
-
|
29
|
+
end
|
26
30
|
|
27
|
-
def initialize(
|
31
|
+
def initialize(inner)
|
28
32
|
@target = inner
|
29
33
|
@setup_done = false
|
30
34
|
end
|
31
35
|
|
32
36
|
def call(env)
|
33
37
|
self.class.setup unless self.class.setup_done
|
34
|
-
|
38
|
+
|
35
39
|
result = Praxis::Application.instance.call(env)
|
36
40
|
|
37
|
-
|
38
|
-
# Respect X-Cascade header if it doesn't specify 'pass'
|
39
|
-
result
|
40
|
-
else
|
41
|
+
if [404, 405].include?(result[0].to_i) && result[1]['X-Cascade'] == 'pass'
|
41
42
|
last_body = result[2]
|
42
43
|
last_body.close if last_body.respond_to? :close
|
43
44
|
target.call(env)
|
45
|
+
else
|
46
|
+
# Respect X-Cascade header if it doesn't specify 'pass'
|
47
|
+
result
|
44
48
|
end
|
45
49
|
end
|
46
|
-
|
47
50
|
end
|
48
|
-
end
|
51
|
+
end
|