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,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  module Extensions
3
5
  module Pagination
4
6
  class PaginationHandler
5
- class PaginationException < Exception; end
7
+ class PaginationException < RuntimeError; end
6
8
 
7
9
  def self.paginate(query, pagination)
8
10
  return query unless pagination.paginator
@@ -10,57 +12,57 @@ module Praxis
10
12
  paginator = pagination.paginator
11
13
 
12
14
  q = if paginator.page
13
- offset(query, (paginator.page - 1) * paginator.items)
14
- else
15
- # If there is an order clause that complies with the "by" field sorting, we can use it directly
16
- # i.e., We can be smart about allowing the main sort field matching the pagination one (in case you want to sub-order in a custom way)
17
- oclause = if pagination.order.nil? || pagination.order.empty? # No ordering specified => use ascending based on the "by" field
18
- direction = :asc
19
- order(query, [{ asc: paginator.by }])
20
- else
21
- first_ordering = pagination.order.items.first
22
- direction = first_ordering.keys.first
23
- unless first_ordering[direction].to_sym == pagination.paginator.by.to_sym
24
- string_clause = pagination.order.items.map { |h|
25
- dir, name = h.first
26
- "#{name} #{dir}"
27
- }.join(',')
28
- raise PaginationException,
29
- "Ordering clause [#{string_clause}] is incompatible with pagination by field '#{pagination.paginator.by}'. " \
30
- "When paginating by a field value, one cannot specify the 'order' clause " \
31
- "unless the clause's primary field matches the pagination field."
32
- end
33
- order(query, pagination.order)
34
- end
35
-
36
- if paginator.from
37
- if direction == :desc
38
- where_lt(oclause, paginator.by, paginator.from)
15
+ offset(query, (paginator.page - 1) * paginator.items)
39
16
  else
40
- where_gt(oclause, paginator.by, paginator.from)
41
- end
42
- else
43
- oclause
44
- end
17
+ # If there is an order clause that complies with the "by" field sorting, we can use it directly
18
+ # i.e., We can be smart about allowing the main sort field matching the pagination one (in case you want to sub-order in a custom way)
19
+ oclause = if pagination.order.nil? || pagination.order.empty? # No ordering specified => use ascending based on the "by" field
20
+ direction = :asc
21
+ order(query, [{ asc: paginator.by }])
22
+ else
23
+ first_ordering = pagination.order.items.first
24
+ direction = first_ordering.keys.first
25
+ unless first_ordering[direction].to_sym == pagination.paginator.by.to_sym
26
+ string_clause = pagination.order.items.map do |h|
27
+ dir, name = h.first
28
+ "#{name} #{dir}"
29
+ end.join(',')
30
+ raise PaginationException,
31
+ "Ordering clause [#{string_clause}] is incompatible with pagination by field '#{pagination.paginator.by}'. " \
32
+ "When paginating by a field value, one cannot specify the 'order' clause " \
33
+ "unless the clause's primary field matches the pagination field."
34
+ end
35
+ order(query, pagination.order)
36
+ end
45
37
 
46
- end
38
+ if paginator.from
39
+ if direction == :desc
40
+ where_lt(oclause, paginator.by, paginator.from)
41
+ else
42
+ where_gt(oclause, paginator.by, paginator.from)
43
+ end
44
+ else
45
+ oclause
46
+ end
47
+
48
+ end
47
49
  limit(q, paginator.items)
48
50
  end
49
51
 
50
- def self.where_lt(query, attr, value)
51
- raise "implement in derived class"
52
+ def self.where_lt(_query, _attr, _value)
53
+ raise 'implement in derived class'
52
54
  end
53
-
54
- def self.where_gt(query, attr, value)
55
- raise "implement in derived class"
55
+
56
+ def self.where_gt(_query, _attr, _value)
57
+ raise 'implement in derived class'
56
58
  end
57
59
 
58
- def self.offset(query, offset)
59
- raise "implement in derived class"
60
+ def self.offset(_query, _offset)
61
+ raise 'implement in derived class'
60
62
  end
61
63
 
62
- def self.limit(query, limit)
63
- raise "implement in derived class"
64
+ def self.limit(_query, _limit)
65
+ raise 'implement in derived class'
64
66
  end
65
67
  end
