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/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
|
@@ -48,11 +48,11 @@ module Praxis
|
|
48
48
|
|
49
49
|
if action.params
|
50
50
|
route_params = \
|
51
|
-
|
51
|
+
unless action.route
|
52
52
|
warn "Warning: No routes defined for action #{action.name}"
|
53
53
|
[]
|
54
54
|
else
|
55
|
-
action.
|
55
|
+
action.route.path.named_captures.keys.collect(&:to_sym)
|
56
56
|
end
|
57
57
|
(action.params.attributes||{}).each_with_object(output) do |(name, info), out|
|
58
58
|
in_type = route_params.include?(name) ? :path : :query
|
@@ -31,21 +31,18 @@ module Praxis
|
|
31
31
|
# fill in the paths hash with a key for each path for each action/route
|
32
32
|
resource.actions.each do |action_name, action|
|
33
33
|
params_example = action.params ? action.params.example(nil) : nil
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
action_tags = [resource.display_name]
|
47
|
-
path_entry[verb] = OperationObject.new( id: action_uid, url: url, action: action, tags: action_tags).dump
|
48
|
-
end
|
34
|
+
url = ActionDefinition.url_description(route: action.route, params: action.params, params_example: params_example)
|
35
|
+
|
36
|
+
verb = url[:verb].downcase
|
37
|
+
templetized_path = OpenApiGenerator.templatize_url(url[:path])
|
38
|
+
path_entry = paths[templetized_path]
|
39
|
+
# Let's fill in verb stuff within the working hash
|
40
|
+
raise "VERB #{_verb} already defined for #{id}!?!?!" if path_entry[verb]
|
41
|
+
|
42
|
+
action_uid = "action-#{action_name}-#{id}"
|
43
|
+
# Add a tag matching the resource name (hoping all actions of a resource are grouped)
|
44
|
+
action_tags = [resource.display_name]
|
45
|
+
path_entry[verb] = OperationObject.new( id: action_uid, url: url, action: action, tags: action_tags).dump
|
49
46
|
end
|
50
47
|
# For each path, we can further annotate with
|
51
48
|
# servers
|
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
|