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