praxis 2.0.pre.18 → 2.0.pre.19
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 +6 -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 +64 -94
- 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 +64 -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 +10 -13
- 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 +6 -3
- 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 +16 -16
- data/lib/praxis/docs/open_api/server_object.rb +5 -2
- data/lib/praxis/docs/open_api/tag_object.rb +6 -3
- data/lib/praxis/docs/open_api_generator.rb +92 -95
- 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 +171 -180
- 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 +46 -47
- 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 -16
- data/lib/praxis/request_stages/validate_payload.rb +25 -27
- data/lib/praxis/request_superclassing.rb +3 -3
- data/lib/praxis/resource_definition.rb +1 -0
- data/lib/praxis/response.rb +13 -25
- 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 +15 -15
- 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 +88 -112
- 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 +40 -52
- data/spec/praxis/action_definition_spec.rb +36 -46
- 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 +27 -30
- data/spec/praxis/mapper/selector_generator_spec.rb +50 -50
- 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 +28 -39
- 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 -18
- 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 +5 -5
- 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 +9 -15
- 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 +2 -1
- 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 +20 -18
- 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,22 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Praxis
|
2
4
|
class ApiGeneralInfo
|
3
|
-
|
4
5
|
attr_reader :version
|
5
6
|
|
6
|
-
def initialize(global_info=nil, version: nil)
|
7
|
-
@data =
|
7
|
+
def initialize(global_info = nil, version: nil)
|
8
|
+
@data = {}
|
8
9
|
@global_info = global_info
|
9
10
|
@version = version
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
return unless @global_info.nil?
|
13
|
+
|
14
|
+
# this *is* the global info
|
15
|
+
version_with %i[header params]
|
16
|
+
consumes 'json', 'x-www-form-urlencoded'
|
17
|
+
produces 'json'
|
16
18
|
end
|
17
19
|
|
18
20
|
# Allow any custom method to get/set any value
|
19
|
-
def method_missing(name, val=nil)
|
21
|
+
def method_missing(name, val = nil)
|
20
22
|
if val.nil?
|
21
23
|
get(name)
|
22
24
|
else
|
@@ -28,17 +30,18 @@ module Praxis
|
|
28
30
|
true
|
29
31
|
end
|
30
32
|
|
31
|
-
def get(
|
32
|
-
return @data[
|
33
|
-
return @global_info.get(
|
33
|
+
def get(key)
|
34
|
+
return @data[key] if @data.key?(key)
|
35
|
+
return @global_info.get(key) if @global_info
|
36
|
+
|
34
37
|
nil
|
35
38
|
end
|
36
39
|
|
37
|
-
def set(
|
38
|
-
@data[
|
40
|
+
def set(key, val)
|
41
|
+
@data[key] = val
|
39
42
|
end
|
40
43
|
|
41
|
-
def name(val=nil)
|
44
|
+
def name(val = nil)
|
42
45
|
if val.nil?
|
43
46
|
get(:name)
|
44
47
|
else
|
@@ -46,7 +49,7 @@ module Praxis
|
|
46
49
|
end
|
47
50
|
end
|
48
51
|
|
49
|
-
def title(val=nil)
|
52
|
+
def title(val = nil)
|
50
53
|
if val.nil?
|
51
54
|
get(:title)
|
52
55
|
else
|
@@ -54,7 +57,7 @@ module Praxis
|
|
54
57
|
end
|
55
58
|
end
|
56
59
|
|
57
|
-
def logo_url(val=nil)
|
60
|
+
def logo_url(val = nil)
|
58
61
|
if val.nil?
|
59
62
|
get(:logo_url)
|
60
63
|
else
|
@@ -62,7 +65,7 @@ module Praxis
|
|
62
65
|
end
|
63
66
|
end
|
64
67
|
|
65
|
-
def description(val=nil)
|
68
|
+
def description(val = nil)
|
66
69
|
if val.nil?
|
67
70
|
get(:description)
|
68
71
|
else
|
@@ -70,106 +73,92 @@ module Praxis
|
|
70
73
|
end
|
71
74
|
end
|
72
75
|
|
73
|
-
def version_with(val=nil)
|
76
|
+
def version_with(val = nil)
|
74
77
|
if val.nil?
|
75
78
|
get(:version_with)
|
79
|
+
elsif @global_info.nil?
|
80
|
+
Application.instance.versioning_scheme = val
|
81
|
+
set(:version_with, val) # this *is* the global info
|
76
82
|
else
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
else
|
81
|
-
raise "Use of version_with is only allowed in the global part of " \
|
82
|
-
"the API definition (but you are attempting to use it in the API " \
|
83
|
-
"definition of version #{self.version}"
|
84
|
-
end
|
83
|
+
raise 'Use of version_with is only allowed in the global part of ' \
|
84
|
+
'the API definition (but you are attempting to use it in the API ' \
|
85
|
+
"definition of version #{version}"
|
85
86
|
end
|
86
87
|
end
|
87
88
|
|
88
|
-
def endpoint(val=nil)
|
89
|
+
def endpoint(val = nil)
|
89
90
|
if val.nil?
|
90
91
|
get(:endpoint)
|
92
|
+
elsif @global_info.nil?
|
93
|
+
set(:endpoint, val) # this *is* the global info
|
91
94
|
else
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
raise "Use of endpoint is only allowed in the global part of " \
|
96
|
-
"the API definition (but you are attempting to use it in the API " \
|
97
|
-
"definition of version #{self.version}"
|
98
|
-
end
|
95
|
+
raise 'Use of endpoint is only allowed in the global part of ' \
|
96
|
+
'the API definition (but you are attempting to use it in the API ' \
|
97
|
+
"definition of version #{version}"
|
99
98
|
end
|
100
99
|
end
|
101
100
|
|
102
|
-
def documentation_url(val=nil)
|
101
|
+
def documentation_url(val = nil)
|
103
102
|
if val.nil?
|
104
103
|
get(:documentation_url)
|
104
|
+
elsif @global_info.nil?
|
105
|
+
set(:documentation_url, val) # this *is* the global info
|
105
106
|
else
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
raise "Use of documentation_url is only allowed in the global part of " \
|
110
|
-
"the API definition (but you are attempting to use it in the API " \
|
111
|
-
"definition of version #{self.version}"
|
112
|
-
end
|
107
|
+
raise 'Use of documentation_url is only allowed in the global part of ' \
|
108
|
+
'the API definition (but you are attempting to use it in the API ' \
|
109
|
+
"definition of version #{version}"
|
113
110
|
end
|
114
111
|
end
|
115
112
|
|
116
|
-
def base_path(val=nil)
|
117
|
-
if val
|
118
|
-
return set(:base_path, val)
|
119
|
-
end
|
113
|
+
def base_path(val = nil)
|
114
|
+
return set(:base_path, val) if val
|
120
115
|
|
121
116
|
if @global_info # this is for a specific version
|
122
117
|
global_path = @global_info.base_path
|
123
118
|
if version_with == :path
|
124
119
|
global_pattern = Mustermann.new(global_path)
|
125
|
-
global_path = global_pattern.expand(Request::API_VERSION_PARAM_NAME =>
|
120
|
+
global_path = global_pattern.expand(Request::API_VERSION_PARAM_NAME => version.to_s)
|
126
121
|
end
|
127
122
|
|
128
|
-
version_path = @data.fetch(:base_path,'')
|
123
|
+
version_path = @data.fetch(:base_path, '')
|
129
124
|
"#{global_path}#{version_path}"
|
130
125
|
else
|
131
|
-
@data.fetch(:base_path,'')
|
126
|
+
@data.fetch(:base_path, '')
|
132
127
|
end
|
133
128
|
end
|
134
129
|
|
135
130
|
def consumes(*vals)
|
136
131
|
if vals.empty?
|
137
|
-
|
132
|
+
get(:consumes)
|
138
133
|
else
|
139
|
-
|
134
|
+
set(:consumes, vals)
|
140
135
|
end
|
141
136
|
end
|
142
137
|
|
143
|
-
|
144
138
|
def produces(*vals)
|
145
139
|
if vals.empty?
|
146
|
-
|
140
|
+
get(:produces)
|
147
141
|
else
|
148
|
-
|
142
|
+
set(:produces, vals)
|
149
143
|
end
|
150
144
|
end
|
151
145
|
|
152
|
-
|
153
|
-
def base_params(type=Attributor::Struct, **opts, &block)
|
146
|
+
def base_params(type = Attributor::Struct, **opts, &block)
|
154
147
|
if !block && type == Attributor::Struct
|
155
148
|
get(:base_params)
|
156
149
|
else
|
157
|
-
set(:base_params, Attributor::Attribute.new(type, opts, &block)
|
150
|
+
set(:base_params, Attributor::Attribute.new(type, opts, &block))
|
158
151
|
end
|
159
152
|
end
|
160
153
|
|
161
154
|
def describe
|
162
|
-
hash = { schema_version:
|
163
|
-
[
|
164
|
-
val =
|
155
|
+
hash = { schema_version: '1.0' }
|
156
|
+
%i[name title description base_path version_with endpoint consumes produces].each do |attr|
|
157
|
+
val = __send__(attr)
|
165
158
|
hash[attr] = val unless val.nil?
|
166
159
|
end
|
167
|
-
if base_params
|
168
|
-
hash[:base_params] = base_params.describe[:type][:attributes]
|
169
|
-
end
|
160
|
+
hash[:base_params] = base_params.describe[:type][:attributes] if base_params
|
170
161
|
hash
|
171
162
|
end
|
172
|
-
|
173
163
|
end
|
174
|
-
|
175
164
|
end
|
data/lib/praxis/application.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'singleton'
|
2
4
|
require 'mustermann'
|
3
5
|
require 'logger'
|
@@ -6,28 +8,12 @@ module Praxis
|
|
6
8
|
class Application
|
7
9
|
include Singleton
|
8
10
|
|
9
|
-
attr_reader :router
|
10
|
-
attr_reader :controllers
|
11
|
-
attr_reader :endpoint_definitions
|
12
|
-
attr_reader :app
|
13
|
-
attr_reader :builder
|
14
|
-
|
15
|
-
attr_accessor :bootloader
|
16
|
-
attr_accessor :file_layout
|
17
|
-
attr_accessor :loaded_files
|
18
|
-
attr_accessor :logger
|
19
|
-
attr_accessor :plugins
|
20
|
-
attr_accessor :doc_browser_plugin_paths
|
21
|
-
attr_accessor :handlers
|
22
|
-
attr_accessor :root
|
23
|
-
attr_accessor :error_handler
|
24
|
-
attr_accessor :validation_handler
|
25
|
-
|
26
|
-
attr_accessor :versioning_scheme
|
11
|
+
attr_reader :router, :controllers, :endpoint_definitions, :app, :builder
|
27
12
|
|
13
|
+
attr_accessor :bootloader, :file_layout, :loaded_files, :logger, :plugins, :doc_browser_plugin_paths, :handlers, :root, :error_handler, :validation_handler, :versioning_scheme
|
28
14
|
|
29
15
|
def self.configure
|
30
|
-
yield(
|
16
|
+
yield(instance)
|
31
17
|
end
|
32
18
|
|
33
19
|
def initialize
|
@@ -44,16 +30,15 @@ module Praxis
|
|
44
30
|
|
45
31
|
@bootloader = Bootloader.new(self)
|
46
32
|
@file_layout = nil
|
47
|
-
@plugins =
|
33
|
+
@plugins = {}
|
48
34
|
@doc_browser_plugin_paths = []
|
49
|
-
@handlers =
|
35
|
+
@handlers = {}
|
50
36
|
@loaded_files = Set.new
|
51
37
|
@config = Config.new
|
52
38
|
@root = nil
|
53
|
-
@logger = Logger.new(
|
39
|
+
@logger = Logger.new($stdout)
|
54
40
|
end
|
55
41
|
|
56
|
-
|
57
42
|
def setup(root: '.')
|
58
43
|
return self unless @app.nil?
|
59
44
|
|
@@ -92,9 +77,7 @@ module Praxis
|
|
92
77
|
handler = handler.new
|
93
78
|
|
94
79
|
# Make sure it quacks like a handler.
|
95
|
-
unless handler.respond_to?(:generate) && handler.respond_to?(:parse)
|
96
|
-
raise ArgumentError, "Media type handlers must respond to #generate and #parse"
|
97
|
-
end
|
80
|
+
raise ArgumentError, 'Media type handlers must respond to #generate and #parse' unless handler.respond_to?(:generate) && handler.respond_to?(:parse)
|
98
81
|
|
99
82
|
# Register that thing!
|
100
83
|
@handlers[name.to_s] = handler
|
@@ -102,17 +85,17 @@ module Praxis
|
|
102
85
|
|
103
86
|
def call(env)
|
104
87
|
response = []
|
105
|
-
Notifications.instrument 'rack.request.all'
|
88
|
+
Notifications.instrument 'rack.request.all', response: response do
|
106
89
|
response.push(*@app.call(env))
|
107
90
|
end
|
108
91
|
end
|
109
92
|
|
110
93
|
def layout(&block)
|
111
|
-
self.file_layout = FileGroup.new(
|
94
|
+
self.file_layout = FileGroup.new(root, &block)
|
112
95
|
end
|
113
96
|
|
114
|
-
def config(key=nil, type=Attributor::Struct, **opts, &block)
|
115
|
-
if block_given? || (type==Attributor::Struct && !opts.empty?
|
97
|
+
def config(key = nil, type = Attributor::Struct, **opts, &block)
|
98
|
+
if block_given? || (type == Attributor::Struct && !opts.empty?)
|
116
99
|
@config.define(key, type, **opts, &block)
|
117
100
|
else
|
118
101
|
@config.get
|
@@ -123,9 +106,9 @@ module Praxis
|
|
123
106
|
@config.set(config)
|
124
107
|
end
|
125
108
|
|
126
|
-
# [DEPRECATED] - Warn of the change of method name for the transition
|
109
|
+
# [DEPRECATED] - Warn of the change of method name for the transition
|
127
110
|
def resource_definitions
|
128
|
-
raise
|
111
|
+
raise 'Praxis::Application.instance does not use `resource_definitions` any longer. Use `endpoint_definitions` instead.'
|
129
112
|
end
|
130
113
|
end
|
131
114
|
end
|
data/lib/praxis/blueprint.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Praxis
|
4
|
-
class Blueprint
|
5
|
-
|
4
|
+
class Blueprint
|
6
5
|
# Simple helper class that can parse the `attribute :foobar` dsl into
|
7
6
|
# an equivalent structure hash. Example:
|
8
7
|
# do
|
@@ -13,22 +12,25 @@ module Praxis
|
|
13
12
|
# end
|
14
13
|
# is parsed as: { one: true, complex: { sub1: true} }
|
15
14
|
class FieldsetParser
|
16
|
-
def initialize(
|
15
|
+
def initialize(&block)
|
17
16
|
@hash = nil
|
18
17
|
@block = block
|
19
18
|
end
|
20
|
-
|
19
|
+
|
21
20
|
def attribute(name, **args, &block)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
unless args.empty?
|
22
|
+
raise "Default fieldset definitions do not accept parameters (got: #{args})" \
|
23
|
+
"If you're upgrading from a previous version of Praxis and still using the view :default " \
|
24
|
+
"block syntax, make sure you don't use any view: X parameters when you define the attributes " \
|
25
|
+
'(expand them explicitly if you want deeper structure)' \
|
26
|
+
"The offending view with parameters is defined in:\n#{Kernel.caller.first}"
|
27
|
+
end
|
27
28
|
@hash[name] = block_given? ? FieldsetParser.new(&block).fieldset : true
|
28
29
|
end
|
29
30
|
|
30
31
|
def fieldset
|
31
32
|
return @hash if @hash
|
33
|
+
|
32
34
|
# Lazy eval
|
33
35
|
@hash = {}
|
34
36
|
instance_eval(&@block)
|
@@ -40,14 +42,13 @@ module Praxis
|
|
40
42
|
|
41
43
|
extend Finalizable
|
42
44
|
|
43
|
-
@@caching_enabled = false
|
45
|
+
@@caching_enabled = false # rubocop:disable Style/ClassVars
|
44
46
|
|
45
47
|
attr_reader :validating
|
46
48
|
attr_accessor :object
|
47
49
|
|
48
50
|
class << self
|
49
|
-
attr_reader :attribute
|
50
|
-
attr_reader :options
|
51
|
+
attr_reader :attribute, :options
|
51
52
|
attr_accessor :reference
|
52
53
|
end
|
53
54
|
|
@@ -65,14 +66,14 @@ module Praxis
|
|
65
66
|
def self.new(object)
|
66
67
|
# TODO: do we want to allow the identity map thing in the object?...maybe not.
|
67
68
|
if @@caching_enabled
|
68
|
-
return
|
69
|
-
blueprint =
|
69
|
+
return cache[object] ||= begin
|
70
|
+
blueprint = allocate
|
70
71
|
blueprint.send(:initialize, object)
|
71
72
|
blueprint
|
72
73
|
end
|
73
74
|
end
|
74
75
|
|
75
|
-
blueprint =
|
76
|
+
blueprint = allocate
|
76
77
|
blueprint.send(:initialize, object)
|
77
78
|
blueprint
|
78
79
|
end
|
@@ -83,15 +84,11 @@ module Praxis
|
|
83
84
|
|
84
85
|
def self.attributes(opts = {}, &block)
|
85
86
|
if block_given?
|
86
|
-
raise 'Redefining Blueprint attributes is not currently supported' if
|
87
|
+
raise 'Redefining Blueprint attributes is not currently supported' if const_defined?(:Struct, false)
|
87
88
|
|
88
|
-
if opts.key?(:reference) && opts[:reference] !=
|
89
|
-
|
90
|
-
|
91
|
-
opts[:reference] = self.reference # pass the reference Class down
|
92
|
-
else
|
93
|
-
opts[:reference] = self
|
94
|
-
end
|
89
|
+
raise "Reference mismatch in #{inspect}. Given :reference option #{opts[:reference].inspect}, while using #{reference.inspect}" if opts.key?(:reference) && opts[:reference] != reference
|
90
|
+
|
91
|
+
opts[:reference] = (reference || self)
|
95
92
|
|
96
93
|
@options.merge!(opts)
|
97
94
|
@block = block
|
@@ -99,13 +96,14 @@ module Praxis
|
|
99
96
|
return @attribute
|
100
97
|
end
|
101
98
|
|
102
|
-
raise "@attribute not defined yet for #{
|
99
|
+
raise "@attribute not defined yet for #{name}" unless @attribute
|
103
100
|
|
104
101
|
@attribute.attributes
|
105
102
|
end
|
106
103
|
|
107
104
|
def self.domain_model(klass = nil)
|
108
105
|
return @domain_model if klass.nil?
|
106
|
+
|
109
107
|
@domain_model = klass
|
110
108
|
end
|
111
109
|
|
@@ -118,16 +116,16 @@ module Praxis
|
|
118
116
|
when self
|
119
117
|
value
|
120
118
|
when nil, Hash, String
|
121
|
-
if (value =
|
122
|
-
|
119
|
+
if (value = attribute.load(value, context, **options))
|
120
|
+
new(value)
|
123
121
|
end
|
124
122
|
else
|
125
|
-
if value.is_a?(
|
123
|
+
if value.is_a?(domain_model) || value.is_a?(self::Struct)
|
126
124
|
# Wrap the value directly
|
127
|
-
|
125
|
+
new(value)
|
128
126
|
else
|
129
127
|
# Wrap the object inside the domain_model
|
130
|
-
|
128
|
+
new(domain_model.new(value))
|
131
129
|
end
|
132
130
|
end
|
133
131
|
end
|
@@ -141,7 +139,7 @@ module Praxis
|
|
141
139
|
end
|
142
140
|
|
143
141
|
def self.caching_enabled=(caching_enabled)
|
144
|
-
@@caching_enabled = caching_enabled
|
142
|
+
@@caching_enabled = caching_enabled # rubocop:disable Style/ClassVars
|
145
143
|
end
|
146
144
|
|
147
145
|
# Fetch current blueprint cache, scoped by this class
|
@@ -154,29 +152,28 @@ module Praxis
|
|
154
152
|
end
|
155
153
|
|
156
154
|
def self.valid_type?(value)
|
157
|
-
value.is_a?(self) || value.is_a?(
|
155
|
+
value.is_a?(self) || value.is_a?(attribute.type)
|
158
156
|
end
|
159
157
|
|
160
158
|
def self.example(context = nil, **values)
|
161
159
|
context = case context
|
162
160
|
when nil
|
163
|
-
["#{
|
161
|
+
["#{name}-#{values.object_id}"]
|
164
162
|
when ::String
|
165
163
|
[context]
|
166
164
|
else
|
167
165
|
context
|
168
166
|
end
|
169
167
|
|
170
|
-
|
168
|
+
new(attribute.example(context, values: values))
|
171
169
|
end
|
172
170
|
|
173
171
|
def self.validate(value, context = Attributor::DEFAULT_ROOT_CONTEXT, _attribute = nil)
|
174
|
-
raise ArgumentError, "Invalid context received (nil) while validating value of type #{
|
172
|
+
raise ArgumentError, "Invalid context received (nil) while validating value of type #{name}" if context.nil?
|
173
|
+
|
175
174
|
context = [context] if context.is_a? ::String
|
176
175
|
|
177
|
-
unless value.is_a?(self)
|
178
|
-
raise ArgumentError, "Error validating #{Attributor.humanize_context(context)} as #{self.name} for an object of type #{value.class.name}."
|
179
|
-
end
|
176
|
+
raise ArgumentError, "Error validating #{Attributor.humanize_context(context)} as #{name} for an object of type #{value.class.name}." unless value.is_a?(self)
|
180
177
|
|
181
178
|
value.validate(context)
|
182
179
|
end
|
@@ -187,13 +184,14 @@ module Praxis
|
|
187
184
|
@block_for_default_fieldset = block
|
188
185
|
end
|
189
186
|
|
190
|
-
def self.view(name, **
|
187
|
+
def self.view(name, **_options, &block)
|
191
188
|
unless name == :default
|
192
189
|
raise "[ERROR] Views are no longer supported. Please use fully expanded fields when rendering.\n" \
|
193
190
|
"NOTE that defining the :default view is deprecated, but still temporarily allowed, as an alias to define the default_fieldset.\n" \
|
194
191
|
"A view for name #{name} is attempted to be defined in:\n#{Kernel.caller.first}"
|
195
192
|
end
|
196
|
-
raise
|
193
|
+
raise 'Cannot define the default fieldset through the default view unless a block is passed' unless block_given?
|
194
|
+
|
197
195
|
puts "[DEPRECATED] default fieldsets should be defined through `default_fieldset` instead of using the view :default block.\n" \
|
198
196
|
"A default view is attempted to be defined in:\n#{Kernel.caller.first}"
|
199
197
|
default_fieldset(&block)
|
@@ -219,45 +217,45 @@ module Praxis
|
|
219
217
|
# Internal finalize! logic
|
220
218
|
def self._finalize!
|
221
219
|
if @block
|
222
|
-
|
223
|
-
|
220
|
+
define_attribute!
|
221
|
+
define_readers!
|
224
222
|
# Don't blindly override a the default fieldset if the MediaType wants to define it on its own
|
225
223
|
if @block_for_default_fieldset
|
226
|
-
parse_default_fieldset(@block_for_default_fieldset)
|
224
|
+
parse_default_fieldset(@block_for_default_fieldset)
|
227
225
|
else
|
228
|
-
|
226
|
+
generate_default_fieldset!
|
229
227
|
end
|
230
|
-
|
228
|
+
resolve_domain_model!
|
231
229
|
end
|
232
230
|
super
|
233
231
|
end
|
234
232
|
|
235
233
|
def self.resolve_domain_model!
|
236
|
-
return unless
|
234
|
+
return unless domain_model.is_a?(String)
|
237
235
|
|
238
|
-
@domain_model =
|
236
|
+
@domain_model = domain_model.constantize
|
239
237
|
end
|
240
238
|
|
241
239
|
def self.define_attribute!
|
242
240
|
@attribute = Attributor::Attribute.new(Attributor::Struct, @options, &@block)
|
243
241
|
@block = nil
|
244
242
|
@attribute.type.anonymous_type true
|
245
|
-
|
243
|
+
const_set(:Struct, @attribute.type)
|
246
244
|
end
|
247
245
|
|
248
246
|
def self.define_readers!
|
249
|
-
|
247
|
+
attributes.each do |name, _attribute|
|
250
248
|
name = name.to_sym
|
251
249
|
|
252
250
|
# Don't redefine existing methods
|
253
|
-
next if
|
251
|
+
next if instance_methods.include? name
|
254
252
|
|
255
253
|
define_reader! name
|
256
254
|
end
|
257
255
|
end
|
258
256
|
|
259
257
|
def self.define_reader!(name)
|
260
|
-
attribute =
|
258
|
+
attribute = attributes[name]
|
261
259
|
# TODO: profile and optimize
|
262
260
|
# because we use the attribute in the reader,
|
263
261
|
# it's likely faster to use define_method here
|
@@ -265,6 +263,7 @@ module Praxis
|
|
265
263
|
define_method(name) do
|
266
264
|
value = @object.__send__(name)
|
267
265
|
return value if value.nil? || value.is_a?(attribute.type)
|
266
|
+
|
268
267
|
attribute.load(value)
|
269
268
|
end
|
270
269
|
end
|
@@ -274,15 +273,16 @@ module Praxis
|
|
274
273
|
|
275
274
|
@default_fieldset = {}
|
276
275
|
attributes.each do |name, attr|
|
277
|
-
the_type =
|
276
|
+
the_type = attr.type < Attributor::Collection ? attr.type.member_type : attr.type
|
278
277
|
next if the_type < Blueprint
|
279
|
-
|
278
|
+
|
279
|
+
# NOTE: we won't try to expand fields here, as we want to be lazy (and we're expanding)
|
280
280
|
# every time a request comes in anyway. This could be an optimization we do at some point
|
281
281
|
# or we can 'memoize it' to avoid trying to expand it over an over...
|
282
282
|
@default_fieldset[name] = true
|
283
283
|
end
|
284
284
|
end
|
285
|
-
|
285
|
+
|
286
286
|
def initialize(object)
|
287
287
|
@object = object
|
288
288
|
@validating = false
|
@@ -291,18 +291,14 @@ module Praxis
|
|
291
291
|
# By default we'll use the object identity, to avoid rendering the same object twice
|
292
292
|
# Override, if there is a better way cache things up
|
293
293
|
def _cache_key
|
294
|
-
|
294
|
+
object
|
295
295
|
end
|
296
296
|
|
297
297
|
# Render the wrapped data with the given fields (or using the default fieldset otherwise)
|
298
|
-
def render(fields: self.class.default_fieldset, context: Attributor::DEFAULT_ROOT_CONTEXT, renderer: Renderer.new, **
|
299
|
-
|
298
|
+
def render(fields: self.class.default_fieldset, context: Attributor::DEFAULT_ROOT_CONTEXT, renderer: Renderer.new, **_opts)
|
300
299
|
# Accept a simple array of fields, and transform it to a 1-level hash with true values
|
301
|
-
if fields.is_a? Array
|
302
|
-
fields = fields.each_with_object({}) { |field, hash| hash[field] = true }
|
303
|
-
end
|
300
|
+
fields = fields.each_with_object({}) { |field, hash| hash[field] = true } if fields.is_a? Array
|
304
301
|
|
305
|
-
expanded = Praxis::FieldExpander.new.expand(self, fields)
|
306
302
|
renderer.render(self, fields, context: context)
|
307
303
|
end
|
308
304
|
|
@@ -313,11 +309,12 @@ module Praxis
|
|
313
309
|
end
|
314
310
|
|
315
311
|
def validate(context = Attributor::DEFAULT_ROOT_CONTEXT)
|
316
|
-
raise ArgumentError, "Invalid context received (nil) while validating value of type #{
|
312
|
+
raise ArgumentError, "Invalid context received (nil) while validating value of type #{name}" if context.nil?
|
313
|
+
|
317
314
|
context = [context] if context.is_a? ::String
|
318
|
-
keys_with_values = []
|
319
315
|
|
320
316
|
raise 'validation conflict' if @validating
|
317
|
+
|
321
318
|
@validating = true
|
322
319
|
|
323
320
|
errors = []
|
@@ -328,18 +325,12 @@ module Praxis
|
|
328
325
|
value = _get_attr(key)
|
329
326
|
keys_provided << key if @object.key?(key)
|
330
327
|
|
331
|
-
if value.respond_to?(:validating) # really, it's a thing with sub-attributes
|
332
|
-
next if value.validating
|
333
|
-
end
|
328
|
+
next if value.respond_to?(:validating) && value.validating # really, it's a thing with sub-attributes
|
334
329
|
|
335
330
|
# Isn't this handled by the requirements validation? NO! we might want to combine
|
336
|
-
if attribute.options[:required] && !@object.key?(key)
|
337
|
-
errors.concat ["Attribute #{Attributor.humanize_context(sub_context)} is required."]
|
338
|
-
end
|
331
|
+
errors.concat ["Attribute #{Attributor.humanize_context(sub_context)} is required."] if attribute.options[:required] && !@object.key?(key)
|
339
332
|
if @object[key].nil?
|
340
|
-
if !Attributor::Attribute.nullable_attribute?(attribute.options) && @object.key?(key) # It is only nullable if there's an explicite null: true (undefined defaults to false)
|
341
|
-
errors.concat ["Attribute #{Attributor.humanize_context(sub_context)} is not nullable."]
|
342
|
-
end
|
333
|
+
errors.concat ["Attribute #{Attributor.humanize_context(sub_context)} is not nullable."] if !Attributor::Attribute.nullable_attribute?(attribute.options) && @object.key?(key) # It is only nullable if there's an explicite null: true (undefined defaults to false)
|
343
334
|
# No need to validate the attribute further if the key wasn't passed...(or we would get nullable errors etc..cause the attribute has no
|
344
335
|
# context if its containing key was even passed (and there might not be a containing key for a top level attribute anyways))
|
345
336
|
else
|
@@ -357,14 +348,14 @@ module Praxis
|
|
357
348
|
|
358
349
|
# generic semi-private getter used by Renderer
|
359
350
|
def _get_attr(name)
|
360
|
-
|
351
|
+
send(name)
|
361
352
|
end
|
362
353
|
|
363
354
|
# Delegates the json-schema methods to the underlying attribute/member_type
|
364
355
|
def self.as_json_schema(**args)
|
365
356
|
@attribute.type.as_json_schema(args)
|
366
357
|
end
|
367
|
-
|
358
|
+
|
368
359
|
def self.json_schema_type
|
369
360
|
@attribute.type.json_schema_type
|
370
361
|
end
|