praxis 2.0.pre.19 → 2.0.pre.22
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/.travis.yml +1 -1
- data/CHANGELOG.md +21 -0
- data/lib/praxis/blueprint.rb +1 -0
- data/lib/praxis/collection.rb +1 -1
- data/lib/praxis/docs/open_api/media_type_object.rb +2 -2
- data/lib/praxis/docs/open_api/operation_object.rb +1 -1
- data/lib/praxis/docs/open_api/schema_object.rb +46 -14
- data/lib/praxis/docs/open_api/tag_object.rb +4 -4
- data/lib/praxis/docs/open_api_generator.rb +37 -70
- data/lib/praxis/mapper/active_model_compat.rb +21 -0
- data/lib/praxis/mapper/resource.rb +150 -4
- data/lib/praxis/mapper/resources/callbacks.rb +35 -0
- data/lib/praxis/mapper/resources/query_methods.rb +39 -0
- data/lib/praxis/mapper/resources/query_proxy.rb +58 -0
- data/lib/praxis/mapper/resources/typed_methods.rb +133 -0
- data/lib/praxis/response.rb +11 -1
- data/lib/praxis/tasks/api_docs.rb +2 -1
- data/lib/praxis/version.rb +1 -1
- data/lib/praxis.rb +7 -0
- data/praxis.gemspec +2 -2
- data/spec/functional_spec.rb +4 -4
- data/spec/praxis/mapper/resource_spec.rb +53 -2
- data/spec/praxis/mapper/resources/callbacks_spec.rb +56 -0
- data/spec/praxis/mapper/resources/query_proxy_spec.rb +137 -0
- data/spec/praxis/mapper/resources/typed_methods_spec.rb +201 -0
- data/spec/praxis/response_spec.rb +8 -2
- data/spec/support/spec_resources.rb +132 -0
- metadata +16 -9
@@ -0,0 +1,201 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Praxis::Mapper::Resources::TypedMethods do
|
6
|
+
let(:resource_class) { TypedResource }
|
7
|
+
|
8
|
+
context '._finalize!' do
|
9
|
+
# The class is already finalized by loading the TypedResource from the spec_resources
|
10
|
+
# So we will simply check that all the right things are created
|
11
|
+
it 'builds the MethodSignatures constant within the class' do
|
12
|
+
expect(TypedResource::MethodSignatures).to_not be_nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'builds the inner class type for the defined signatures' do
|
16
|
+
expect(TypedResource::MethodSignatures::UpdateBang).to_not be_nil
|
17
|
+
expect(TypedResource::MethodSignatures::UpdateBang).to be TypedResource.signature(:update!)
|
18
|
+
|
19
|
+
expect(TypedResource::MethodSignatures::SelfCreate).to_not be_nil
|
20
|
+
expect(TypedResource::MethodSignatures::SelfCreate).to be TypedResource.signature(:'self.create')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'Subsitutes a ! for a Bang when creating the constant' do
|
24
|
+
expect(TypedResource::MethodSignatures::UpdateBang).to be TypedResource.signature(:update!)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'defines the coercing methods' do
|
28
|
+
expect(TypedResource.methods).to include(:_coerce_params_for_class_create)
|
29
|
+
expect(TypedResource.instance_methods).to include(:_coerce_params_for_update!)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context '.signature' do
|
34
|
+
# We are not creating more classes and signatures, simply checking that the ones created
|
35
|
+
# for TypedResource in the spec_resource_files are correctly processed
|
36
|
+
it 'defines it in the @signatures hash' do
|
37
|
+
expect(TypedResource.signatures.keys).to match_array(
|
38
|
+
%i[
|
39
|
+
self.create
|
40
|
+
self.singlearg_create
|
41
|
+
create
|
42
|
+
update!
|
43
|
+
singlearg_update!
|
44
|
+
]
|
45
|
+
)
|
46
|
+
expect(TypedResource.signature(:'self.create')).to be < Attributor::Struct
|
47
|
+
expect(TypedResource.signature(:create)).to be < Attributor::Struct
|
48
|
+
expect(TypedResource.signature(:update!)).to be < Attributor::Struct
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'holds the right definition for class create' do
|
52
|
+
definition = TypedResource.signature(:'self.create')
|
53
|
+
expect(definition.attributes.keys).to eq %i[name payload]
|
54
|
+
expect(definition.attributes[:payload].attributes.keys).to eq %i[string_param struct_param]
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'holds the right definition for instance create' do
|
58
|
+
definition = TypedResource.signature(:create)
|
59
|
+
expect(definition.attributes.keys).to eq %i[id]
|
60
|
+
end
|
61
|
+
it 'holds the right definition for update!' do
|
62
|
+
definition = TypedResource.signature(:update!)
|
63
|
+
expect(definition.attributes.keys).to eq %i[string_param struct_param]
|
64
|
+
expect(definition.attributes[:struct_param].attributes.keys).to eq %i[id]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'coerce_params_for' do
|
69
|
+
let(:resource_class) do
|
70
|
+
Class.new(Praxis::Mapper::Resource) do
|
71
|
+
include Praxis::Mapper::Resources::TypedMethods
|
72
|
+
def imethod_args(args)
|
73
|
+
args
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.cmethod_args(args)
|
77
|
+
args
|
78
|
+
end
|
79
|
+
|
80
|
+
def imethod_kwargs(**args)
|
81
|
+
args
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.cmethod_kwargs(**args)
|
85
|
+
args
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
let(:hook_coercer) { methods.each { |method| resource_class.coerce_params_for(method, type) } }
|
91
|
+
# Note, we're associating the same type signature for both imethod and cmethod signatures!
|
92
|
+
let(:type) do
|
93
|
+
Class.new(Attributor::Struct) do
|
94
|
+
attributes do
|
95
|
+
attribute :id, Integer, required: true
|
96
|
+
attribute :name, String, null: false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
before do
|
102
|
+
# None of our wrappers before invoking the function
|
103
|
+
our_wrappers = resource_class.methods.select { |m| m.to_s =~ /^_coerce_params_for_class_/ }
|
104
|
+
our_wrappers += resource_class.instance_methods.select { |m| m.to_s =~ /^_coerce_params_for_/ }
|
105
|
+
expect(our_wrappers).to be_empty
|
106
|
+
end
|
107
|
+
context 'instance methods' do
|
108
|
+
let(:methods) { %i[imethod_args imethod_kwargs] }
|
109
|
+
it 'creates the wrapper methods' do
|
110
|
+
hook_coercer
|
111
|
+
iwrappers = resource_class.instance_methods.select { |m| m.to_s =~ /^_coerce_params_for_/ }
|
112
|
+
expect(iwrappers).to match_array %i[_coerce_params_for_imethod_args _coerce_params_for_imethod_kwargs]
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'sets an around callback for them' do
|
116
|
+
hook_coercer
|
117
|
+
expect(resource_class.around_callbacks[:imethod_args]).to eq([:_coerce_params_for_imethod_args])
|
118
|
+
expect(resource_class.around_callbacks[:imethod_kwargs]).to eq([:_coerce_params_for_imethod_kwargs])
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'when hooking in the callbacks' do
|
122
|
+
before do
|
123
|
+
hook_coercer
|
124
|
+
resource_class._finalize!
|
125
|
+
end
|
126
|
+
context 'calls the wrapper to validate and load' do
|
127
|
+
it 'fails if invalid (id is required)' do
|
128
|
+
expect do
|
129
|
+
resource_class.new(nil).imethod_args({ name: 'Praxis' })
|
130
|
+
end.to raise_error(
|
131
|
+
Praxis::Mapper::Resources::IncompatibleTypeForMethodArguments,
|
132
|
+
/.imethod_args.id is required/
|
133
|
+
)
|
134
|
+
expect do
|
135
|
+
resource_class.new(nil).imethod_kwargs(name: 'Praxis')
|
136
|
+
end.to raise_error(
|
137
|
+
Praxis::Mapper::Resources::IncompatibleTypeForMethodArguments,
|
138
|
+
/.imethod_kwargs.id is required/
|
139
|
+
)
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'succeeds and returns the coerced struct if compatible' do
|
143
|
+
result = resource_class.new(nil).imethod_args({ id: '1', name: 'Praxis' })
|
144
|
+
expect(result[:id]).to eq(1) # Coerces to Integer!
|
145
|
+
expect(result[:name]).to eq('Praxis')
|
146
|
+
result = resource_class.new(nil).imethod_kwargs(id: '1', name: 'Praxis')
|
147
|
+
expect(result[:id]).to eq(1) # Coerces to Integer!
|
148
|
+
expect(result[:name]).to eq('Praxis')
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context 'class methods' do
|
155
|
+
let(:methods) { %i[self.cmethod_args self.cmethod_kwargs] }
|
156
|
+
it 'creates the wrapper methods' do
|
157
|
+
hook_coercer
|
158
|
+
cwrappers = resource_class.methods.select { |m| m.to_s =~ /^_coerce_params_for_class_/ }
|
159
|
+
expect(cwrappers).to match_array %i[_coerce_params_for_class_cmethod_args _coerce_params_for_class_cmethod_kwargs]
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'sets an around callback for them' do
|
163
|
+
hook_coercer
|
164
|
+
expect(resource_class.around_callbacks[:'self.cmethod_args']).to eq([:_coerce_params_for_class_cmethod_args])
|
165
|
+
expect(resource_class.around_callbacks[:'self.cmethod_kwargs']).to eq([:_coerce_params_for_class_cmethod_kwargs])
|
166
|
+
end
|
167
|
+
|
168
|
+
context 'when hooking in the callbacks' do
|
169
|
+
before do
|
170
|
+
hook_coercer
|
171
|
+
resource_class._finalize!
|
172
|
+
end
|
173
|
+
context 'calls the wrapper to validate and load' do
|
174
|
+
it 'fails if invalid (id is required)' do
|
175
|
+
expect do
|
176
|
+
resource_class.cmethod_args({ name: 'Praxis' })
|
177
|
+
end.to raise_error(
|
178
|
+
Praxis::Mapper::Resources::IncompatibleTypeForMethodArguments,
|
179
|
+
/.cmethod_args.id is required/
|
180
|
+
)
|
181
|
+
expect do
|
182
|
+
resource_class.cmethod_kwargs(name: 'Praxis')
|
183
|
+
end.to raise_error(
|
184
|
+
Praxis::Mapper::Resources::IncompatibleTypeForMethodArguments,
|
185
|
+
/.cmethod_kwargs.id is required/
|
186
|
+
)
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'succeeds and returns the coerced struct if compatible' do
|
190
|
+
result = resource_class.cmethod_args({ id: '1', name: 'Praxis' })
|
191
|
+
expect(result[:id]).to eq(1) # Coerces to Integer!
|
192
|
+
expect(result[:name]).to eq('Praxis')
|
193
|
+
result = resource_class.cmethod_kwargs(id: '1', name: 'Praxis')
|
194
|
+
expect(result[:id]).to eq(1) # Coerces to Integer!
|
195
|
+
expect(result[:name]).to eq('Praxis')
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
@@ -61,9 +61,15 @@ describe Praxis::Response do
|
|
61
61
|
end
|
62
62
|
|
63
63
|
it 'should raise an error' do
|
64
|
+
resp = nil
|
64
65
|
expect do
|
65
|
-
response.validate(action)
|
66
|
-
end.
|
66
|
+
resp = response.validate(action)
|
67
|
+
end.to_not raise_error
|
68
|
+
|
69
|
+
expect(resp.status).to eq(500)
|
70
|
+
expect(resp.body[:name]).to eq('ValidationError')
|
71
|
+
expect(resp.body[:summary]).to eq('Error validating response')
|
72
|
+
expect(resp.body[:errors]).to include(/response definition with that name can be found/)
|
67
73
|
end
|
68
74
|
end
|
69
75
|
|
@@ -77,6 +77,12 @@ class YamlArrayModel < OpenStruct
|
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
|
+
class TypedModel < OpenStruct
|
81
|
+
def self._praxis_associations
|
82
|
+
{}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
80
86
|
# A set of resource classes for use in specs
|
81
87
|
class BaseResource < Praxis::Mapper::Resource
|
82
88
|
def href
|
@@ -100,6 +106,8 @@ class ParentResource < BaseResource
|
|
100
106
|
end
|
101
107
|
|
102
108
|
class SimpleResource < BaseResource
|
109
|
+
include Praxis::Mapper::Resources::Callbacks
|
110
|
+
|
103
111
|
model SimpleModel
|
104
112
|
|
105
113
|
resource_delegate other_model: [:other_attribute]
|
@@ -123,8 +131,132 @@ class SimpleResource < BaseResource
|
|
123
131
|
property :no_deps, dependencies: []
|
124
132
|
|
125
133
|
property :deep_nested_deps, dependencies: ['parent.simple_children.other_model.parent.display_name']
|
134
|
+
|
135
|
+
before(:update!, :do_before_update)
|
136
|
+
around(:update!, :do_around_update_nested)
|
137
|
+
around(:update!, :do_around_update)
|
138
|
+
# Define an after as a proc
|
139
|
+
after(:update!) do |number:|
|
140
|
+
_unused = number
|
141
|
+
record.after_count += 1
|
142
|
+
end
|
143
|
+
|
144
|
+
def do_before_update(number:)
|
145
|
+
_unused = number
|
146
|
+
record.before_count += 1
|
147
|
+
end
|
148
|
+
|
149
|
+
def do_around_update_nested(number:)
|
150
|
+
record.around_count += 100
|
151
|
+
yield(number: number)
|
152
|
+
end
|
153
|
+
|
154
|
+
def do_around_update(number:)
|
155
|
+
record.around_count += 50
|
156
|
+
yield(number: number)
|
157
|
+
end
|
158
|
+
|
159
|
+
around(:change_name, :do_around_change_name)
|
160
|
+
after(:change_name, :do_after_change_name)
|
161
|
+
# Define a before as a proc
|
162
|
+
before(:change_name) do |name, force:|
|
163
|
+
_unused = force
|
164
|
+
record.before_count += 1
|
165
|
+
record.name = name
|
166
|
+
record.force = false # Force always false in before
|
167
|
+
end
|
168
|
+
|
169
|
+
def do_after_change_name(name, force:)
|
170
|
+
_unused = force
|
171
|
+
record.after_count += 1
|
172
|
+
record.name += "-#{name}"
|
173
|
+
end
|
174
|
+
|
175
|
+
def do_around_change_name(name, force:)
|
176
|
+
record.around_count += 50
|
177
|
+
|
178
|
+
record.name += "-#{name}"
|
179
|
+
yield(name, force: force)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Appends the name and overrides the force
|
183
|
+
def change_name(name, force:)
|
184
|
+
record.name += "-#{name}"
|
185
|
+
record.force = force
|
186
|
+
self
|
187
|
+
end
|
188
|
+
|
189
|
+
around(:argsonly, :do_around_argsonly)
|
190
|
+
def do_around_argsonly(name)
|
191
|
+
record.around_count += 50
|
192
|
+
record.name += name.to_s
|
193
|
+
yield(name)
|
194
|
+
end
|
195
|
+
|
196
|
+
def argsonly(name)
|
197
|
+
record.name += "-#{name}"
|
198
|
+
self
|
199
|
+
end
|
200
|
+
|
201
|
+
# Adds 1000 to the around count, plus whatever has been accumulated in before_count
|
202
|
+
def update!(number:)
|
203
|
+
record.around_count += number + record.before_count
|
204
|
+
self
|
205
|
+
end
|
126
206
|
end
|
127
207
|
|
128
208
|
class YamlArrayResource < BaseResource
|
129
209
|
model YamlArrayModel
|
130
210
|
end
|
211
|
+
|
212
|
+
class TypedResource < BaseResource
|
213
|
+
include Praxis::Mapper::Resources::TypedMethods
|
214
|
+
|
215
|
+
model TypedModel
|
216
|
+
|
217
|
+
signature(:update!) do
|
218
|
+
attribute :string_param, String, null: false
|
219
|
+
attribute :struct_param do
|
220
|
+
attribute :id, Integer
|
221
|
+
end
|
222
|
+
end
|
223
|
+
def update!(**payload)
|
224
|
+
payload
|
225
|
+
end
|
226
|
+
|
227
|
+
signature(:singlearg_update!) do
|
228
|
+
attribute :string_param, String, null: false
|
229
|
+
attribute :struct_param do
|
230
|
+
attribute :id, Integer
|
231
|
+
end
|
232
|
+
end
|
233
|
+
def singlearg_update!(payload)
|
234
|
+
payload
|
235
|
+
end
|
236
|
+
|
237
|
+
# Instance method: create, to make sure we support both an instance and a class method signature
|
238
|
+
signature(:create) do
|
239
|
+
attribute :id, String
|
240
|
+
end
|
241
|
+
def create(id:)
|
242
|
+
id
|
243
|
+
end
|
244
|
+
|
245
|
+
signature('self.create') do
|
246
|
+
attribute :name, String, regexp: /Praxis/
|
247
|
+
attribute :payload, TypedResource.signature(:update!), required: true
|
248
|
+
end
|
249
|
+
|
250
|
+
def self.create(**payload)
|
251
|
+
payload
|
252
|
+
end
|
253
|
+
|
254
|
+
signature('self.singlearg_create') do
|
255
|
+
attribute :name, String, regexp: /Praxis/
|
256
|
+
attribute :payload, TypedResource.signature(:update!), required: true
|
257
|
+
end
|
258
|
+
|
259
|
+
def self.singlearg_create(payload)
|
260
|
+
payload
|
261
|
+
end
|
262
|
+
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.22
|
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: 2022-
|
12
|
+
date: 2022-05-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -31,14 +31,14 @@ dependencies:
|
|
31
31
|
requirements:
|
32
32
|
- - ">="
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: '6.
|
34
|
+
version: '6.2'
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - ">="
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: '6.
|
41
|
+
version: '6.2'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: mime
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -359,7 +359,7 @@ dependencies:
|
|
359
359
|
- - "~>"
|
360
360
|
- !ruby/object:Gem::Version
|
361
361
|
version: '5'
|
362
|
-
description:
|
362
|
+
description:
|
363
363
|
email:
|
364
364
|
- blanquer@gmail.com
|
365
365
|
- dane.jensen@gmail.com
|
@@ -463,6 +463,10 @@ files:
|
|
463
463
|
- lib/praxis/handlers/xml_sample.rb
|
464
464
|
- lib/praxis/mapper/active_model_compat.rb
|
465
465
|
- lib/praxis/mapper/resource.rb
|
466
|
+
- lib/praxis/mapper/resources/callbacks.rb
|
467
|
+
- lib/praxis/mapper/resources/query_methods.rb
|
468
|
+
- lib/praxis/mapper/resources/query_proxy.rb
|
469
|
+
- lib/praxis/mapper/resources/typed_methods.rb
|
466
470
|
- lib/praxis/mapper/selector_generator.rb
|
467
471
|
- lib/praxis/mapper/sequel_compat.rb
|
468
472
|
- lib/praxis/media_type.rb
|
@@ -543,6 +547,9 @@ files:
|
|
543
547
|
- spec/praxis/file_group_spec.rb
|
544
548
|
- spec/praxis/handlers/json_spec.rb
|
545
549
|
- spec/praxis/mapper/resource_spec.rb
|
550
|
+
- spec/praxis/mapper/resources/callbacks_spec.rb
|
551
|
+
- spec/praxis/mapper/resources/query_proxy_spec.rb
|
552
|
+
- spec/praxis/mapper/resources/typed_methods_spec.rb
|
546
553
|
- spec/praxis/mapper/selector_generator_spec.rb
|
547
554
|
- spec/praxis/media_type_identifier_spec.rb
|
548
555
|
- spec/praxis/media_type_spec.rb
|
@@ -660,7 +667,7 @@ homepage: https://github.com/praxis/praxis
|
|
660
667
|
licenses:
|
661
668
|
- MIT
|
662
669
|
metadata: {}
|
663
|
-
post_install_message:
|
670
|
+
post_install_message:
|
664
671
|
rdoc_options: []
|
665
672
|
require_paths:
|
666
673
|
- lib
|
@@ -675,8 +682,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
675
682
|
- !ruby/object:Gem::Version
|
676
683
|
version: 1.3.1
|
677
684
|
requirements: []
|
678
|
-
rubygems_version: 3.
|
679
|
-
signing_key:
|
685
|
+
rubygems_version: 3.3.7
|
686
|
+
signing_key:
|
680
687
|
specification_version: 4
|
681
688
|
summary: Building APIs the way you want it.
|
682
689
|
test_files: []
|