praxis 2.0.pre.11 → 2.0.pre.16

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/CHANGELOG.md +22 -0
  4. data/bin/praxis +6 -0
  5. data/lib/praxis/api_definition.rb +8 -4
  6. data/lib/praxis/collection.rb +11 -0
  7. data/lib/praxis/docs/open_api/response_object.rb +21 -6
  8. data/lib/praxis/extensions/attribute_filtering.rb +14 -1
  9. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +154 -63
  10. data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +3 -2
  11. data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +46 -43
  12. data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +193 -0
  13. data/lib/praxis/mapper/resource.rb +2 -2
  14. data/lib/praxis/media_type_identifier.rb +11 -1
  15. data/lib/praxis/response_definition.rb +46 -66
  16. data/lib/praxis/responses/http.rb +3 -1
  17. data/lib/praxis/tasks/routes.rb +6 -6
  18. data/lib/praxis/version.rb +1 -1
  19. data/spec/praxis/action_definition_spec.rb +3 -1
  20. data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +259 -172
  21. data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +25 -6
  22. data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +117 -19
  23. data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +148 -0
  24. data/spec/praxis/mapper/resource_spec.rb +3 -3
  25. data/spec/praxis/media_type_identifier_spec.rb +15 -1
  26. data/spec/praxis/response_definition_spec.rb +37 -129
  27. data/tasks/thor/templates/generator/example_app/app/v1/concerns/href.rb +33 -0
  28. data/tasks/thor/templates/generator/example_app/app/v1/resources/base.rb +4 -0
  29. data/tasks/thor/templates/generator/example_app/config/environment.rb +1 -1
  30. data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +2 -2
  31. metadata +9 -6
@@ -8,7 +8,8 @@ describe Praxis::ResponseDefinition do
8
8
  Proc.new do
9
9
  status 200
10
10
  description 'test description'
11
- headers({ "X-Header" => "value", "Content-Type" => "application/some-type" })
11
+ header( "X-Header", "value", description: 'Very nais header')
12
+ header( "Content-Type", "application/some-type" )
12
13
  end
13
14
  end
14
15
 
@@ -17,7 +18,7 @@ describe Praxis::ResponseDefinition do
17
18
  its(:parts) { should be(nil) }
18
19
  let(:response_status) { 200 }
19
20
  let(:response_content_type) { "application/some-type" }
20
- let(:response_headers) { { "X-Header" => "value", "Content-Type" => response_content_type} }
21
+ let(:response_headers) { { "X-Header" => "value", "Content-Type" => response_content_type, "Location" => '/somewhere/over/the/rainbow'} }
21
22
 
22
23
  let(:response) { instance_double("Praxis::Response", status: response_status , headers: response_headers, content_type: response_content_type ) }
23
24
 
@@ -105,29 +106,6 @@ describe Praxis::ResponseDefinition do
105
106
  end
106
107
  end
107
108
 
108
- context '#headers' do
109
- it 'accepts a Hash' do
110
- response_definition.headers Hash["X-Header" => "value", "Content-Type" => "application/some-type"]
111
- expect(response_definition.headers).to be_a(Hash)
112
- end
113
-
114
- it 'accepts an Array' do
115
- response_definition.headers ["X-Header: value", "Content-Type: application/some-type"]
116
- expect(response_definition.headers).to be_a(Hash)
117
- expect(response_definition.headers.keys).to include("X-Header: value", "Content-Type: application/some-type")
118
- end
119
-
120
- it 'accepts a String' do
121
- response_definition.headers "X-Header: value"
122
- expect(response_definition.headers).to be_a(Hash)
123
- expect(response_definition.headers.keys).to include("X-Header: value")
124
- end
125
-
126
- it 'should return an error when headers are not a Hash, Array or String object' do
127
- expect{ response_definition.headers Object.new }. to raise_error(Praxis::Exceptions::InvalidConfiguration)
128
- end
129
- end
130
-
131
109
  context '#parts' do
