praxis 2.0.pre.17 → 2.0.pre.21

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.
Files changed (235) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +54 -0
  3. data/.simplecov +3 -1
  4. data/.travis.yml +2 -1
  5. data/CHANGELOG.md +19 -0
  6. data/CONTRIBUTING.md +2 -79
  7. data/Gemfile +5 -1
  8. data/Guardfile +6 -4
  9. data/LICENSE +0 -2
  10. data/MAINTAINERS.md +1 -0
  11. data/README.md +15 -22
  12. data/Rakefile +4 -2
  13. data/bin/praxis +55 -58
  14. data/lib/praxis/action_definition/headers_dsl_compiler.rb +5 -6
  15. data/lib/praxis/action_definition.rb +65 -95
  16. data/lib/praxis/api_definition.rb +21 -29
  17. data/lib/praxis/api_general_info.rb +55 -66
  18. data/lib/praxis/application.rb +15 -32
  19. data/lib/praxis/blueprint.rb +80 -73
  20. data/lib/praxis/bootloader.rb +24 -33
  21. data/lib/praxis/bootloader_stages/environment.rb +5 -10
  22. data/lib/praxis/bootloader_stages/file_loader.rb +3 -6
  23. data/lib/praxis/bootloader_stages/plugin_config_load.rb +4 -6
  24. data/lib/praxis/bootloader_stages/plugin_config_prepare.rb +2 -2
  25. data/lib/praxis/bootloader_stages/plugin_loader.rb +3 -7
  26. data/lib/praxis/bootloader_stages/plugin_setup.rb +3 -3
  27. data/lib/praxis/bootloader_stages/routing.rb +5 -8
  28. data/lib/praxis/bootloader_stages/subgroup_loader.rb +2 -10
  29. data/lib/praxis/bootloader_stages/warn_unloaded_files.rb +15 -19
  30. data/lib/praxis/callbacks.rb +12 -11
  31. data/lib/praxis/collection.rb +11 -14
  32. data/lib/praxis/config.rb +17 -28
  33. data/lib/praxis/config_hash.rb +2 -1
  34. data/lib/praxis/controller.rb +7 -6
  35. data/lib/praxis/dispatcher.rb +34 -42
  36. data/lib/praxis/docs/open_api/info_object.rb +11 -8
  37. data/lib/praxis/docs/open_api/media_type_object.rb +18 -17
  38. data/lib/praxis/docs/open_api/operation_object.rb +7 -4
  39. data/lib/praxis/docs/open_api/parameter_object.rb +17 -14
  40. data/lib/praxis/docs/open_api/paths_object.rb +11 -9
  41. data/lib/praxis/docs/open_api/request_body_object.rb +14 -13
  42. data/lib/praxis/docs/open_api/response_object.rb +24 -18
  43. data/lib/praxis/docs/open_api/responses_object.rb +3 -1
  44. data/lib/praxis/docs/open_api/schema_object.rb +61 -29
  45. data/lib/praxis/docs/open_api/server_object.rb +5 -2
  46. data/lib/praxis/docs/open_api/tag_object.rb +9 -6
  47. data/lib/praxis/docs/open_api_generator.rb +114 -150
  48. data/lib/praxis/endpoint_definition.rb +60 -77
  49. data/lib/praxis/error_handler.rb +2 -2
  50. data/lib/praxis/exception.rb +2 -0
  51. data/lib/praxis/exceptions/config.rb +3 -1
  52. data/lib/praxis/exceptions/config_load.rb +2 -0
  53. data/lib/praxis/exceptions/config_validation.rb +3 -1
  54. data/lib/praxis/exceptions/invalid_configuration.rb +3 -1
  55. data/lib/praxis/exceptions/invalid_response.rb +3 -1
  56. data/lib/praxis/exceptions/invalid_trait.rb +3 -1
  57. data/lib/praxis/exceptions/stage_not_found.rb +3 -1
  58. data/lib/praxis/exceptions/validation.rb +4 -3
  59. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +163 -149
  60. data/lib/praxis/extensions/attribute_filtering/active_record_patches/5x.rb +18 -13
  61. data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_0.rb +13 -9
  62. data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_1_plus.rb +14 -11
  63. data/lib/praxis/extensions/attribute_filtering/active_record_patches.rb +12 -9
  64. data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +8 -5
  65. data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +89 -65
  66. data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +68 -62
  67. data/lib/praxis/extensions/attribute_filtering.rb +3 -1
  68. data/lib/praxis/extensions/field_expansion.rb +6 -4
  69. data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +10 -8
  70. data/lib/praxis/extensions/field_selection/field_selector.rb +91 -92
  71. data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +12 -12
  72. data/lib/praxis/extensions/field_selection.rb +3 -1
  73. data/lib/praxis/extensions/pagination/active_record_pagination_handler.rb +6 -4
  74. data/lib/praxis/extensions/pagination/header_generator.rb +16 -11
  75. data/lib/praxis/extensions/pagination/ordering_params.rb +29 -28
  76. data/lib/praxis/extensions/pagination/pagination_handler.rb +44 -42
  77. data/lib/praxis/extensions/pagination/pagination_params.rb +29 -48
  78. data/lib/praxis/extensions/pagination/sequel_pagination_handler.rb +8 -7
  79. data/lib/praxis/extensions/pagination.rb +10 -15
  80. data/lib/praxis/extensions/rails_compat/request_methods.rb +3 -4
  81. data/lib/praxis/extensions/rails_compat.rb +2 -0
  82. data/lib/praxis/extensions/rendering.rb +12 -12
  83. data/lib/praxis/field_expander.rb +8 -9
  84. data/lib/praxis/file_group.rb +8 -12
  85. data/lib/praxis/finalizable.rb +1 -0
  86. data/lib/praxis/handlers/json.rb +5 -2
  87. data/lib/praxis/handlers/plain.rb +2 -1
  88. data/lib/praxis/handlers/www_form.rb +6 -3
  89. data/lib/praxis/handlers/{xml-sample.rb → xml_sample.rb} +26 -22
  90. data/lib/praxis/mapper/active_model_compat.rb +13 -10
  91. data/lib/praxis/mapper/resource.rb +196 -181
  92. data/lib/praxis/mapper/selector_generator.rb +106 -112
  93. data/lib/praxis/mapper/sequel_compat.rb +70 -67
  94. data/lib/praxis/media_type.rb +2 -2
  95. data/lib/praxis/media_type_identifier.rb +26 -22
  96. data/lib/praxis/middleware_app.rb +18 -15
  97. data/lib/praxis/multipart/parser.rb +46 -51
  98. data/lib/praxis/multipart/part.rb +78 -110
  99. data/lib/praxis/notifications.rb +2 -4
  100. data/lib/praxis/plugin.rb +11 -18
  101. data/lib/praxis/plugin_concern.rb +12 -15
  102. data/lib/praxis/plugins/mapper_plugin.rb +15 -13
  103. data/lib/praxis/plugins/pagination_plugin.rb +8 -6
  104. data/lib/praxis/plugins/rails_plugin.rb +33 -28
  105. data/lib/praxis/renderer.rb +11 -15
  106. data/lib/praxis/request.rb +48 -44
  107. data/lib/praxis/request_stages/action.rb +4 -6
  108. data/lib/praxis/request_stages/load_request.rb +2 -4
  109. data/lib/praxis/request_stages/request_stage.rb +19 -23
  110. data/lib/praxis/request_stages/response.rb +4 -6
  111. data/lib/praxis/request_stages/validate.rb +3 -5
  112. data/lib/praxis/request_stages/validate_params_and_headers.rb +15 -22
  113. data/lib/praxis/request_stages/validate_payload.rb +25 -28
  114. data/lib/praxis/request_superclassing.rb +3 -3
  115. data/lib/praxis/resource_definition.rb +1 -0
  116. data/lib/praxis/response.rb +24 -26
  117. data/lib/praxis/response_definition.rb +77 -122
  118. data/lib/praxis/response_template.rb +11 -15
  119. data/lib/praxis/responses/http.rb +23 -44
  120. data/lib/praxis/responses/internal_server_error.rb +18 -21
  121. data/lib/praxis/responses/multipart_ok.rb +4 -9
  122. data/lib/praxis/responses/validation_error.rb +8 -15
  123. data/lib/praxis/route.rb +8 -10
  124. data/lib/praxis/router/rack.rb +13 -7
  125. data/lib/praxis/router/simple.rb +10 -5
  126. data/lib/praxis/router.rb +27 -34
  127. data/lib/praxis/routing_config.rb +52 -29
  128. data/lib/praxis/simple_media_type.rb +5 -8
  129. data/lib/praxis/stage.rb +17 -25
  130. data/lib/praxis/tasks/api_docs.rb +17 -16
  131. data/lib/praxis/tasks/console.rb +3 -1
  132. data/lib/praxis/tasks/environment.rb +2 -0
  133. data/lib/praxis/tasks/routes.rb +26 -24
  134. data/lib/praxis/tasks.rb +3 -1
  135. data/lib/praxis/trait.rb +37 -46
  136. data/lib/praxis/types/fuzzy_hash.rb +13 -14
  137. data/lib/praxis/types/media_type_common.rb +11 -10
  138. data/lib/praxis/types/multipart_array/part_definition.rb +14 -17
  139. data/lib/praxis/types/multipart_array.rb +100 -115
  140. data/lib/praxis/validation_handler.rb +5 -3
  141. data/lib/praxis/version.rb +3 -1
  142. data/lib/praxis.rb +4 -5
  143. data/praxis.gemspec +22 -21
  144. data/spec/functional_spec.rb +44 -56
  145. data/spec/praxis/action_definition_spec.rb +39 -48
  146. data/spec/praxis/api_definition_spec.rb +45 -47
  147. data/spec/praxis/api_general_info_spec.rb +28 -29
  148. data/spec/praxis/application_spec.rb +18 -14
  149. data/spec/praxis/blueprint_spec.rb +33 -34
  150. data/spec/praxis/bootloader_spec.rb +32 -30
  151. data/spec/praxis/callbacks_spec.rb +37 -37
  152. data/spec/praxis/collection_spec.rb +18 -25
  153. data/spec/praxis/config_hash_spec.rb +5 -4
  154. data/spec/praxis/config_spec.rb +27 -26
  155. data/spec/praxis/controller_spec.rb +8 -9
  156. data/spec/praxis/endpoint_definition_spec.rb +25 -32
  157. data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +171 -114
  158. data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +22 -21
  159. data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +112 -60
  160. data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +37 -38
  161. data/spec/praxis/extensions/field_expansion_spec.rb +8 -10
  162. data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +14 -13
  163. data/spec/praxis/extensions/field_selection/field_selector_spec.rb +9 -16
  164. data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +50 -49
  165. data/spec/praxis/extensions/pagination/active_record_pagination_handler_spec.rb +32 -31
  166. data/spec/praxis/extensions/rendering_spec.rb +9 -9
  167. data/spec/praxis/extensions/support/spec_resources_active_model.rb +32 -49
  168. data/spec/praxis/extensions/support/spec_resources_sequel.rb +48 -48
  169. data/spec/praxis/field_expander_spec.rb +6 -5
  170. data/spec/praxis/file_group_spec.rb +3 -1
  171. data/spec/praxis/handlers/json_spec.rb +6 -5
  172. data/spec/praxis/mapper/resource_spec.rb +39 -29
  173. data/spec/praxis/mapper/selector_generator_spec.rb +80 -46
  174. data/spec/praxis/media_type_identifier_spec.rb +13 -10
  175. data/spec/praxis/media_type_spec.rb +12 -12
  176. data/spec/praxis/middleware_app_spec.rb +23 -22
  177. data/spec/praxis/multipart/parser_spec.rb +7 -9
  178. data/spec/praxis/notifications_spec.rb +4 -4
  179. data/spec/praxis/plugin_concern_spec.rb +5 -6
  180. data/spec/praxis/renderer_spec.rb +10 -9
  181. data/spec/praxis/request_spec.rb +38 -41
  182. data/spec/praxis/request_stages/action_spec.rb +14 -15
  183. data/spec/praxis/request_stages/request_stage_spec.rb +30 -41
  184. data/spec/praxis/request_stages/validate_spec.rb +3 -1
  185. data/spec/praxis/response_definition_spec.rb +79 -92
  186. data/spec/praxis/response_spec.rb +35 -40
  187. data/spec/praxis/responses/internal_server_error_spec.rb +6 -9
  188. data/spec/praxis/responses/validation_error_spec.rb +17 -18
  189. data/spec/praxis/route_spec.rb +4 -7
  190. data/spec/praxis/router_spec.rb +69 -79
  191. data/spec/praxis/routing_config_spec.rb +15 -14
  192. data/spec/praxis/stage_spec.rb +56 -53
  193. data/spec/praxis/trait_spec.rb +17 -17
  194. data/spec/praxis/types/fuzzy_hash_spec.rb +11 -9
  195. data/spec/praxis/types/multipart_array/part_definition_spec.rb +3 -2
  196. data/spec/praxis/types/multipart_array_spec.rb +33 -48
  197. data/spec/spec_app/app/concerns/authenticated.rb +5 -5
  198. data/spec/spec_app/app/concerns/basic_api.rb +3 -1
  199. data/spec/spec_app/app/concerns/log_wrapper.rb +5 -3
  200. data/spec/spec_app/app/controllers/base_class.rb +6 -5
  201. data/spec/spec_app/app/controllers/instances.rb +31 -34
  202. data/spec/spec_app/app/controllers/volumes.rb +6 -6
  203. data/spec/spec_app/app/responses/multipart.rb +1 -2
  204. data/spec/spec_app/app/responses/other_response.rb +2 -2
  205. data/spec/spec_app/config/environment.rb +19 -6
  206. data/spec/spec_app/config.ru +4 -3
  207. data/spec/spec_app/design/api.rb +13 -15
  208. data/spec/spec_app/design/media_types/instance.rb +6 -6
  209. data/spec/spec_app/design/media_types/volume.rb +2 -1
  210. data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -1
  211. data/spec/spec_app/design/resources/instances.rb +11 -17
  212. data/spec/spec_app/design/resources/volume_snapshots.rb +4 -5
  213. data/spec/spec_app/design/resources/volumes.rb +4 -5
  214. data/spec/spec_helper.rb +11 -13
  215. data/spec/support/be_deep_equal_matcher.rb +5 -0
  216. data/spec/support/spec_authorization_plugin.rb +7 -12
  217. data/spec/support/spec_blueprints.rb +5 -4
  218. data/spec/support/spec_complex_authentication_plugin.rb +17 -34
  219. data/spec/support/spec_endpoint_definitions.rb +2 -3
  220. data/spec/support/spec_media_types.rb +28 -35
  221. data/spec/support/spec_resources.rb +22 -16
  222. data/spec/support/spec_simple_authentication_plugin.rb +5 -9
  223. data/tasks/loader.thor +4 -2
  224. data/tasks/thor/app.rb +7 -5
  225. data/tasks/thor/example.rb +23 -22
  226. data/tasks/thor/model.rb +7 -7
  227. data/tasks/thor/scaffold.rb +23 -23
  228. data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +0 -8
  229. data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +1 -2
  230. metadata +72 -84
  231. data/MAINTAINERS +0 -2
  232. data/TODO.md +0 -25
  233. data/spec/praxis/api_resource_spec.rb +0 -0
  234. data/spec/praxis/dispatcher_spec.rb +0 -0
  235. data/spec/spec_app/app/responses/bulk_response.rb +0 -0
