trailblazer-macro-contract 2.1.0 → 2.1.3.beta1
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/.github/workflows/ci.yml +17 -0
- data/CHANGES.md +15 -0
- data/Gemfile +2 -0
- data/Rakefile +1 -1
- data/lib/trailblazer/macro/contract/build.rb +87 -0
- data/lib/trailblazer/{operation → macro/contract}/persist.rb +1 -1
- data/lib/trailblazer/macro/contract/validate.rb +85 -0
- data/lib/trailblazer/macro/contract/version.rb +1 -1
- data/lib/trailblazer/macro/contract.rb +17 -5
- data/test/docs/contract_test.rb +187 -34
- data/test/docs/dry_test.rb +27 -27
- data/test/operation/contract_test.rb +2 -0
- data/test/test_helper.rb +4 -2
- data/trailblazer-macro-contract.gemspec +7 -4
- metadata +51 -24
- data/.travis.yml +0 -8
- data/lib/trailblazer/operation/contract.rb +0 -63
- data/lib/trailblazer/operation/validate.rb +0 -75
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9c3c0092a4bd1f8b71d4506ed24e77af326b28d2991a88bdb45d67586dad746
|
4
|
+
data.tar.gz: 6de99c14b4bc553a0e32e568a9c31a74abf839f0d82f36dac33884300d979777
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 15de139140c545de9ab6d1b6d19a83053815b78b7e4ae990a9b5a57dea89e77566524d2eb84326694f5399f41d3d7f367a761572badf465fdaa58cd32c2e1a79
|
7
|
+
data.tar.gz: 4b9fc7eb3a4f30a7f57f2ec95c6b0459ad5ccff8e4adbfd3ddc324570d8647b1046277e61f7f1b402bbbb663e28fbe3b1161d649db5036e74c91da66b9e6b8c1
|
@@ -0,0 +1,17 @@
|
|
1
|
+
name: CI
|
2
|
+
on: [push, pull_request]
|
3
|
+
jobs:
|
4
|
+
test:
|
5
|
+
strategy:
|
6
|
+
fail-fast: false
|
7
|
+
matrix:
|
8
|
+
# Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
|
9
|
+
ruby: [2.6, 2.7, '3.0', jruby]
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
steps:
|
12
|
+
- uses: actions/checkout@v2
|
13
|
+
- uses: ruby/setup-ruby@v1
|
14
|
+
with:
|
15
|
+
ruby-version: ${{ matrix.ruby }}
|
16
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
17
|
+
- run: bundle exec rake
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
# 2.1.3
|
2
|
+
|
3
|
+
* Use `trailblazer-activity-dsl-linear` >= 1.0.0.
|
4
|
+
* Use Inject() API instead of `:inject`, `:input` etc in macros.
|
5
|
+
|
6
|
+
# 2.1.2
|
7
|
+
|
8
|
+
* Refactor `Contract::Build` to use TRB mechanics:
|
9
|
+
* `:input` and `:inject` to allow injection of the contract class.
|
10
|
+
* an `Option()` to wrap the builder code.
|
11
|
+
|
12
|
+
# 2.1.1
|
13
|
+
|
14
|
+
* Support for Ruby 3.0.
|
15
|
+
|
1
16
|
# 2.1.0
|
2
17
|
|
3
18
|
* Finally.
|
data/Gemfile
CHANGED
@@ -9,6 +9,8 @@ gem "dry-matcher"
|
|
9
9
|
# gem "trailblazer-macro", path: "../trailblazer-macro"
|
10
10
|
# gem "trailblazer-activity", path: "../trailblazer-activity"
|
11
11
|
# gem "trailblazer-activity-dsl-linear", path: "../trailblazer-activity-dsl-linear"
|
12
|
+
# gem "trailblazer-errors", path: "../trailblazer-errors"
|
13
|
+
# gem "trailblazer-developer", path: "../trailblazer-developer"
|
12
14
|
|
13
15
|
gem "minitest-line"
|
14
16
|
|
data/Rakefile
CHANGED
@@ -0,0 +1,87 @@
|
|
1
|
+
require "reform"
|
2
|
+
|
3
|
+
module Trailblazer
|
4
|
+
module Macro
|
5
|
+
# This Circuit-task calls the {task} Option, then allows
|
6
|
+
# to run an arbitary block to process the option's result.
|
7
|
+
# @private
|
8
|
+
class CircuitTaskWithResultProcessing < Activity::TaskBuilder::Task # DISCUSS: extract to public?
|
9
|
+
def initialize(task, user_proc, block)
|
10
|
+
@block = block
|
11
|
+
super(task, user_proc)
|
12
|
+
end
|
13
|
+
|
14
|
+
def call_option(task_with_option_interface, (ctx, flow_options), **circuit_options)
|
15
|
+
result = super
|
16
|
+
|
17
|
+
@block.call(result, ctx)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Contract
|
22
|
+
def self.Build(name: "default", constant: nil, builder: nil)
|
23
|
+
contract_path = :"contract.#{name}"
|
24
|
+
|
25
|
+
injections = {
|
26
|
+
Activity::Railway.Inject() => {
|
27
|
+
"#{contract_path}.class": ->(*) { constant }, # default to {constant} if not injected.
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
# DISCUSS: can we force-default this via Inject()?
|
32
|
+
input = {
|
33
|
+
Activity::Railway.In() => ->(ctx, **) do
|
34
|
+
ctx.to_hash.merge(
|
35
|
+
constant: constant,
|
36
|
+
name: contract_path
|
37
|
+
)
|
38
|
+
end
|
39
|
+
}
|
40
|
+
|
41
|
+
output = {
|
42
|
+
Activity::Railway.Out() => [contract_path]
|
43
|
+
}
|
44
|
+
|
45
|
+
default_contract_builder = ->(ctx, model: nil, **) { ctx[:"#{contract_path}.class"].new(model) }
|
46
|
+
|
47
|
+
# proc is called via {Option()}.
|
48
|
+
task_option_proc = builder ? builder : default_contract_builder
|
49
|
+
|
50
|
+
# after the builder proc is run, assign its result to {:"contract.default"}.
|
51
|
+
ctx_assign_block = ->(result, ctx) { ctx[contract_path] = result }
|
52
|
+
|
53
|
+
task = CircuitTaskWithResultProcessing.new(Trailblazer::Option(task_option_proc), task_option_proc, ctx_assign_block)
|
54
|
+
|
55
|
+
{
|
56
|
+
task: task, id: "contract.build",
|
57
|
+
}.
|
58
|
+
merge(injections).
|
59
|
+
merge(input).
|
60
|
+
merge(output)
|
61
|
+
end
|
62
|
+
|
63
|
+
module DSL
|
64
|
+
def self.extended(extender)
|
65
|
+
extender.extend(ClassDependencies)
|
66
|
+
warn "[Trailblazer] Using `contract do...end` is deprecated. Please use a form class and the Builder( constant: <Form> ) option."
|
67
|
+
end
|
68
|
+
|
69
|
+
# This is the class level DSL method.
|
70
|
+
# Op.contract #=> returns contract class
|
71
|
+
# Op.contract do .. end # defines contract
|
72
|
+
# Op.contract CommentForm # copies (and subclasses) external contract.
|
73
|
+
# Op.contract CommentForm do .. end # copies and extends contract.
|
74
|
+
def contract(name = :default, constant = nil, base: Reform::Form, &block)
|
75
|
+
heritage.record(:contract, name, constant, &block)
|
76
|
+
|
77
|
+
path, form_class = Trailblazer::DSL::Build.new.(
|
78
|
+
{prefix: :contract, class: base, container: self},
|
79
|
+
name, constant, block
|
80
|
+
)
|
81
|
+
|
82
|
+
self[path] = form_class
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
module Macro
|
3
|
+
module Contract
|
4
|
+
# result.contract = {..}
|
5
|
+
# result.contract.errors = {..}
|
6
|
+
# Deviate to left track if optional key is not found in params.
|
7
|
+
# Deviate to left if validation result falsey.
|
8
|
+
def self.Validate(skip_extract: false, name: "default", representer: false, key: nil, constant: nil, invalid_data_terminus: false) # DISCUSS: should we introduce something like Validate::Deserializer?
|
9
|
+
contract_path = :"contract.#{name}" # the contract instance
|
10
|
+
params_path = :"contract.#{name}.params" # extract_params! save extracted params here.
|
11
|
+
key_path = :"contract.#{name}.extract_key"
|
12
|
+
|
13
|
+
extract = Validate::Extract.new(key_path: key_path, params_path: params_path)
|
14
|
+
validate = Validate.new(name: name, representer: representer, params_path: params_path, contract_path: contract_path)
|
15
|
+
|
16
|
+
# These are defaulting dependency injection, more here
|
17
|
+
# https://trailblazer.to/2.1/docs/activity.html#activity-dependency-injection-inject-defaulting
|
18
|
+
extract_injections = {key_path => ->(*) { key }} # default to {key} if not injected.
|
19
|
+
validate_injections = {contract_path => ->(*) { constant }} # default the contract instance to {constant}, if not injected (or passed down from {Build()})
|
20
|
+
|
21
|
+
# Build a simple Railway {Activity} for the internal flow.
|
22
|
+
activity = Class.new(Activity::Railway(name: "Contract::Validate")) do
|
23
|
+
step extract, id: "#{params_path}_extract", Output(:failure) => End(:extract_failure), Activity::Railway.Inject() => extract_injections unless skip_extract# || representer
|
24
|
+
step validate, id: "contract.#{name}.call", Activity::Railway.Inject() => validate_injections
|
25
|
+
end
|
26
|
+
|
27
|
+
options = activity.Subprocess(activity)
|
28
|
+
options = options.merge(id: "contract.#{name}.validate")
|
29
|
+
|
30
|
+
# Deviate End.extract_failure to the standard failure track as a default. This can be changed from the user side.
|
31
|
+
options = options.merge(activity.Output(:extract_failure) => activity.Track(:failure)) unless skip_extract
|
32
|
+
|
33
|
+
# Halt failure track to End with {contract.name.invalid}.
|
34
|
+
options = options.merge(activity.Output(:failure) => activity.End(:invalid_data)) if invalid_data_terminus
|
35
|
+
|
36
|
+
options
|
37
|
+
end
|
38
|
+
|
39
|
+
class Validate
|
40
|
+
# Task: extract the contract's input from params by reading `:key`.
|
41
|
+
class Extract
|
42
|
+
def initialize(key_path: nil, params_path: nil)
|
43
|
+
@key_path, @params_path = key_path, params_path
|
44
|
+
end
|
45
|
+
|
46
|
+
def call(ctx, params: {}, **)
|
47
|
+
key = ctx[@key_path] # e.g. {:song}.
|
48
|
+
ctx[@params_path] = key ? params[key] : params
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(name: "default", representer: false, params_path: nil, contract_path: )
|
53
|
+
@name, @representer, @params_path, @contract_path = name, representer, params_path, contract_path
|
54
|
+
end
|
55
|
+
|
56
|
+
# Task: Validates contract `:name`.
|
57
|
+
def call(ctx, **)
|
58
|
+
validate!(
|
59
|
+
ctx,
|
60
|
+
representer: ctx[:"representer.#{@name}.class"] ||= @representer, # FIXME: maybe @representer should use DI.
|
61
|
+
params_path: @params_path
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
def validate!(ctx, representer: false, from: :document, params_path: nil)
|
66
|
+
contract = ctx[@contract_path] # grab contract instance from "contract.default" (usually set in {Contract::Build()})
|
67
|
+
|
68
|
+
# this is for 1.1-style compatibility and should be removed once we have Deserializer in place:
|
69
|
+
ctx[:"result.#{@contract_path}"] = result =
|
70
|
+
if representer
|
71
|
+
# use :document as the body and let the representer deserialize to the contract.
|
72
|
+
# this will be simplified once we have Deserializer.
|
73
|
+
# translates to contract.("{document: bla}") { MyRepresenter.new(contract).from_json .. }
|
74
|
+
contract.(ctx[from]) { |document| representer.new(contract).parse(document) }
|
75
|
+
else
|
76
|
+
# let Reform handle the deserialization.
|
77
|
+
contract.(ctx[params_path])
|
78
|
+
end
|
79
|
+
|
80
|
+
result.success?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end # Macro
|
85
|
+
end
|
@@ -1,5 +1,17 @@
|
|
1
|
-
require "
|
2
|
-
require "trailblazer/
|
3
|
-
|
4
|
-
require "trailblazer/
|
5
|
-
require "trailblazer/
|
1
|
+
require "trailblazer/activity"
|
2
|
+
require "trailblazer/activity/dsl/linear"
|
3
|
+
|
4
|
+
require "trailblazer/macro/contract/build"
|
5
|
+
require "trailblazer/macro/contract/validate"
|
6
|
+
require "trailblazer/macro/contract/persist"
|
7
|
+
|
8
|
+
module Trailblazer
|
9
|
+
module Macro
|
10
|
+
module Contract
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# All macros sit in the {Trailblazer::Macro::Contract} namespace, where we forward calls from
|
15
|
+
# operations and activities to.
|
16
|
+
Activity::DSL::Linear::Helper::Constants::Contract = Macro::Contract
|
17
|
+
end
|
data/test/docs/contract_test.rb
CHANGED
@@ -57,17 +57,21 @@ class DocsContractOverviewTest < Minitest::Spec
|
|
57
57
|
it "shows 2-level tracing" do
|
58
58
|
result = Create.trace( params: { length: "A" } )
|
59
59
|
result.wtf.gsub(/0x\w+/, "").must_equal %{`-- DocsContractOverviewTest::Create
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
60
|
+
|-- Start.default
|
61
|
+
|-- model.build
|
62
|
+
|-- contract.build
|
63
|
+
|-- contract.default.validate
|
64
|
+
| |-- Start.default
|
65
|
+
| |-- contract.default.params_extract
|
66
|
+
| |-- contract.default.call
|
67
|
+
| `-- End.failure
|
68
|
+
`-- End.failure}
|
69
|
+
end
|
70
|
+
|
71
|
+
# internal variables from {:builder} are excluded in public ctx.
|
72
|
+
it { Create.(params: {}).keys.inspect.must_equal %{[:params, :model, :\"result.model\", :\"contract.default\", :\"contract.default.params\", :\"representer.default.class\", :\"result.contract.default\"]} }
|
70
73
|
end
|
74
|
+
|
71
75
|
#---
|
72
76
|
# contract MyContract
|
73
77
|
class DocsContractExplicitTest < Minitest::Spec
|
@@ -257,6 +261,42 @@ class DocsContractKeyTest < Minitest::Spec
|
|
257
261
|
end
|
258
262
|
end
|
259
263
|
|
264
|
+
#---
|
265
|
+
#- Validate() with injected {:"contract.default.extract_key"}
|
266
|
+
class DocsContractInjectedKeyTest < Minitest::Spec
|
267
|
+
Song = Class.new(ContractConstantTest::Song)
|
268
|
+
|
269
|
+
module Song::Contract
|
270
|
+
Create = ContractConstantTest::Song::Contract::Create
|
271
|
+
end
|
272
|
+
|
273
|
+
#:inject-key-op
|
274
|
+
class Song::Create < Trailblazer::Operation
|
275
|
+
#~meths
|
276
|
+
step Model(Song, :new)
|
277
|
+
step Contract::Build(constant: Song::Contract::Create)
|
278
|
+
#~meths end
|
279
|
+
step Contract::Validate() # we don't define a key here! E.g. {key: "song"}
|
280
|
+
step Contract::Persist()
|
281
|
+
end
|
282
|
+
#:inject-key-op end
|
283
|
+
|
284
|
+
# empty {:params}, validation fails
|
285
|
+
it { Song::Create.(params: {}).inspect(:model, "result.contract.default.extract").must_equal %{<Result:false [#<struct DocsContractInjectedKeyTest::Song title=nil, length=nil>, nil] >} }
|
286
|
+
# no {:key} injected/defined, we don't find the data in {params}.
|
287
|
+
it { Song::Create.(params: {"song" => { title: "SVG", length: 13 }}).inspect(:model, "result.contract.default.extract").must_equal %{<Result:false [#<struct DocsContractInjectedKeyTest::Song title=nil, length=nil>, nil] >} }
|
288
|
+
# {:key} defined and everything works smoothly
|
289
|
+
it {
|
290
|
+
params = {"song" => { title: "SVG", length: 13 }}
|
291
|
+
#:inject-key-call
|
292
|
+
res = Song::Create.(
|
293
|
+
params: params,
|
294
|
+
"contract.default.extract_key": "song"
|
295
|
+
)
|
296
|
+
#:inject-key-call end
|
297
|
+
res.inspect(:model).must_equal %{<Result:true [#<struct DocsContractInjectedKeyTest::Song title=\"SVG\", length=13>] >} }
|
298
|
+
end
|
299
|
+
|
260
300
|
#---
|
261
301
|
#- Validate( key: :song ), Output(:extract_failure) => End(:my_new_end)
|
262
302
|
class DocsContractKeyWithOutputTest < Minitest::Spec
|
@@ -297,6 +337,77 @@ class DocsContractKeyWithOutputTest < Minitest::Spec
|
|
297
337
|
end
|
298
338
|
end
|
299
339
|
|
340
|
+
#---
|
341
|
+
#- Validate() with injected {:"contract.default"} and no `Build()`.
|
342
|
+
class DocsContractInjectedContractTest < Minitest::Spec
|
343
|
+
Song = Class.new(ContractConstantTest::Song)
|
344
|
+
|
345
|
+
module Song::Contract
|
346
|
+
Create = ContractConstantTest::Song::Contract::Create
|
347
|
+
end
|
348
|
+
|
349
|
+
#:inject-contract-op
|
350
|
+
class Song::Create < Trailblazer::Operation
|
351
|
+
# we omit the {Model()} call as the run-time contract contains the model.
|
352
|
+
# we don't have a {Contract::Build()} step here.
|
353
|
+
step Contract::Validate(key: "song") # you could use an injection here, too!
|
354
|
+
step Contract::Persist()
|
355
|
+
end
|
356
|
+
#:inject-contract-op end
|
357
|
+
|
358
|
+
it {
|
359
|
+
params = {"song" => { title: "SVG", length: 13 }}
|
360
|
+
#:inject-contract-call
|
361
|
+
res = Song::Create.(
|
362
|
+
params: params,
|
363
|
+
"contract.default": Song::Contract::Create.new(Song.new) # we build the contract ourselves!
|
364
|
+
)
|
365
|
+
#:inject-contract-call end
|
366
|
+
res.inspect(:model).must_equal %{<Result:true [nil] >}
|
367
|
+
res[:"contract.default"].model.inspect.must_equal %{#<struct DocsContractInjectedContractTest::Song title=\"SVG\", length=13>}
|
368
|
+
}
|
369
|
+
end
|
370
|
+
|
371
|
+
#---
|
372
|
+
#- Validate( name: "default", invalid_data_terminus: true )
|
373
|
+
class DocsContractInvalidEndTest < Minitest::Spec
|
374
|
+
Song = Class.new(ContractConstantTest::Song)
|
375
|
+
|
376
|
+
module Song::Contract
|
377
|
+
Create = ContractConstantTest::Song::Contract::Create
|
378
|
+
end
|
379
|
+
|
380
|
+
#:invalid-end
|
381
|
+
class Song::Create < Trailblazer::Operation
|
382
|
+
step Model( Song, :new )
|
383
|
+
step Contract::Build( constant: Song::Contract::Create )
|
384
|
+
step Contract::Validate( key: :song, invalid_data_terminus: true )
|
385
|
+
step Contract::Persist( )
|
386
|
+
end
|
387
|
+
#:invalid-end end
|
388
|
+
|
389
|
+
it do
|
390
|
+
result = Song::Create.(params: {song: { title: nil, length: nil }})
|
391
|
+
result.event.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:invalid_data>}
|
392
|
+
end
|
393
|
+
|
394
|
+
it { Song::Create.(params: {song: { title: "SVG", length: 13 }}).inspect(:model).must_equal %{<Result:true [#<struct DocsContractInvalidEndTest::Song title=\"SVG\", length=13>] >} }
|
395
|
+
|
396
|
+
it do
|
397
|
+
#:invalid-end-res
|
398
|
+
result = Song::Create.(params: { title: "Rising Force", length: 13 })
|
399
|
+
result.success? #=> false
|
400
|
+
result.event #=> #<Trailblazer::Activity::End semantic=:"contract.default.invalid">
|
401
|
+
#:invalid-end-res end
|
402
|
+
|
403
|
+
#:invalid-end-res-false
|
404
|
+
result = Song::Create.(params: { "song" => { title: "Rising Force", length: 13 } })
|
405
|
+
result.success? #=> true
|
406
|
+
#:invalid-end-res-false end
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
|
300
411
|
#- Contract::Build[ constant: XXX, name: AAA ]
|
301
412
|
class ContractNamedConstantTest < Minitest::Spec
|
302
413
|
Song = Class.new(ContractConstantTest::Song)
|
@@ -337,24 +448,24 @@ class ContractInjectConstantTest < Minitest::Spec
|
|
337
448
|
end
|
338
449
|
#:di-constant-contract end
|
339
450
|
#:di-constant
|
340
|
-
class Create < Trailblazer::Operation
|
341
|
-
step Model(
|
342
|
-
step Contract::Build()
|
451
|
+
class Song::Create < Trailblazer::Operation
|
452
|
+
step Model(Song, :new)
|
453
|
+
step Contract::Build() # no constant provided here!
|
343
454
|
step Contract::Validate()
|
344
|
-
step Contract::Persist(
|
455
|
+
step Contract::Persist(method: :sync)
|
345
456
|
end
|
346
457
|
#:di-constant end
|
347
458
|
|
348
459
|
it do
|
349
460
|
#:di-contract-call
|
350
|
-
Create.(
|
351
|
-
params:
|
352
|
-
|
461
|
+
Song::Create.(
|
462
|
+
params: { title: "Anthony's Song" },
|
463
|
+
"contract.default.class": MyContract # dependency injection!
|
353
464
|
)
|
354
465
|
#:di-contract-call end
|
355
466
|
end
|
356
|
-
it { Create.(params: { title: "A" }, :"contract.default.class" => MyContract).inspect(:model).must_equal %{<Result:false [#<struct ContractInjectConstantTest::Song id=nil, title=nil>] >} }
|
357
|
-
it { Create.(params: { title: "Anthony's Song" }, :"contract.default.class" => MyContract).inspect(:model).must_equal %{<Result:true [#<struct ContractInjectConstantTest::Song id=nil, title="Anthony's Song">] >} }
|
467
|
+
it { Song::Create.(params: { title: "A" }, :"contract.default.class" => MyContract).inspect(:model).must_equal %{<Result:false [#<struct ContractInjectConstantTest::Song id=nil, title=nil>] >} }
|
468
|
+
it { Song::Create.(params: { title: "Anthony's Song" }, :"contract.default.class" => MyContract).inspect(:model).must_equal %{<Result:true [#<struct ContractInjectConstantTest::Song id=nil, title="Anthony's Song">] >} }
|
358
469
|
end
|
359
470
|
|
360
471
|
class DryValidationContractTest < Minitest::Spec
|
@@ -367,16 +478,20 @@ class DryValidationContractTest < Minitest::Spec
|
|
367
478
|
class Create < Trailblazer::Operation
|
368
479
|
# contract to verify params formally.
|
369
480
|
class MyContract < Reform::Form
|
370
|
-
feature
|
481
|
+
feature Dry
|
371
482
|
property :id
|
372
483
|
property :title
|
373
484
|
|
374
485
|
validation name: :default do
|
375
|
-
|
486
|
+
params do
|
487
|
+
required(:id).filled
|
488
|
+
end
|
376
489
|
end
|
377
490
|
|
378
491
|
validation name: :extra, if: :default do
|
379
|
-
|
492
|
+
params do
|
493
|
+
required(:title).filled(min_size?: 2)
|
494
|
+
end
|
380
495
|
end
|
381
496
|
end
|
382
497
|
#~form end
|
@@ -402,28 +517,47 @@ class DryValidationContractTest < Minitest::Spec
|
|
402
517
|
it { Create.(params: { id: 1, title: "Y" }).inspect(:model).must_equal %{<Result:false [#<struct DryValidationContractTest::Song id=nil, title=nil>] >} }
|
403
518
|
it { Create.(params: { id: 1, title: "Yo" }).inspect(:model).must_equal %{<Result:true [#<struct DryValidationContractTest::Song id=1, title="Yo">] >} }
|
404
519
|
|
405
|
-
|
520
|
+
##---
|
406
521
|
# Contract::Validate(constant: DrySchema)
|
407
|
-
class OpWithSchema < Trailblazer::Operation
|
408
|
-
Schema = Dry::Validation.Schema do
|
409
|
-
required(:title).filled
|
410
|
-
end
|
411
522
|
|
412
|
-
|
413
|
-
|
523
|
+
#:dry-schema-contract
|
524
|
+
module Song::Operation
|
525
|
+
class Archive < Trailblazer::Operation
|
526
|
+
Schema = Dry::Validation.Contract do
|
527
|
+
params do
|
528
|
+
required(:id).filled
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
# step Model(Song, :new) # You don't need {ctx[:model]}.
|
533
|
+
step Contract::Validate(constant: Schema, key: :song) # Your validation.
|
534
|
+
#~methods
|
535
|
+
# step Contract::Persist() # this is not possible!
|
536
|
+
#~methods end
|
537
|
+
end
|
414
538
|
end
|
539
|
+
#:dry-schema-contract end
|
415
540
|
|
416
541
|
# success
|
417
|
-
it {
|
542
|
+
it { _(Song::Operation::Archive.(params: {song: {id: "SVG"}}).success?).must_equal true }
|
418
543
|
# failure
|
419
|
-
it {
|
544
|
+
it { _(Song::Operation::Archive.(params: {song: {id: nil}}).success?).must_equal false }
|
545
|
+
# shows error messages
|
420
546
|
it "shows error messages" do
|
421
|
-
|
547
|
+
#:dry-contract-call
|
548
|
+
result = Song::Operation::Archive.(params: {song: {id: nil}})
|
549
|
+
#:dry-contract-call end
|
550
|
+
|
551
|
+
_(result[:"result.contract.default"].errors.inspect).must_equal %{#<Dry::Validation::MessageSet messages=[#<Dry::Schema::Message text=\"must be filled\" path=[:id] predicate=:filled? input=nil>] options={:source=>[#<Dry::Schema::Message text=\"must be filled\" path=[:id] predicate=:filled? input=nil>], :hints=>false}>}
|
422
552
|
|
423
|
-
result[:"result.contract.default"].errors.
|
553
|
+
# raise result[:"result.contract.default"].errors.messages[0].to_s.inspect
|
554
|
+
assert_equal result[:"result.contract.default"].errors[:id].inspect, %{["must be filled"]}
|
555
|
+
#:dry-contract-result
|
556
|
+
result[:"result.contract.default"].errors[:id] #=> ["must be filled"]
|
557
|
+
#:dry-contract-result end
|
424
558
|
end
|
425
559
|
# key not found
|
426
|
-
it {
|
560
|
+
it { _(Song::Operation::Archive.(params: {}).success?).must_equal false }
|
427
561
|
end
|
428
562
|
|
429
563
|
class DocContractBuilderTest < Minitest::Spec
|
@@ -459,6 +593,8 @@ class DocContractBuilderTest < Minitest::Spec
|
|
459
593
|
|
460
594
|
it { Create.(params: {}).inspect(:model).must_equal %{<Result:false [#<struct DocContractBuilderTest::Song id=nil, title=nil>] >} }
|
461
595
|
it { Create.(params: { title: "title"}, current_user: Module).inspect(:model).must_equal %{<Result:true [#<struct DocContractBuilderTest::Song id=nil, title="title">] >} }
|
596
|
+
# internal variables from {:builder} are excluded in public ctx.
|
597
|
+
it { Create.(params: {}).keys.inspect.must_equal %{[:params, :model, :\"result.model\", :\"contract.default\", :\"contract.default.params\", :\"representer.default.class\", :\"result.contract.default\"]} }
|
462
598
|
end
|
463
599
|
|
464
600
|
class DocContractTest < Minitest::Spec
|
@@ -501,3 +637,20 @@ class DocContractTest < Minitest::Spec
|
|
501
637
|
|
502
638
|
it { Break.(params: { id:1, title: "Fame" }).inspect(:model).must_equal %{<Result:true [#<struct DocContractTest::Song id=1, title=nil>] >} }
|
503
639
|
end
|
640
|
+
|
641
|
+
class ModelMissingTest < Minitest::Spec
|
642
|
+
class Create < Trailblazer::Operation
|
643
|
+
class MyContract < Reform::Form
|
644
|
+
property :duration, virtual: true
|
645
|
+
end
|
646
|
+
|
647
|
+
step Contract::Build(constant: MyContract)
|
648
|
+
step Contract::Validate()
|
649
|
+
end
|
650
|
+
|
651
|
+
it do
|
652
|
+
result = Create.(params: {duration: 18})
|
653
|
+
assert_equal true, result.success?
|
654
|
+
assert_equal 18, result[:"contract.default"].duration
|
655
|
+
end
|
656
|
+
end
|
data/test/docs/dry_test.rb
CHANGED
@@ -1,33 +1,33 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
require "dry/container"
|
1
|
+
# require "test_helper"
|
2
|
+
# require "dry/container"
|
3
3
|
|
4
|
-
class DryContainerTest < Minitest::Spec
|
5
|
-
|
4
|
+
# class DryContainerTest < Minitest::Spec
|
5
|
+
# Song = Struct.new(:id, :title)
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
# class MyContract < Reform::Form
|
8
|
+
# property :title
|
9
|
+
# validates :title, length: 2..33
|
10
|
+
# end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
# my_container = Dry::Container.new
|
13
|
+
# my_container.register("contract.default.class", MyContract)
|
14
|
+
# # my_container.namespace("contract") do
|
15
|
+
# # register("create") { Array }
|
16
|
+
# # end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
# #---
|
19
|
+
# #- dependency injection
|
20
|
+
# #- with Dry-container
|
21
|
+
# class Create < Trailblazer::Operation
|
22
|
+
# extend Trailblazer::Operation::Container
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
# step Model(Song, :new)
|
25
|
+
# step Contract::Build()
|
26
|
+
# step Contract::Validate()
|
27
|
+
# step Contract::Persist(method: :sync)
|
28
|
+
# end
|
29
|
+
# #:key end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
end
|
31
|
+
# it { Create.({params: {title: "A" } }, my_container).inspect(:model).must_equal %{<Result:false [#<struct DryContainerTest::Song id=nil, title=nil>] >} }
|
32
|
+
# it { Create.({params: {title: "Anthony's Song" } }, my_container).inspect(:model).must_equal %{<Result:true [#<struct DryContainerTest::Song id=nil, title="Anthony's Song">] >} }
|
33
|
+
# end
|
@@ -59,6 +59,8 @@ class ContractTest < Minitest::Spec
|
|
59
59
|
it { Upsert.(params: {song: { title: nil }}).success?.must_equal false }
|
60
60
|
# key not found
|
61
61
|
it { Upsert.(params: {}).success?.must_equal false }
|
62
|
+
# no params passed
|
63
|
+
it { Upsert.().success?.must_equal false }
|
62
64
|
|
63
65
|
#---
|
64
66
|
# contract.default.params gets set (TODO: change in 2.1)
|
data/test/test_helper.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
require "trailblazer-macro-contract"
|
3
|
+
|
1
4
|
require "pp"
|
2
5
|
require 'delegate'
|
6
|
+
require "trailblazer/operation"
|
3
7
|
require "trailblazer/developer"
|
4
8
|
require "trailblazer/macro"
|
5
|
-
require "trailblazer-macro-contract"
|
6
|
-
require "trailblazer/developer/render/linear"
|
7
9
|
require "minitest/autorun"
|
8
10
|
|
9
11
|
# TODO: convert tests to non-rails.
|
@@ -7,8 +7,8 @@ Gem::Specification.new do |spec|
|
|
7
7
|
spec.version = Trailblazer::Version::Macro::Contract::VERSION
|
8
8
|
spec.authors = ["Nick Sutterer"]
|
9
9
|
spec.email = ["apotonick@gmail.com"]
|
10
|
-
spec.description = '
|
11
|
-
spec.summary = 'Macros for form
|
10
|
+
spec.description = 'Operation macros for form objects'
|
11
|
+
spec.summary = 'Macros for form objects: Build, Validate, Persist'
|
12
12
|
spec.homepage = "http://trailblazer.to"
|
13
13
|
spec.license = "LGPL-3.0"
|
14
14
|
|
@@ -21,13 +21,16 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.add_dependency "reform", ">= 2.2.0", "< 3.0.0"
|
22
22
|
|
23
23
|
spec.add_development_dependency "bundler"
|
24
|
-
spec.add_development_dependency "dry-validation"
|
24
|
+
spec.add_development_dependency "dry-validation"
|
25
25
|
spec.add_development_dependency "reform-rails", "~> 0.2.0.rc2"
|
26
|
-
spec.add_development_dependency "trailblazer-macro", ">= 2.1.
|
26
|
+
spec.add_development_dependency "trailblazer-macro", ">= 2.1.9"
|
27
27
|
spec.add_development_dependency "trailblazer-developer"
|
28
|
+
spec.add_development_dependency "activemodel", "~> 6.0.0" # FIXME: we still don't support the Rails 6.1 errors object.
|
28
29
|
|
29
30
|
spec.add_development_dependency "minitest"
|
30
31
|
spec.add_development_dependency "rake"
|
31
32
|
|
33
|
+
spec.add_dependency "trailblazer-activity-dsl-linear", ">= 1.0.0.beta1", "< 1.1.0"
|
34
|
+
|
32
35
|
spec.required_ruby_version = ">= 2.0.0"
|
33
36
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trailblazer-macro-contract
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.
|
4
|
+
version: 2.1.3.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-07-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: reform
|
@@ -48,16 +48,16 @@ dependencies:
|
|
48
48
|
name: dry-validation
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
|
-
- -
|
51
|
+
- - ">="
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: 0
|
53
|
+
version: '0'
|
54
54
|
type: :development
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
57
|
requirements:
|
58
|
-
- -
|
58
|
+
- - ">="
|
59
59
|
- !ruby/object:Gem::Version
|
60
|
-
version: 0
|
60
|
+
version: '0'
|
61
61
|
- !ruby/object:Gem::Dependency
|
62
62
|
name: reform-rails
|
63
63
|
requirement: !ruby/object:Gem::Requirement
|
@@ -78,20 +78,14 @@ dependencies:
|
|
78
78
|
requirements:
|
79
79
|
- - ">="
|
80
80
|
- !ruby/object:Gem::Version
|
81
|
-
version: 2.1.
|
82
|
-
- - "<"
|
83
|
-
- !ruby/object:Gem::Version
|
84
|
-
version: 2.2.0
|
81
|
+
version: 2.1.9
|
85
82
|
type: :development
|
86
83
|
prerelease: false
|
87
84
|
version_requirements: !ruby/object:Gem::Requirement
|
88
85
|
requirements:
|
89
86
|
- - ">="
|
90
87
|
- !ruby/object:Gem::Version
|
91
|
-
version: 2.1.
|
92
|
-
- - "<"
|
93
|
-
- !ruby/object:Gem::Version
|
94
|
-
version: 2.2.0
|
88
|
+
version: 2.1.9
|
95
89
|
- !ruby/object:Gem::Dependency
|
96
90
|
name: trailblazer-developer
|
97
91
|
requirement: !ruby/object:Gem::Requirement
|
@@ -106,6 +100,20 @@ dependencies:
|
|
106
100
|
- - ">="
|
107
101
|
- !ruby/object:Gem::Version
|
108
102
|
version: '0'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: activemodel
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 6.0.0
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: 6.0.0
|
109
117
|
- !ruby/object:Gem::Dependency
|
110
118
|
name: minitest
|
111
119
|
requirement: !ruby/object:Gem::Requirement
|
@@ -134,18 +142,38 @@ dependencies:
|
|
134
142
|
- - ">="
|
135
143
|
- !ruby/object:Gem::Version
|
136
144
|
version: '0'
|
137
|
-
|
145
|
+
- !ruby/object:Gem::Dependency
|
146
|
+
name: trailblazer-activity-dsl-linear
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: 1.0.0.beta1
|
152
|
+
- - "<"
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: 1.1.0
|
155
|
+
type: :runtime
|
156
|
+
prerelease: false
|
157
|
+
version_requirements: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: 1.0.0.beta1
|
162
|
+
- - "<"
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: 1.1.0
|
165
|
+
description: Operation macros for form objects
|
138
166
|
email:
|
139
167
|
- apotonick@gmail.com
|
140
168
|
executables: []
|
141
169
|
extensions: []
|
142
170
|
extra_rdoc_files: []
|
143
171
|
files:
|
172
|
+
- ".github/workflows/ci.yml"
|
144
173
|
- ".gitignore"
|
145
174
|
- ".rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml"
|
146
175
|
- ".rubocop.yml"
|
147
176
|
- ".rubocop_todo.yml"
|
148
|
-
- ".travis.yml"
|
149
177
|
- CHANGES.md
|
150
178
|
- COMM-LICENSE
|
151
179
|
- Gemfile
|
@@ -154,10 +182,10 @@ files:
|
|
154
182
|
- Rakefile
|
155
183
|
- lib/trailblazer-macro-contract.rb
|
156
184
|
- lib/trailblazer/macro/contract.rb
|
185
|
+
- lib/trailblazer/macro/contract/build.rb
|
186
|
+
- lib/trailblazer/macro/contract/persist.rb
|
187
|
+
- lib/trailblazer/macro/contract/validate.rb
|
157
188
|
- lib/trailblazer/macro/contract/version.rb
|
158
|
-
- lib/trailblazer/operation/contract.rb
|
159
|
-
- lib/trailblazer/operation/persist.rb
|
160
|
-
- lib/trailblazer/operation/validate.rb
|
161
189
|
- test/docs/contract_test.rb
|
162
190
|
- test/docs/dry_test.rb
|
163
191
|
- test/operation/contract_test.rb
|
@@ -179,15 +207,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
179
207
|
version: 2.0.0
|
180
208
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
181
209
|
requirements:
|
182
|
-
- - "
|
210
|
+
- - ">"
|
183
211
|
- !ruby/object:Gem::Version
|
184
|
-
version:
|
212
|
+
version: 1.3.1
|
185
213
|
requirements: []
|
186
|
-
|
187
|
-
rubygems_version: 2.7.6
|
214
|
+
rubygems_version: 3.2.3
|
188
215
|
signing_key:
|
189
216
|
specification_version: 4
|
190
|
-
summary: 'Macros for form
|
217
|
+
summary: 'Macros for form objects: Build, Validate, Persist'
|
191
218
|
test_files:
|
192
219
|
- test/docs/contract_test.rb
|
193
220
|
- test/docs/dry_test.rb
|
data/.travis.yml
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
module Trailblazer
|
2
|
-
class Operation
|
3
|
-
module Contract
|
4
|
-
def self.Build(name: "default", constant: nil, builder: nil)
|
5
|
-
task = lambda do |(options, flow_options), **circuit_options|
|
6
|
-
result = Build.(options, circuit_options, name: name, constant: constant, builder: builder)
|
7
|
-
|
8
|
-
return Activity::TaskBuilder.binary_signal_for(result, Activity::Right, Activity::Left),
|
9
|
-
[options, flow_options]
|
10
|
-
end
|
11
|
-
|
12
|
-
{task: task, id: "contract.build"}
|
13
|
-
end
|
14
|
-
|
15
|
-
module Build
|
16
|
-
# Build contract at runtime.
|
17
|
-
def self.call(options, circuit_options, name: "default", constant: nil, builder: nil)
|
18
|
-
# TODO: we could probably clean this up a bit at some point.
|
19
|
-
contract_class = constant || options[:"contract.#{name}.class"] # DISCUSS: Injection possible here?
|
20
|
-
model = options[:model]
|
21
|
-
name = :"contract.#{name}"
|
22
|
-
|
23
|
-
options[name] = if builder
|
24
|
-
call_builder(options, circuit_options, builder: builder, constant: contract_class, name: name)
|
25
|
-
else
|
26
|
-
contract_class.new(model)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.call_builder(options, circuit_options, builder: raise, constant: raise, name: raise)
|
31
|
-
tmp_options = options.to_hash.merge(
|
32
|
-
constant: constant,
|
33
|
-
name: name
|
34
|
-
)
|
35
|
-
Trailblazer::Option(builder).(options, tmp_options, circuit_options)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
module DSL
|
40
|
-
def self.extended(extender)
|
41
|
-
extender.extend(ClassDependencies)
|
42
|
-
warn "[Trailblazer] Using `contract do...end` is deprecated. Please use a form class and the Builder( constant: <Form> ) option."
|
43
|
-
end
|
44
|
-
|
45
|
-
# This is the class level DSL method.
|
46
|
-
# Op.contract #=> returns contract class
|
47
|
-
# Op.contract do .. end # defines contract
|
48
|
-
# Op.contract CommentForm # copies (and subclasses) external contract.
|
49
|
-
# Op.contract CommentForm do .. end # copies and extends contract.
|
50
|
-
def contract(name = :default, constant = nil, base: Reform::Form, &block)
|
51
|
-
heritage.record(:contract, name, constant, &block)
|
52
|
-
|
53
|
-
path, form_class = Trailblazer::DSL::Build.new.(
|
54
|
-
{prefix: :contract, class: base, container: self},
|
55
|
-
name, constant, block
|
56
|
-
)
|
57
|
-
|
58
|
-
self[path] = form_class
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
@@ -1,75 +0,0 @@
|
|
1
|
-
module Trailblazer
|
2
|
-
class Operation
|
3
|
-
module Contract
|
4
|
-
# result.contract = {..}
|
5
|
-
# result.contract.errors = {..}
|
6
|
-
# Deviate to left track if optional key is not found in params.
|
7
|
-
# Deviate to left if validation result falsey.
|
8
|
-
def self.Validate(skip_extract: false, name: "default", representer: false, key: nil, constant: nil) # DISCUSS: should we introduce something like Validate::Deserializer?
|
9
|
-
params_path = :"contract.#{name}.params" # extract_params! save extracted params here.
|
10
|
-
|
11
|
-
extract = Validate::Extract.new(key: key, params_path: params_path).freeze
|
12
|
-
validate = Validate.new(name: name, representer: representer, params_path: params_path, constant: constant).freeze
|
13
|
-
|
14
|
-
# Build a simple Railway {Activity} for the internal flow.
|
15
|
-
activity = Class.new(Activity::Railway(name: "Contract::Validate")) do
|
16
|
-
step extract, id: "#{params_path}_extract", Output(:failure) => End(:extract_failure) unless skip_extract# || representer
|
17
|
-
step validate, id: "contract.#{name}.call"
|
18
|
-
end
|
19
|
-
|
20
|
-
options = activity.Subprocess(activity)
|
21
|
-
options = options.merge(id: "contract.#{name}.validate")
|
22
|
-
|
23
|
-
# Deviate End.extract_failure to the standard failure track as a default. This can be changed from the user side.
|
24
|
-
options = options.merge(activity.Output(:extract_failure) => activity.Track(:failure)) unless skip_extract
|
25
|
-
|
26
|
-
options
|
27
|
-
end
|
28
|
-
|
29
|
-
class Validate
|
30
|
-
# Task: extract the contract's input from params by reading `:key`.
|
31
|
-
class Extract
|
32
|
-
def initialize(key: nil, params_path: nil)
|
33
|
-
@key, @params_path = key, params_path
|
34
|
-
end
|
35
|
-
|
36
|
-
def call(ctx, params:, **)
|
37
|
-
ctx[@params_path] = @key ? params[@key] : params
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def initialize(name: "default", representer: false, params_path: nil, constant: nil)
|
42
|
-
@name, @representer, @params_path, @constant = name, representer, params_path, constant
|
43
|
-
end
|
44
|
-
|
45
|
-
# Task: Validates contract `:name`.
|
46
|
-
def call(ctx, **)
|
47
|
-
validate!(
|
48
|
-
ctx,
|
49
|
-
representer: ctx["representer.#{@name}.class"] ||= @representer, # FIXME: maybe @representer should use DI.
|
50
|
-
params_path: @params_path
|
51
|
-
)
|
52
|
-
end
|
53
|
-
|
54
|
-
def validate!(options, representer: false, from: :document, params_path: nil)
|
55
|
-
path = :"contract.#{@name}"
|
56
|
-
contract = @constant || options[path]
|
57
|
-
|
58
|
-
# this is for 1.1-style compatibility and should be removed once we have Deserializer in place:
|
59
|
-
options[:"result.#{path}"] = result =
|
60
|
-
if representer
|
61
|
-
# use :document as the body and let the representer deserialize to the contract.
|
62
|
-
# this will be simplified once we have Deserializer.
|
63
|
-
# translates to contract.("{document: bla}") { MyRepresenter.new(contract).from_json .. }
|
64
|
-
contract.(options[from]) { |document| representer.new(contract).parse(document) }
|
65
|
-
else
|
66
|
-
# let Reform handle the deserialization.
|
67
|
-
contract.(options[params_path])
|
68
|
-
end
|
69
|
-
|
70
|
-
result.success?
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end # Operation
|
75
|
-
end
|