praxis 2.0.pre.8 → 2.0.pre.13
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/.gitignore +2 -0
- data/.ruby-version +1 -1
- data/.travis.yml +1 -3
- data/CHANGELOG.md +33 -0
- data/TODO.md +1 -4
- data/bin/praxis +67 -12
- data/lib/praxis.rb +10 -3
- data/lib/praxis/action_definition.rb +15 -13
- data/lib/praxis/action_definition/headers_dsl_compiler.rb +0 -7
- data/lib/praxis/api_general_info.rb +1 -1
- data/lib/praxis/application.rb +6 -2
- data/lib/praxis/blueprint.rb +357 -0
- data/lib/praxis/bootloader.rb +9 -3
- data/lib/praxis/bootloader_stages/environment.rb +16 -13
- data/lib/praxis/collection.rb +1 -11
- data/lib/praxis/config_hash.rb +44 -0
- data/lib/praxis/docs/{openapi → open_api}/info_object.rb +18 -10
- data/lib/praxis/docs/{openapi → open_api}/media_type_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/operation_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/parameter_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/paths_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/request_body_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/response_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/responses_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/schema_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/server_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/tag_object.rb +0 -0
- data/lib/praxis/docs/open_api_generator.rb +91 -6
- data/lib/praxis/endpoint_definition.rb +273 -0
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +182 -58
- data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +3 -2
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +47 -56
- data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +153 -0
- data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +20 -8
- data/lib/praxis/extensions/field_expansion.rb +3 -36
- data/lib/praxis/extensions/pagination.rb +5 -32
- data/lib/praxis/extensions/pagination/ordering_params.rb +1 -1
- data/lib/praxis/extensions/pagination/pagination_params.rb +6 -4
- data/lib/praxis/field_expander.rb +90 -0
- data/lib/praxis/finalizable.rb +34 -0
- data/lib/praxis/mapper/active_model_compat.rb +4 -0
- data/lib/praxis/mapper/resource.rb +18 -2
- data/lib/praxis/mapper/selector_generator.rb +2 -1
- data/lib/praxis/mapper/sequel_compat.rb +7 -0
- data/lib/praxis/media_type.rb +3 -68
- data/lib/praxis/plugin_concern.rb +1 -1
- data/lib/praxis/plugins/mapper_plugin.rb +24 -15
- data/lib/praxis/plugins/pagination_plugin.rb +34 -4
- data/lib/praxis/renderer.rb +88 -0
- data/lib/praxis/request.rb +1 -1
- data/lib/praxis/resource_definition.rb +2 -311
- data/lib/praxis/response_definition.rb +2 -10
- data/lib/praxis/response_template.rb +3 -3
- data/lib/praxis/router.rb +2 -2
- data/lib/praxis/routing_config.rb +1 -1
- data/lib/praxis/tasks/api_docs.rb +17 -64
- data/lib/praxis/tasks/routes.rb +1 -1
- data/lib/praxis/types/media_type_common.rb +1 -11
- data/lib/praxis/version.rb +1 -1
- data/praxis.gemspec +0 -1
- data/spec/functional_spec.rb +5 -9
- data/spec/praxis/action_definition_spec.rb +12 -20
- data/spec/praxis/blueprint_spec.rb +373 -0
- data/spec/praxis/bootloader_spec.rb +10 -2
- data/spec/praxis/collection_spec.rb +0 -13
- data/spec/praxis/config_hash_spec.rb +64 -0
- data/spec/praxis/{resource_definition_spec.rb → endpoint_definition_spec.rb} +37 -64
- data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +249 -168
- data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +25 -6
- data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +190 -8
- data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +140 -0
- data/spec/praxis/extensions/field_expansion_spec.rb +5 -24
- data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +1 -1
- data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +1 -1
- data/spec/praxis/extensions/support/spec_resources_active_model.rb +1 -1
- data/spec/praxis/field_expander_spec.rb +149 -0
- data/spec/praxis/mapper/selector_generator_spec.rb +1 -1
- data/spec/praxis/media_type_identifier_spec.rb +5 -4
- data/spec/praxis/media_type_spec.rb +4 -93
- data/spec/praxis/renderer_spec.rb +188 -0
- data/spec/praxis/response_definition_spec.rb +0 -31
- data/spec/praxis/response_spec.rb +1 -1
- data/spec/praxis/router_spec.rb +8 -8
- data/spec/praxis/routing_config_spec.rb +3 -3
- data/spec/spec_app/app/controllers/instances.rb +13 -7
- data/spec/spec_app/design/media_types/instance.rb +1 -19
- data/spec/spec_app/design/media_types/volume.rb +1 -1
- data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -14
- data/spec/spec_app/design/resources/instances.rb +5 -8
- data/spec/spec_app/design/resources/volume_snapshots.rb +1 -1
- data/spec/spec_app/design/resources/volumes.rb +1 -1
- data/spec/support/spec_authorization_plugin.rb +1 -1
- data/spec/support/spec_blueprints.rb +72 -0
- data/spec/support/{spec_resource_definitions.rb → spec_endpoint_definitions.rb} +2 -2
- data/spec/support/spec_media_types.rb +6 -26
- data/tasks/thor/app.rb +8 -34
- data/tasks/thor/example.rb +51 -285
- data/tasks/thor/model.rb +40 -0
- data/tasks/thor/scaffold.rb +117 -0
- data/tasks/thor/templates/generator/empty_app/.gitignore +0 -1
- data/tasks/thor/templates/generator/empty_app/Gemfile +7 -23
- data/tasks/thor/templates/generator/empty_app/README.md +1 -1
- data/tasks/thor/templates/generator/empty_app/Rakefile +4 -13
- data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/config/environment.rb +26 -17
- data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/docs/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/docs/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/spec/spec_helper.rb +14 -9
- data/tasks/thor/templates/generator/example_app/.gitignore +1 -0
- data/tasks/thor/templates/generator/example_app/Gemfile +19 -0
- data/tasks/thor/templates/generator/example_app/Rakefile +61 -0
- data/tasks/thor/templates/generator/example_app/app/models/user.rb +6 -0
- data/tasks/thor/templates/generator/example_app/app/v1/concerns/controller_base.rb +24 -0
- data/tasks/thor/templates/generator/example_app/app/v1/controllers/users.rb +17 -0
- data/tasks/thor/templates/generator/example_app/app/v1/resources/base.rb +11 -0
- data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +25 -0
- data/tasks/thor/templates/generator/example_app/config.ru +30 -0
- data/tasks/thor/templates/generator/example_app/config/environment.rb +41 -0
- data/tasks/thor/templates/generator/example_app/db/migrate/20201010101010_create_users_table.rb +12 -0
- data/tasks/thor/templates/generator/example_app/db/seeds.rb +6 -0
- data/tasks/thor/templates/generator/example_app/design/api.rb +18 -0
- data/tasks/thor/templates/generator/example_app/design/v1/endpoints/users.rb +37 -0
- data/tasks/thor/templates/generator/example_app/design/v1/media_types/user.rb +21 -0
- data/tasks/thor/templates/generator/example_app/spec/helpers/database_helper.rb +20 -0
- data/tasks/thor/templates/generator/example_app/spec/spec_helper.rb +42 -0
- data/tasks/thor/templates/generator/example_app/spec/v1/controllers/users_spec.rb +37 -0
- data/tasks/thor/templates/generator/scaffold/design/endpoints/collection.rb +98 -0
- data/tasks/thor/templates/generator/scaffold/design/media_types/item.rb +18 -0
- data/tasks/thor/templates/generator/scaffold/implementation/controllers/collection.rb +77 -0
- data/tasks/thor/templates/generator/scaffold/implementation/resources/base.rb +11 -0
- data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +45 -0
- data/tasks/thor/templates/generator/scaffold/models/active_record.rb +6 -0
- data/tasks/thor/templates/generator/scaffold/models/sequel.rb +6 -0
- metadata +64 -136
- data/lib/api_browser/.bowerrc +0 -3
- data/lib/api_browser/.editorconfig +0 -21
- data/lib/api_browser/Gruntfile.js +0 -581
- data/lib/api_browser/app/index.html +0 -59
- data/lib/api_browser/app/js/app.js +0 -48
- data/lib/api_browser/app/js/controllers/action.js +0 -47
- data/lib/api_browser/app/js/controllers/controller.js +0 -10
- data/lib/api_browser/app/js/controllers/menu.js +0 -93
- data/lib/api_browser/app/js/controllers/trait.js +0 -10
- data/lib/api_browser/app/js/controllers/type.js +0 -24
- data/lib/api_browser/app/js/directives/attribute_description.js +0 -56
- data/lib/api_browser/app/js/directives/attribute_table.js +0 -28
- data/lib/api_browser/app/js/directives/conditional_requirements.js +0 -13
- data/lib/api_browser/app/js/directives/fixed_if_fits.js +0 -38
- data/lib/api_browser/app/js/directives/highlight.js +0 -14
- data/lib/api_browser/app/js/directives/menu_item.js +0 -59
- data/lib/api_browser/app/js/directives/no_container.js +0 -8
- data/lib/api_browser/app/js/directives/readable_list.js +0 -87
- data/lib/api_browser/app/js/directives/request_examples.js +0 -31
- data/lib/api_browser/app/js/directives/type_placeholder.js +0 -30
- data/lib/api_browser/app/js/directives/url.js +0 -15
- data/lib/api_browser/app/js/factories/Configuration.js +0 -12
- data/lib/api_browser/app/js/factories/Documentation.js +0 -61
- data/lib/api_browser/app/js/factories/Example.js +0 -51
- data/lib/api_browser/app/js/factories/PageInfo.js +0 -9
- data/lib/api_browser/app/js/factories/normalize_attributes.js +0 -20
- data/lib/api_browser/app/js/factories/prepare_template.js +0 -15
- data/lib/api_browser/app/js/factories/template_for.js +0 -128
- data/lib/api_browser/app/js/filters/attribute_name.js +0 -10
- data/lib/api_browser/app/js/filters/friendly_json.js +0 -5
- data/lib/api_browser/app/js/filters/has_requirement.js +0 -14
- data/lib/api_browser/app/js/filters/header_info.js +0 -9
- data/lib/api_browser/app/js/filters/is_empty.js +0 -8
- data/lib/api_browser/app/js/filters/markdown.js +0 -6
- data/lib/api_browser/app/js/filters/resource_name.js +0 -5
- data/lib/api_browser/app/js/filters/tag_requirement.js +0 -13
- data/lib/api_browser/app/sass/modules/_body.scss +0 -40
- data/lib/api_browser/app/sass/modules/_cloke.scss +0 -8
- data/lib/api_browser/app/sass/modules/_header.scss +0 -10
- data/lib/api_browser/app/sass/modules/_nav.scss +0 -7
- data/lib/api_browser/app/sass/modules/_sidebar.scss +0 -134
- data/lib/api_browser/app/sass/modules/_switch.scss +0 -55
- data/lib/api_browser/app/sass/modules/_table.scss +0 -13
- data/lib/api_browser/app/sass/praxis.scss +0 -70
- data/lib/api_browser/app/sass/variables/_bootstrap-variables.scss +0 -774
- data/lib/api_browser/app/views/action.html +0 -97
- data/lib/api_browser/app/views/builtin/field-selector.html +0 -24
- data/lib/api_browser/app/views/controller.html +0 -55
- data/lib/api_browser/app/views/directives/attribute_description.html +0 -2
- data/lib/api_browser/app/views/directives/attribute_description/default.html +0 -2
- data/lib/api_browser/app/views/directives/attribute_description/example.html +0 -13
- data/lib/api_browser/app/views/directives/attribute_description/headers.html +0 -8
- data/lib/api_browser/app/views/directives/attribute_description/member_options.html +0 -4
- data/lib/api_browser/app/views/directives/attribute_description/values.html +0 -14
- data/lib/api_browser/app/views/directives/attribute_table.html +0 -17
- data/lib/api_browser/app/views/directives/menu_item.html +0 -8
- data/lib/api_browser/app/views/directives/url.html +0 -3
- data/lib/api_browser/app/views/examples/general.html +0 -26
- data/lib/api_browser/app/views/home.html +0 -5
- data/lib/api_browser/app/views/layout.html +0 -8
- data/lib/api_browser/app/views/menu.html +0 -42
- data/lib/api_browser/app/views/navbar.html +0 -9
- data/lib/api_browser/app/views/trait.html +0 -13
- data/lib/api_browser/app/views/type.html +0 -6
- data/lib/api_browser/app/views/type/details.html +0 -33
- data/lib/api_browser/app/views/types/embedded/array.html +0 -2
- data/lib/api_browser/app/views/types/embedded/default.html +0 -12
- data/lib/api_browser/app/views/types/embedded/field-selector.html +0 -13
- data/lib/api_browser/app/views/types/embedded/links.html +0 -11
- data/lib/api_browser/app/views/types/embedded/requirements.html +0 -6
- data/lib/api_browser/app/views/types/embedded/single_req.html +0 -9
- data/lib/api_browser/app/views/types/embedded/struct.html +0 -14
- data/lib/api_browser/app/views/types/label/link.html +0 -1
- data/lib/api_browser/app/views/types/label/primitive.html +0 -1
- data/lib/api_browser/app/views/types/label/primitive_collection.html +0 -1
- data/lib/api_browser/app/views/types/label/type.html +0 -1
- data/lib/api_browser/app/views/types/label/type_collection.html +0 -1
- data/lib/api_browser/app/views/types/main/array.html +0 -22
- data/lib/api_browser/app/views/types/main/default.html +0 -23
- data/lib/api_browser/app/views/types/main/hash.html +0 -23
- data/lib/api_browser/app/views/types/standalone/array.html +0 -3
- data/lib/api_browser/app/views/types/standalone/default.html +0 -18
- data/lib/api_browser/app/views/types/standalone/struct.html +0 -2
- data/lib/api_browser/bower_template.json +0 -41
- data/lib/api_browser/package-lock.json +0 -7110
- data/lib/api_browser/package.json +0 -43
- data/lib/praxis/docs/generator.rb +0 -243
- data/lib/praxis/docs/link_builder.rb +0 -30
- data/lib/praxis/links.rb +0 -135
- data/lib/praxis/types/multipart.rb +0 -109
- data/spec/api_browser/directives/type_placeholder_spec.js +0 -134
- data/spec/api_browser/factories/configuration_spec.js +0 -32
- data/spec/api_browser/factories/documentation_spec.js +0 -100
- data/spec/api_browser/factories/normalize_attributes_spec.js +0 -92
- data/spec/api_browser/factories/template_for_spec.js +0 -67
- data/spec/api_browser/filters/attribute_name_spec.js +0 -23
- data/spec/praxis/types/multipart_spec.rb +0 -112
- data/tasks/thor/templates/generator/empty_app/.rspec +0 -1
- data/tasks/thor/templates/generator/empty_app/Guardfile +0 -3
- data/tasks/thor/templates/generator/empty_app/config/rainbows.rb +0 -57
- data/tasks/thor/templates/generator/empty_app/docs/app.js +0 -1
- data/tasks/thor/templates/generator/empty_app/docs/styles.scss +0 -3
data/lib/praxis/tasks/routes.rb
CHANGED
|
@@ -11,7 +11,7 @@ namespace :praxis do
|
|
|
11
11
|
]
|
|
12
12
|
|
|
13
13
|
rows = []
|
|
14
|
-
Praxis::Application.instance.
|
|
14
|
+
Praxis::Application.instance.endpoint_definitions.each do |resource_definition|
|
|
15
15
|
resource_definition.actions.each do |name, action|
|
|
16
16
|
method = begin
|
|
17
17
|
m = resource_definition.controller.instance_method(name)
|
|
@@ -6,14 +6,6 @@ module Praxis
|
|
|
6
6
|
extend ::ActiveSupport::Concern
|
|
7
7
|
|
|
8
8
|
module ClassMethods
|
|
9
|
-
def describe(shallow = false, **opts)
|
|
10
|
-
hash = super
|
|
11
|
-
unless shallow
|
|
12
|
-
hash.merge!(identifier: @identifier.to_s, description: @description, display_name: self.display_name)
|
|
13
|
-
end
|
|
14
|
-
hash
|
|
15
|
-
end
|
|
16
|
-
|
|
17
9
|
def as_json_schema(**args)
|
|
18
10
|
the_type = @attribute && @attribute.type || member_type
|
|
19
11
|
the_type.as_json_schema(args)
|
|
@@ -38,9 +30,7 @@ module Praxis
|
|
|
38
30
|
|
|
39
31
|
# Get or set the identifier of this media type.
|
|
40
32
|
#
|
|
41
|
-
# @
|
|
42
|
-
#
|
|
43
|
-
# @return [String] the string-representation of this type's identifier
|
|
33
|
+
# @return [MediaTypeIdentifier] the string-representation of this type's identifier
|
|
44
34
|
def identifier(identifier=nil)
|
|
45
35
|
return @identifier unless identifier
|
|
46
36
|
@identifier = MediaTypeIdentifier.load(identifier)
|
data/lib/praxis/version.rb
CHANGED
data/praxis.gemspec
CHANGED
|
@@ -24,7 +24,6 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
spec.add_dependency 'mustermann', '>=1.1', '<=2'
|
|
25
25
|
spec.add_dependency 'activesupport', '>= 3'
|
|
26
26
|
spec.add_dependency 'mime', '~> 0'
|
|
27
|
-
spec.add_dependency 'praxis-blueprints', '>= 3.5'
|
|
28
27
|
spec.add_dependency 'attributor', '>= 5.5'
|
|
29
28
|
spec.add_dependency 'thor'
|
|
30
29
|
spec.add_dependency 'terminal-table', '~> 1.4'
|
data/spec/functional_spec.rb
CHANGED
|
@@ -146,7 +146,7 @@ describe 'Functional specs' do
|
|
|
146
146
|
context 'bulk_create multipart' do
|
|
147
147
|
|
|
148
148
|
let(:instance) { Instance.example }
|
|
149
|
-
let(:instance_json) { JSON.pretty_generate(instance.render(
|
|
149
|
+
let(:instance_json) { JSON.pretty_generate(instance.render(fields: {id: true, name: true})) }
|
|
150
150
|
|
|
151
151
|
let(:form) do
|
|
152
152
|
form_data = MIME::Multipart::FormData.new
|
|
@@ -174,7 +174,7 @@ describe 'Functional specs' do
|
|
|
174
174
|
|
|
175
175
|
response_instance = JSON.parse(instance_part.body)
|
|
176
176
|
expect(response_instance["key"]).to eq(instance.id)
|
|
177
|
-
expect(response_instance["value"].values).to eq(instance.render(
|
|
177
|
+
expect(response_instance["value"].values).to eq(instance.render(fields: {id: true, name: true}).values)
|
|
178
178
|
end
|
|
179
179
|
end
|
|
180
180
|
|
|
@@ -205,12 +205,10 @@ describe 'Functional specs' do
|
|
|
205
205
|
its(['destination_path']) { should eq '/etc/defaults' }
|
|
206
206
|
|
|
207
207
|
context 'response["file"]' do
|
|
208
|
-
subject(:file) { response['file'] }
|
|
209
|
-
|
|
210
208
|
its(['filename']) { should eq('docker') }
|
|
211
209
|
its(['type']) { should eq('text/plain') }
|
|
212
210
|
its(['name']) { should eq('file') }
|
|
213
|
-
its(['
|
|
211
|
+
its(['contents']) { should eq('DOCKER_HOST=tcp://127.0.0.1:2375') }
|
|
214
212
|
end
|
|
215
213
|
end
|
|
216
214
|
|
|
@@ -231,7 +229,7 @@ describe 'Functional specs' do
|
|
|
231
229
|
response = JSON.parse(last_response.body)
|
|
232
230
|
|
|
233
231
|
expect(response['name']).to eq('ValidationError')
|
|
234
|
-
expect(response['errors']).to eq(["Attribute $.payload.
|
|
232
|
+
expect(response['errors']).to eq(["Attribute $.payload.destination_path is required"])
|
|
235
233
|
end
|
|
236
234
|
|
|
237
235
|
end
|
|
@@ -259,7 +257,7 @@ describe 'Functional specs' do
|
|
|
259
257
|
before do
|
|
260
258
|
post '/api/clouds/1/instances/2/files?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
|
|
261
259
|
end
|
|
262
|
-
its(:keys){ should eq(['destination_path','
|
|
260
|
+
its(:keys){ should eq(['destination_path','name','filename','type','contents','options'])}
|
|
263
261
|
its(['options']){ should eq({"extra_thing"=>"I am extra"})}
|
|
264
262
|
end
|
|
265
263
|
|
|
@@ -334,8 +332,6 @@ describe 'Functional specs' do
|
|
|
334
332
|
let(:content_type){ 'application/json' }
|
|
335
333
|
it 'can terminate instances with POST' do
|
|
336
334
|
post '/api/clouds/23/instances/1/terminate?api_version=1.0', nil, 'CONTENT_TYPE' => content_type, 'global_session' => session
|
|
337
|
-
puts last_response.body
|
|
338
|
-
#binding.pry
|
|
339
335
|
expect(last_response.status).to eq(200)
|
|
340
336
|
end
|
|
341
337
|
it 'can terminate instances with DELETE' do
|
|
@@ -8,14 +8,14 @@ describe Praxis::ActionDefinition do
|
|
|
8
8
|
attribute :one, String
|
|
9
9
|
attribute :two, Integer
|
|
10
10
|
end
|
|
11
|
-
|
|
11
|
+
default_fieldset do
|
|
12
12
|
attribute :one
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
let(:
|
|
16
|
+
let(:endpoint_definition) do
|
|
17
17
|
Class.new do
|
|
18
|
-
include Praxis::
|
|
18
|
+
include Praxis::EndpointDefinition
|
|
19
19
|
|
|
20
20
|
def self.name
|
|
21
21
|
'FooBar'
|
|
@@ -43,7 +43,7 @@ describe Praxis::ActionDefinition do
|
|
|
43
43
|
headers headers if headers
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
|
-
Praxis::ActionDefinition.new(:foo,
|
|
46
|
+
Praxis::ActionDefinition.new(:foo, endpoint_definition) do
|
|
47
47
|
routing { get '/:one' }
|
|
48
48
|
payload { attribute :two, String }
|
|
49
49
|
headers { header "X_REQUESTED_WITH", 'XMLHttpRequest' }
|
|
@@ -54,7 +54,7 @@ describe Praxis::ActionDefinition do
|
|
|
54
54
|
|
|
55
55
|
context '#initialize' do
|
|
56
56
|
its('name') { should eq :foo }
|
|
57
|
-
its('
|
|
57
|
+
its('endpoint_definition') { should be endpoint_definition }
|
|
58
58
|
its('params.attributes') { should have_key :one }
|
|
59
59
|
its('params.attributes') { should have_key :inherited }
|
|
60
60
|
its('payload.attributes') { should have_key :two }
|
|
@@ -78,17 +78,9 @@ describe Praxis::ActionDefinition do
|
|
|
78
78
|
it { should include :created }
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
describe '#allowed_responses' do
|
|
82
|
-
it 'has some tests after we stop using ApiDefinition.instance'
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
describe '#use' do
|
|
86
|
-
it 'has some tests after we stop using ApiDefinition.instance'
|
|
87
|
-
end
|
|
88
|
-
|
|
89
81
|
describe 'when a trait is used' do
|
|
90
82
|
subject(:action) do
|
|
91
|
-
Praxis::ActionDefinition.new(:bar,
|
|
83
|
+
Praxis::ActionDefinition.new(:bar, endpoint_definition) do
|
|
92
84
|
trait :test
|
|
93
85
|
routing { get '/:one' }
|
|
94
86
|
params { attribute :one, String }
|
|
@@ -277,11 +269,11 @@ describe Praxis::ActionDefinition do
|
|
|
277
269
|
end
|
|
278
270
|
|
|
279
271
|
context 'href generation' do
|
|
280
|
-
let(:
|
|
281
|
-
subject(:action) {
|
|
272
|
+
let(:endpoint_definition) { ApiResources::Instances }
|
|
273
|
+
subject(:action) { endpoint_definition.actions[:show] }
|
|
282
274
|
|
|
283
275
|
it 'works' do
|
|
284
|
-
expansion = action.route.path.expand(cloud_id:232, id: 2)
|
|
276
|
+
expansion = action.route.path.expand(cloud_id: '232', id: '2')
|
|
285
277
|
expect(expansion).to eq "/api/clouds/232/instances/2"
|
|
286
278
|
end
|
|
287
279
|
|
|
@@ -332,9 +324,9 @@ describe Praxis::ActionDefinition do
|
|
|
332
324
|
|
|
333
325
|
context 'where the action overrides a base_param' do
|
|
334
326
|
|
|
335
|
-
let(:
|
|
327
|
+
let(:endpoint_definition) do
|
|
336
328
|
Class.new do
|
|
337
|
-
include Praxis::
|
|
329
|
+
include Praxis::EndpointDefinition
|
|
338
330
|
|
|
339
331
|
def self.name
|
|
340
332
|
'FooBar'
|
|
@@ -349,7 +341,7 @@ describe Praxis::ActionDefinition do
|
|
|
349
341
|
end
|
|
350
342
|
|
|
351
343
|
let(:action) do
|
|
352
|
-
Praxis::ActionDefinition.new(:foo,
|
|
344
|
+
Praxis::ActionDefinition.new(:foo, endpoint_definition) do
|
|
353
345
|
routing { get '' }
|
|
354
346
|
params { attribute :app_name, Integer }
|
|
355
347
|
end
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
|
3
|
+
|
|
4
|
+
describe Praxis::Blueprint do
|
|
5
|
+
subject(:blueprint_class) { PersonBlueprint }
|
|
6
|
+
|
|
7
|
+
its(:family) { should eq('hash') }
|
|
8
|
+
|
|
9
|
+
context 'deterministic examples' do
|
|
10
|
+
it 'works' do
|
|
11
|
+
person_1 = PersonBlueprint.example('person 1')
|
|
12
|
+
person_2 = PersonBlueprint.example('person 1')
|
|
13
|
+
|
|
14
|
+
expect(person_1.name).to eq(person_2.name)
|
|
15
|
+
expect(person_1.address.name).to eq(person_2.address.name)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context 'implicit default_fieldset (when not defined in the blueprint)' do
|
|
20
|
+
subject(:default_fieldset) { AddressBlueprint.default_fieldset }
|
|
21
|
+
|
|
22
|
+
it { should_not be(nil) }
|
|
23
|
+
it 'contains all attributes' do
|
|
24
|
+
simple_attributes = [:id, :name, :street, :state]
|
|
25
|
+
expect(default_fieldset.keys).to match_array(simple_attributes)
|
|
26
|
+
# Should not have blueprint-derived attributes (or collections of them)
|
|
27
|
+
expect(default_fieldset.keys).to_not include( AddressBlueprint.attributes.keys - simple_attributes )
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context 'creating a new Blueprint class' do
|
|
32
|
+
subject!(:blueprint_class) do
|
|
33
|
+
Class.new(Praxis::Blueprint) do
|
|
34
|
+
domain_model Hash
|
|
35
|
+
attributes do
|
|
36
|
+
attribute :id, Integer
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
its(:finalized?) { should be(false) }
|
|
42
|
+
its(:domain_model) { should be(Hash) }
|
|
43
|
+
|
|
44
|
+
context '.finalize on Praxis::Blueprint' do
|
|
45
|
+
before do
|
|
46
|
+
expect(blueprint_class).to receive(:_finalize!).and_call_original
|
|
47
|
+
Praxis::Blueprint.finalize!
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
its(:finalized?) { should be(true) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
context '.finalize on that subclass' do
|
|
54
|
+
before do
|
|
55
|
+
expect(blueprint_class).to receive(:_finalize!).and_call_original
|
|
56
|
+
blueprint_class.finalize!
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
its(:finalized?) { should be(true) }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
context 'creating a base abstract Blueprint class without attributes' do
|
|
64
|
+
subject!(:blueprint_class) do
|
|
65
|
+
Class.new(Praxis::Blueprint)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'skips attribute definition' do
|
|
69
|
+
expect(blueprint_class).to receive(:_finalize!).and_call_original
|
|
70
|
+
expect(blueprint_class).to_not receive(:define_attribute)
|
|
71
|
+
blueprint_class.finalize!
|
|
72
|
+
expect(blueprint_class.finalized?).to be(true)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'has an inner Struct class for the attributes' do
|
|
77
|
+
expect(blueprint_class.attribute.type).to be blueprint_class::Struct
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
context 'an instance' do
|
|
82
|
+
shared_examples 'a blueprint instance' do
|
|
83
|
+
let(:expected_name) { blueprint_instance.name }
|
|
84
|
+
|
|
85
|
+
context '#render' do
|
|
86
|
+
subject(:output) { blueprint_instance.render }
|
|
87
|
+
|
|
88
|
+
it { should have_key(:name) }
|
|
89
|
+
it 'has the right values' do
|
|
90
|
+
expect(subject[:name]).to eq(expected_name)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context 'validation' do
|
|
95
|
+
subject(:errors) { blueprint_class.validate(blueprint_instance) }
|
|
96
|
+
it { should be_empty }
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
context 'from Blueprint.example' do
|
|
101
|
+
subject(:blueprint_instance) do
|
|
102
|
+
blueprint_class.example('ExamplePersonBlueprint',
|
|
103
|
+
address: nil,
|
|
104
|
+
prior_addresses: [],
|
|
105
|
+
work_address: nil,
|
|
106
|
+
myself: nil,
|
|
107
|
+
friends: []
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
it_behaves_like 'a blueprint instance'
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
context 'wrapping an object' do
|
|
114
|
+
let(:data) do
|
|
115
|
+
{
|
|
116
|
+
name: 'Bob',
|
|
117
|
+
full_name: FullName.example,
|
|
118
|
+
address: nil,
|
|
119
|
+
email: 'bob@example.com',
|
|
120
|
+
aliases: [],
|
|
121
|
+
prior_addresses: [],
|
|
122
|
+
parents: { father: Randgen.first_name, mother: Randgen.first_name },
|
|
123
|
+
href: 'www.example.com',
|
|
124
|
+
alive: true
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
let(:resource) { blueprint_class.load(data).object }
|
|
129
|
+
|
|
130
|
+
subject(:blueprint_instance) { blueprint_class.new(resource) }
|
|
131
|
+
|
|
132
|
+
it_behaves_like 'a blueprint instance'
|
|
133
|
+
|
|
134
|
+
context 'creating additional blueprint instances from that object' do
|
|
135
|
+
subject(:additional_instance) { blueprint_class.new(resource) }
|
|
136
|
+
|
|
137
|
+
context 'with caching enabled' do
|
|
138
|
+
around do |example|
|
|
139
|
+
Praxis::Blueprint.caching_enabled = true
|
|
140
|
+
Praxis::Blueprint.cache = Hash.new { |h, k| h[k] = {} }
|
|
141
|
+
example.run
|
|
142
|
+
|
|
143
|
+
Praxis::Blueprint.caching_enabled = false
|
|
144
|
+
Praxis::Blueprint.cache = nil
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it 'uses the cache to memoize instance creation' do
|
|
148
|
+
expect(additional_instance).to be(additional_instance)
|
|
149
|
+
expect(blueprint_class.cache).to have_key(resource)
|
|
150
|
+
expect(blueprint_class.cache[resource]).to be(blueprint_instance)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
context 'with caching disabled' do
|
|
155
|
+
it { should_not be blueprint_instance }
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
context '.validate' do
|
|
162
|
+
let(:hash) { { name: 'bob' } }
|
|
163
|
+
let(:person) { PersonBlueprint.load(hash) }
|
|
164
|
+
subject(:errors) { person.validate }
|
|
165
|
+
|
|
166
|
+
context 'that is valid' do
|
|
167
|
+
it { should be_empty }
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
context 'with invalid sub-attribute' do
|
|
171
|
+
let(:hash) { { name: 'bob', address: { state: 'ME' } } }
|
|
172
|
+
|
|
173
|
+
it { should have(1).item }
|
|
174
|
+
its(:first) { should =~ /Attribute \$.address.state/ }
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
context 'for objects of the wrong type' do
|
|
178
|
+
it 'raises an error' do
|
|
179
|
+
expect do
|
|
180
|
+
PersonBlueprint.validate(Object.new)
|
|
181
|
+
end.to raise_error(ArgumentError, /Error validating .* as PersonBlueprint for an object of type Object/)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
context '.load' do
|
|
187
|
+
let(:hash) do
|
|
188
|
+
{
|
|
189
|
+
name: 'Bob',
|
|
190
|
+
full_name: { first: 'Robert', last: 'Robertson' },
|
|
191
|
+
address: { street: 'main', state: 'OR' }
|
|
192
|
+
}
|
|
193
|
+
end
|
|
194
|
+
subject(:person) { PersonBlueprint.load(hash) }
|
|
195
|
+
|
|
196
|
+
it { should be_kind_of(PersonBlueprint) }
|
|
197
|
+
|
|
198
|
+
context 'recursively loading sub-attributes' do
|
|
199
|
+
context 'for a Blueprint' do
|
|
200
|
+
subject(:address) { person.address }
|
|
201
|
+
it { should be_kind_of(AddressBlueprint) }
|
|
202
|
+
end
|
|
203
|
+
context 'for an Attributor::Model' do
|
|
204
|
+
subject(:full_name) { person.full_name }
|
|
205
|
+
it { should be_kind_of(FullName) }
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
context 'with a provided :reference option on attributes' do
|
|
211
|
+
context 'that does not match the value set on the class' do
|
|
212
|
+
subject(:mismatched_reference) do
|
|
213
|
+
Class.new(Praxis::Blueprint) do
|
|
214
|
+
self.reference = Class.new(Praxis::Blueprint)
|
|
215
|
+
attributes(reference: Class.new(Praxis::Blueprint)) {}
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it 'should raise an error' do
|
|
220
|
+
expect do
|
|
221
|
+
mismatched_reference.attributes
|
|
222
|
+
end.to raise_error(/Reference mismatch/)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
context '.example' do
|
|
228
|
+
context 'with some attribute values provided' do
|
|
229
|
+
let(:name) { 'Sir Bobbert' }
|
|
230
|
+
subject(:person) { PersonBlueprint.example(name: name) }
|
|
231
|
+
its(:name) { should eq(name) }
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
context '.render' do
|
|
236
|
+
let(:person) { PersonBlueprint.example('1') }
|
|
237
|
+
it 'is an alias to dump' do
|
|
238
|
+
person.object.contents
|
|
239
|
+
rendered = PersonBlueprint.render(person, fields: [:name, :full_name])
|
|
240
|
+
dumped = PersonBlueprint.dump(person, fields: [:name, :full_name])
|
|
241
|
+
expect(rendered).to eq(dumped)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
context '#render' do
|
|
246
|
+
let(:person) { PersonBlueprint.example }
|
|
247
|
+
let(:fields) do
|
|
248
|
+
{
|
|
249
|
+
name: true,
|
|
250
|
+
full_name: true,
|
|
251
|
+
address: {
|
|
252
|
+
street: true,
|
|
253
|
+
state: true,
|
|
254
|
+
},
|
|
255
|
+
prior_addresses: {
|
|
256
|
+
street: true,
|
|
257
|
+
state: true,
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
end
|
|
261
|
+
let(:render_opts) { {} }
|
|
262
|
+
subject(:output) { person.render(fields: fields, **render_opts) }
|
|
263
|
+
|
|
264
|
+
context 'without passing fields' do
|
|
265
|
+
it 'renders the default field set defined' do
|
|
266
|
+
rendered = person.render( **render_opts)
|
|
267
|
+
default_top_fields = PersonBlueprint.default_fieldset.keys
|
|
268
|
+
expect(rendered.keys).to match_array(default_top_fields)
|
|
269
|
+
expect(default_top_fields).to match_array([
|
|
270
|
+
:name,
|
|
271
|
+
:full_name,
|
|
272
|
+
:address,
|
|
273
|
+
:prior_addresses])
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
context 'with a sub-attribute that is a blueprint' do
|
|
277
|
+
it { should have_key(:name) }
|
|
278
|
+
it { should have_key(:address) }
|
|
279
|
+
it 'renders the sub-attribute correctly' do
|
|
280
|
+
expect(output[:address]).to have_key(:street)
|
|
281
|
+
expect(output[:address]).to have_key(:state)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
it 'reports a dump error with the appropriate context' do
|
|
285
|
+
expect(person.address).to receive(:state).and_raise('Kaboom')
|
|
286
|
+
expect do
|
|
287
|
+
person.render(fields: fields, context: ['special_root'])
|
|
288
|
+
end.to raise_error(/Error while dumping attribute state of type AddressBlueprint for context special_root.address. Reason: .*Kaboom/)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
context 'with sub-attribute that is an Attributor::Model' do
|
|
293
|
+
it { should have_key(:full_name) }
|
|
294
|
+
it 'renders the model correctly' do
|
|
295
|
+
expect(output[:full_name]).to be_kind_of(Hash)
|
|
296
|
+
expect(output[:full_name]).to have_key(:first)
|
|
297
|
+
expect(output[:full_name]).to have_key(:last)
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
context 'using the `fields` option' do
|
|
302
|
+
context 'as a hash' do
|
|
303
|
+
subject(:output) { person.render(fields: { address: { state: true } }) }
|
|
304
|
+
it 'should only have the address rendered' do
|
|
305
|
+
expect(output.keys).to eq [:address]
|
|
306
|
+
end
|
|
307
|
+
it 'address should only have state' do
|
|
308
|
+
expect(output[:address].keys).to eq [:state]
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
context 'as a simple array' do
|
|
312
|
+
subject(:output) { person.render(fields: [:full_name]) }
|
|
313
|
+
it 'accepts it as the list of top-level attributes to be rendered' do
|
|
314
|
+
expect(output.keys).to match_array([:full_name])
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
context 'using un-expanded fields for blueprints' do
|
|
320
|
+
let(:fields) do
|
|
321
|
+
{
|
|
322
|
+
name: true,
|
|
323
|
+
address: true, # A blueprint!
|
|
324
|
+
}
|
|
325
|
+
end
|
|
326
|
+
it 'should still render the blueprint sub-attribute with its default fieldset' do
|
|
327
|
+
address_default_top_fieldset = AddressBlueprint.default_fieldset.keys
|
|
328
|
+
expect(output[:address].keys).to match(address_default_top_fieldset)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
context '.as_json_schema' do
|
|
335
|
+
it 'delegates to the attribute type' do
|
|
336
|
+
expect(PersonBlueprint.attribute.type).to receive(:as_json_schema)
|
|
337
|
+
PersonBlueprint.as_json_schema
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
context '.json_schema_type' do
|
|
341
|
+
it 'delegates to the attribute type' do
|
|
342
|
+
expect(PersonBlueprint.attribute.type).to receive(:json_schema_type)
|
|
343
|
+
PersonBlueprint.json_schema_type
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
context 'FieldsetParser' do
|
|
348
|
+
let(:definition_block) do
|
|
349
|
+
Proc.new do
|
|
350
|
+
attribute :one
|
|
351
|
+
attribute :two do
|
|
352
|
+
attribute :sub_two
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
subject { described_class::FieldsetParser.new(&definition_block) }
|
|
357
|
+
|
|
358
|
+
it 'parses properly' do
|
|
359
|
+
expect(subject.fieldset).to eq(one: true, two: { sub_two: true} )
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
context 'with attribute parameters' do
|
|
363
|
+
let(:definition_block) do
|
|
364
|
+
Proc.new do
|
|
365
|
+
attribute :one, view: :other
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
it 'complains and gives instructions if legacy view :default' do
|
|
369
|
+
expect{ subject.fieldset }.to raise_error(/Default fieldset definitions do not accept parameters/)
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|