praxis 2.0.pre.6 → 2.0.pre.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +1 -3
- data/CHANGELOG.md +25 -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 +2 -2
- data/lib/praxis/docs/{openapi → open_api}/paths_object.rb +12 -15
- 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 +57 -8
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +3 -16
- 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 +5 -1
- data/lib/praxis/extensions/pagination/pagination_params.rb +10 -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 +2 -2
- 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 +19 -8
- data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +106 -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 +62 -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)
|
@@ -27,7 +27,7 @@ namespace :praxis do
|
|
27
27
|
implementation: method_name,
|
28
28
|
}
|
29
29
|
|
30
|
-
|
30
|
+
unless action.route
|
31
31
|
warn "Warning: No routes defined for #{resource_definition.name}##{name}."
|
32
32
|
rows << row
|
33
33
|
else
|
@@ -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
|