66
68
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  module Extensions
3
5
  module Pagination
@@ -76,40 +78,32 @@ module Praxis
76
78
  default_mode, default_value = target.defaults[:default_mode].first
77
79
  case pagination_type
78
80
  when :paging
79
- if default_mode == :page
80
- raise "Cannot disallow page-based pagination if you define a default pagination of: page: #{default_value}"
81
- end
81
+ raise "Cannot disallow page-based pagination if you define a default pagination of: page: #{default_value}" if default_mode == :page
82
+
82
83
  target.defaults[:disallow_paging] = true
83
84
  when :cursor
84
- if default_mode == :by
85
- raise "Cannot disallow cursor-based pagination if you define a default pagination of: by: #{default_value}"
86
- end
85
+ raise "Cannot disallow cursor-based pagination if you define a default pagination of: by: #{default_value}" if default_mode == :by
86
+
87
87
  target.defaults[:disallow_cursor] = true
88
88
  end
89
89
  end
90
90
 
91
91
  def default(spec)
92
- unless spec.is_a?(Hash) && spec.keys.size == 1 && [:by, :page].include?(spec.keys.first)
92
+ unless spec.is_a?(Hash) && spec.keys.size == 1 && %i[by page].include?(spec.keys.first)
93
93
  raise "'default' syntax for pagination takes exactly one key specification. Either by: <:fieldname> or page: <num>" \
94
94
  "#{spec} is invalid"
95
95
  end
96
96
  mode, value = spec.first
97
97
  def_mode = case mode
98
98
  when :by
99
- if target.fields_allowed && !target.fields_allowed&.include?(value)
100
- raise "Error setting default pagination. Field #{value} is not amongst the allowed fields."
101
- end
102
- if target.defaults[:disallow_cursor]
103
- raise "Cannot define a default pagination that is cursor based, if cursor-based pagination is disallowed."
104
- end
99
+ raise "Error setting default pagination. Field #{value} is not amongst the allowed fields." if target.fields_allowed && !target.fields_allowed&.include?(value)
100
+ raise 'Cannot define a default pagination that is cursor based, if cursor-based pagination is disallowed.' if target.defaults[:disallow_cursor]
101
+
105
102
  { by: value }
106
103
  when :page
107
- unless value.is_a?(Integer)
108
- raise "Error setting default pagination. Initial page should be a integer (but got #{value})"
109
- end
110
- if target.defaults[:disallow_paging]
111
- raise "Cannot define a default pagination that is page-based, if page-based pagination is disallowed."
112
- end
104
+ raise "Error setting default pagination. Initial page should be a integer (but got #{value})" unless value.is_a?(Integer)
105
+ raise 'Cannot define a default pagination that is page-based, if page-based pagination is disallowed.' if target.defaults[:disallow_paging]
106
+
113
107
  { page: value }
114
108
  end
115
109
  target.defaults[:default_mode] = def_mode
@@ -141,9 +135,8 @@ module Praxis
141
135
 
142
136
  def self.paging_default_mode(newval = nil)
143
137
  if newval
144
- unless newval.respond_to?(:keys) && newval.keys.size == 1 && [:by, :page].include?(newval.keys.first)
145
- raise "Error setting paging_default_mode, value must be a hash with :by or :page keys"
146
- end
138
+ raise 'Error setting paging_default_mode, value must be a hash with :by or :page keys' unless newval.respond_to?(:keys) && newval.keys.size == 1 && %i[by page].include?(newval.keys.first)
139
+
147
140
  @paging_default_mode = newval
148
141
  end
149
142
  @paging_default_mode
@@ -152,14 +145,13 @@ module Praxis
152
145
  # Abstract class, which needs to be used by subclassing it through the .for method, to link it to a particular
153
146
  # MediaType, so that the field name checking and value coercion can be performed
154
147
  class << self
155
- attr_reader :media_type
156
- attr_reader :defaults
148
+ attr_reader :media_type, :defaults
157
149
  attr_accessor :fields_allowed
158
150
 
159
151
  def for(media_type, **_opts)
160
152
  unless media_type < Praxis::MediaType
161
153
  raise ArgumentError, "Invalid type: #{media_type.name} for Paginator. " \
162
- "Must be a subclass of MediaType"
154
+ 'Must be a subclass of MediaType'
163
155
  end
