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

Sign up to get free protection for your applications and to get access to all the features.
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