@@ -1,9 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  module RequestStages
3
-
4
5
  class Validate < RequestStage
5
-
6
- def initialize(name, context,**opts)
6
+ def initialize(name, context, **opts)
7
7
  super
8
8
  # Add our sub-stages
9
9
  @stages = [
@@ -11,8 +11,6 @@ module Praxis
11
11
  RequestStages::ValidatePayload.new(:payload, context, parent: self)
12
12
  ]
13
13
  end
14
-
15
14
  end
16
-
17
15
  end
18
16
  end
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  module RequestStages
3
-
4
5
  class ValidateParamsAndHeaders < RequestStage
5
6
  attr_reader :parent
6
7
 
@@ -10,14 +11,14 @@ module Praxis
10
11
  end
11
12
 
12
13
  def path
13
- @_path ||= ( @parent.path + [name] )
14
+ @path ||= (@parent.path + [name])
14
15
  end
15
16
 
16
17
  def execute
17
18
  begin
18
19
  request.load_headers(CONTEXT_FOR[:headers])
19
- rescue => e
20
- message = "Error loading headers."
20
+ rescue StandardError => e
21
+ message = 'Error loading headers.'
21
22
  return validation_handler.handle!(
22
23
  exception: e,
23
24
  summary: message,
@@ -29,7 +30,7 @@ module Praxis
29
30
  begin
30
31
  request.load_params(CONTEXT_FOR[:params])
31
32
  rescue Attributor::AttributorException => e
32
- message = "Error loading params."
33
+ message = 'Error loading params.'
33
34
  return validation_handler.handle!(
34
35
  exception: e,
35
36
  summary: message,
@@ -38,26 +39,18 @@ module Praxis
38
39
  )
39
40
  end
40
41
 
41
- attribute_resolver = Attributor::AttributeResolver.new
42
- Attributor::AttributeResolver.current = attribute_resolver
43
-
44
- attribute_resolver.register("headers",request.headers)
45
- attribute_resolver.register("params",request.params)
46
-
47
42
  errors = request.validate_headers(CONTEXT_FOR[:headers])
48
43
  errors += request.validate_params(CONTEXT_FOR[:params])
49
- if errors.any?
50
- message = "Error validating request data."
51
- return validation_handler.handle!(
52
- summary: message,
53
- errors: errors,
54
- request: request,
55
- stage: name
56
- )
57
- end
44
+ return unless errors.any?
45
+
46
+ message = 'Error validating request data.'
47
+ validation_handler.handle!(
48
+ summary: message,
49
+ errors: errors,
50
+ request: request,
51
+ stage: name
52
+ )
58
53
  end
59
-
60
54
  end
61
-
62
55
  end
63
56
  end
@@ -1,8 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  module RequestStages
3
-
4
5
  class ValidatePayload < RequestStage
5
-
6
6
  attr_reader :parent
7
7
 
8
8
  def initialize(name, context, parent:)
@@ -11,37 +11,34 @@ module Praxis
11
11
  end
12
12
 
13
13
  def path
14
- @_path ||= ( @parent.path + [name] )
14
+ @path ||= (@parent.path + [name])
15
15
  end
16
16
 
17
17
  def execute
18
- if request.action.payload
19
- begin
20
- request.load_payload(CONTEXT_FOR[:payload])
21
- rescue => e
22
- message = "Error loading payload. Used Content-Type: '#{request.content_type}'"
23
- return validation_handler.handle!(
24
- exception: e,
25
- summary: message,
26
- request: request,
27
- stage: name
28
- )
29
- end
30
- Attributor::AttributeResolver.current.register("payload",request.payload)
31
-
32
- errors = request.validate_payload(CONTEXT_FOR[:payload])
33
- if errors.any?
34
- return validation_handler.handle!(
35
- summary: "Errors validating payload data",
36
- errors: errors,
37
- request: request,
38
- stage: name
39
- )
40
- end
18
+ return unless request.action.payload
19
+
20
+ begin
21
+ request.load_payload(CONTEXT_FOR[:payload])
22
+ rescue StandardError => e
23
+ message = "Error loading payload. Used Content-Type: '#{request.content_type}'"
24
+ return validation_handler.handle!(
25
+ exception: e,
26
+ summary: message,
27
+ request: request,
28
+ stage: name
29
+ )
41
30
  end
42
- end
43
31
 
44
- end
32
+ errors = request.validate_payload(CONTEXT_FOR[:payload])
33
+ return unless errors.any?
45
34
 
35
+ validation_handler.handle!(
36
+ summary: 'Errors validating payload data',
37
+ errors: errors,
38
+ request: request,
39
+ stage: name
40
+ )
41
+ end
42
+ end
46
43
  end
47
44
  end
@@ -1,11 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  class << self
3
-
4
5
  attr_writer :request_superclass
5
6
 
6
7
  def request_superclass
7
8
  @request_superclass || Object
8
9
  end
9
-
10
10
  end
11
- end
11
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  raise "ResourceDefinition class has changed name in this version of Praxis.\n" \
3
4
  'Please blanket-replace all uses of ResourceDefinition with EndpointDefinition instead.'
@@ -1,31 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  class Response
3
- attr_reader :name
4
- attr_reader :parts
5
+ attr_reader :name, :parts
5
6
 
6
- attr_accessor :status
7
- attr_accessor :headers
8
- attr_accessor :body
9
- attr_accessor :request
7
+ attr_accessor :status, :headers, :body, :request
10
8
 
11
9
  class << self
12
- attr_accessor :response_name
13
- attr_accessor :status
10
+ attr_accessor :response_name, :status
14
11
  end
15
12
 
16
13
  def self.inherited(klass)
17
14
  klass.response_name = klass.name.demodulize.underscore.to_sym
18
- klass.status = self.status if self.status
15
+ klass.status = status if status
19
16
  end
20
17
 
21
- def initialize(status:self.class.status, headers:{}, body: nil, location: nil)
18
+ def initialize(status: self.class.status, headers: {}, body: nil, location: nil)
22
19
  @name = response_name
23
20
  @status = status
24
21
  @headers = headers
25
22
  @body = body
26
23
  @headers['Location'] = location if location
27
24
  @form_data = nil
28
- @parts = Hash.new
25
+ @parts = {}
29
26
  end
30
27
 
31
28
  # Determine the content type of this response.
@@ -44,10 +41,9 @@ module Praxis
44
41
  headers['Content-Type'] = MediaTypeIdentifier.load(identifier).to_s
45
42
  end
46
43
 
47
- def handle
48
- end
44
+ def handle; end
49
45
 
50
- def add_part(name=nil, part)
46
+ def add_part(part, name = nil)
51
47
  @form_data ||= begin
52
48
  form = MIME::Multipart::FormData.new
53
49
  @headers.merge! form.headers.headers
@@ -63,8 +59,7 @@ module Praxis
63
59
  self.class.response_name
64
60
  end
65
61
 
66
- def format!
67
- end
62
+ def format!; end
68
63
 
69
64
  def encode!
70
65
  case @body
@@ -85,11 +80,7 @@ module Praxis
85
80
  @body = Array(@body)
86
81
 
87
82
  if @form_data
88
- if @body.any?
89
- unless @body.last =~ /\n$/
90
- @body << "\r\n"
91
- end
92
- end
83
+ @body << "\r\n" if @body.any? && @body.last !~ /\n$/
93
84
 
94
85
  @parts.each do |name, part|
95
86
  part.encode!
@@ -108,7 +99,6 @@ module Praxis
108
99
  [@status, @headers, @body]
109
100
  end
110
101
 
111
-
112
102
  # Validates the response
113
103
  #
114
104
  # @param [Object] action
@@ -118,12 +108,20 @@ module Praxis
118
108
 
119
109
  unless (response_definition = action.responses[response_name])
120
110
  raise Exceptions::Validation, "Attempting to return a response with name #{response_name} " \
121
- "but no response definition with that name can be found"
111
+ 'but no response definition with that name can be found'
122
112
  end
123
-
124
113
  response_definition.validate(self, validate_body: validate_body)
114
+ rescue Exceptions::Validation => e
115
+ ve = Application.instance.validation_handler.handle!(
116
+ summary: 'Error validating response',
117
+ exception: e,
118
+ request: request,
119
+ stage: 'response',
120
+ errors: e.errors
121
+ )
122
+ body = ve.format!
123
+
124
+ Responses::InternalServerError.new(body: body)
125
125
  end
126
-
127
-
128
126
  end
129
127
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Praxis
3
4
  # Response spec DSL container
@@ -6,57 +7,46 @@ module Praxis
6
7
  attr_reader :name
7
8
 
8
9
  def initialize(response_name, **spec, &block)
9
- unless response_name
10
- raise Exceptions::InvalidConfiguration.new(
11
- "Response name is required for a response specification"
12
- )
13
- end
14
- @spec = { headers:{} }
10
+ raise Exceptions::InvalidConfiguration, 'Response name is required for a response specification' unless response_name
11
+
12
+ @spec = { headers: {} }
15
13
  @name = response_name
16
- self.instance_exec(**spec, &block) if block_given?
14
+ instance_exec(**spec, &block) if block_given?
17
15
 
18
- if self.status.nil?
19
- raise Exceptions::InvalidConfiguration.new(
20
- "Status code is required for a response specification"
21
- )
22
- end
16
+ raise Exceptions::InvalidConfiguration, 'Status code is required for a response specification' if status.nil?
23
17
  end
24
18
 
25
- def description(text=nil)
19
+ def description(text = nil)
26
20
  return @spec[:description] if text.nil?
21
+
27
22
  @spec[:description] = text
28
23
  end
29
24
 
30
- def status(code=nil)
25
+ def status(code = nil)
31
26
  return @spec[:status] if code.nil?
27
+
32
28
  @spec[:status] = code
33
29
  end
34
30
 
35
- def media_type(media_type=nil)
31
+ def media_type(media_type = nil)
36
32
  return @spec[:media_type] if media_type.nil?
37
33
 
38
34
  @spec[:media_type] = case media_type
39
- when String
40
- SimpleMediaType.new(media_type)
41
- when Class
42
- if media_type < Praxis::Types::MediaTypeCommon
43
- media_type
44
- else
45
- raise Exceptions::InvalidConfiguration.new(
46
- 'Invalid media_type specification. media_type must be a Praxis::MediaType'
47
- )
48
- end
49
- when SimpleMediaType
50
- media_type
51
- else
52
- raise Exceptions::InvalidConfiguration.new(
53
- 'Invalid media_type specification. media_type must be a String, MediaType or SimpleMediaType'
54
- )
55
- end
35
+ when String
36
+ SimpleMediaType.new(media_type)
37
+ when Class
38
+ raise Exceptions::InvalidConfiguration, 'Invalid media_type specification. media_type must be a Praxis::MediaType' unless media_type < Praxis::Types::MediaTypeCommon
39
+
40
+ media_type
41
+ when SimpleMediaType
42
+ media_type
43
+ else
44
+ raise Exceptions::InvalidConfiguration, 'Invalid media_type specification. media_type must be a String, MediaType or SimpleMediaType'
45
+ end
56
46
  end
57
47
 
58
- def location(loc=nil, description: nil)
59
- return headers.dig('Location',:value) if loc.nil?
48
+ def location(loc = nil, description: nil)
49
+ return headers.dig('Location', :value) if loc.nil?
60
50
 
61
51
  header('Location', loc, description: description)
62
52
  end
@@ -67,101 +57,88 @@ module Praxis
67
57
 
68
58
  def header(name, value, description: nil)
69
59
  the_type, args = case value
70
- when nil,String
71
- [String, {}]
72
- when Regexp
73
- # A regexp means it's gonna be a String typed, attached to a regexp
74
- [String, { regexp: value }]
75
- else
76
- raise Exceptions::InvalidConfiguration.new(
77
- "A header definition for a response can only take String, Regexp or nil values (to match anything)." +
78
- "Received the following value for header name #{name}: #{value}"
79
- )
80
- end
81
-
82
- info = {
83
- value: value,
60
+ when nil, String
61
+ [String, {}]
62
+ when Regexp
63
+ # A regexp means it's gonna be a String typed, attached to a regexp
64
+ [String, { regexp: value }]
65
+ else
66
+ raise Exceptions::InvalidConfiguration, 'A header definition for a response can only take String, Regexp or nil values (to match anything).' \
67
+ "Received the following value for header name #{name}: #{value}"
68
+ end
69
+
70
+ info = {
71
+ value: value,
84
72
  attribute: Attributor::Attribute.new(the_type, **args)
85
73
  }
86
74
  info[:description] = description if description
87
75
  @spec[:headers][name] = info
88
76
  end
89
77
 
90
- def example(context=nil)
91
- return nil if self.media_type.nil?
92
- return nil if self.media_type.kind_of?(SimpleMediaType)
78
+ def example(context = nil)
79
+ return nil if media_type.nil?
80
+ return nil if media_type.is_a?(SimpleMediaType)
93
81
 
94
- if context.nil?
95
- context = "#{self.media_type.name}-#{self.name}"
96
- end
82
+ context = "#{media_type.name}-#{name}" if context.nil?
97
83
 
98
- self.media_type.example(context)
84
+ media_type.example(context)
99
85
  end
100
86
 
101
-
102
87
  def describe(context: nil)
103
- location_type = location.is_a?(Regexp) ? :regexp : :string
104
- location_value = location.is_a?(Regexp) ? location.inspect : location
105
88
  content = {
106
- :description => description,
107
- :status => status,
108
- :headers => {}
89
+ description: description,
90
+ status: status,
91
+ headers: {}
109
92
  }
110
93
 
111
- unless headers == nil
112
- headers.each do |name, value|
113
- content[:headers][name] = _describe_header(value)
114
- end
94
+ headers&.each do |name, value|
95
+ content[:headers][name] = _describe_header(value)
115
96
  end
116
97
  content[:location] = content[:headers]['Location']
117
98
 
118
-
119
- if self.media_type
99
+ if media_type
120
100
  payload = media_type.describe(true)
121
101
 
122
- if (example_payload = self.example(context))
102
+ if (example_payload = example(context))
123
103
  payload[:examples] = {}
124
104
  rendered_payload = example_payload.dump
125
105
 
126
106
  # FIXME: remove load when when MediaTypeCommon.identifier returns a MediaTypeIdentifier
127
- identifier = MediaTypeIdentifier.load(self.media_type.identifier)
107
+ identifier = MediaTypeIdentifier.load(media_type.identifier)
128
108
 
129
109
  default_handlers = ApiDefinition.instance.info.produces
130
110
 
131
- handlers = Praxis::Application.instance.handlers.select do |k,v|
111
+ handlers = Praxis::Application.instance.handlers.select do |k, _v|
132
112
  default_handlers.include?(k)
133
113
  end
134
114
 
135
- if (identifier && handler = handlers[identifier.handler_name])
115
+ if identifier && (handler = handlers[identifier.handler_name])
136
116
  payload[:examples][identifier.handler_name] = {
137
117
  content_type: identifier.to_s,
138
118
  body: handler.generate(rendered_payload)
139
119
  }
140
120
  else
141
- handlers.each do |name, handler|
142
- content_type = ( identifier ) ? identifier + name : 'application/' + name
121
+ handlers.each do |name, handler_class|
122
+ content_type = identifier ? identifier + name : "application/#{name}"
143
123
  payload[:examples][name] = {
144
124
  content_type: content_type.to_s,
145
- body: handler.generate(rendered_payload)
125
+ body: handler_class.generate(rendered_payload)
146
126
  }
147
127
  end
148
128
  end
149
129
  end
150
130
 
151
- content[:payload] = {type: payload}
131
+ content[:payload] = { type: payload }
152
132
  end
153
133
 
154
- unless parts == nil
155
- content[:parts_like] = parts.describe
156
- end
134
+ content[:parts_like] = parts.describe unless parts.nil?
157
135
  content
158
136
  end
159
137
 
160
138
  def _describe_header(data)
161
-
162
139
  data_type = data[:value].is_a?(Regexp) ? :regexp : :string
163
140
  data_value = data[:value].is_a?(Regexp) ? data[:value].inspect : data[:value]
164
- { :value => data_value, :type => data_type }
141
+ { value: data_value, type: data_type }
165
142
  end
166
143
 
167
144
  def validate(response, validate_body: false)
@@ -170,20 +147,18 @@ module Praxis
170
147
  validate_content_type!(response)
171
148
  validate_parts!(response)
172
149
 
173
- if validate_body
174
- validate_body!(response)
175
- end
150
+ validate_body!(response) if validate_body
176
151
  end
177
152
 
178
- def parts(proc=nil, like: nil, **args, &block)
153
+ def parts(proc = nil, like: nil, **args, &block)
179
154
  a_proc = proc || block
180
155
  if like.nil? && !a_proc
181
- raise ArgumentError, "Parts definition for response #{name} needs a :like argument or a block/proc" if !args.empty?
156
+ raise ArgumentError, "Parts definition for response #{name} needs a :like argument or a block/proc" unless args.empty?
157
+
182
158
  return @parts
183
159
  end
184
- if like && a_proc
185
- raise ArgumentError, "Parts definition for response #{name} does not allow :like and a block simultaneously"
186
- end
160
+ raise ArgumentError, "Parts definition for response #{name} does not allow :like and a block simultaneously" if like && a_proc
161
+
187
162
  if like
188
163
  template = ApiDefinition.instance.response(like)
189
164
  @parts = template.compile(nil, **args)
@@ -198,13 +173,9 @@ module Praxis
198
173
  #
199
174
  def validate_status!(response)
200
175
  return unless status
176
+
201
177
  # Validate status code if defined in the spec
202
- if response.status != status
203
- raise Exceptions::Validation.new(
204
- "Invalid response code detected. Response %s dictates status of %s but this response is returning %s." %
205
- [name, status.inspect, response.status.inspect]
206
- )
207
- end
178
+ raise Exceptions::Validation, format('Invalid response code detected. Response %<name>s dictates status of %<status>s but this response is returning %<response>s.', name: name, status: status.inspect, response: response.status.inspect) if response.status != status
208
179
  end
209
180
 
210
181
  # Validates Headers
@@ -215,23 +186,13 @@ module Praxis
215
186
  return unless headers
216
187
 
217
188
  headers.each do |name, value|
218
- if name.is_a? Symbol
219
- raise Exceptions::Validation.new(
220
- "Symbols are not supported in headers"
221
- )
222
- end
189
+ raise Exceptions::Validation, 'Symbols are not supported in headers' if name.is_a? Symbol
223
190
 
224
- unless response.headers.has_key?(name)
225
- raise Exceptions::Validation.new(
226
- "Header #{name.inspect} was required but is missing"
227
- )
228
- end
191
+ raise Exceptions::Validation, "Header #{name.inspect} was required but is missing" unless response.headers.key?(name)
229
192
 
230
193
  errors = value[:attribute].validate(response.headers[name])
231
194
 
232
- unless errors.empty?
233
- raise Exceptions::Validation.new("Header #{name.inspect}, with value #{value.inspect} does not match #{response.headers[name]}.")
234
- end
195
+ raise Exceptions::Validation, "Header #{name.inspect}, with value #{value.inspect} does not match #{response.headers[name]}." unless errors.empty?
235
196
  # case value
236
197
  # when String
237
198
  # if response.headers[name] != value
@@ -249,7 +210,6 @@ module Praxis
249
210
  end
250
211
  end
251
212
 
252
-
253
213
  # Validates Content-Type header and response media type
254
214
  #
255
215
  # @param [Object] response
@@ -261,13 +221,10 @@ module Praxis
261
221
 
262
222
  response_content_type = response.content_type
263
223
  expected_content_type = Praxis::MediaTypeIdentifier.load(media_type.identifier)
224
+ return if expected_content_type.match(response_content_type)
264
225
 
265
- unless expected_content_type.match(response_content_type)
266
- raise Exceptions::Validation.new(
267
- "Bad Content-Type header. #{response_content_type}" +
268
- " is incompatible with #{expected_content_type} as described in response: #{self.name}"
269
- )
270
- end
226
+ raise Exceptions::Validation, "Bad Content-Type header. #{response_content_type}" \
227
+ " is incompatible with #{expected_content_type} as described in response: #{name}"
271
228
  end
272
229
 
273
230
  # Validates response body
@@ -277,15 +234,14 @@ module Praxis
277
234
  # @raise [Exceptions::Validation] When there is a missing required header..
278
235
  def validate_body!(response)
279
236
  return unless media_type
280
- return if media_type.kind_of? SimpleMediaType
237
+ return if media_type.is_a? SimpleMediaType
281
238
 
282
- errors = self.media_type.validate(self.media_type.load(response.body))
283
- if errors.any?
284
- message = "Invalid response body for #{media_type.identifier}." +
285
- "Errors: #{errors.inspect}"
286
- raise Exceptions::Validation.new(message, errors: errors)
287
- end
239
+ errors = media_type.validate(media_type.load(response.body))
240
+ return unless errors.any?
288
241
 
242
+ message = "Invalid response body for #{media_type.identifier}." \
243
+ "Errors: #{errors.inspect}"
244
+ raise Exceptions::Validation.new(message, errors: errors)
289
245
  end
290
246
 
291
247
  def validate_parts!(response)
@@ -295,6 +251,5 @@ module Praxis
295
251
  parts.validate(part)
296
252
  end
297
253
  end
298
-
299
254
  end
300
255
  end
@@ -1,5 +1,6 @@
1
- module Praxis
1
+ # frozen_string_literal: true
2
2
 
3
+ module Praxis
3
4
  class ResponseTemplate
4
5
  attr_reader :name, :block
5
6
 
@@ -8,27 +9,22 @@ module Praxis
8
9
  @block = block
9
10
  end
10
11
 
11
- def compile(action=nil, **args)
12
+ def compile(action = nil, **args)
12
13
  # Default media_type to the endpoint_definition one, if the block has it in
13
14
  # its required args but no value is passed (funky, but can help in the common case)
14
- if block.parameters.any? { |(type, name)| name == :media_type && type == :keyreq } && action
15
- unless args.has_key? :media_type
16
- media_type = action.endpoint_definition.media_type
17
- unless media_type
18
- raise Exceptions::InvalidConfiguration.new(
19
- "Could not default :media_type argument for response template #{@name}." +
20
- " Endpoint #{action.endpoint_definition} does not have an associated mediatype and none was passed"
21
- )
22
- end
23
- args[:media_type] = media_type
15
+ if block.parameters.any? { |(type, name)| name == :media_type && type == :keyreq } && action && !(args.key? :media_type)
16
+ media_type = action.endpoint_definition.media_type
17
+ unless media_type
18
+ raise Exceptions::InvalidConfiguration, "Could not default :media_type argument for response template #{@name}." \
19
+ " Endpoint #{action.endpoint_definition} does not have an associated mediatype and none was passed"
24
20
  end
21
+ args[:media_type] = media_type
25
22
  end
26
- Praxis::ResponseDefinition.new(name, **args, &block)
23
+ Praxis::ResponseDefinition.new(name, **args, &block)
27
24
  end
28
25
 
29
26
  def describe
30
- puts "TODO!!!!!!"
27
+ puts 'TODO!!!!!!'
31
28
  end
32
29
  end
33
-
34
30
  end