164
156
 
165
157
  ::Class.new(self) do
@@ -206,11 +198,7 @@ module Praxis
206
198
  self
207
199
  end
208
200
 
209
- attr_reader :by
210
- attr_reader :from
211
- attr_reader :items
212
- attr_reader :page
213
- attr_reader :total_count
201
+ attr_reader :by, :from, :items, :page, :total_count
214
202
 
215
203
  def self.example(_context = Attributor::DEFAULT_ROOT_CONTEXT, **_options)
216
204
  fields = if media_type
@@ -224,10 +212,10 @@ module Praxis
224
212
  from = media_type.attributes[by].example(parent: mt_example).to_s
225
213
  # Make sure to encode the value of the from, as it can contain commas and such
226
214
  # Only add the from parameter if it's not empty (an empty from is not allowed and it's gonna blow up in load)
227
- optional_from_component = (from && !from.empty?) ? ",from=#{CGI.escape(from)}" : ''
215
+ optional_from_component = from && !from.empty? ? ",from=#{CGI.escape(from)}" : ''
228
216
  "by=#{by}#{optional_from_component},items=#{defaults[:page_size]}"
229
217
  else
230
- "by=id,from=20,items=100"
218
+ 'by=id,from=20,items=100'
231
219
  end
232
220
  load(fields)
233
221
  end
@@ -237,9 +225,10 @@ module Praxis
237
225
  instance.validate(context)
238
226
  end
239
227
 
240
- CLAUSE_REGEX = /(?<type>[^=]+)=(?<value>.+)$/
228
+ CLAUSE_REGEX = /(?<type>[^=]+)=(?<value>.+)$/.freeze
241
229
  def self.load(paginator, _context = Attributor::DEFAULT_ROOT_CONTEXT, **_options)
242
230
  return paginator if paginator.is_a?(native_type) || paginator.nil?
231
+
243
232
  parsed = {}
244
233
  unless paginator.nil?
245
234
  parsed = paginator.split(',').each_with_object({}) do |paginator_string, hash|
@@ -305,7 +294,7 @@ module Praxis
305
294
  if media_type&.attributes
306
295
  attrs = media_type&.attributes || {}
307
296
  attribute = attrs[name.to_sym]
308
- attribute.type.load(value) if attribute
297
+ attribute&.type&.load(value)
309
298
  else
310
299
  value
311
300
  end
@@ -320,28 +309,20 @@ module Praxis
320
309
  @total_count = parsed[:total_count]
321
310
  end
322
311
 
323
- def validate(_context = Attributor::DEFAULT_ROOT_CONTEXT) # rubocop:disable Metrics/PerceivedComplexity
312
+ def validate(_context = Attributor::DEFAULT_ROOT_CONTEXT)
324
313
  errors = []
325
314
 
326
315
  if page
327
- if self.class.defaults[:disallow_paging]
328
- errors << "Page-based pagination is disallowed (i.e., using 'page=' parameter)"
329
- end
316
+ errors << "Page-based pagination is disallowed (i.e., using 'page=' parameter)" if self.class.defaults[:disallow_paging]
330
317
  elsif self.class.defaults[:disallow_cursor]
331
318
  errors << "Cursor-based pagination is disallowed (i.e., using 'by=' or 'from=' parameter)"
332
319
  end
333
320
 
334
- if page && page <= 0
335
- errors << "Page parameter cannot be zero or negative! (got: #{parsed.page})"
336
- end
321
+ errors << "Page parameter cannot be zero or negative! (got: #{parsed.page})" if page && page <= 0
337
322
 
338
- if items && (items <= 0 || ( self.class.defaults[:max_items] && items > self.class.defaults[:max_items]) )
339
- errors << "Value of 'items' is invalid (got: #{items}). It must be positive, and smaller than the maximum amount of items per request (set to #{self.class.defaults[:max_items]})"
340
- end
323
+ errors << "Value of 'items' is invalid (got: #{items}). It must be positive, and smaller than the maximum amount of items per request (set to #{self.class.defaults[:max_items]})" if items && (items <= 0 || (self.class.defaults[:max_items] && items > self.class.defaults[:max_items]))
341
324
 