132
110
  context 'with a :like argument (and no block)' do
133
111
  before do
@@ -242,7 +220,6 @@ describe Praxis::ResponseDefinition do
242
220
 
243
221
  it "calls all the validation sub-functions" do
244
222
  expect(response_definition).to receive(:validate_status!).once
245
- expect(response_definition).to receive(:validate_location!).once
246
223
  expect(response_definition).to receive(:validate_headers!).once
247
224
  expect(response_definition).to receive(:validate_content_type!).once
248
225
  response_definition.validate(response)
@@ -272,112 +249,39 @@ describe Praxis::ResponseDefinition do
272
249
 
273
250
  end
274
251
 
275
- describe "#validate_location!" do
276
- let(:block) { proc { status 200 } }
277
-
278
- context 'checking location mismatches' do
279
- before { response_definition.location(location) }
280
-
281
- context 'for Regexp' do
282
- let(:location) { /no_match/ }
283
-
284
- it 'should raise an error' do
285
- expect {
286
- response_definition.validate_location!(response)
287
- }.to raise_error(Praxis::Exceptions::Validation)
288
- end
289
- end
290
-
291
- context 'for String' do
292
- let(:location) { "no_match" }
293
- it 'should raise error' do
294
- expect {
295
- response_definition.validate_location!(response)
296
- }.to raise_error(Praxis::Exceptions::Validation)
297
- end
252
+ describe "#validate_headers!" do
253
+ context 'when there are missing headers' do
254
+ it 'should raise error' do
255
+ response_definition.header('X-Unknown', 'test')
256
+ expect {
257
+ response_definition.validate_headers!(response)
258
+ }.to raise_error(Praxis::Exceptions::Validation)
298
259
  end
299
-
300
260
  end
301
- end
302
-
303
- describe "#validate_headers!" do
304
- before { response_definition.headers(headers) }
305
- context 'checking headers are set' do
306
- context 'when there are missing headers' do
307
- let (:headers) { { 'X-some' => 'test' } }
308
- it 'should raise error' do
309
- expect {
310
- response_definition.validate_headers!(response)
311
- }.to raise_error(Praxis::Exceptions::Validation)
312
- end
261
+ context 'when headers with same names are returned' do
262
+ it 'a simply required header should not raise error just by being there' do
263
+ response_definition.header('X-Header', nil)
264
+ expect {
265
+ response_definition.validate_headers!(response)
266
+ }.to_not raise_error
313
267
  end
314
-
315
- context "when headers specs are name strings" do
316
- context "and is missing" do
317
- let (:headers) { [ "X-Just-Key" ] }
318
- it 'should raise error' do
319
- expect {
320
- response_definition.validate_headers!(response)
321
- }.to raise_error(Praxis::Exceptions::Validation)
322
- end
323
- end
324
-
325
- context "and is not missing" do
326
- let (:headers) { [ "X-Header" ] }
327
- it 'should not raise error' do
328
- expect {
329
- response_definition.validate_headers!(response)
330
- }.not_to raise_error
331
- end
332
- end
268
+ it 'an exact string header should not raise error if it fully matches' do
269
+ response_definition.header('X-Header', 'value')
270
+ expect {
271
+ response_definition.validate_headers!(response)
272
+ }.to_not raise_error
333
273
  end
334
-
335
- context "when header specs are hashes" do
336
- context "and is missing" do
337
- let (:headers) {
338
- [ { "X-Just-Key" => "notfoodbar" } ]
339
- }
340
- it 'should raise error' do
341
- expect {
342
- response_definition.validate_headers!(response)
343
- }.to raise_error(Praxis::Exceptions::Validation)
344
- end
345
- end
346
-
347
- context "and is not missing" do
348
- let (:headers) {
349
- [ { "X-Header" => "value" } ]
350
- }
351
- it 'should not raise error' do
352
- expect {
353
- response_definition.validate_headers!(response)
354
- }.not_to raise_error
355
- end
356
- end
274
+ it 'a regexp header should not raise error if it matches the regexp' do
275
+ response_definition.header('X-Header', /value/)
276
+ expect {
277
+ response_definition.validate_headers!(response)
278
+ }.to_not raise_error
357
279
  end
