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/bootloader.rb
CHANGED
|
@@ -48,7 +48,7 @@ module Praxis
|
|
|
48
48
|
|
|
49
49
|
after(:app) do
|
|
50
50
|
Praxis::Blueprint.finalize!
|
|
51
|
-
Praxis::
|
|
51
|
+
Praxis::EndpointDefinition.finalize!
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
end
|
|
@@ -66,12 +66,18 @@ module Praxis
|
|
|
66
66
|
|
|
67
67
|
def before(*stage_path, &block)
|
|
68
68
|
stage_name = stage_path.shift
|
|
69
|
-
stages.find { |stage| stage.name == stage_name }
|
|
69
|
+
the_stage = stages.find { |stage| stage.name == stage_name }
|
|
70
|
+
raise Exceptions::StageNotFound.new("Error running a before block for stage #{stage_name}") unless the_stage
|
|
71
|
+
|
|
72
|
+
the_stage.before(*stage_path, &block)
|
|
70
73
|
end
|
|
71
74
|
|
|
72
75
|
def after(*stage_path, &block)
|
|
73
76
|
stage_name = stage_path.shift
|
|
74
|
-
stages.find { |stage| stage.name == stage_name }
|
|
77
|
+
the_stage = stages.find { |stage| stage.name == stage_name }
|
|
78
|
+
raise Exceptions::StageNotFound.new("Error running an after block for stage #{stage_name}") unless the_stage
|
|
79
|
+
|
|
80
|
+
the_stage.after(*stage_path, &block)
|
|
75
81
|
end
|
|
76
82
|
|
|
77
83
|
def use(plugin,**options, &block)
|
|
@@ -20,19 +20,22 @@ module Praxis
|
|
|
20
20
|
|
|
21
21
|
def setup_default_layout!
|
|
22
22
|
application.layout do
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
map :
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
23
|
+
map :initializers, 'config/initializers/**/*'
|
|
24
|
+
map :lib, 'lib/**/*'
|
|
25
|
+
map :design, 'design/' do
|
|
26
|
+
map :api, 'api.rb'
|
|
27
|
+
map :helpers, '**/helpers/**/*'
|
|
28
|
+
map :types, '**/types/**/*'
|
|
29
|
+
map :media_types, '**/media_types/**/*'
|
|
30
|
+
map :endpoints, '**/endpoints/**/*'
|
|
31
|
+
end
|
|
32
|
+
map :app, 'app/' do
|
|
33
|
+
map :models, 'models/**/*'
|
|
34
|
+
map :responses, '**/responses/**/*'
|
|
35
|
+
map :exceptions, '**/exceptions/**/*'
|
|
36
|
+
map :concerns, '**/concerns/**/*'
|
|
37
|
+
map :resources, '**/resources/**/*'
|
|
38
|
+
map :controllers, '**/controllers/**/*'
|
|
36
39
|
end
|
|
37
40
|
end
|
|
38
41
|
end
|
data/lib/praxis/collection.rb
CHANGED
|
@@ -14,8 +14,7 @@ module Praxis
|
|
|
14
14
|
klass.member_type type
|
|
15
15
|
type.const_set :Collection, klass
|
|
16
16
|
else
|
|
17
|
-
|
|
18
|
-
Attributor::Collection.of(type)
|
|
17
|
+
raise "Praxis::Collection.of() for non-MediaTypes is unsupported. Use Attributor::Collection.of() instead."
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
end
|
|
@@ -23,21 +22,12 @@ module Praxis
|
|
|
23
22
|
def self.member_type(type=nil)
|
|
24
23
|
unless type.nil?
|
|
25
24
|
@member_type = type
|
|
26
|
-
@views = nil
|
|
27
25
|
self.identifier(type.identifier + ';type=collection') unless type.identifier.nil?
|
|
28
26
|
end
|
|
29
27
|
|
|
30
28
|
@member_type
|
|
31
29
|
end
|
|
32
30
|
|
|
33
|
-
def self.views
|
|
34
|
-
@views ||= begin
|
|
35
|
-
@member_type.views.each_with_object(Hash.new) do |(name, view), hash|
|
|
36
|
-
hash[name] = Praxis::CollectionView.new(name, @member_type, view)
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
31
|
def self.domain_model
|
|
42
32
|
@member_type.domain_model
|
|
43
33
|
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# TODO: move to Praxis proper...it's not used here
|
|
3
|
+
module Praxis
|
|
4
|
+
class ConfigHash < BasicObject
|
|
5
|
+
attr_reader :hash
|
|
6
|
+
|
|
7
|
+
def self.from(hash = {}, &block)
|
|
8
|
+
new(hash, &block)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(hash = {}, &block)
|
|
12
|
+
@hash = hash
|
|
13
|
+
@block = block
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_hash
|
|
17
|
+
instance_eval(&@block)
|
|
18
|
+
@hash
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def respond_to_missing?(_method_name, _include_private = false)
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def method_missing(name, value, *rest, &block) # rubocop:disable Style/MethodMissing
|
|
26
|
+
if (existing = @hash[name])
|
|
27
|
+
if block
|
|
28
|
+
existing << [value, block]
|
|
29
|
+
else
|
|
30
|
+
existing << value
|
|
31
|
+
rest.each do |v|
|
|
32
|
+
existing << v
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
else
|
|
36
|
+
@hash[name] = if rest.any?
|
|
37
|
+
[value] + rest
|
|
38
|
+
else
|
|
39
|
+
value
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -10,20 +10,28 @@ module Praxis
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def dump
|
|
13
|
-
data ={
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
data = { version: version }
|
|
14
|
+
[
|
|
15
|
+
:title,
|
|
16
|
+
:description,
|
|
17
|
+
:termsOfService,
|
|
18
|
+
:contact,
|
|
19
|
+
:license
|
|
20
|
+
].each do |attr|
|
|
21
|
+
val = info.send(attr)
|
|
22
|
+
data[attr] = val if val
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Special attributes
|
|
26
|
+
data[:'x-name'] = info.name
|
|
27
|
+
if info.logo_url
|
|
28
|
+
data[:'x-logo'] = {
|
|
22
29
|
url: info.logo_url,
|
|
23
30
|
backgroundColor: "#FFFFFF",
|
|
24
31
|
altText: info.title
|
|
25
32
|
}
|
|
26
|
-
|
|
33
|
+
end
|
|
34
|
+
data
|
|
27
35
|
end
|
|
28
36
|
end
|
|
29
37
|
end
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,13 +1,31 @@
|
|
|
1
|
-
require_relative '
|
|
2
|
-
require_relative '
|
|
3
|
-
require_relative '
|
|
4
|
-
require_relative '
|
|
1
|
+
require_relative 'open_api/info_object.rb'
|
|
2
|
+
require_relative 'open_api/server_object.rb'
|
|
3
|
+
require_relative 'open_api/paths_object.rb'
|
|
4
|
+
require_relative 'open_api/tag_object.rb'
|
|
5
5
|
|
|
6
6
|
module Praxis
|
|
7
7
|
module Docs
|
|
8
8
|
|
|
9
|
-
class OpenApiGenerator
|
|
9
|
+
class OpenApiGenerator
|
|
10
|
+
require 'active_support/core_ext/enumerable' # For index_by
|
|
11
|
+
|
|
10
12
|
API_DOCS_DIRNAME = 'docs/openapi'
|
|
13
|
+
EXCLUDED_TYPES_FROM_OUTPUT = Set.new([
|
|
14
|
+
Attributor::Boolean,
|
|
15
|
+
Attributor::CSV,
|
|
16
|
+
Attributor::DateTime,
|
|
17
|
+
Attributor::Date,
|
|
18
|
+
Attributor::Float,
|
|
19
|
+
Attributor::Hash,
|
|
20
|
+
Attributor::Ids,
|
|
21
|
+
Attributor::Integer,
|
|
22
|
+
Attributor::Object,
|
|
23
|
+
Attributor::String,
|
|
24
|
+
Attributor::Symbol,
|
|
25
|
+
Attributor::URI,
|
|
26
|
+
]).freeze
|
|
27
|
+
|
|
28
|
+
attr_reader :resources_by_version, :types_by_id, :infos_by_version, :doc_root_dir
|
|
11
29
|
|
|
12
30
|
# substitutes ":params_like_so" for {params_like_so}
|
|
13
31
|
def self.templatize_url( string )
|
|
@@ -37,10 +55,77 @@ module Praxis
|
|
|
37
55
|
|
|
38
56
|
private
|
|
39
57
|
|
|
58
|
+
def collect_resources
|
|
59
|
+
# load all resource definitions registered with Praxis
|
|
60
|
+
Praxis::Application.instance.endpoint_definitions.map do |resource|
|
|
61
|
+
# skip resources with doc_visibility of :none
|
|
62
|
+
next if resource.metadata[:doc_visibility] == :none
|
|
63
|
+
version = resource.version
|
|
64
|
+
# TODO: it seems that we shouldn't hardcode n/a in Praxis
|
|
65
|
+
# version = "unversioned" if version == "n/a"
|
|
66
|
+
@resources_by_version[version] << resource
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def collect_types
|
|
71
|
+
@types_by_id = ObjectSpace.each_object( Class ).select do |obj|
|
|
72
|
+
obj < Attributor::Type
|
|
73
|
+
end.index_by(&:id)
|
|
74
|
+
end
|
|
75
|
+
|
|
40
76
|
def write_index_file( for_versions: )
|
|
41
77
|
# TODO. create a simple html file that can link to the individual versions available
|
|
42
78
|
end
|
|
43
79
|
|
|
80
|
+
def scan_types_for_version(version, dumped_resources)
|
|
81
|
+
found_media_types = resources_by_version[version].select{|r| r.media_type}.collect {|r| r.media_type.describe }
|
|
82
|
+
|
|
83
|
+
# We'll start by processing the rendered mediatypes
|
|
84
|
+
processed_types = Set.new(resources_by_version[version].select do|r|
|
|
85
|
+
r.media_type && !r.media_type.is_a?(Praxis::SimpleMediaType)
|
|
86
|
+
end.collect(&:media_type))
|
|
87
|
+
|
|
88
|
+
newfound = Set.new
|
|
89
|
+
found_media_types.each do |mt|
|
|
90
|
+
newfound += scan_dump_for_types( { type: mt} , processed_types )
|
|
91
|
+
end
|
|
92
|
+
# Then will process the rendered resources (noting)
|
|
93
|
+
newfound += scan_dump_for_types( dumped_resources, Set.new )
|
|
94
|
+
|
|
95
|
+
# At this point we've done a scan of the dumped resources and mediatypes.
|
|
96
|
+
# In that scan we've discovered a bunch of types, however, many of those might have appeared in the JSON
|
|
97
|
+
# rendered in just shallow mode, so it is not guaranteed that we've seen all the available types.
|
|
98
|
+
# For that we'll do a (non-shallow) dump of all the types we found, and scan them until the scans do not
|
|
99
|
+
# yield types we haven't seen before
|
|
100
|
+
while !newfound.empty? do
|
|
101
|
+
dumped = newfound.collect(&:describe)
|
|
102
|
+
processed_types += newfound
|
|
103
|
+
newfound = scan_dump_for_types( dumped, processed_types )
|
|
104
|
+
end
|
|
105
|
+
processed_types
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def scan_dump_for_types( data, processed_types )
|
|
109
|
+
newfound_types = Set.new
|
|
110
|
+
case data
|
|
111
|
+
when Array
|
|
112
|
+
data.collect{|item| newfound_types += scan_dump_for_types( item , processed_types ) }
|
|
113
|
+
when Hash
|
|
114
|
+
if data.key?(:type) && data[:type].kind_of?(Hash) && ( [:id,:name,:family] - data[:type].keys ).empty?
|
|
115
|
+
type_id = data[:type][:id]
|
|
116
|
+
unless type_id.nil? || type_id == Praxis::SimpleMediaType.id #SimpleTypes shouldn't be collected
|
|
117
|
+
unless types_by_id[type_id]
|
|
118
|
+
raise "Error! We have detected a reference to a 'Type' with id='#{type_id}' which is not derived from Attributor::Type" +
|
|
119
|
+
" Document generation cannot proceed."
|
|
120
|
+
end
|
|
121
|
+
newfound_types << types_by_id[type_id] unless processed_types.include? types_by_id[type_id]
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
data.values.map{|item| newfound_types += scan_dump_for_types( item , processed_types)}
|
|
125
|
+
end
|
|
126
|
+
newfound_types
|
|
127
|
+
end
|
|
128
|
+
|
|
44
129
|
def write_version_file( version )
|
|
45
130
|
# version_info = infos_by_version[version]
|
|
46
131
|
# # Hack, let's "inherit/copy" all traits of a version from the global definition
|
|
@@ -159,7 +244,7 @@ module Praxis
|
|
|
159
244
|
resources_by_version.keys.each do |version|
|
|
160
245
|
FileUtils.mkdir_p @doc_root_dir + '/' + version
|
|
161
246
|
end
|
|
162
|
-
FileUtils.mkdir_p @doc_root_dir + '/unversioned'
|
|
247
|
+
FileUtils.mkdir_p @doc_root_dir + '/unversioned' if resources_by_version.keys.include?('n/a')
|
|
163
248
|
end
|
|
164
249
|
|
|
165
250
|
def normalize_media_types( mtis )
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
require 'active_support/concern'
|
|
2
|
+
require 'active_support/inflector'
|
|
3
|
+
|
|
4
|
+
module Praxis
|
|
5
|
+
module EndpointDefinition
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
DEFAULT_RESOURCE_HREF_ACTION = :show
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
@version = 'n/a'.freeze
|
|
12
|
+
@actions = Hash.new
|
|
13
|
+
@responses = Hash.new
|
|
14
|
+
|
|
15
|
+
@action_defaults = Trait.new &EndpointDefinition.generate_defaults_block
|
|
16
|
+
|
|
17
|
+
@version_options = {}
|
|
18
|
+
@metadata = {}
|
|
19
|
+
@traits = []
|
|
20
|
+
|
|
21
|
+
if self.name
|
|
22
|
+
@prefix = '/' + self.name.split("::").last.underscore
|
|
23
|
+
else
|
|
24
|
+
@prefix = '/'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
@version_prefix = ''
|
|
28
|
+
|
|
29
|
+
@parent = nil
|
|
30
|
+
@parent_prefix = ''
|
|
31
|
+
|
|
32
|
+
@routing_prefix = nil
|
|
33
|
+
|
|
34
|
+
@on_finalize = Array.new
|
|
35
|
+
|
|
36
|
+
Application.instance.endpoint_definitions << self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.generate_defaults_block( version: nil )
|
|
40
|
+
|
|
41
|
+
# Ensure we inherit any base params defined in the API definition for the passed in version
|
|
42
|
+
base_attributes = if (base_params = ApiDefinition.instance.info(version).base_params)
|
|
43
|
+
base_params.attributes
|
|
44
|
+
else
|
|
45
|
+
{}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Proc.new do
|
|
49
|
+
unless base_attributes.empty?
|
|
50
|
+
params do
|
|
51
|
+
base_attributes.each do |base_name, base_attribute|
|
|
52
|
+
attribute base_name, base_attribute.type, **base_attribute.options
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.finalize!
|
|
60
|
+
Application.instance.endpoint_definitions.each do |resource_definition|
|
|
61
|
+
while (block = resource_definition.on_finalize.shift)
|
|
62
|
+
block.call
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
module ClassMethods
|
|
70
|
+
attr_reader :actions
|
|
71
|
+
attr_reader :responses
|
|
72
|
+
attr_reader :version_options
|
|
73
|
+
attr_reader :traits
|
|
74
|
+
attr_reader :version_prefix
|
|
75
|
+
attr_reader :parent_prefix
|
|
76
|
+
|
|
77
|
+
# opaque hash of user-defined medata, used to decorate the definition,
|
|
78
|
+
# and also available in the generated JSON documents
|
|
79
|
+
attr_reader :metadata
|
|
80
|
+
|
|
81
|
+
attr_accessor :controller
|
|
82
|
+
|
|
83
|
+
def display_name( string=nil )
|
|
84
|
+
unless string
|
|
85
|
+
return @display_name ||= self.name.split("::").last # Best guess at a display name?
|
|
86
|
+
end
|
|
87
|
+
@display_name = string
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def on_finalize(&block)
|
|
91
|
+
if block_given?
|
|
92
|
+
@on_finalize << proc(&block)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
@on_finalize
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def prefix(prefix=nil)
|
|
99
|
+
return @prefix if prefix.nil?
|
|
100
|
+
@routing_prefix = nil # reset routing_prefix
|
|
101
|
+
@prefix = prefix
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def media_type(media_type=nil)
|
|
105
|
+
return @media_type if media_type.nil?
|
|
106
|
+
|
|
107
|
+
if media_type.kind_of?(String)
|
|
108
|
+
media_type = SimpleMediaType.new(media_type)
|
|
109
|
+
end
|
|
110
|
+
@media_type = media_type
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def parent(parent=nil, **mapping)
|
|
115
|
+
return @parent if parent.nil?
|
|
116
|
+
|
|
117
|
+
@routing_prefix = nil # reset routing_prefix
|
|
118
|
+
|
|
119
|
+
parent_action = parent.canonical_path
|
|
120
|
+
parent_route = parent_action.route.path
|
|
121
|
+
|
|
122
|
+
# if a mapping is passed, it *must* resolve any param name conflicts
|
|
123
|
+
unless mapping.any?
|
|
124
|
+
# assume last capture is the relevant one to replace
|
|
125
|
+
# if not... then I quit.
|
|
126
|
+
parent_param_name = parent_route.names.last
|
|
127
|
+
|
|
128
|
+
# more assumptions about names
|
|
129
|
+
parent_name = parent.name.demodulize.underscore.singularize
|
|
130
|
+
|
|
131
|
+
# put it together to find what we should call this new param
|
|
132
|
+
param = "#{parent_name}_#{parent_param_name}".to_sym
|
|
133
|
+
mapping[parent_param_name.to_sym] = param
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# complete the mapping and massage the route
|
|
137
|
+
parent_route.names.collect(&:to_sym).each do |name|
|
|
138
|
+
if mapping.key?(name)
|
|
139
|
+
param = mapping[name]
|
|
140
|
+
# FIXME: this won't handle URI Template type paths, ie '/{parent_id}'
|
|
141
|
+
prefixed_path = parent_action.route.prefixed_path
|
|
142
|
+
@parent_prefix = prefixed_path.gsub(/(:)(#{name})(\W+|$)/, "\\1#{param.to_s}\\3")
|
|
143
|
+
else
|
|
144
|
+
mapping[name] = name
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
self.on_finalize do
|
|
149
|
+
self.inherit_params_from_parent(parent_action, **mapping)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
@parent = parent
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def inherit_params_from_parent(parent_action, **mapping)
|
|
156
|
+
actions.each do |name, action|
|
|
157
|
+
action.params do
|
|
158
|
+
mapping.each do |parent_name, name|
|
|
159
|
+
next if action.params && action.params.attributes.key?(name)
|
|
160
|
+
|
|
161
|
+
parent_attribute = parent_action.params.attributes[parent_name]
|
|
162
|
+
|
|
163
|
+
attribute name, parent_attribute.type, **parent_attribute.options
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
attr_writer :routing_prefix
|
|
171
|
+
|
|
172
|
+
def routing_prefix
|
|
173
|
+
return @routing_prefix if @routing_prefix
|
|
174
|
+
|
|
175
|
+
@routing_prefix = parent_prefix + prefix
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def version(version=nil)
|
|
180
|
+
return @version unless version
|
|
181
|
+
|
|
182
|
+
@version = version
|
|
183
|
+
@action_defaults.instance_eval &EndpointDefinition.generate_defaults_block( version: version )
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def canonical_path(action_name=nil)
|
|
188
|
+
if action_name
|
|
189
|
+
raise "Canonical path for #{self.name} is already defined as: '#{@canonical_action_name}'. 'canonical_path' can only be defined once." if @canonical_action_name
|
|
190
|
+
@canonical_action_name = action_name
|
|
191
|
+
else
|
|
192
|
+
# Resolution of the actual action definition needs to be done lazily, since we can use the `canonical_path` stanza
|
|
193
|
+
# at the top of the resource, well before the actual action is defined.
|
|
194
|
+
unless @canonical_action
|
|
195
|
+
href_action = @canonical_action_name || DEFAULT_RESOURCE_HREF_ACTION
|
|
196
|
+
@canonical_action = actions.fetch(href_action) do
|
|
197
|
+
raise "Error: trying to set canonical_href of #{self.name}. Action '#{href_action}' does not exist"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
return @canonical_action
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def to_href( params )
|
|
205
|
+
canonical_path.route.path.expand(params.transform_values(&:to_s))
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def parse_href(path)
|
|
209
|
+
if path.kind_of?(::URI::Generic)
|
|
210
|
+
path = path.path
|
|
211
|
+
end
|
|
212
|
+
param_values = canonical_path.route.path.params(path)
|
|
213
|
+
attrs = canonical_path.params.attributes
|
|
214
|
+
param_values.each_with_object({}) do |(key,value),hash|
|
|
215
|
+
hash[key.to_sym] = attrs[key.to_sym].load(value,[key])
|
|
216
|
+
end
|
|
217
|
+
rescue => e
|
|
218
|
+
raise Praxis::Exception.new("Error parsing or coercing parameters from href: #{path}\n"+e.message)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def trait(trait_name)
|
|
222
|
+
unless ApiDefinition.instance.traits.has_key? trait_name
|
|
223
|
+
raise Exceptions::InvalidTrait.new("Trait #{trait_name} not found in the system")
|
|
224
|
+
end
|
|
225
|
+
trait = ApiDefinition.instance.traits.fetch(trait_name)
|
|
226
|
+
@traits << trait_name
|
|
227
|
+
end
|
|
228
|
+
alias_method :use, :trait
|
|
229
|
+
|
|
230
|
+
def action_defaults(&block)
|
|
231
|
+
if block_given?
|
|
232
|
+
@action_defaults.instance_eval(&block)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
@action_defaults
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def action(name, &block)
|
|
239
|
+
raise ArgumentError, "can not create ActionDefinition without block" unless block_given?
|
|
240
|
+
raise ArgumentError, "Action names must be defined using symbols (Got: #{name} (of type #{name.class}))" unless name.is_a? Symbol
|
|
241
|
+
@actions[name] = ActionDefinition.new(name, self, &block)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def description(text=nil)
|
|
245
|
+
@description = text if text
|
|
246
|
+
@description
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def id
|
|
250
|
+
self.name.gsub('::'.freeze,'-'.freeze)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def describe(context: nil)
|
|
254
|
+
{}.tap do |hash|
|
|
255
|
+
hash[:description] = description
|
|
256
|
+
hash[:media_type] = media_type.describe(true) if media_type
|
|
257
|
+
hash[:actions] = actions.values.collect{|action| action.describe(context: context)}
|
|
258
|
+
hash[:name] = self.name
|
|
259
|
+
hash[:parent] = self.parent.id if self.parent
|
|
260
|
+
hash[:display_name] = self.display_name
|
|
261
|
+
hash[:metadata] = metadata
|
|
262
|
+
hash[:traits] = self.traits
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def nodoc!
|
|
267
|
+
metadata[:doc_visibility] = :none
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
end
|
|
273
|
+
end
|