342
- if page && (by || from)
343
- errors << "Cannot specify the field to use and its start value to paginate from when using a fix pager (i.e., `by` and/or `from` params are not compabible with `page`)"
344
- end
325
+ errors << 'Cannot specify the field to use and its start value to paginate from when using a fix pager (i.e., `by` and/or `from` params are not compabible with `page`)' if page && (by || from)
345
326
 
346
327
  if by && self.class.fields_allowed && !self.class.fields_allowed.include?(by.to_sym)
347
328
  errors << if self.class.media_type.attributes.key?(by.to_sym)
@@ -364,7 +345,7 @@ module Praxis
364
345
  s
365
346
  end
366
347
  str += ",items=#{items}" if @items
367
- str += ",total_count=true" if @total_count
348
+ str += ',total_count=true' if @total_count
368
349
  str
369
350
  end
370
351
  end
@@ -1,20 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'pagination_handler'
2
4
 
3
5
  module Praxis
4
6
  module Extensions
5
7
  module Pagination
6
8
  class SequelPaginationHandler < PaginationHandler
7
-
8
9
  def self.where_lt(query, attr, value)
9
10
  query.where("#{attr} < ?", value)
10
11
  end
11
-
12
+
12
13
  def self.where_gt(query, attr, value)
13
14
  query.where("#{attr} > ?", value)
14
15
  end
15
16
 
16
17
  def self.order(query, order)
17
18
  return query unless order
19
+
18
20
  order_clause = order.map do |spec_hash|
19
21
  direction, name = spec_hash.first
20
22
  case direction.to_sym
@@ -24,21 +26,20 @@ module Praxis
24
26
  Sequel.asc(name.to_sym)
25
27
  end
26
28
  end
27
- query = query.order(*order_clause)
28
- query
29
+ query.order(*order_clause)
29
30
  end
30
-
31
+
31
32
  def self.count(query)
32
33
  query.count
33
34
  end
34
-
35
+
35
36
  def self.offset(query, offset)
36
37
  query.offset(offset)
37
38
  end
38
39
 
39
40
  def self.limit(query, limit)
40
41
  query.limit(limit)
41
- end
42
+ end
42
43
  end
43
44
  end
44
45
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'link_header'
3
5
  rescue LoadError
@@ -43,17 +45,13 @@ module Praxis
43
45
 
44
46
  included do
45
47
  after :action do |controller, _callee|
46
- if controller.response.status < 300
47
- # If this action has the pagination parameter defined,
48
- # calculate and set the pagination headers (Link header and possibly Total-Count)
49
- if controller._pagination.paginator
50
- headers = controller.build_pagination_headers(
51
- pagination: controller._pagination,
52
- current_url: controller.request.path,
53
- current_query_params: controller.request.query
54
- )
55
- controller.response.headers.merge! headers
56
- end
48
+ if controller.response.status < 300 && controller._pagination.paginator
49
+ headers = controller.build_pagination_headers(
50
+ pagination: controller._pagination,
51
+ current_url: controller.request.path,
52
+ current_query_params: controller.request.query
53
+ )
54
+ controller.response.headers.merge! headers
57
55
  end
58
56
  end
59
57
  end
@@ -75,9 +73,7 @@ module Praxis
75
73
  links = if pagination.paginator.by
76
74
  # We're assuming that the last element has a "symbol/string" field with the same name of the "by" pagination.
77
75
  last_element = response.body.last
78
- if last_element
79
- last_value = last_element[pagination.paginator.by.to_sym] || last_element[pagination.paginator.by]
80
- end
76
+ last_value = last_element[pagination.paginator.by.to_sym] || last_element[pagination.paginator.by] if last_element
81
77
  HeaderGenerator.build_cursor_headers(
82
78
  paginator: pagination.paginator,
83
79
  last_value: last_value,
@@ -97,7 +93,6 @@ module Praxis
97
93
  total_count: pagination.total_count
98
94
  )
99
95
  end
100
-
101
96
  end
102
97
  end
103
98
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Make Praxis' request derive from ActionDispatch
2
4
  if defined? Praxis::Request
3
5
  puts "IT seems that we're trying to redefine Praxis' request parent too late."
4
- puts "-> try to include the Rails compat pieces earlier in the bootstrap process (before Praxis::Request is requried)"
6
+ puts '-> try to include the Rails compat pieces earlier in the bootstrap process (before Praxis::Request is requried)'
5
7
  exit(-1)
