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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/CHANGELOG.md +22 -0
- data/bin/praxis +6 -0
- data/lib/praxis/api_definition.rb +8 -4
- data/lib/praxis/collection.rb +11 -0
- data/lib/praxis/docs/open_api/response_object.rb +21 -6
- data/lib/praxis/extensions/attribute_filtering.rb +14 -1
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +154 -63
- data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +3 -2
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +46 -43
- data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +193 -0
- data/lib/praxis/mapper/resource.rb +2 -2
- data/lib/praxis/media_type_identifier.rb +11 -1
- data/lib/praxis/response_definition.rb +46 -66
- data/lib/praxis/responses/http.rb +3 -1
- data/lib/praxis/tasks/routes.rb +6 -6
- data/lib/praxis/version.rb +1 -1
- data/spec/praxis/action_definition_spec.rb +3 -1
- data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +259 -172
- data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +25 -6
- data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +117 -19
- data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +148 -0
- data/spec/praxis/mapper/resource_spec.rb +3 -3
- data/spec/praxis/media_type_identifier_spec.rb +15 -1
- data/spec/praxis/response_definition_spec.rb +37 -129
- data/tasks/thor/templates/generator/example_app/app/v1/concerns/href.rb +33 -0
- data/tasks/thor/templates/generator/example_app/app/v1/resources/base.rb +4 -0
- data/tasks/thor/templates/generator/example_app/config/environment.rb +1 -1
- data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +2 -2
- 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
|
-
|
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 "#
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
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
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
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
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
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
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
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
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
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
|
-
|
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(
|
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(
|
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(
|
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.
|
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-
|
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: []
|