praxis 2.0.pre.18 → 2.0.pre.19
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +54 -0
- data/.simplecov +3 -1
- data/.travis.yml +2 -1
- data/CHANGELOG.md +6 -0
- data/CONTRIBUTING.md +2 -79
- data/Gemfile +5 -1
- data/Guardfile +6 -4
- data/LICENSE +0 -2
- data/MAINTAINERS.md +1 -0
- data/README.md +15 -22
- data/Rakefile +4 -2
- data/bin/praxis +55 -58
- data/lib/praxis/action_definition/headers_dsl_compiler.rb +5 -6
- data/lib/praxis/action_definition.rb +64 -94
- data/lib/praxis/api_definition.rb +21 -29
- data/lib/praxis/api_general_info.rb +55 -66
- data/lib/praxis/application.rb +15 -32
- data/lib/praxis/blueprint.rb +64 -73
- data/lib/praxis/bootloader.rb +24 -33
- data/lib/praxis/bootloader_stages/environment.rb +5 -10
- data/lib/praxis/bootloader_stages/file_loader.rb +3 -6
- data/lib/praxis/bootloader_stages/plugin_config_load.rb +4 -6
- data/lib/praxis/bootloader_stages/plugin_config_prepare.rb +2 -2
- data/lib/praxis/bootloader_stages/plugin_loader.rb +3 -7
- data/lib/praxis/bootloader_stages/plugin_setup.rb +3 -3
- data/lib/praxis/bootloader_stages/routing.rb +5 -8
- data/lib/praxis/bootloader_stages/subgroup_loader.rb +2 -10
- data/lib/praxis/bootloader_stages/warn_unloaded_files.rb +15 -19
- data/lib/praxis/callbacks.rb +12 -11
- data/lib/praxis/collection.rb +10 -13
- data/lib/praxis/config.rb +17 -28
- data/lib/praxis/config_hash.rb +2 -1
- data/lib/praxis/controller.rb +7 -6
- data/lib/praxis/dispatcher.rb +34 -42
- data/lib/praxis/docs/open_api/info_object.rb +11 -8
- data/lib/praxis/docs/open_api/media_type_object.rb +18 -17
- data/lib/praxis/docs/open_api/operation_object.rb +6 -3
- data/lib/praxis/docs/open_api/parameter_object.rb +17 -14
- data/lib/praxis/docs/open_api/paths_object.rb +11 -9
- data/lib/praxis/docs/open_api/request_body_object.rb +14 -13
- data/lib/praxis/docs/open_api/response_object.rb +24 -18
- data/lib/praxis/docs/open_api/responses_object.rb +3 -1
- data/lib/praxis/docs/open_api/schema_object.rb +16 -16
- data/lib/praxis/docs/open_api/server_object.rb +5 -2
- data/lib/praxis/docs/open_api/tag_object.rb +6 -3
- data/lib/praxis/docs/open_api_generator.rb +92 -95
- data/lib/praxis/endpoint_definition.rb +60 -77
- data/lib/praxis/error_handler.rb +2 -2
- data/lib/praxis/exception.rb +2 -0
- data/lib/praxis/exceptions/config.rb +3 -1
- data/lib/praxis/exceptions/config_load.rb +2 -0
- data/lib/praxis/exceptions/config_validation.rb +3 -1
- data/lib/praxis/exceptions/invalid_configuration.rb +3 -1
- data/lib/praxis/exceptions/invalid_response.rb +3 -1
- data/lib/praxis/exceptions/invalid_trait.rb +3 -1
- data/lib/praxis/exceptions/stage_not_found.rb +3 -1
- data/lib/praxis/exceptions/validation.rb +4 -3
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +163 -149
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/5x.rb +18 -13
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_0.rb +13 -9
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_1_plus.rb +14 -11
- data/lib/praxis/extensions/attribute_filtering/active_record_patches.rb +12 -9
- data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +8 -5
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +89 -65
- data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +68 -62
- data/lib/praxis/extensions/attribute_filtering.rb +3 -1
- data/lib/praxis/extensions/field_expansion.rb +6 -4
- data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +10 -8
- data/lib/praxis/extensions/field_selection/field_selector.rb +91 -92
- data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +12 -12
- data/lib/praxis/extensions/field_selection.rb +3 -1
- data/lib/praxis/extensions/pagination/active_record_pagination_handler.rb +6 -4
- data/lib/praxis/extensions/pagination/header_generator.rb +16 -11
- data/lib/praxis/extensions/pagination/ordering_params.rb +29 -28
- data/lib/praxis/extensions/pagination/pagination_handler.rb +44 -42
- data/lib/praxis/extensions/pagination/pagination_params.rb +29 -48
- data/lib/praxis/extensions/pagination/sequel_pagination_handler.rb +8 -7
- data/lib/praxis/extensions/pagination.rb +10 -15
- data/lib/praxis/extensions/rails_compat/request_methods.rb +3 -4
- data/lib/praxis/extensions/rails_compat.rb +2 -0
- data/lib/praxis/extensions/rendering.rb +12 -12
- data/lib/praxis/field_expander.rb +8 -9
- data/lib/praxis/file_group.rb +8 -12
- data/lib/praxis/finalizable.rb +1 -0
- data/lib/praxis/handlers/json.rb +5 -2
- data/lib/praxis/handlers/plain.rb +2 -1
- data/lib/praxis/handlers/www_form.rb +6 -3
- data/lib/praxis/handlers/{xml-sample.rb → xml_sample.rb} +26 -22
- data/lib/praxis/mapper/active_model_compat.rb +13 -10
- data/lib/praxis/mapper/resource.rb +171 -180
- data/lib/praxis/mapper/selector_generator.rb +106 -112
- data/lib/praxis/mapper/sequel_compat.rb +70 -67
- data/lib/praxis/media_type.rb +2 -2
- data/lib/praxis/media_type_identifier.rb +26 -22
- data/lib/praxis/middleware_app.rb +18 -15
- data/lib/praxis/multipart/parser.rb +46 -51
- data/lib/praxis/multipart/part.rb +78 -110
- data/lib/praxis/notifications.rb +2 -4
- data/lib/praxis/plugin.rb +11 -18
- data/lib/praxis/plugin_concern.rb +12 -15
- data/lib/praxis/plugins/mapper_plugin.rb +15 -13
- data/lib/praxis/plugins/pagination_plugin.rb +8 -6
- data/lib/praxis/plugins/rails_plugin.rb +33 -28
- data/lib/praxis/renderer.rb +11 -15
- data/lib/praxis/request.rb +46 -47
- data/lib/praxis/request_stages/action.rb +4 -6
- data/lib/praxis/request_stages/load_request.rb +2 -4
- data/lib/praxis/request_stages/request_stage.rb +19 -23
- data/lib/praxis/request_stages/response.rb +4 -6
- data/lib/praxis/request_stages/validate.rb +3 -5
- data/lib/praxis/request_stages/validate_params_and_headers.rb +15 -16
- data/lib/praxis/request_stages/validate_payload.rb +25 -27
- data/lib/praxis/request_superclassing.rb +3 -3
- data/lib/praxis/resource_definition.rb +1 -0
- data/lib/praxis/response.rb +13 -25
- data/lib/praxis/response_definition.rb +77 -122
- data/lib/praxis/response_template.rb +11 -15
- data/lib/praxis/responses/http.rb +23 -44
- data/lib/praxis/responses/internal_server_error.rb +18 -21
- data/lib/praxis/responses/multipart_ok.rb +4 -9
- data/lib/praxis/responses/validation_error.rb +8 -15
- data/lib/praxis/route.rb +8 -10
- data/lib/praxis/router/rack.rb +13 -7
- data/lib/praxis/router/simple.rb +10 -5
- data/lib/praxis/router.rb +27 -34
- data/lib/praxis/routing_config.rb +52 -29
- data/lib/praxis/simple_media_type.rb +5 -8
- data/lib/praxis/stage.rb +17 -25
- data/lib/praxis/tasks/api_docs.rb +15 -15
- data/lib/praxis/tasks/console.rb +3 -1
- data/lib/praxis/tasks/environment.rb +2 -0
- data/lib/praxis/tasks/routes.rb +26 -24
- data/lib/praxis/tasks.rb +3 -1
- data/lib/praxis/trait.rb +37 -46
- data/lib/praxis/types/fuzzy_hash.rb +13 -14
- data/lib/praxis/types/media_type_common.rb +11 -10
- data/lib/praxis/types/multipart_array/part_definition.rb +14 -17
- data/lib/praxis/types/multipart_array.rb +88 -112
- data/lib/praxis/validation_handler.rb +5 -3
- data/lib/praxis/version.rb +3 -1
- data/lib/praxis.rb +4 -5
- data/praxis.gemspec +22 -21
- data/spec/functional_spec.rb +40 -52
- data/spec/praxis/action_definition_spec.rb +36 -46
- data/spec/praxis/api_definition_spec.rb +45 -47
- data/spec/praxis/api_general_info_spec.rb +28 -29
- data/spec/praxis/application_spec.rb +18 -14
- data/spec/praxis/blueprint_spec.rb +33 -34
- data/spec/praxis/bootloader_spec.rb +32 -30
- data/spec/praxis/callbacks_spec.rb +37 -37
- data/spec/praxis/collection_spec.rb +18 -25
- data/spec/praxis/config_hash_spec.rb +5 -4
- data/spec/praxis/config_spec.rb +27 -26
- data/spec/praxis/controller_spec.rb +8 -9
- data/spec/praxis/endpoint_definition_spec.rb +25 -32
- data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +171 -114
- data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +22 -21
- data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +112 -60
- data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +37 -38
- data/spec/praxis/extensions/field_expansion_spec.rb +8 -10
- data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +14 -13
- data/spec/praxis/extensions/field_selection/field_selector_spec.rb +9 -16
- data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +50 -49
- data/spec/praxis/extensions/pagination/active_record_pagination_handler_spec.rb +32 -31
- data/spec/praxis/extensions/rendering_spec.rb +9 -9
- data/spec/praxis/extensions/support/spec_resources_active_model.rb +32 -49
- data/spec/praxis/extensions/support/spec_resources_sequel.rb +48 -48
- data/spec/praxis/field_expander_spec.rb +6 -5
- data/spec/praxis/file_group_spec.rb +3 -1
- data/spec/praxis/handlers/json_spec.rb +6 -5
- data/spec/praxis/mapper/resource_spec.rb +27 -30
- data/spec/praxis/mapper/selector_generator_spec.rb +50 -50
- data/spec/praxis/media_type_identifier_spec.rb +13 -10
- data/spec/praxis/media_type_spec.rb +12 -12
- data/spec/praxis/middleware_app_spec.rb +23 -22
- data/spec/praxis/multipart/parser_spec.rb +7 -9
- data/spec/praxis/notifications_spec.rb +4 -4
- data/spec/praxis/plugin_concern_spec.rb +5 -6
- data/spec/praxis/renderer_spec.rb +10 -9
- data/spec/praxis/request_spec.rb +38 -41
- data/spec/praxis/request_stages/action_spec.rb +14 -15
- data/spec/praxis/request_stages/request_stage_spec.rb +30 -41
- data/spec/praxis/request_stages/validate_spec.rb +3 -1
- data/spec/praxis/response_definition_spec.rb +79 -92
- data/spec/praxis/response_spec.rb +28 -39
- data/spec/praxis/responses/internal_server_error_spec.rb +6 -9
- data/spec/praxis/responses/validation_error_spec.rb +17 -18
- data/spec/praxis/route_spec.rb +4 -7
- data/spec/praxis/router_spec.rb +69 -79
- data/spec/praxis/routing_config_spec.rb +15 -14
- data/spec/praxis/stage_spec.rb +56 -53
- data/spec/praxis/trait_spec.rb +17 -18
- data/spec/praxis/types/fuzzy_hash_spec.rb +11 -9
- data/spec/praxis/types/multipart_array/part_definition_spec.rb +3 -2
- data/spec/praxis/types/multipart_array_spec.rb +33 -48
- data/spec/spec_app/app/concerns/authenticated.rb +5 -5
- data/spec/spec_app/app/concerns/basic_api.rb +3 -1
- data/spec/spec_app/app/concerns/log_wrapper.rb +5 -3
- data/spec/spec_app/app/controllers/base_class.rb +6 -5
- data/spec/spec_app/app/controllers/instances.rb +31 -34
- data/spec/spec_app/app/controllers/volumes.rb +6 -6
- data/spec/spec_app/app/responses/multipart.rb +1 -2
- data/spec/spec_app/app/responses/other_response.rb +2 -2
- data/spec/spec_app/config/environment.rb +19 -6
- data/spec/spec_app/config.ru +4 -3
- data/spec/spec_app/design/api.rb +13 -15
- data/spec/spec_app/design/media_types/instance.rb +5 -5
- data/spec/spec_app/design/media_types/volume.rb +2 -1
- data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -1
- data/spec/spec_app/design/resources/instances.rb +9 -15
- data/spec/spec_app/design/resources/volume_snapshots.rb +4 -5
- data/spec/spec_app/design/resources/volumes.rb +4 -5
- data/spec/spec_helper.rb +11 -13
- data/spec/support/be_deep_equal_matcher.rb +5 -0
- data/spec/support/spec_authorization_plugin.rb +7 -12
- data/spec/support/spec_blueprints.rb +2 -1
- data/spec/support/spec_complex_authentication_plugin.rb +17 -34
- data/spec/support/spec_endpoint_definitions.rb +2 -3
- data/spec/support/spec_media_types.rb +28 -35
- data/spec/support/spec_resources.rb +20 -18
- data/spec/support/spec_simple_authentication_plugin.rb +5 -9
- data/tasks/loader.thor +4 -2
- data/tasks/thor/app.rb +7 -5
- data/tasks/thor/example.rb +23 -22
- data/tasks/thor/model.rb +7 -7
- data/tasks/thor/scaffold.rb +23 -23
- data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +0 -8
- data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +1 -2
- metadata +72 -84
- data/MAINTAINERS +0 -2
- data/TODO.md +0 -25
- data/spec/praxis/api_resource_spec.rb +0 -0
- data/spec/praxis/dispatcher_spec.rb +0 -0
- data/spec/spec_app/app/responses/bulk_response.rb +0 -0
data/lib/praxis/dispatcher.rb
CHANGED
@@ -1,35 +1,33 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module Praxis
|
3
4
|
CONTEXT_FOR = {
|
4
|
-
params: [Attributor::ROOT_PREFIX,
|
5
|
-
headers: [Attributor::ROOT_PREFIX,
|
6
|
-
payload: [Attributor::ROOT_PREFIX,
|
5
|
+
params: [Attributor::ROOT_PREFIX, 'params'],
|
6
|
+
headers: [Attributor::ROOT_PREFIX, 'headers'],
|
7
|
+
payload: [Attributor::ROOT_PREFIX, 'payload']
|
7
8
|
}.freeze
|
8
9
|
|
9
10
|
class Dispatcher
|
10
|
-
attr_reader :controller
|
11
|
-
attr_reader :action
|
12
|
-
attr_reader :request
|
13
|
-
attr_reader :application
|
11
|
+
attr_reader :controller, :action, :request, :application
|
14
12
|
|
15
|
-
@deferred_callbacks = Hash.new do |hash,stage|
|
16
|
-
hash[stage] = {before: [], after:[]}
|
13
|
+
@deferred_callbacks = Hash.new do |hash, stage|
|
14
|
+
hash[stage] = { before: [], after: [] }
|
17
15
|
end
|
18
16
|
|
19
17
|
class << self
|
20
18
|
attr_reader :deferred_callbacks
|
21
19
|
end
|
22
20
|
|
23
|
-
def self.before(*
|
21
|
+
def self.before(*_stage_path, **conditions, &block)
|
24
22
|
@deferred_callbacks[:before] << [conditions, block]
|
25
23
|
end
|
26
24
|
|
27
|
-
def self.after(*
|
25
|
+
def self.after(*_stage_path, **conditions, &block)
|
28
26
|
@deferred_callbacks[:after] << [conditions, block]
|
29
27
|
end
|
30
28
|
|
31
29
|
def self.current(thread: Thread.current, application: Application.instance)
|
32
|
-
thread[:praxis_dispatcher] ||=
|
30
|
+
thread[:praxis_dispatcher] ||= new(application: application)
|
33
31
|
end
|
34
32
|
|
35
33
|
def initialize(application: Application.instance)
|
@@ -43,20 +41,18 @@ module Praxis
|
|
43
41
|
@stages << RequestStages::Validate.new(:validate, self)
|
44
42
|
@stages << RequestStages::Action.new(:action, self)
|
45
43
|
@stages << RequestStages::Response.new(:response, self)
|
46
|
-
@stages.each
|
47
|
-
s.setup!
|
48
|
-
end
|
44
|
+
@stages.each(&:setup!)
|
49
45
|
setup_deferred_callbacks!
|
50
46
|
end
|
51
47
|
|
52
48
|
def setup_deferred_callbacks!
|
53
49
|
self.class.deferred_callbacks.each do |stage_name, callbacks|
|
54
50
|
callbacks[:before].each do |(*stage_path, block)|
|
55
|
-
|
51
|
+
before(stage_name, *stage_path, &block)
|
56
52
|
end
|
57
53
|
|
58
54
|
callbacks[:after].each do |(*stage_path, block)|
|
59
|
-
|
55
|
+
after(stage_name, *stage_path, &block)
|
60
56
|
end
|
61
57
|
end
|
62
58
|
end
|
@@ -76,38 +72,35 @@ module Praxis
|
|
76
72
|
@action = action
|
77
73
|
@request = request
|
78
74
|
|
79
|
-
payload = {request: request, response: nil, controller: @controller}
|
80
|
-
|
81
|
-
instrumented_dispatch( payload )
|
75
|
+
payload = { request: request, response: nil, controller: @controller }
|
82
76
|
|
77
|
+
instrumented_dispatch(payload)
|
83
78
|
ensure
|
84
79
|
@controller = nil
|
85
80
|
@action = nil
|
86
81
|
@request = nil
|
87
82
|
end
|
88
83
|
|
89
|
-
def instrumented_dispatch(
|
90
|
-
Notifications.instrument 'praxis.request.all'
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
break
|
101
|
-
end
|
84
|
+
def instrumented_dispatch(payload)
|
85
|
+
Notifications.instrument 'praxis.request.all', payload do
|
86
|
+
# the response stage must be the final stage in the list
|
87
|
+
*stages, response_stage = @stages
|
88
|
+
|
89
|
+
stages.each do |stage|
|
90
|
+
result = stage.run
|
91
|
+
case result
|
92
|
+
when Response
|
93
|
+
controller.response = result
|
94
|
+
break
|
102
95
|
end
|
96
|
+
end
|
103
97
|
|
104
|
-
|
98
|
+
response_stage.run
|
105
99
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
end
|
100
|
+
payload[:response] = controller.response
|
101
|
+
controller.response.finish
|
102
|
+
rescue StandardError => e
|
103
|
+
@application.error_handler.handle!(request, e)
|
111
104
|
end
|
112
105
|
end
|
113
106
|
|
@@ -116,9 +109,8 @@ module Praxis
|
|
116
109
|
return unless Praxis::Blueprint.caching_enabled?
|
117
110
|
|
118
111
|
Praxis::Blueprint.cache = Hash.new do |hash, key|
|
119
|
-
hash[key] =
|
112
|
+
hash[key] = {}
|
120
113
|
end
|
121
114
|
end
|
122
|
-
|
123
115
|
end
|
124
116
|
end
|
@@ -1,9 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Praxis
|
2
4
|
module Docs
|
3
5
|
module OpenApi
|
4
6
|
class InfoObject
|
5
7
|
attr_reader :info, :version
|
6
|
-
|
8
|
+
|
9
|
+
def initialize(version:, api_definition_info:)
|
7
10
|
@version = version
|
8
11
|
@info = api_definition_info
|
9
12
|
raise "OpenApi docs require a 'Title' for your API." unless info.title
|
@@ -11,12 +14,12 @@ module Praxis
|
|
11
14
|
|
12
15
|
def dump
|
13
16
|
data = { version: version }
|
14
|
-
[
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
%i[
|
18
|
+
title
|
19
|
+
description
|
20
|
+
termsOfService
|
21
|
+
contact
|
22
|
+
license
|
20
23
|
].each do |attr|
|
21
24
|
val = info.send(attr)
|
22
25
|
data[attr] = val if val
|
@@ -27,7 +30,7 @@ module Praxis
|
|
27
30
|
if info.logo_url
|
28
31
|
data[:'x-logo'] = {
|
29
32
|
url: info.logo_url,
|
30
|
-
backgroundColor:
|
33
|
+
backgroundColor: '#FFFFFF',
|
31
34
|
altText: info.title
|
32
35
|
}
|
33
36
|
end
|
@@ -1,8 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Praxis
|
2
4
|
module Docs
|
3
5
|
module OpenApi
|
4
6
|
class MediaTypeObject
|
5
7
|
attr_reader :schema, :example
|
8
|
+
|
6
9
|
def initialize(schema:, example:)
|
7
10
|
@schema = schema
|
8
11
|
@example = example
|
@@ -10,26 +13,24 @@ module Praxis
|
|
10
13
|
|
11
14
|
def dump
|
12
15
|
{
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
+
schema: schema,
|
17
|
+
example: example
|
18
|
+
# encoding: TODO SUPPORT IT maybe be great/necessary for multipart
|
16
19
|
}
|
17
20
|
end
|
18
21
|
|
19
22
|
# Helper to create the typical content attribute for responses and request bodies
|
20
|
-
def self.create_content_attribute_helper(type
|
23
|
+
def self.create_content_attribute_helper(type:, example_payload:, example_handlers: nil)
|
21
24
|
# Will produce 1 example encoded with a given handler (and marking it with the given content type)
|
22
|
-
|
23
|
-
example_handlers = [ {'application/json' => 'json' } ]
|
24
|
-
end
|
25
|
+
example_handlers ||= [{ 'application/json' => 'json' }]
|
25
26
|
# NOTE2: we should just create a $ref here unless it's an anon mediatype...
|
26
|
-
return {} if type.is_a? SimpleMediaType
|
27
|
+
return {} if type.is_a? SimpleMediaType # NOTE: skip if it's a SimpleMediaType?? ... is that correct?
|
27
28
|
|
28
|
-
the_schema = if type.anonymous? || !
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
the_schema = if type.anonymous? || !(type < Praxis::MediaType) # Avoid referencing custom/simple Types? (i.e., just MTs)
|
30
|
+
SchemaObject.new(info: type).dump_schema
|
31
|
+
else
|
32
|
+
{ '$ref': "#/components/schemas/#{type.id}" }
|
33
|
+
end
|
33
34
|
|
34
35
|
if example_payload
|
35
36
|
examples_by_content_type = {}
|
@@ -40,16 +41,16 @@ module Praxis
|
|
40
41
|
handler = Praxis::Application.instance.handlers[handler_name]
|
41
42
|
# ReDoc is not happy to display json generated outputs when served as JSON...wtf?
|
42
43
|
generated = handler.generate(rendered_payload)
|
43
|
-
final =
|
44
|
+
final = handler_name == 'json' ? JSON.parse(generated) : generated
|
44
45
|
examples_by_content_type[content_type] = final
|
45
46
|
end
|
46
47
|
end
|
47
48
|
|
48
49
|
# Key string (of MT) , value MTObject
|
49
|
-
|
50
|
-
|
50
|
+
examples_by_content_type.transform_values do |example_hash|
|
51
|
+
MediaTypeObject.new(
|
51
52
|
schema: the_schema, # Every MT will have the same exact type..oh well .. maybe a REF?
|
52
|
-
example: example_hash
|
53
|
+
example: example_hash
|
53
54
|
).dump
|
54
55
|
end
|
55
56
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'parameter_object'
|
2
4
|
require_relative 'request_body_object'
|
3
5
|
require_relative 'responses_object'
|
@@ -8,6 +10,7 @@ module Praxis
|
|
8
10
|
class OperationObject
|
9
11
|
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operation-object
|
10
12
|
attr_reader :id, :url, :action, :tags
|
13
|
+
|
11
14
|
def initialize(id:, url:, action:, tags:)
|
12
15
|
@id = id
|
13
16
|
@url = url
|
@@ -21,9 +24,9 @@ module Praxis
|
|
21
24
|
h = {
|
22
25
|
summary: action.name.to_s,
|
23
26
|
description: action.description,
|
24
|
-
#externalDocs: {}, # TODO/FIXME
|
27
|
+
# externalDocs: {}, # TODO/FIXME
|
25
28
|
operationId: id,
|
26
|
-
responses: ResponsesObject.new(responses: action.responses).dump
|
29
|
+
responses: ResponsesObject.new(responses: action.responses).dump
|
27
30
|
# callbacks
|
28
31
|
# deprecated: false
|
29
32
|
# security: [{}]
|
@@ -31,7 +34,7 @@ module Praxis
|
|
31
34
|
}
|
32
35
|
h[:tags] = all_tags.uniq unless all_tags.empty?
|
33
36
|
h[:parameters] = all_parameters unless all_parameters.empty?
|
34
|
-
h[:requestBody] = RequestBodyObject.new(attribute: action.payload
|
37
|
+
h[:requestBody] = RequestBodyObject.new(attribute: action.payload).dump if action.payload
|
35
38
|
h
|
36
39
|
end
|
37
40
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'schema_object'
|
2
4
|
|
3
5
|
module Praxis
|
@@ -6,7 +8,8 @@ module Praxis
|
|
6
8
|
class ParameterObject
|
7
9
|
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#parameter-object
|
8
10
|
attr_reader :location, :name, :is_required, :info
|
9
|
-
|
11
|
+
|
12
|
+
def initialize(location:, name:, is_required:, info:)
|
10
13
|
@location = location
|
11
14
|
@name = name
|
12
15
|
@info = info
|
@@ -25,39 +28,39 @@ module Praxis
|
|
25
28
|
# style
|
26
29
|
# explode
|
27
30
|
# allowReserved
|
28
|
-
|
31
|
+
|
29
32
|
# Now merge the rest schema and example
|
30
33
|
# schema
|
31
34
|
# example
|
32
35
|
# examples (Example and Examples are mutually exclusive)
|
33
36
|
schema = SchemaObject.new(info: info)
|
34
37
|
h[:schema] = schema.dump_schema
|
35
|
-
#
|
38
|
+
# NOTE: we do not support the 'content' key...we always use schema
|
36
39
|
h[:example] = schema.dump_example
|
37
40
|
h
|
38
41
|
end
|
39
42
|
|
40
|
-
def self.process_parameters(
|
43
|
+
def self.process_parameters(action)
|
41
44
|
output = []
|
42
|
-
# An array, with one hash per param inside
|
45
|
+
# An array, with one hash per param inside
|
43
46
|
if action.headers
|
44
|
-
(action.headers.attributes||{}).each_with_object(output) do |(name, info), out|
|
45
|
-
out << ParameterObject.new(
|
47
|
+
(action.headers.attributes || {}).each_with_object(output) do |(name, info), out|
|
48
|
+
out << ParameterObject.new(location: 'header', name: name, is_required: info.options[:required], info: info).dump
|
46
49
|
end
|
47
50
|
end
|
48
|
-
|
51
|
+
|
49
52
|
if action.params
|
50
53
|
route_params = \
|
51
|
-
|
54
|
+
if action.route
|
55
|
+
action.route.path.named_captures.keys.collect(&:to_sym)
|
56
|
+
else
|
52
57
|
warn "Warning: No routes defined for action #{action.name}"
|
53
58
|
[]
|
54
|
-
else
|
55
|
-
action.route.path.named_captures.keys.collect(&:to_sym)
|
56
59
|
end
|
57
|
-
(action.params.attributes||{}).each_with_object(output) do |(name, info), out|
|
60
|
+
(action.params.attributes || {}).each_with_object(output) do |(name, info), out|
|
58
61
|
in_type = route_params.include?(name) ? :path : :query
|
59
|
-
is_required =
|
60
|
-
out << ParameterObject.new(
|
62
|
+
is_required = in_type == :path ? true : info.options[:required]
|
63
|
+
out << ParameterObject.new(location: in_type, name: name, is_required: is_required, info: info).dump
|
61
64
|
end
|
62
65
|
end
|
63
66
|
|
@@ -1,10 +1,13 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'operation_object'
|
2
4
|
module Praxis
|
3
5
|
module Docs
|
4
6
|
module OpenApi
|
5
7
|
class PathsObject
|
6
8
|
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#paths-object
|
7
9
|
attr_reader :resources, :paths
|
10
|
+
|
8
11
|
def initialize(resources:)
|
9
12
|
@resources = resources
|
10
13
|
# A hash with keys of paths, and values of hash
|
@@ -15,18 +18,17 @@ module Praxis
|
|
15
18
|
# "post": { ...}
|
16
19
|
# "/humans": {
|
17
20
|
# "get": {...},
|
18
|
-
@paths = Hash.new {|h,k| h[k] = {} }
|
21
|
+
@paths = Hash.new { |h, k| h[k] = {} }
|
19
22
|
end
|
20
23
|
|
21
|
-
|
22
24
|
def dump
|
23
25
|
resources.each do |resource|
|
24
|
-
compute_resource_paths(
|
26
|
+
compute_resource_paths(resource)
|
25
27
|
end
|
26
28
|
paths
|
27
29
|
end
|
28
30
|
|
29
|
-
def compute_resource_paths(
|
31
|
+
def compute_resource_paths(resource)
|
30
32
|
id = resource.id
|
31
33
|
# fill in the paths hash with a key for each path for each action/route
|
32
34
|
resource.actions.each do |action_name, action|
|
@@ -38,15 +40,15 @@ module Praxis
|
|
38
40
|
path_entry = paths[templetized_path]
|
39
41
|
# Let's fill in verb stuff within the working hash
|
40
42
|
raise "VERB #{_verb} already defined for #{id}!?!?!" if path_entry[verb]
|
41
|
-
|
43
|
+
|
42
44
|
action_uid = "action-#{action_name}-#{id}"
|
43
45
|
# Add a tag matching the resource name (hoping all actions of a resource are grouped)
|
44
46
|
action_tags = [resource.display_name]
|
45
|
-
path_entry[verb] = OperationObject.new(
|
47
|
+
path_entry[verb] = OperationObject.new(id: action_uid, url: url, action: action, tags: action_tags).dump
|
46
48
|
end
|
47
|
-
# For each path, we can further annotate with
|
49
|
+
# For each path, we can further annotate with
|
48
50
|
# servers
|
49
|
-
# parameters
|
51
|
+
# parameters
|
50
52
|
# But we don't have that concept in praxis
|
51
53
|
end
|
52
54
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'schema_object'
|
2
4
|
|
3
5
|
module Praxis
|
@@ -5,7 +7,8 @@ module Praxis
|
|
5
7
|
module OpenApi
|
6
8
|
class RequestBodyObject
|
7
9
|
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#request-body-object
|
8
|
-
attr_reader :attribute
|
10
|
+
attr_reader :attribute
|
11
|
+
|
9
12
|
def initialize(attribute:)
|
10
13
|
@attribute = attribute
|
11
14
|
end
|
@@ -18,18 +21,18 @@ module Praxis
|
|
18
21
|
# OpenApi wants a set of bodies per MediaType/Content-Type
|
19
22
|
# For us there's really only one schema (regardless of encoding)...
|
20
23
|
# so we'll show all the supported MTs...but repeating the schema
|
21
|
-
#dumped_schema = SchemaObject.new(info: attribute).dump_schema
|
24
|
+
# dumped_schema = SchemaObject.new(info: attribute).dump_schema
|
22
25
|
|
23
26
|
example_handlers = if attribute.type < Praxis::Types::MultipartArray
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
h[:content] = MediaTypeObject.create_content_attribute_helper(type: attribute.type,
|
32
|
-
example_payload: attribute.example(nil),
|
27
|
+
ident = MediaTypeIdentifier.load('multipart/form-data')
|
28
|
+
[{ ident.to_s => 'plain' }] # Multipart content type, but with the plain renderer (so there's no modification)
|
29
|
+
else
|
30
|
+
# TODO: We could run it through other handlers I guess...if they're registered
|
31
|
+
[{ 'application/json' => 'json' }]
|
32
|
+
end
|
33
|
+
|
34
|
+
h[:content] = MediaTypeObject.create_content_attribute_helper(type: attribute.type,
|
35
|
+
example_payload: attribute.example(nil),
|
33
36
|
example_handlers: example_handlers)
|
34
37
|
# # Key string (of MT) , value MTObject
|
35
38
|
# content_hash = info[:examples].each_with_object({}) do |(handler, example_hash),accum|
|
@@ -43,8 +46,6 @@ module Praxis
|
|
43
46
|
# h[:content] = content_hash
|
44
47
|
h
|
45
48
|
end
|
46
|
-
|
47
|
-
|
48
49
|
end
|
49
50
|
end
|
50
51
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'media_type_object'
|
2
4
|
|
3
5
|
module Praxis
|
@@ -6,33 +8,39 @@ module Praxis
|
|
6
8
|
class ResponseObject
|
7
9
|
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#response-object
|
8
10
|
attr_reader :info
|
11
|
+
|
9
12
|
def initialize(info:)
|
10
13
|
@info = info
|
11
14
|
default_handlers = ApiDefinition.instance.info.produces
|
12
|
-
@output_handlers = Praxis::Application.instance.handlers.select do |k,
|
15
|
+
@output_handlers = Praxis::Application.instance.handlers.select do |k, _v|
|
13
16
|
default_handlers.include?(k)
|
14
17
|
end
|
15
18
|
end
|
16
19
|
|
17
|
-
def dump_response_headers_object(
|
18
|
-
headers.each_with_object({}) do |(name,data),accum|
|
19
|
-
# each header comes from Praxis::ResponseDefinition
|
20
|
+
def dump_response_headers_object(headers)
|
21
|
+
headers.each_with_object({}) do |(name, data), accum|
|
22
|
+
# each header comes from Praxis::ResponseDefinition
|
20
23
|
# the keys are the header names, and value can be:
|
21
24
|
# "true" => means it only needs to exist
|
22
25
|
# String => which means that it has to fully match
|
23
26
|
# Regex => which means it has to regexp match it
|
24
27
|
|
25
28
|
# Get the schema from the type (defaulting to string in case the type doesn't have the as_json_schema defined)
|
26
|
-
schema =
|
29
|
+
schema = begin
|
30
|
+
data[:attribute].type.as_json_schema
|
31
|
+
rescue StandardError
|
32
|
+
{ type: :string }
|
33
|
+
end
|
27
34
|
hash = { description: data[:description] || '', schema: schema }
|
28
|
-
# Note, our Headers in response definition are not full types...they're basically only
|
35
|
+
# Note, our Headers in response definition are not full types...they're basically only
|
29
36
|
# strings, which can either match anything, match the exact word or match a regex
|
30
37
|
# they don't even have a description...
|
31
38
|
data_value = data[:value]
|
32
|
-
|
39
|
+
case data_value
|
40
|
+
when String
|
33
41
|
hash[:pattern] = "^#{data_value}$" # Exact String match
|
34
|
-
|
35
|
-
sanitized_pattern = data_value.inspect[1..-2] #inspect returns enclosing '/' characters
|
42
|
+
when Regexp
|
43
|
+
sanitized_pattern = data_value.inspect[1..-2] # inspect returns enclosing '/' characters
|
36
44
|
hash[:pattern] = sanitized_pattern
|
37
45
|
end
|
38
46
|
|
@@ -41,10 +49,10 @@ module Praxis
|
|
41
49
|
end
|
42
50
|
|
43
51
|
def dump
|
44
|
-
data = {
|
52
|
+
data = {
|
45
53
|
description: info.description || ''
|
46
54
|
}
|
47
|
-
if headers_object = dump_response_headers_object(
|
55
|
+
if (headers_object = dump_response_headers_object(info.headers))
|
48
56
|
data[:headers] = headers_object
|
49
57
|
end
|
50
58
|
|
@@ -52,12 +60,13 @@ module Praxis
|
|
52
60
|
|
53
61
|
identifier = MediaTypeIdentifier.load(info.media_type.identifier)
|
54
62
|
example_handlers = @output_handlers.each_with_object([]) do |(name, _handler), accum|
|
55
|
-
accum.push({ (identifier + name).to_s => name})
|
63
|
+
accum.push({ (identifier + name).to_s => name })
|
56
64
|
end
|
57
65
|
data[:content] = MediaTypeObject.create_content_attribute_helper(
|
58
|
-
|
59
|
-
|
60
|
-
|
66
|
+
type: info.media_type,
|
67
|
+
example_payload: info.example(nil),
|
68
|
+
example_handlers: example_handlers
|
69
|
+
)
|
61
70
|
end
|
62
71
|
|
63
72
|
# if payload = info[:payload]
|
@@ -66,12 +75,9 @@ module Praxis
|
|
66
75
|
# data[:schema] = {"$ref" => "#/definitions/#{body_type}" }
|
67
76
|
# end
|
68
77
|
|
69
|
-
|
70
78
|
# TODO: we do not support 'links'
|
71
79
|
data
|
72
80
|
end
|
73
|
-
|
74
|
-
|
75
81
|
end
|
76
82
|
end
|
77
83
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'response_object'
|
2
4
|
|
3
5
|
module Praxis
|
@@ -6,11 +8,11 @@ module Praxis
|
|
6
8
|
class ResponsesObject
|
7
9
|
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responses-object
|
8
10
|
attr_reader :responses
|
11
|
+
|
9
12
|
def initialize(responses:)
|
10
13
|
@responses = responses
|
11
14
|
end
|
12
15
|
|
13
|
-
|
14
16
|
def dump
|
15
17
|
# {
|
16
18
|
# "200": {
|
@@ -1,11 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Praxis
|
2
4
|
module Docs
|
3
5
|
module OpenApi
|
4
6
|
class SchemaObject
|
5
7
|
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schema-object
|
6
8
|
attr_reader :type, :attribute
|
9
|
+
|
7
10
|
def initialize(info:)
|
8
|
-
#info could be an attribute ... or a type?
|
11
|
+
# info could be an attribute ... or a type?
|
9
12
|
if info.is_a? Attributor::Attribute
|
10
13
|
@attribute = info
|
11
14
|
else
|
@@ -29,7 +32,7 @@ module Praxis
|
|
29
32
|
else
|
30
33
|
type.as_json_schema(shallow: true, example: nil)
|
31
34
|
end
|
32
|
-
# # TODO: FIXME: return a generic object type if the passed info was weird.
|
35
|
+
# # TODO: FIXME: return a generic object type if the passed info was weird.
|
33
36
|
# return { type: :object } unless info
|
34
37
|
|
35
38
|
# h = {
|
@@ -41,7 +44,7 @@ module Praxis
|
|
41
44
|
# h[:default] = info[:default] if info[:default]
|
42
45
|
# h[:pattern] = info[:regexp] if info[:regexp]
|
43
46
|
# # TODO: there are other possible things we can do..maximum, minimum...etc
|
44
|
-
|
47
|
+
|
45
48
|
# if h[:type] == :array
|
46
49
|
# # FIXME: ... hack it for MultiPart arrays...where there's no member attr
|
47
50
|
# member_type = info[:type][:member_attribute]
|
@@ -51,27 +54,25 @@ module Praxis
|
|
51
54
|
# h[:items] = SchemaObject.new(info: member_type ).dump_schema
|
52
55
|
# end
|
53
56
|
# h
|
54
|
-
rescue => e
|
57
|
+
rescue StandardError => e
|
55
58
|
puts "Error dumping schema #{e}"
|
56
59
|
end
|
57
|
-
|
58
|
-
def convert_family_to_json_type(
|
60
|
+
|
61
|
+
def convert_family_to_json_type(praxis_type)
|
59
62
|
case praxis_type[:family].to_sym
|
60
63
|
when :string
|
61
64
|
:string
|
62
65
|
when :hash
|
63
66
|
:object
|
64
|
-
when :array #Warning! Multipart types are arrays!
|
67
|
+
when :array # Warning! Multipart types are arrays!
|
65
68
|
:array
|
66
69
|
when :numeric
|
67
|
-
|
68
|
-
|
69
|
-
:integer
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
:number
|
74
|
-
end
|
70
|
+
jtypes = {
|
71
|
+
'Attributor-Integer' => :integer,
|
72
|
+
'Attributor-BigDecimal' => :integer,
|
73
|
+
'Attributor-Float' => :number
|
74
|
+
}
|
75
|
+
jtypes[praxis_type[:id]]
|
75
76
|
when :temporal
|
76
77
|
:string
|
77
78
|
when :boolean
|
@@ -80,7 +81,6 @@ module Praxis
|
|
80
81
|
raise "Unknown praxis family type: #{praxis_type[:family]}"
|
81
82
|
end
|
82
83
|
end
|
83
|
-
|
84
84
|
end
|
85
85
|
end
|
86
86
|
end
|