358
-
359
- context "when header specs are of mixed type " do
360
- context "and is missing" do
361
- let (:headers) {
362
- [ { "X-Header" => "value" }, "not-gonna-find-me" ]
363
- }
364
- it 'should raise error' do
365
- expect {
366
- response_definition.validate_headers!(response)
367
- }.to raise_error(Praxis::Exceptions::Validation)
368
- end
369
- end
370
-
371
- context "and is not missing" do
372
- let (:headers) {
373
- [ { "X-Header" => "value" }, "Content-Type" ]
374
- }
375
- it 'should not raise error' do
376
- expect {
377
- response_definition.validate_headers!(response)
378
- }.not_to raise_error
379
- end
380
- end
280
+ it 'a regexp header should raise error if it does not match the regexp' do
281
+ response_definition.header('X-Header', /anotherthing/)
282
+ expect {
283
+ response_definition.validate_headers!(response)
284
+ }.to raise_error(Praxis::Exceptions::Validation)
381
285
  end
382
286
  end
383
287
  end
@@ -478,7 +382,10 @@ describe Praxis::ResponseDefinition do
478
382
  if parts || parts_block
479
383
  parts ? response.parts(nil, **parts, &parts_block) : response.parts(nil, &parts_block)
480
384
  end
481
- response.headers(headers) if headers
385
+
386
+ headers&.each do |(name, value)|
387
+ response.header(name, value)
388
+ end
482
389
  end
483
390
 
484
391
  context 'for a definition with a media type' do
@@ -520,8 +427,9 @@ describe Praxis::ResponseDefinition do
520
427
  its([:location]){ should == {value: location.inspect ,type: :regexp} }
521
428
 
522
429
  it 'should have a header defined with value and type keys' do
523
- expect( output[:headers] ).to have(1).keys
430
+ expect( output[:headers] ).to have(2).keys
524
431
  expect( output[:headers]['Header1'] ).to eq({value: 'Value1' ,type: :string })
432
+ expect( output[:headers]['Location'] ).to eq({value: "/\\/my\\/url\\//" ,type: :regexp })
525
433
  end
526
434
  end
527
435
 
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module V1
4
+ module Resources
5
+ module Concerns
6
+ module Href
7
+ extend ActiveSupport::Concern
8
+
9
+ # Base module where the href concern will grab constants from
10
+ included do
11
+ def self.base_module
12
+ ::V1
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ def endpoint_path_template
18
+ # memoize a templated path for an endpoint, like
19
+ # /im/contacts/%{id}
20
+ return @endpoint_path_template if @endpoint_path_template
21
+
22
+ path = self.base_module.const_get(:Endpoints).const_get(model.name.split(':').last.pluralize).canonical_path.route.path
23
+ @endpoint_path_template = path.names.inject(path.to_s) { |p, name| p.sub(':' + name, "%{#{name}}") }
24
+ end
25
+ end
26
+
27
+ def href
28
+ format(self.class.endpoint_path_template, id: id)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../concerns/href'
4
+
3
5
  module V1
4
6
  module Resources
5
7
  class Base < Praxis::Mapper::Resource
8
+ include Resources::Concerns::Href
9
+
6
10
  # Base for all V1 resources.
7
11
  # Resources withing a single version should have resource mappings separate from other versions
8
12
  # and the Mapper::Resource will appropriately maintain different model_maps for each Base classes
@@ -4,7 +4,7 @@ Praxis::Application.configure do |application|
4
4
  # Configure the Mapper plugin (if we want to use all the filtering/field_selection extensions)
5
5
  application.bootloader.use Praxis::Plugins::MapperPlugin