6
8
  end
7
9
 
@@ -14,6 +16,3 @@ begin
14
16
  end
15
17
  require 'praxis/request'
16
18
  end
17
-
18
-
19
-
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'praxis/extensions/rails_compat/request_methods'
2
4
  require 'praxis/plugins/rails_plugin'
@@ -1,32 +1,33 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  module Extensions
3
-
4
5
  module Rendering
5
6
  extend ActiveSupport::Concern
6
7
  include FieldExpansion
7
8
 
8
9
  def render(object, include_nil: false)
9
- loaded = self.media_type.load(object)
10
+ loaded = media_type.load(object)
10
11
  renderer = Praxis::Renderer.new(include_nil: include_nil)
11
- renderer.render(loaded, self.expanded_fields)
12
+ renderer.render(loaded, expanded_fields)
12
13
  rescue Attributor::DumpError
13
- if self.media_type.domain_model == Object
14
- warn "Detected the rendering of an object of type #{self.media_type} without having a domain object model set.\n" +
15
- "Did you forget to define it?"
14
+ if media_type.domain_model == Object
15
+ warn "Detected the rendering of an object of type #{media_type} without having a domain object model set.\n" \
16
+ 'Did you forget to define it?'
16
17
  end
17
18
  raise
18
19
  end
19
20
 
20
- def display(object, include_nil: false, encoder: self.default_encoder )
21
- identifier = Praxis::MediaTypeIdentifier.load(self.media_type.identifier)
21
+ def display(object, include_nil: false, encoder: default_encoder)
22
+ identifier = Praxis::MediaTypeIdentifier.load(media_type.identifier)
22
23
  identifier += encoder unless encoder.blank?
23
24
  response.headers['Content-Type'] = identifier.to_s
24
25
  response.body = render(object, include_nil: include_nil)
25
26
  response
26
27
  rescue Praxis::Renderer::CircularRenderingError => e
