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
data/spec/praxis/trait_spec.rb
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
|
|
4
4
|
|
|
5
|
+
describe Praxis::Trait do
|
|
5
6
|
subject(:trait) do
|
|
6
7
|
Praxis::Trait.new do
|
|
7
8
|
description 'my awesome trait'
|
|
@@ -19,14 +20,13 @@ describe Praxis::Trait do
|
|
|
19
20
|
end
|
|
20
21
|
params do
|
|
21
22
|
attribute :order, String,
|
|
22
|
-
|
|
23
|
+
description: 'Field to sort by.'
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
headers do
|
|
26
|
-
header
|
|
27
|
-
key
|
|
27
|
+
header 'Authorization'
|
|
28
|
+
key 'Header2', String, required: true, null: false
|
|
28
29
|
end
|
|
29
|
-
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
@@ -35,24 +35,24 @@ describe Praxis::Trait do
|
|
|
35
35
|
|
|
36
36
|
its([:description]) { should eq('my awesome trait') }
|
|
37
37
|
|
|
38
|
-
its([
|
|
39
|
-
its([
|
|
38
|
+
its(%i[responses something]) { should eq({}) }
|
|
39
|
+
its(%i[responses nothing]) { should eq({}) }
|
|
40
40
|
|
|
41
|
-
its([
|
|
42
|
-
its([
|
|
43
|
-
its([
|
|
41
|
+
its(%i[params app_name type name]) { should eq 'String' }
|
|
42
|
+
its(%i[params order type name]) { should eq 'String' }
|
|
43
|
+
its(%i[routing prefix]) { should eq '/:app_name' }
|
|
44
44
|
|
|
45
|
-
its([:headers,
|
|
45
|
+
its([:headers, 'Header2']) { should include({ required: true, null: false }) }
|
|
46
46
|
context 'using the special DSL syntax for headers' do
|
|
47
|
-
subject(:dsl_header) { describe[:headers][
|
|
48
|
-
its([:required]){ should be(true) }
|
|
49
|
-
its([:
|
|
47
|
+
subject(:dsl_header) { describe[:headers]['Authorization'] }
|
|
48
|
+
its([:required]) { should be(true) }
|
|
49
|
+
its([:null]) { should be_nil }
|
|
50
|
+
its([:type]) { should eq({ id: 'Attributor-String', name: 'String', family: 'string' }) }
|
|
50
51
|
end
|
|
51
|
-
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
context 'apply!' do
|
|
55
|
-
let(:target) { double(
|
|
55
|
+
let(:target) { double('Target') }
|
|
56
56
|
it 'does' do
|
|
57
57
|
expect(target).to receive(:routing).once
|
|
58
58
|
expect(target).to receive(:response).twice
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require 'spec_helper'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
describe Praxis::Types::FuzzyHash do
|
|
6
|
+
let(:initial_value) do
|
|
7
|
+
{
|
|
8
|
+
'key' => 'value',
|
|
9
|
+
/bob/ => 'rob',
|
|
10
|
+
/\d+/ => 'one'
|
|
11
|
+
}
|
|
12
|
+
end
|
|
10
13
|
|
|
11
14
|
subject(:hash) { Praxis::Types::FuzzyHash.new(initial_value) }
|
|
12
15
|
|
|
13
16
|
its(['key']) { should eq 'value' }
|
|
14
17
|
its([/bob/]) { should eq 'rob' }
|
|
15
18
|
its(['bobby']) { should eq 'rob' }
|
|
16
|
-
|
|
19
|
+
|
|
17
20
|
its([1]) { should eq 'one' }
|
|
18
21
|
its(['1']) { should eq 'one' }
|
|
19
22
|
end
|
|
20
|
-
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require 'spec_helper'
|
|
4
4
|
|
|
5
|
+
describe Praxis::Types::MultipartArray do
|
|
5
6
|
let(:type) do
|
|
6
7
|
Class.new(Praxis::Types::MultipartArray) do
|
|
7
|
-
|
|
8
|
+
options[:case_insensitive_load] = true
|
|
8
9
|
|
|
9
10
|
part 'title', String, required: true
|
|
10
11
|
part(/nam/, String, regexp: /Bob/i)
|
|
11
12
|
|
|
12
|
-
part
|
|
13
|
+
part(/stuff/) do
|
|
13
14
|
payload Hash
|
|
14
15
|
end
|
|
15
16
|
|
|
@@ -22,33 +23,31 @@ describe Praxis::Types::MultipartArray do
|
|
|
22
23
|
file 'thumbnail', Attributor::Tempfile
|
|
23
24
|
|
|
24
25
|
part 'image', Attributor::Tempfile, filename: true
|
|
25
|
-
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
|
|
30
29
|
let(:form) do
|
|
31
30
|
form_data = MIME::Multipart::FormData.new
|
|
32
31
|
entity = MIME::Text.new('Bob')
|
|
33
|
-
form_data.add entity,'name'
|
|
32
|
+
form_data.add entity, 'name'
|
|
34
33
|
|
|
35
34
|
entity = MIME::Text.new('Captain')
|
|
36
|
-
form_data.add entity,'title'
|
|
35
|
+
form_data.add entity, 'title'
|
|
37
36
|
|
|
38
37
|
entity = MIME::Application.new('file1')
|
|
39
|
-
form_data.add entity,'files', 'file1'
|
|
38
|
+
form_data.add entity, 'files', 'file1'
|
|
40
39
|
|
|
41
40
|
entity = MIME::Application.new('file2')
|
|
42
|
-
form_data.add entity,'files', 'file2'
|
|
41
|
+
form_data.add entity, 'files', 'file2'
|
|
43
42
|
|
|
44
43
|
entity = MIME::Application.new('{"first_name": "Frank"}', 'json')
|
|
45
|
-
form_data.add entity,'stuff2'
|
|
44
|
+
form_data.add entity, 'stuff2'
|
|
46
45
|
|
|
47
46
|
entity = MIME::Application.new('SOMEBINARYDATA', 'jpg')
|
|
48
|
-
form_data.add entity,'thumbnail', 'thumb.jpg'
|
|
47
|
+
form_data.add entity, 'thumbnail', 'thumb.jpg'
|
|
49
48
|
|
|
50
49
|
entity = MIME::Application.new('', 'jpg')
|
|
51
|
-
form_data.add entity,'image', 'image.jpg'
|
|
50
|
+
form_data.add entity, 'image', 'image.jpg'
|
|
52
51
|
|
|
53
52
|
form_data
|
|
54
53
|
end
|
|
@@ -59,7 +58,6 @@ describe Praxis::Types::MultipartArray do
|
|
|
59
58
|
subject(:payload) { type.load(body, content_type: content_type) }
|
|
60
59
|
|
|
61
60
|
it 'validates' do
|
|
62
|
-
part = payload.part('files').first
|
|
63
61
|
expect(payload.validate).to be_empty
|
|
64
62
|
end
|
|
65
63
|
|
|
@@ -94,9 +92,9 @@ describe Praxis::Types::MultipartArray do
|
|
|
94
92
|
end
|
|
95
93
|
end
|
|
96
94
|
|
|
97
|
-
let(:json_payload) { {sub_hash: {key:'value'}}.to_json }
|
|
95
|
+
let(:json_payload) { { sub_hash: { key: 'value' } }.to_json }
|
|
98
96
|
let(:body) { StringIO.new("--boundary\r\nContent-Disposition: form-data; name=blah\r\n\r\n#{json_payload}\r\n--boundary--") }
|
|
99
|
-
let(:content_type) {
|
|
97
|
+
let(:content_type) { 'multipart/form-data; boundary=boundary' }
|
|
100
98
|
|
|
101
99
|
it do
|
|
102
100
|
expect(payload.part('blah').payload['sub_hash']).to eq('key' => 'value')
|
|
@@ -117,9 +115,9 @@ describe Praxis::Types::MultipartArray do
|
|
|
117
115
|
end
|
|
118
116
|
end
|
|
119
117
|
|
|
120
|
-
let(:json_payload) { {attr: 'value'}.to_json }
|
|
118
|
+
let(:json_payload) { { attr: 'value' }.to_json }
|
|
121
119
|
let(:body) { StringIO.new("--boundary\r\nContent-Disposition: form-data; name=blah\r\n\r\n#{json_payload}\r\n--boundary--") }
|
|
122
|
-
let(:content_type) {
|
|
120
|
+
let(:content_type) { 'multipart/form-data; boundary=boundary' }
|
|
123
121
|
|
|
124
122
|
it do
|
|
125
123
|
expect(payload.part('blah').payload.class.ancestors).to include(Attributor::Struct)
|
|
@@ -142,7 +140,7 @@ describe Praxis::Types::MultipartArray do
|
|
|
142
140
|
|
|
143
141
|
context 'attributes' do
|
|
144
142
|
subject(:attributes) { description[:attributes] }
|
|
145
|
-
its(:keys) { should match_array [
|
|
143
|
+
its(:keys) { should match_array %w[title files thumbnail image] }
|
|
146
144
|
|
|
147
145
|
context 'the "title" part' do
|
|
148
146
|
subject(:title_description) { attributes['title'] }
|
|
@@ -172,14 +170,12 @@ describe Praxis::Types::MultipartArray do
|
|
|
172
170
|
expect(filename[:options]).to eq(regexp: /file/)
|
|
173
171
|
expect(filename[:type]).to eq Attributor::String.describe
|
|
174
172
|
end
|
|
175
|
-
|
|
176
173
|
end
|
|
177
|
-
|
|
178
174
|
end
|
|
179
175
|
|
|
180
176
|
context 'pattern attributes' do
|
|
181
177
|
subject(:pattern_attributes) { description[:pattern_attributes] }
|
|
182
|
-
its(:keys) { should match_array [
|
|
178
|
+
its(:keys) { should match_array %w[nam stuff] }
|
|
183
179
|
end
|
|
184
180
|
|
|
185
181
|
context 'with no parts defined' do
|
|
@@ -226,17 +222,16 @@ describe Praxis::Types::MultipartArray do
|
|
|
226
222
|
subject(:dumped) { payload.dump }
|
|
227
223
|
|
|
228
224
|
it 'dumps' do
|
|
229
|
-
|
|
225
|
+
type.load(dumped, content_type: payload.content_type)
|
|
230
226
|
end
|
|
231
227
|
context 'an example' do
|
|
232
228
|
let(:payload) { type.example }
|
|
233
229
|
|
|
234
230
|
it 'dumps' do
|
|
235
|
-
|
|
231
|
+
type.load(dumped, content_type: payload.content_type)
|
|
236
232
|
end
|
|
237
233
|
end
|
|
238
234
|
|
|
239
|
-
|
|
240
235
|
context 'with default_format' do
|
|
241
236
|
let(:type) do
|
|
242
237
|
Class.new(Praxis::Types::MultipartArray) do
|
|
@@ -267,7 +262,7 @@ describe Praxis::Types::MultipartArray do
|
|
|
267
262
|
|
|
268
263
|
let(:output) { example.dump(default_format: default_format) }
|
|
269
264
|
|
|
270
|
-
let(:parts) { Praxis::MultipartParser.parse({'Content-Type'=>example.content_type}, output).last }
|
|
265
|
+
let(:parts) { Praxis::MultipartParser.parse({ 'Content-Type' => example.content_type }, output).last }
|
|
271
266
|
|
|
272
267
|
it 'dumps the parts with the proper handler' do
|
|
273
268
|
json_handler = Praxis::Application.instance.handlers['json']
|
|
@@ -290,9 +285,7 @@ describe Praxis::Types::MultipartArray do
|
|
|
290
285
|
expect(instance.payload).to eq json_handler.generate(example.part('instances')[i].payload.dump)
|
|
291
286
|
end
|
|
292
287
|
end
|
|
293
|
-
|
|
294
288
|
end
|
|
295
|
-
|
|
296
289
|
end
|
|
297
290
|
|
|
298
291
|
context 'with errors' do
|
|
@@ -300,11 +293,10 @@ describe Praxis::Types::MultipartArray do
|
|
|
300
293
|
form_data = MIME::Multipart::FormData.new
|
|
301
294
|
|
|
302
295
|
entity = MIME::Text.new('James')
|
|
303
|
-
form_data.add entity,'name'
|
|
304
|
-
|
|
296
|
+
form_data.add entity, 'name'
|
|
305
297
|
|
|
306
298
|
entity = MIME::Text.new('file1')
|
|
307
|
-
form_data.add entity,'files', 'file1'
|
|
299
|
+
form_data.add entity, 'files', 'file1'
|
|
308
300
|
|
|
309
301
|
form_data
|
|
310
302
|
end
|
|
@@ -314,10 +306,10 @@ describe Praxis::Types::MultipartArray do
|
|
|
314
306
|
it 'validates part headers' do
|
|
315
307
|
expect(errors).to have(3).items
|
|
316
308
|
expect(errors).to match_array([
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
309
|
+
/\.name\.payload value .* does not match regexp/,
|
|
310
|
+
/\.files\.headers.* is not within the allowed values/,
|
|
311
|
+
/\.title is required/
|
|
312
|
+
])
|
|
321
313
|
end
|
|
322
314
|
end
|
|
323
315
|
|
|
@@ -327,14 +319,14 @@ describe Praxis::Types::MultipartArray do
|
|
|
327
319
|
it 'generates parts' do
|
|
328
320
|
title = payload.part('title')
|
|
329
321
|
expect(title).to_not be_nil
|
|
330
|
-
expect(title.payload).to match
|
|
322
|
+
expect(title.payload).to match(/\w+/)
|
|
331
323
|
|
|
332
324
|
files = payload.part('files')
|
|
333
325
|
expect(files).to have(2).items
|
|
334
326
|
|
|
335
327
|
files.each do |file|
|
|
336
328
|
expect(file.payload).to be_kind_of(Tempfile)
|
|
337
|
-
expect(file.filename).to match
|
|
329
|
+
expect(file.filename).to match(/\w+/)
|
|
338
330
|
|
|
339
331
|
expect(file.headers['Content-Type']).to eq 'application/octet-stream'
|
|
340
332
|
end
|
|
@@ -343,15 +335,14 @@ describe Praxis::Types::MultipartArray do
|
|
|
343
335
|
it 'is valid' do
|
|
344
336
|
expect(payload.validate).to have(0).items
|
|
345
337
|
end
|
|
346
|
-
|
|
347
338
|
end
|
|
348
339
|
|
|
349
340
|
context 'with a hackish default part definition' do
|
|
350
341
|
let(:type) do
|
|
351
342
|
Class.new(Praxis::Types::MultipartArray) do
|
|
352
|
-
|
|
343
|
+
options[:case_insensitive_load] = true
|
|
353
344
|
|
|
354
|
-
part
|
|
345
|
+
part(/\d+/, Instance)
|
|
355
346
|
end
|
|
356
347
|
end
|
|
357
348
|
|
|
@@ -362,7 +353,7 @@ describe Praxis::Types::MultipartArray do
|
|
|
362
353
|
instance = Instance.example("instance-#{i}")
|
|
363
354
|
body = JSON.pretty_generate(instance.render)
|
|
364
355
|
entity = MIME::Text.new(body)
|
|
365
|
-
form_data.add entity,i.to_s
|
|
356
|
+
form_data.add entity, i.to_s
|
|
366
357
|
end
|
|
367
358
|
|
|
368
359
|
form_data
|
|
@@ -373,10 +364,8 @@ describe Praxis::Types::MultipartArray do
|
|
|
373
364
|
expect(part.payload).to be_kind_of(Instance)
|
|
374
365
|
end
|
|
375
366
|
end
|
|
376
|
-
|
|
377
367
|
end
|
|
378
368
|
|
|
379
|
-
|
|
380
369
|
context 'anonymous generation' do
|
|
381
370
|
let(:definition_block) do
|
|
382
371
|
proc do
|
|
@@ -396,7 +385,7 @@ describe Praxis::Types::MultipartArray do
|
|
|
396
385
|
instance = Instance.example("instance-#{i}")
|
|
397
386
|
body = JSON.pretty_generate(instance.render)
|
|
398
387
|
entity = MIME::Text.new(body)
|
|
399
|
-
form_data.add entity,i.to_s
|
|
388
|
+
form_data.add entity, i.to_s
|
|
400
389
|
end
|
|
401
390
|
|
|
402
391
|
form_data
|
|
@@ -411,9 +400,5 @@ describe Praxis::Types::MultipartArray do
|
|
|
411
400
|
expect(part.payload).to be_kind_of(Instance)
|
|
412
401
|
end
|
|
413
402
|
end
|
|
414
|
-
|
|
415
|
-
|
|
416
403
|
end
|
|
417
|
-
|
|
418
|
-
|
|
419
404
|
end
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Concerns
|
|
2
4
|
module Authenticated
|
|
3
5
|
extend ActiveSupport::Concern
|
|
4
6
|
include Praxis::Callbacks
|
|
5
|
-
|
|
7
|
+
|
|
6
8
|
included do
|
|
7
9
|
before :action do |controller|
|
|
8
10
|
auth_data = controller.request.headers['Authorization']
|
|
9
|
-
if auth_data && auth_data !~ /secret/
|
|
10
|
-
Praxis::Responses::Unauthorized.new(body: 'Authentication info is invalid')
|
|
11
|
-
end
|
|
11
|
+
Praxis::Responses::Unauthorized.new(body: 'Authentication info is invalid') if auth_data && auth_data !~ /secret/
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
|
-
end
|
|
15
|
+
end
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Concerns
|
|
2
4
|
module LogWrapper
|
|
3
5
|
extend ActiveSupport::Concern
|
|
4
6
|
include Praxis::Callbacks
|
|
5
|
-
|
|
7
|
+
|
|
6
8
|
included do
|
|
7
|
-
before :around do |
|
|
9
|
+
before :around do |_controller, callee|
|
|
8
10
|
# Log something at the beginning
|
|
9
11
|
callee.call
|
|
10
12
|
# Log something at the end
|
|
11
13
|
end
|
|
12
14
|
end
|
|
13
15
|
end
|
|
14
|
-
end
|
|
16
|
+
end
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
class BaseClass
|
|
2
4
|
# Do NOT use a controller inherited base class to add concerns!
|
|
3
5
|
# If you do that, you'd be sharing state (i.e. overriding/adding) from concerns across controllers
|
|
4
6
|
# which will probably lead to sharing issues you didn't expect.
|
|
5
7
|
# For example: any controller adding after/before/around filters will be visible
|
|
6
8
|
# to any other controllers sharing the concern.
|
|
7
|
-
# Include a concern to all of them instead
|
|
8
|
-
|
|
9
|
+
# Include a concern to all of them instead
|
|
10
|
+
|
|
9
11
|
# Inheritance of classes should be independent from the concerns.
|
|
10
12
|
# I.e., you can use class inheritance in cases where it makes sense from an OO point of view
|
|
11
13
|
# but for the most part, you can probably share code through modules/concerns too.
|
|
12
|
-
def this_is_shared
|
|
13
|
-
|
|
14
|
-
end
|
|
14
|
+
def this_is_shared; end
|
|
15
|
+
end
|
|
@@ -1,59 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
class Instances < BaseClass
|
|
2
4
|
include Praxis::Controller
|
|
3
5
|
|
|
4
6
|
implements ApiResources::Instances
|
|
5
7
|
include Concerns::BasicApi
|
|
6
8
|
|
|
7
|
-
before :validate, actions: [:index]
|
|
8
|
-
#p [:before, :validate, :params_and_headers, controller.request.action.name]
|
|
9
|
+
before :validate, actions: [:index] do |controller|
|
|
10
|
+
# p [:before, :validate, :params_and_headers, controller.request.action.name]
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
before actions: [:show] do |controller|
|
|
12
|
-
#puts "before action"
|
|
13
|
-
if controller.request.params.fail_filter
|
|
14
|
-
Praxis::Responses::Unauthorized.new
|
|
15
|
-
end
|
|
14
|
+
# puts "before action"
|
|
15
|
+
Praxis::Responses::Unauthorized.new if controller.request.params.fail_filter
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
around :validate, actions: [:show] do |
|
|
19
|
-
#puts "Before validate decorator (for show)"
|
|
18
|
+
around :validate, actions: [:show] do |_controller, blk|
|
|
19
|
+
# puts "Before validate decorator (for show)"
|
|
20
20
|
blk.call
|
|
21
|
-
#puts "After validate decorator"
|
|
21
|
+
# puts "After validate decorator"
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
around :action do |
|
|
25
|
-
#puts "Decorator one (all actions) start"
|
|
24
|
+
around :action do |_controller, blk|
|
|
25
|
+
# puts "Decorator one (all actions) start"
|
|
26
26
|
blk.call
|
|
27
|
-
#puts "Decorator one end"
|
|
27
|
+
# puts "Decorator one end"
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
around :action, actions: [:show] do |
|
|
31
|
-
#puts "Decorator two (show action) start"
|
|
30
|
+
around :action, actions: [:show] do |_controller, blk|
|
|
31
|
+
# puts "Decorator two (show action) start"
|
|
32
32
|
blk.call
|
|
33
|
-
#puts "Decorator two end"
|
|
33
|
+
# puts "Decorator two end"
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
around :action, actions: [:index] do |
|
|
37
|
-
#puts "Decorator three (index action) start"
|
|
36
|
+
around :action, actions: [:index] do |_controller, blk|
|
|
37
|
+
# puts "Decorator three (index action) start"
|
|
38
38
|
blk.call
|
|
39
|
-
#puts "Decorator three end"
|
|
39
|
+
# puts "Decorator three end"
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
def index(
|
|
42
|
+
def index(response_content_type: 'application/vnd.acme.instance;type=collection', **_params)
|
|
43
43
|
instances = Instance::Collection.example
|
|
44
|
-
response.body = JSON.pretty_generate(instances.collect
|
|
45
|
-
response.headers['Content-Type'] = response_content_type #'application/vnd.acme.instance;type=collection'
|
|
44
|
+
response.body = JSON.pretty_generate(instances.collect(&:render))
|
|
45
|
+
response.headers['Content-Type'] = response_content_type # 'application/vnd.acme.instance;type=collection'
|
|
46
46
|
response
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def show(cloud_id:, id:, junk:, create_identity_map:, **other_params)
|
|
50
|
-
if create_identity_map
|
|
51
|
-
request.identity_map
|
|
52
|
-
end
|
|
50
|
+
request.identity_map if create_identity_map
|
|
53
51
|
|
|
54
52
|
payload = request.payload
|
|
55
53
|
|
|
56
|
-
response.body = {cloud_id: cloud_id, id: id, junk: junk, other_params: other_params, payload: payload && payload.dump}
|
|
54
|
+
response.body = { cloud_id: cloud_id, id: id, junk: junk, other_params: other_params, payload: payload && payload.dump }
|
|
57
55
|
response.headers['Content-Type'] = 'application/json'
|
|
58
56
|
response
|
|
59
57
|
end
|
|
@@ -68,12 +66,12 @@ class Instances < BaseClass
|
|
|
68
66
|
|
|
69
67
|
part_body = {
|
|
70
68
|
key: instance_id,
|
|
71
|
-
value: instance.render(fields: {id: true, name: true})
|
|
69
|
+
value: instance.render(fields: { id: true, name: true })
|
|
72
70
|
}
|
|
73
71
|
|
|
74
72
|
headers = {
|
|
75
73
|
'Status' => '201',
|
|
76
|
-
'Content-Type' =>
|
|
74
|
+
'Content-Type' => "#{Instance.identifier}+json".to_s,
|
|
77
75
|
'Location' => definition.to_href(cloud_id: cloud_id, id: instance.id)
|
|
78
76
|
}
|
|
79
77
|
|
|
@@ -84,14 +82,13 @@ class Instances < BaseClass
|
|
|
84
82
|
response
|
|
85
83
|
end
|
|
86
84
|
|
|
87
|
-
|
|
88
|
-
def attach_file(id:, cloud_id:)
|
|
85
|
+
def attach_file(*)
|
|
89
86
|
response.headers['Content-Type'] = 'application/json'
|
|
90
87
|
|
|
91
88
|
destination_path = request.payload.part('destination_path').payload
|
|
92
89
|
file = request.payload.part('file')
|
|
93
90
|
file.payload.rewind # Ensure contents is at the beggining.
|
|
94
|
-
extra_part_names = request.payload.map(&:name) - [
|
|
91
|
+
extra_part_names = request.payload.map(&:name) - %w[destination_path file]
|
|
95
92
|
extra_parts = extra_part_names.each_with_object({}) do |pname, hash|
|
|
96
93
|
hash[pname] = request.payload.part(pname).body
|
|
97
94
|
end
|
|
@@ -109,23 +106,23 @@ class Instances < BaseClass
|
|
|
109
106
|
response
|
|
110
107
|
end
|
|
111
108
|
|
|
112
|
-
def terminate(
|
|
109
|
+
def terminate(*)
|
|
113
110
|
response.headers['Content-Type'] = 'application/json'
|
|
114
111
|
response
|
|
115
112
|
end
|
|
116
113
|
|
|
117
|
-
def stop(
|
|
114
|
+
def stop(*)
|
|
118
115
|
response.headers['Content-Type'] = 'application/json'
|
|
119
116
|
response
|
|
120
117
|
end
|
|
121
118
|
|
|
122
|
-
def update(
|
|
119
|
+
def update(*)
|
|
123
120
|
response.body = JSON.pretty_generate(request.payload.dump)
|
|
124
121
|
response.headers['Content-Type'] = 'application/vnd.acme.instance'
|
|
125
122
|
response
|
|
126
123
|
end
|
|
127
124
|
|
|
128
|
-
def exceptional(
|
|
125
|
+
def exceptional(*)
|
|
129
126
|
response.headers['Content-Type'] = 'application/json'
|
|
130
127
|
response
|
|
131
128
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
class Volumes < BaseClass
|
|
2
4
|
include Praxis::Controller
|
|
3
5
|
|
|
@@ -5,22 +7,20 @@ class Volumes < BaseClass
|
|
|
5
7
|
include Concerns::BasicApi
|
|
6
8
|
|
|
7
9
|
before actions: [:show] do |controller|
|
|
8
|
-
#puts "before action for volumes"
|
|
10
|
+
# puts "before action for volumes"
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
def index
|
|
12
14
|
volumes = Volume::Collection.example
|
|
13
|
-
|
|
14
|
-
response.body = volumes.collect
|
|
15
|
+
|
|
16
|
+
response.body = volumes.collect(&:render)
|
|
15
17
|
response.headers['Content-Type'] = 'application/vnd.acme.volumes'
|
|
16
18
|
response
|
|
17
19
|
end
|
|
18
20
|
|
|
19
|
-
def show(
|
|
21
|
+
def show(*)
|
|
20
22
|
response.body = JSON.pretty_generate(Volume.example.render)
|
|
21
23
|
response.headers['Content-Type'] = 'application/vnd.acme.volume'
|
|
22
24
|
response
|
|
23
25
|
end
|
|
24
|
-
|
|
25
|
-
|
|
26
26
|
end
|