6
6
  # Configure the Pagination plugin (if we want to use all the pagination/ordering extensions)
7
- application.bootloader.use Praxis::Plugins::PaginationPlugin, {
7
+ application.bootloader.use Praxis::Plugins::PaginationPlugin, **{
8
8
  # max_items: 500, # Unlimited by default,
9
9
  # default_page_size: 100,
10
10
  # paging_default_mode: {by: :id},
@@ -18,7 +18,7 @@ module <%= version_module %>
18
18
  <%- if action_enabled?(:create) -%>
19
19
  def self.create(payload)
20
20
  # Assuming the API field names directly map the the model attributes. Massage if appropriate.
21
- self.new(model.create(*payload.to_h))
21
+ self.new(model.create(**payload.to_h))
22
22
  end
23
23
  <%- end -%>
24
24
 
@@ -27,7 +27,7 @@ module <%= version_module %>
27
27
  record = model.find_by(id: id)
28
28
  return nil unless record
29
29
  # Assuming the API field names directly map the the model attributes. Massage if appropriate.
30
- record.update(*payload.to_h)
30
+ record.update(**payload.to_h)
31
31
  self.new(record)
32
32
  end
33
33
  <%- end -%>
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: praxis
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.pre.11
4
+ version: 2.0.pre.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josep M. Blanquer
8
8
  - Dane Jensen
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-02-05 00:00:00.000000000 Z
12
+ date: 2021-07-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -367,7 +367,7 @@ dependencies:
367
367
  - - ">"
368
368
  - !ruby/object:Gem::Version
369
369
  version: '4'
370
- description:
370
+ description:
371
371
  email:
372
372
  - blanquer@gmail.com
373
373
  - dane.jensen@gmail.com
@@ -446,6 +446,7 @@ files:
446
446
  - lib/praxis/extensions/attribute_filtering/active_record_patches/6_1_plus.rb
447
447
  - lib/praxis/extensions/attribute_filtering/filter_tree_node.rb
448
448
  - lib/praxis/extensions/attribute_filtering/filtering_params.rb
449
+ - lib/praxis/extensions/attribute_filtering/filters_parser.rb
449
450
  - lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb
450
451
  - lib/praxis/extensions/field_expansion.rb
451
452
  - lib/praxis/extensions/field_selection.rb
@@ -540,6 +541,7 @@ files:
540
541
  - spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb
541
542
  - spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb
542
543
  - spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb
544
+ - spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb
543
545
  - spec/praxis/extensions/field_expansion_spec.rb
544
546
  - spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb
545
547
  - spec/praxis/extensions/field_selection/field_selector_spec.rb
@@ -645,6 +647,7 @@ files:
645
647
  - tasks/thor/templates/generator/example_app/Rakefile
646
648
  - tasks/thor/templates/generator/example_app/app/models/user.rb
647
649
  - tasks/thor/templates/generator/example_app/app/v1/concerns/controller_base.rb
650
+ - tasks/thor/templates/generator/example_app/app/v1/concerns/href.rb
648
651
  - tasks/thor/templates/generator/example_app/app/v1/controllers/users.rb
649
652
  - tasks/thor/templates/generator/example_app/app/v1/resources/base.rb
650
653
  - tasks/thor/templates/generator/example_app/app/v1/resources/user.rb
@@ -669,7 +672,7 @@ homepage: https://github.com/praxis/praxis
669
672
  licenses:
670
673
  - MIT
671
674
  metadata: {}
672
- post_install_message:
675
+ post_install_message:
673
676
  rdoc_options: []
674
677
  require_paths:
675
678
  - lib
@@ -685,7 +688,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
685
688
  version: 1.3.1
686
689
  requirements: []
687
690
  rubygems_version: 3.1.2
688
- signing_key:
691
+ signing_key:
689
692
  specification_version: 4
690
693
  summary: Building APIs the way you want it.
691
694
  test_files: []