27
- Praxis::Application.instance.validation_handler.handle!(
28
- summary: "Circular Rendering Error when rendering response. " +
29
- "Please especify a view to narrow the dependent fields, or narrow your field set.",
28
+ Praxis::Application.instance.validation_handler.handle!(
29
+ summary: 'Circular Rendering Error when rendering response. ' \
30
+ 'Please especify a view to narrow the dependent fields, or narrow your field set.',
30
31
  exception: e,
31
32
  request: request,
32
33
  stage: :action,
@@ -37,7 +38,6 @@ module Praxis
37
38
  def default_encoder
38
39
  ''
39
40
  end
40
-
41
41
  end
42
42
  end
43
43
  end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Praxis
3
4
  class FieldExpander
4
5
  def self.expand(object, fields = true)
5
6
  new.expand(object, fields)
6
7
  end
7
8
 
8
- attr_reader :stack
9
- attr_reader :history
9
+ attr_reader :stack, :history
10
10
 
11
11
  def initialize
12
12
  @stack = Hash.new do |hash, key|
@@ -20,6 +20,7 @@ module Praxis
20
20
  def expand(object, fields = true)
21
21
  if stack[object].include? fields
22
22
  return history[object][fields] if history[object].include? fields
23
+
23
24
  # We should probably never get here, since we should have a record
24
25
  # of the history of an expansion if we're trying to redo it,
25
26
  # but we should also be conservative and raise here just in case.
@@ -61,11 +62,9 @@ module Praxis
61
62
 
62
63
  def expand_type(object, fields = true)
63
64
  unless object.respond_to?(:attributes)
64
- if object.respond_to?(:member_attribute)
65
- return expand_type(object.member_attribute.type, fields)
66
- else
67
- return true
68
- end
65
+ return expand_type(object.member_attribute.type, fields) if object.respond_to?(:member_attribute)
66
+
67
+ return true
69
68
  end
70
69
 
71
70
  # just include the full thing if it has no attributes
@@ -80,11 +79,11 @@ module Praxis
80
79
  result = expand_fields(object.attributes, fields) do |dumpable, sub_fields|
81
80
  expand(dumpable.type, sub_fields)
82
81
  end
83
- unless fields == true
82
+ unless fields == true
84
83
  non_matching = fields.keys - object.attributes.keys
85
84
  raise "FieldExpansion error: attribute(s) #{non_matching} do not exist in #{object}" unless non_matching.empty?
86
85
  end
87
86
  history[object][fields].merge!(result)
88
87
  end
89
88
  end
90
- end
89
+ end
@@ -1,26 +1,23 @@
1
- module Praxis
1
+ # frozen_string_literal: true
2
2
 
3
+ module Praxis
3
4
  class FileGroup
4
-
5
5
  attr_reader :groups, :base
6
6
 
7
7
  def initialize(base, &block)
8
8
  if base.nil?
9
- raise ArgumentError, "base must not be nil." \
10
- "Are you missing a call Praxis::Application.instance.setup?"
9
+ raise ArgumentError, 'base must not be nil.' \
10
+ 'Are you missing a call Praxis::Application.instance.setup?'
11
11
  end
12
12
 
13
-
14
- @groups = Hash.new
13
+ @groups = {}
15
14
  @base = Pathname.new(base)
16
15
 
17
- if block_given?
18
- self.instance_eval(&block)
19
- end
16
+ instance_eval(&block) if block_given?
20
17
  end
21
18
 
22
19
  def layout(&block)
23
- self.instance_eval(&block)
20
+ instance_eval(&block)
24
21
  end
25
22
 
26
23
  def map(name, pattern, &block)
@@ -30,7 +27,7 @@ module Praxis
30
27
  @groups[name] = FileGroup.new(base + pattern, &block)
31
28
  else
32
29
  @groups[name] ||= []
33
- files = Pathname.glob(base+pattern).select { |file| file.file? }
30
+ files = Pathname.glob(base + pattern).select(&:file?)
34
31
  files.sort_by! { |file| [file.to_s.split('/').size, file.to_s] }
35
32
  files.each { |file| @groups[name] << file }
36
33
  end
@@ -39,6 +36,5 @@ module Praxis
39
36
  def [](*names)
40
37
  names.inject(@groups) { |group, name| group[name] || [] }
41
38
  end
42
-
43
39
  end
44
40
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Praxis
3
4
  module Finalizable
4
5
  def self.extended(klass)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  module Handlers
3
5
  class JSON
@@ -9,7 +11,7 @@ module Praxis
9
11
  rescue LoadError
10
12
  # Should never happen since JSON is a default gem; might as well be cautious!
11
13
  raise Praxis::Exceptions::InvalidConfiguration,
12
- "JSON handler depends on json ~> 1.0; please add it to your Gemfile"
14
+ 'JSON handler depends on json ~> 1.0; please add it to your Gemfile'
13
15
  end
14
16
 
15
17
  # Parse a JSON document into structured data.
@@ -18,7 +20,8 @@ module Praxis
18
20
  # @return [Hash,Array] the structured-data representation of the document
19
21
  def parse(document)
20
22
  # Try to be nice and accept an empty string as an empty payload (seems nice to do for dumb http clients)
21
- return nil if (document.nil? || document == '')
23
+ return nil if document.nil? || document == ''
24
+
22
25
  ::JSON.parse(document)
23
26
  end
24
27
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  module Handlers
3
5
  class Plain
@@ -10,7 +12,6 @@ module Praxis
10
12
  def generate(structured_data)
11
13
  structured_data
12
14
  end
13
-
14
15
  end
15
16
  end
16
17
  end
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This is an example of a handler that can load and generate www-url-encoded payloads.
2
- # Note that if you use your API to pass nil values for attributes as a way to unset their
3
- # values, this handler will not work (as there isn't necessarily a defined "null" value in
4
+ # Note that if you use your API to pass nil values for attributes as a way to unset their
5
+ # values, this handler will not work (as there isn't necessarily a defined "null" value in
4
6
  # this encoding (although you can probably define how to encode/decode it and use it as such)
5
7
  # Use at your own risk.
6
8
  module Praxis
@@ -19,8 +21,9 @@ module Praxis
19
21
  # is not very useful for a response body.
20
22
  def generate(structured_data)
21
23
  return nil if structured_data.nil?
24
+
22
25
  URI.encode_www_form(structured_data)
23
26
  end
24
27
  end
25
28
  end
26
- end
29
+ end