trailblazer-endpoint 0.0.6 → 0.0.7
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/CHANGES.md +6 -0
- data/lib/trailblazer/endpoint.rb +41 -23
- data/lib/trailblazer/endpoint/controller.rb +5 -5
- data/lib/trailblazer/endpoint/dsl.rb +2 -0
- data/lib/trailblazer/endpoint/protocol.rb +2 -3
- data/lib/trailblazer/endpoint/protocol/cipher.rb +27 -0
- data/lib/trailblazer/endpoint/protocol/controller.rb +93 -0
- data/lib/trailblazer/endpoint/protocol/find_process_model.rb +15 -0
- data/lib/trailblazer/endpoint/version.rb +1 -1
- data/test/docs/controller_test.rb +2 -1
- data/test/rails-app/Gemfile +6 -0
- data/test/rails-app/Gemfile.lock +18 -4
- data/test/rails-app/app/controllers/application_controller/web.rb +3 -2
- data/test/rails-app/app/controllers/songs_controller.rb +174 -0
- data/test/rails-app/app/models/song.rb +4 -1
- data/test/rails-app/config/environments/test.rb +2 -0
- data/test/rails-app/config/routes.rb +10 -0
- data/test/rails-app/test/controllers/songs_controller_test.rb +69 -0
- data/trailblazer-endpoint.gemspec +1 -1
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d241ba658c264d5ec48e241c59abe89811646109f07a1f900231d7963245bcad
|
4
|
+
data.tar.gz: '0682d8ad85197f3049c481f3868ae87982991c913ffd8e5f3ae8185b2554a274'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c7b9c72993a46d19c81307a94423e732b7eb0d433526cb9d32e9f7e70e54fa4eb36e3cf062eb3d77eebc9372955c8853ddbbb2b13ef9a22e711c0c7b09a9cad
|
7
|
+
data.tar.gz: ac107aae848481b55309b9c4fc607dd42c505fc16c1a267657cba4852660ecd72c8de4dcc9036b8a09bcf99d0d7d4c75d6fca9a956ba53562b8769f2913801a3
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# 0.0.7
|
2
|
+
|
3
|
+
* BREAKING: Remove `:domain_ctx_filter` in favor of `Controller.insert_copy_to_domain_ctx!`.
|
4
|
+
* Add support for serializing `:suspend_data` and deserializing `:resume_data` so session data can get automatically encrypted and passed to the next action. This used to sit in `workflow`.
|
5
|
+
* Add `:find_process_model`. This introduces a new protocol step before `policy` to find the "process model" instead of letting the domain operation or even the policy (or both!) find the "current model".
|
6
|
+
|
1
7
|
# 0.0.6
|
2
8
|
|
3
9
|
* `Controller::endpoint` short form introduced.
|
data/lib/trailblazer/endpoint.rb
CHANGED
@@ -2,7 +2,12 @@ module Trailblazer
|
|
2
2
|
class Endpoint
|
3
3
|
# Create an {Endpoint} class with the provided adapter and protocol.
|
4
4
|
# This builder also sets up taskWrap filters around the {domain_activity} execution.
|
5
|
-
def self.build(protocol:, adapter:, domain_activity:, scope_domain_ctx: true,
|
5
|
+
def self.build(protocol:, adapter:, domain_activity:, scope_domain_ctx: true, protocol_block: ->(*) { Hash.new },
|
6
|
+
serialize: false, # TODO: plug-in, not hardcoded!
|
7
|
+
deserialize: false,# TODO: plug-in, not hardcoded!
|
8
|
+
find_process_model: false, # TODO: plug-in, not hardcoded!
|
9
|
+
deserialize_process_model_id_from_resume_data: false # TODO: plug-in, not hardcoded!
|
10
|
+
)
|
6
11
|
# special considerations around the {domain_activity} and its taskWrap:
|
7
12
|
#
|
8
13
|
# 1. domain_ctx_filter (e.g. to filter {current_user})
|
@@ -19,14 +24,41 @@ module Trailblazer
|
|
19
24
|
# scoping: {:domain_ctx} becomes ctx
|
20
25
|
extensions_options.merge!(Endpoint.options_for_scope_domain_ctx) if scope_domain_ctx # TODO: test flag
|
21
26
|
|
27
|
+
app_protocol = build_protocol(protocol, domain_activity: domain_activity, extensions_options: extensions_options, protocol_block: protocol_block, serialize: serialize, deserialize: deserialize,
|
28
|
+
find_process_model: find_process_model, deserialize_process_model_id_from_resume_data: deserialize_process_model_id_from_resume_data
|
29
|
+
)
|
30
|
+
|
31
|
+
# puts Trailblazer::Developer.render(app_protocol)
|
32
|
+
|
33
|
+
Class.new(adapter) do
|
34
|
+
step(Subprocess(app_protocol), {inherit: true, id: :protocol, replace: :protocol})
|
35
|
+
end # app_adapter
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
# @private
|
40
|
+
def self.build_protocol(protocol, domain_activity:, extensions_options:, protocol_block:, serialize:, deserialize:, find_process_model:, deserialize_process_model_id_from_resume_data:)
|
41
|
+
Class.new(protocol) do
|
42
|
+
if serialize
|
43
|
+
Protocol::Controller.insert_serialize_steps!(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
if deserialize
|
47
|
+
Protocol::Controller.insert_deserialize_steps!(self)
|
48
|
+
end
|
49
|
+
|
50
|
+
if serialize || deserialize
|
51
|
+
Protocol::Controller.insert_copy_to_domain_ctx!(self, :resume_data => :resume_data)
|
52
|
+
end
|
22
53
|
|
23
|
-
|
24
|
-
|
54
|
+
if find_process_model
|
55
|
+
Protocol::Controller.insert_find_process_model!(self, before: :policy) # TODO: test before: :policy
|
56
|
+
end
|
25
57
|
|
26
|
-
|
27
|
-
|
58
|
+
if deserialize_process_model_id_from_resume_data
|
59
|
+
pass Protocol::Controller.method(:deserialize_process_model_id_from_resume_data), after: :deserialize_resume_data, magnetic_to: :deserialize, Output(:success) => Track(:deserialize)
|
60
|
+
end
|
28
61
|
|
29
|
-
app_protocol = Class.new(protocol) do
|
30
62
|
step(Subprocess(domain_activity), {inherit: true, id: :domain_activity, replace: :domain_activity,
|
31
63
|
|
32
64
|
# FIXME: where does this go?
|
@@ -35,13 +67,6 @@ module Trailblazer
|
|
35
67
|
merge(instance_exec(&protocol_block)) # the block is evaluated in the {Protocol} context.
|
36
68
|
)
|
37
69
|
end
|
38
|
-
|
39
|
-
# puts Trailblazer::Developer.render(app_protocol)
|
40
|
-
|
41
|
-
Class.new(adapter) do
|
42
|
-
step(Subprocess(app_protocol), {inherit: true, id: :protocol, replace: :protocol})
|
43
|
-
end # app_adapter
|
44
|
-
|
45
70
|
end
|
46
71
|
|
47
72
|
def self.options_for_scope_domain_ctx()
|
@@ -51,16 +76,6 @@ module Trailblazer
|
|
51
76
|
}
|
52
77
|
end
|
53
78
|
|
54
|
-
def self.domain_ctx_filter(variables)
|
55
|
-
->(_ctx, ((ctx, a), b)) do # taskWrap interface
|
56
|
-
variables.each do |variable|
|
57
|
-
ctx[:domain_ctx][variable] = ctx[variable]
|
58
|
-
end
|
59
|
-
|
60
|
-
[_ctx, [[ctx, a], b]]
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
79
|
# Runtime
|
65
80
|
# Invokes the endpoint for you and runs one of the three outcome blocks.
|
66
81
|
def self.with_or_etc(activity, args, failure_block:, success_block:, protocol_failure_block:, invoke: Trailblazer::Activity::TaskWrap.method(:invoke))
|
@@ -146,3 +161,6 @@ require "trailblazer/endpoint/adapter"
|
|
146
161
|
require "trailblazer/endpoint/dsl"
|
147
162
|
require "trailblazer/endpoint/controller"
|
148
163
|
require "trailblazer/endpoint/options"
|
164
|
+
require "trailblazer/endpoint/protocol/controller"
|
165
|
+
require "trailblazer/endpoint/protocol/find_process_model"
|
166
|
+
require "trailblazer/endpoint/protocol/cipher"
|
@@ -92,18 +92,18 @@ module Trailblazer
|
|
92
92
|
extended.directive(:endpoints, ->(*) { {} })
|
93
93
|
end
|
94
94
|
|
95
|
+
# Builds and registers an endpoint in a controller class.
|
95
96
|
def endpoint(name, **options, &block)
|
96
97
|
options = options.merge(protocol_block: block) if block_given?
|
97
98
|
|
98
99
|
return generic_endpoint_config(**name, **options) if name.is_a?(Hash)
|
99
|
-
|
100
|
+
build_endpoint(name, **options)
|
100
101
|
end
|
101
102
|
|
102
|
-
|
103
|
+
# Configures generic {:adapter}, {:protocol}, etc.
|
104
|
+
def generic_endpoint_config(**options)
|
103
105
|
self.singleton_class.define_method :generic_options do |ctx,**|
|
104
106
|
{
|
105
|
-
protocol: protocol,
|
106
|
-
adapter: adapter,
|
107
107
|
**options
|
108
108
|
}
|
109
109
|
end
|
@@ -111,7 +111,7 @@ module Trailblazer
|
|
111
111
|
directive :generic_options, method(:generic_options) # FIXME: do we need this?
|
112
112
|
end
|
113
113
|
|
114
|
-
def
|
114
|
+
def build_endpoint(name, domain_activity: name, **options)
|
115
115
|
build_options = options_for(:generic_options, {}).merge(domain_activity: domain_activity, **options) # DISCUSS: why don't we add this as another directive option/step?
|
116
116
|
|
117
117
|
endpoint = Trailblazer::Endpoint.build(build_options)
|
@@ -19,9 +19,11 @@ module Trailblazer
|
|
19
19
|
# #call
|
20
20
|
def to_args(default_block_options)
|
21
21
|
return options,
|
22
|
+
default_block_options.merge( # this adds :invoke.
|
22
23
|
success_block: success_block || default_block_options[:success_block],
|
23
24
|
failure_block: failure_block || default_block_options[:failure_block],
|
24
25
|
protocol_failure_block: protocol_failure_block || default_block_options[:protocol_failure_block]
|
26
|
+
)
|
25
27
|
end
|
26
28
|
end
|
27
29
|
end
|
@@ -24,7 +24,7 @@ module Trailblazer
|
|
24
24
|
|
25
25
|
step :authenticate, Output(:failure) => _Path(semantic: :not_authenticated) do
|
26
26
|
# step :handle_not_authenticated
|
27
|
-
|
27
|
+
end
|
28
28
|
|
29
29
|
step :policy, Output(:failure) => _Path(semantic: :not_authorized) do # user from cookie, etc
|
30
30
|
# step :handle_not_authorized
|
@@ -113,7 +113,6 @@ module Trailblazer
|
|
113
113
|
[[Trailblazer::Activity::TaskWrap::Pipeline.method(:insert_after), "task_wrap.call_task", ["endpoint.end_signal", method(:terminus_handler)]]]
|
114
114
|
end
|
115
115
|
end
|
116
|
-
|
117
|
-
end
|
116
|
+
end # Protocol
|
118
117
|
end
|
119
118
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "openssl"
|
2
|
+
|
3
|
+
module Trailblazer
|
4
|
+
class Endpoint::Protocol
|
5
|
+
module Controller
|
6
|
+
module Cipher # FIXME: copied from Tyrant!
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def encrypt_value(ctx, value:, cipher_key:, **)
|
10
|
+
cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').encrypt
|
11
|
+
cipher.key = Digest::SHA1.hexdigest(cipher_key)[0..23] # ArgumentError: key must be 24 bytes
|
12
|
+
s = cipher.update(value) + cipher.final
|
13
|
+
|
14
|
+
ctx[:encrypted_value] = s.unpack('H*')[0].upcase
|
15
|
+
end
|
16
|
+
|
17
|
+
def decrypt_value(ctx, encrypted_value:, cipher_key:, **)
|
18
|
+
cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').decrypt
|
19
|
+
cipher.key = Digest::SHA1.hexdigest(cipher_key)[0..23]
|
20
|
+
s = [encrypted_value].pack("H*").unpack("C*").pack("c*")
|
21
|
+
|
22
|
+
ctx[:decrypted_value] = cipher.update(s) + cipher.final
|
23
|
+
end
|
24
|
+
end # Cipher
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Endpoint::Protocol
|
3
|
+
# Deserialize incoming state.
|
4
|
+
# Serialize outgoing state.
|
5
|
+
# What else?
|
6
|
+
module Controller
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def decrypt?(ctx, encrypted_resume_data:, **)
|
10
|
+
encrypted_resume_data
|
11
|
+
end
|
12
|
+
|
13
|
+
def deserialize_resume_data(ctx, decrypted_value:, **)
|
14
|
+
ctx[:resume_data] = JSON.parse(decrypted_value)
|
15
|
+
end
|
16
|
+
|
17
|
+
def deserialize_process_model?(ctx, process_model_from_resume_data:, **)
|
18
|
+
process_model_from_resume_data
|
19
|
+
end
|
20
|
+
|
21
|
+
def deserialize_process_model_id(ctx, resume_data:, **)
|
22
|
+
ctx[:process_model_id] = resume_data["id"] # DISCUSS: overriding {:process_model_id}?
|
23
|
+
end
|
24
|
+
|
25
|
+
def encrypt?(ctx, domain_ctx:, **)
|
26
|
+
ctx[:suspend_data] = domain_ctx[:suspend_data]
|
27
|
+
end
|
28
|
+
|
29
|
+
def serialize_suspend_data(ctx, suspend_data:, **)
|
30
|
+
ctx[:serialized_suspend_data] = JSON.dump(suspend_data)
|
31
|
+
end
|
32
|
+
|
33
|
+
def copy_suspend_data_to_endpoint_ctx(ctx, domain_ctx:, **)
|
34
|
+
ctx[:suspend_data] = domain_ctx[:suspend_data]
|
35
|
+
end
|
36
|
+
|
37
|
+
# FIXME: use Model() mechanics.
|
38
|
+
def deserialize_process_model_id_from_resume_data(ctx, resume_data:, **)
|
39
|
+
# DISCUSS: should we warn when overriding an existing {process_model_id}?
|
40
|
+
ctx[:process_model_id] = resume_data["id"] # DISCUSS: overriding {:process_model_id}? # FIXME: stolen from Advance___::Controller
|
41
|
+
end
|
42
|
+
|
43
|
+
def insert_deserialize_steps!(activity, deserialize_before: :policy)
|
44
|
+
activity.module_eval do
|
45
|
+
step Controller.method(:decrypt?), id: :decrypt?, before: deserialize_before # error out if no serialized_resume_data given.
|
46
|
+
step Controller::Cipher.method(:decrypt_value), id: :decrypt,
|
47
|
+
input: {cipher_key: :cipher_key, encrypted_resume_data: :encrypted_value} , before: deserialize_before,
|
48
|
+
# Output(:failure) => Track(:success),
|
49
|
+
Output(:success) => Path(connect_to: Track(:success), track_color: :deserialize, before: deserialize_before) do # usually, Path goes into {policy}
|
50
|
+
|
51
|
+
step Controller.method(:deserialize_resume_data), id: :deserialize_resume_data
|
52
|
+
# DISCUSS: unmarshall?
|
53
|
+
# step Controller.method(:deserialize_process_model_id?), id: :deserialize_process_model_id?, activity.Output(Trailblazer::Activity::Left, :failure) => activity.Id(around_activity_id)
|
54
|
+
# step Controller.method(:deserialize_process_model_id), id: :deserialize_process_model_id
|
55
|
+
|
56
|
+
step ->(*) { true } # FIXME: otherwise we can't insert an element AFTER :deserialize_resume_data
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def insert_serialize_steps!(activity, serialize_after: :domain_activity)
|
62
|
+
activity.module_eval do
|
63
|
+
# FIXME: reverse order for insertion
|
64
|
+
step Controller::Cipher.method(:encrypt_value), id: :encrypt , after: serialize_after,
|
65
|
+
input: {cipher_key: :cipher_key, serialized_suspend_data: :value}, output: {encrypted_value: :encrypted_suspend_data}
|
66
|
+
step Controller.method(:serialize_suspend_data), id: :serialize_suspend_data , after: serialize_after
|
67
|
+
pass Controller.method(:copy_suspend_data_to_endpoint_ctx), id: :copy_suspend_data_to_endpoint_ctx , after: serialize_after
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Insert the "experimental" {find_process_model} steps
|
72
|
+
def insert_find_process_model!(protocol, **options)
|
73
|
+
protocol.module_eval do
|
74
|
+
step Subprocess(FindProcessModel), Output(:failure) => End(:not_found),
|
75
|
+
id: :find_process_model,
|
76
|
+
**options
|
77
|
+
# after: :authenticate
|
78
|
+
end
|
79
|
+
|
80
|
+
insert_copy_to_domain_ctx!(protocol, :process_model => :model)
|
81
|
+
end
|
82
|
+
|
83
|
+
def insert_copy_to_domain_ctx!(protocol, variables)
|
84
|
+
variables.each do |original_name, domain_name|
|
85
|
+
protocol.module_eval do
|
86
|
+
pass ->(ctx, domain_ctx:, **) { domain_ctx[domain_name] = ctx[original_name] if ctx.key?(original_name) },
|
87
|
+
id: :"copy_[#{original_name.inspect}]_to_domain_ctx[#{domain_name.inspect}]", before: :domain_activity
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Trailblazer::Endpoint::Protocol
|
2
|
+
class FindProcessModel < Trailblazer::Activity::Railway
|
3
|
+
# step :find_process_model?, Output(:failure) => Id("End.success")
|
4
|
+
step :find_process_model#, Output(:failure) => End(:not_found) # DISCUSS: currently, {End.failure} implies {not_found}.
|
5
|
+
|
6
|
+
# DISCUSS: should the implementation remain in {Activity}?
|
7
|
+
# def find_process_model?(ctx, find_process_model:, **)
|
8
|
+
# find_process_model
|
9
|
+
# end
|
10
|
+
|
11
|
+
def find_process_model(ctx, process_model_class:, process_model_id:, **)
|
12
|
+
ctx[:process_model] = process_model_class.find_by(id: process_model_id)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -58,7 +58,8 @@ class DocsControllerTest < Minitest::Spec
|
|
58
58
|
include T.def_steps(:authenticate, :policy)
|
59
59
|
end
|
60
60
|
|
61
|
-
endpoint protocol: Protocol, adapter: Trailblazer::Endpoint::Adapter::Web,
|
61
|
+
endpoint protocol: Protocol, adapter: Trailblazer::Endpoint::Adapter::Web,
|
62
|
+
scope_domain_ctx: true
|
62
63
|
end
|
63
64
|
|
64
65
|
class HtmlController < ApplicationController
|
data/test/rails-app/Gemfile
CHANGED
@@ -21,3 +21,9 @@ gem "minitest-line"
|
|
21
21
|
gem "trailblazer-cells"
|
22
22
|
gem "cells-erb"
|
23
23
|
gem "cells-rails"
|
24
|
+
|
25
|
+
|
26
|
+
# FIXME
|
27
|
+
gem "trailblazer-workflow", path: "../../../trailblazer-workflow"
|
28
|
+
# gem "trailblazer-activity-dsl-linear", path: "../../../trailblazer-activity-dsl-linear"
|
29
|
+
gem "trailblazer-activity-dsl-linear", ">= 0.3.4"
|
data/test/rails-app/Gemfile.lock
CHANGED
@@ -1,8 +1,17 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../../../trailblazer-workflow
|
3
|
+
specs:
|
4
|
+
trailblazer-workflow (0.0.8)
|
5
|
+
trailblazer-activity
|
6
|
+
trailblazer-activity-implementation
|
7
|
+
trailblazer-developer (>= 0.0.10)
|
8
|
+
trailblazer-endpoint
|
9
|
+
|
1
10
|
PATH
|
2
11
|
remote: ../..
|
3
12
|
specs:
|
4
|
-
trailblazer-endpoint (0.0.
|
5
|
-
trailblazer-activity-dsl-linear (>= 0.3.
|
13
|
+
trailblazer-endpoint (0.0.7)
|
14
|
+
trailblazer-activity-dsl-linear (>= 0.3.4, < 0.4.0)
|
6
15
|
|
7
16
|
GEM
|
8
17
|
remote: https://rubygems.org/
|
@@ -156,8 +165,11 @@ GEM
|
|
156
165
|
tilt (2.0.10)
|
157
166
|
trailblazer-activity (0.11.3)
|
158
167
|
trailblazer-context (>= 0.3.1, < 0.4.0)
|
159
|
-
trailblazer-activity-dsl-linear (0.3.
|
160
|
-
trailblazer-activity (>= 0.11.
|
168
|
+
trailblazer-activity-dsl-linear (0.3.4)
|
169
|
+
trailblazer-activity (>= 0.11.3, < 1.0.0)
|
170
|
+
trailblazer-activity-implementation (0.0.3)
|
171
|
+
trailblazer-activity (>= 0.8.0)
|
172
|
+
trailblazer-activity-dsl-linear
|
161
173
|
trailblazer-cells (0.0.3)
|
162
174
|
cells (>= 4.1.0.rc1, < 5.0.0)
|
163
175
|
trailblazer-context (0.3.1)
|
@@ -190,9 +202,11 @@ DEPENDENCIES
|
|
190
202
|
multi_json
|
191
203
|
rails (~> 6.0.3, >= 6.0.3.1)
|
192
204
|
sqlite3 (~> 1.4)
|
205
|
+
trailblazer-activity-dsl-linear (>= 0.3.4)
|
193
206
|
trailblazer-cells
|
194
207
|
trailblazer-endpoint!
|
195
208
|
trailblazer-operation
|
209
|
+
trailblazer-workflow!
|
196
210
|
|
197
211
|
BUNDLED WITH
|
198
212
|
2.1.4
|
@@ -30,12 +30,13 @@ class ApplicationController::Web < ApplicationController
|
|
30
30
|
def policy(ctx, domain_ctx:, **)
|
31
31
|
Policy.(domain_ctx)
|
32
32
|
end
|
33
|
+
|
34
|
+
Trailblazer::Endpoint::Protocol::Controller.insert_copy_to_domain_ctx!(self, :current_user => :current_user)
|
33
35
|
end
|
34
36
|
#:protocol end
|
35
37
|
Policy = ->(domain_ctx) { domain_ctx[:params][:policy] == "false" ? false : true }
|
36
38
|
#~gskip end
|
37
|
-
endpoint protocol: Protocol, adapter: Trailblazer::Endpoint::Adapter::Web
|
38
|
-
domain_ctx_filter: ApplicationController.current_user_in_domain_ctx
|
39
|
+
endpoint protocol: Protocol, adapter: Trailblazer::Endpoint::Adapter::Web
|
39
40
|
end
|
40
41
|
#:generic end
|
41
42
|
|
@@ -81,4 +81,178 @@ end
|
|
81
81
|
end
|
82
82
|
#:protocol_failure end
|
83
83
|
end
|
84
|
+
|
85
|
+
|
86
|
+
# endpoint_ctx
|
87
|
+
# :resume_data
|
88
|
+
# domain_ctx
|
89
|
+
# :resume_data (copy)
|
90
|
+
|
91
|
+
|
92
|
+
# authenticate
|
93
|
+
|
94
|
+
# deserialize ==> {resume_data: {id: 1}}
|
95
|
+
# deserialize_process_model_id_from_resume_data
|
96
|
+
|
97
|
+
# find_process_model
|
98
|
+
# policy
|
99
|
+
# domain_activity
|
100
|
+
|
101
|
+
# serialize suspend_data and deserialize resume_data
|
102
|
+
class SerializeController < SongsController
|
103
|
+
endpoint Song::Operation::Create,
|
104
|
+
protocol: ApplicationController::Web::Protocol
|
105
|
+
# serialize: true
|
106
|
+
|
107
|
+
def self.options_for_block_options(ctx, **)
|
108
|
+
{
|
109
|
+
invoke: Trailblazer::Developer.method(:wtf?) # FIXME
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
def self.options_for_endpoint(ctx, controller:, **)
|
115
|
+
{
|
116
|
+
cipher_key: Rails.application.config.cipher_key,
|
117
|
+
|
118
|
+
encrypted_resume_data: controller.params[:encrypted_resume_data],
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
directive :options_for_block_options, method(:options_for_block_options)
|
123
|
+
directive :options_for_endpoint, method(:options_for_endpoint)
|
124
|
+
|
125
|
+
def create
|
126
|
+
encrypted_value = Trailblazer::Workflow::Cipher.encrypt_value({}, cipher_key: cipher_key, value: JSON.dump({id: "findings received", class: Object}))
|
127
|
+
|
128
|
+
endpoint Song::Operation::Create, encrypted_resume_data: encrypted_value, process_model_from_resume_data: false do |ctx, current_user:, endpoint_ctx:, **|
|
129
|
+
render html: cell(Song::Cell::Create, model, current_user: current_user)
|
130
|
+
end.Or do |ctx, contract:, **| # validation failure
|
131
|
+
render html: cell(Song::Cell::New, contract)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# TODO: not really a doc test.
|
137
|
+
# the entire deserialize cycle is skipped since we only use {:serialize}
|
138
|
+
class Serialize1Controller < SerializeController
|
139
|
+
class Create < Trailblazer::Operation
|
140
|
+
pass ->(ctx, **) { ctx[:model] = ctx.key?(:model) ? ctx[:model] : false }
|
141
|
+
end
|
142
|
+
|
143
|
+
endpoint "Create",
|
144
|
+
domain_activity: Create,
|
145
|
+
serialize: true,
|
146
|
+
deserialize: true
|
147
|
+
|
148
|
+
def create
|
149
|
+
# {:model} and {:memory} are from the domain_ctx.
|
150
|
+
# {:encrypted_suspend_data} from endpoint.
|
151
|
+
endpoint "Create" do |ctx, model:, endpoint_ctx:, **|
|
152
|
+
render html: "#{model.inspect}/#{ctx[:memory].inspect}/#{endpoint_ctx[:encrypted_suspend_data]}".html_safe
|
153
|
+
end.Or do |ctx, **| # validation failure
|
154
|
+
render html: "xxx", status: 500
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# TODO: not really a doc test.
|
160
|
+
# ---------deserialize cycle is skipped.
|
161
|
+
# we serialize {:remember}.
|
162
|
+
class Serialize2Controller < Serialize1Controller # "render confirm page"
|
163
|
+
class Create < Trailblazer::Operation
|
164
|
+
pass ->(ctx, **) { ctx[:model] = ctx.key?(:model) ? ctx[:model] : false }
|
165
|
+
step ->(ctx, **) { ctx[:suspend_data] = {remember: OpenStruct.new(id: 1), id: 9} } # write to domain_ctx[:suspend_data]
|
166
|
+
end
|
167
|
+
|
168
|
+
endpoint "Create",
|
169
|
+
domain_activity: Create,
|
170
|
+
serialize: true
|
171
|
+
end
|
172
|
+
|
173
|
+
# we can read from {:resume_data}
|
174
|
+
class Serialize3Controller < Serialize1Controller # "process submitted confirm page"
|
175
|
+
class Create < Trailblazer::Operation
|
176
|
+
pass ->(ctx, **) { ctx[:model] = ctx.key?(:model) ? ctx[:model] : false }
|
177
|
+
pass ->(ctx, **) { ctx[:memory] = ctx[:resume_data] } # read/process the suspended data
|
178
|
+
end
|
179
|
+
|
180
|
+
endpoint "Create",
|
181
|
+
domain_activity: Create,
|
182
|
+
deserialize: true
|
183
|
+
end
|
184
|
+
|
185
|
+
# find process_model via id in suspend/resume data (used to be called {process_model_from_resume_data})
|
186
|
+
class Serialize4Controller < Serialize1Controller
|
187
|
+
class Create < Trailblazer::Operation
|
188
|
+
pass ->(ctx, **) { ctx[:model] = ctx.key?(:model) ? ctx[:model] : false }
|
189
|
+
pass ->(ctx, **) { ctx[:memory] = ctx[:resume_data] } # read/process the suspended data
|
190
|
+
end
|
191
|
+
|
192
|
+
endpoint "Create",
|
193
|
+
domain_activity: Create,
|
194
|
+
deserialize: true,
|
195
|
+
find_process_model: true,
|
196
|
+
deserialize_process_model_id_from_resume_data: true
|
197
|
+
|
198
|
+
def create
|
199
|
+
endpoint "Create", process_model_class: Song do |ctx, endpoint_ctx:, **|
|
200
|
+
render html: "#{endpoint_ctx[:process_model_id].inspect}/#{ctx[:memory].inspect}/#{endpoint_ctx[:encrypted_suspend_data]}".html_safe
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# find process_model from resume
|
206
|
+
# FIXME: what is the diff to Controller4?
|
207
|
+
class Serialize5Controller < Serialize1Controller
|
208
|
+
endpoint "Create",
|
209
|
+
domain_activity: Serialize4Controller::Create,
|
210
|
+
deserialize: true,
|
211
|
+
find_process_model: true,
|
212
|
+
deserialize_process_model_id_from_resume_data: true
|
213
|
+
|
214
|
+
def create
|
215
|
+
endpoint "Create", find_process_model: true, process_model_class: Song, process_model_id: params[:id] do |ctx, model:, endpoint_ctx:, **|
|
216
|
+
render html: "#{model.inspect}/#{ctx[:memory].inspect}/#{endpoint_ctx[:encrypted_suspend_data]}".html_safe
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# find process_model from action_options
|
222
|
+
class Serialize6Controller < Serialize1Controller
|
223
|
+
endpoint "Create",
|
224
|
+
domain_activity: Serialize4Controller::Create,
|
225
|
+
protocol: ApplicationController::Web::Protocol,
|
226
|
+
find_process_model: true
|
227
|
+
|
228
|
+
def create
|
229
|
+
endpoint "Create", find_process_model: true, process_model_class: Song, process_model_id: params[:id] do |ctx, model:, endpoint_ctx:, **|
|
230
|
+
render html: "#{model.inspect}/#{endpoint_ctx[:process_model].inspect}/#{endpoint_ctx[:encrypted_suspend_data]}".html_safe
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Configure only {:find_process_model} and {:protocol}.
|
236
|
+
class Serialize7Controller < Serialize1Controller
|
237
|
+
endpoint find_process_model: true # generic setting for all endpoints in this controller.
|
238
|
+
|
239
|
+
endpoint "Create", # no need to specify {:find_process_model}
|
240
|
+
domain_activity: Serialize4Controller::Create
|
241
|
+
|
242
|
+
endpoint "New",
|
243
|
+
find_process_model: false,
|
244
|
+
domain_activity: Serialize4Controller::Create
|
245
|
+
|
246
|
+
def create
|
247
|
+
endpoint "Create", process_model_class: Song, process_model_id: params[:id] do |ctx, model:, endpoint_ctx:, **|
|
248
|
+
render html: "#{model.inspect}/#{endpoint_ctx[:process_model].inspect}/#{endpoint_ctx[:encrypted_suspend_data]}".html_safe
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def new
|
253
|
+
endpoint "New", process_model_class: Song, process_model_id: params[:id] do |ctx, model:, endpoint_ctx:, **|
|
254
|
+
render html: "#{model.inspect}/#{endpoint_ctx[:process_model].inspect}/#{endpoint_ctx[:encrypted_suspend_data]}".html_safe
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
84
258
|
end
|
@@ -35,4 +35,6 @@ Rails.application.configure do
|
|
35
35
|
|
36
36
|
# Raises error for missing translations.
|
37
37
|
# config.action_view.raise_on_missing_translations = true
|
38
|
+
|
39
|
+
config.cipher_key = "e1e1cc87asdfasdfasdfasfdasdfasdfasvhnfvbdb" # FIXME: this usually happens via the new key mechanism in Rails 6
|
38
40
|
end
|
@@ -15,4 +15,14 @@ Rails.application.routes.draw do
|
|
15
15
|
get "/v1/songs_with_options/:id", to: "api/v1/songs_controller/with_options#show"
|
16
16
|
|
17
17
|
get "/", to: "home#dashboard"
|
18
|
+
|
19
|
+
post "/songs/serialize", to: "songs_controller/serialize#create"
|
20
|
+
post "/songs/serialize1", to: "songs_controller/serialize1#create"
|
21
|
+
post "/songs/serialize2", to: "songs_controller/serialize2#create"
|
22
|
+
post "/songs/serialize3", to: "songs_controller/serialize3#create"
|
23
|
+
post "/songs/serialize4", to: "songs_controller/serialize4#create"
|
24
|
+
post "/songs/serialize5", to: "songs_controller/serialize5#create"
|
25
|
+
post "/songs/serialize6", to: "songs_controller/serialize6#create"
|
26
|
+
post "/songs/serialize7", to: "songs_controller/serialize7#create"
|
27
|
+
post "/songs/serialize72", to: "songs_controller/serialize7#new"
|
18
28
|
end
|
@@ -70,6 +70,75 @@ class SongsControllerTest < ActionDispatch::IntegrationTest
|
|
70
70
|
assert_equal "wrong login, app crashed", response.body
|
71
71
|
end
|
72
72
|
|
73
|
+
test "serializing/deserializing/find_process_model_from_resume_data" do
|
74
|
+
# # 401
|
75
|
+
# post "/songs/serialize/"
|
76
|
+
# assert_response 401
|
77
|
+
|
78
|
+
# sign in
|
79
|
+
post "/auth/sign_in", params: {username: "yogi@trb.to", password: "secret"}
|
80
|
+
assert_equal 1, session[:user_id]
|
81
|
+
|
82
|
+
|
83
|
+
# When {:encrypted_resume_data} is {nil} the entire deserialize cycle is skipped.
|
84
|
+
# Nothing gets serialized.
|
85
|
+
# This is considered an error since we're expecting resume data.
|
86
|
+
post "/songs/serialize1/", params: {} # encrypted_resume_data: nil
|
87
|
+
assert_response 500
|
88
|
+
assert_equal "xxx", response.body # DISCUSS: is this an application or a protocol error?
|
89
|
+
|
90
|
+
# Nothing deserialized, but {:remember} serialized
|
91
|
+
# "confirm_delete form"
|
92
|
+
post "/songs/serialize2/", params: {} # {:remember} serialized
|
93
|
+
assert_response 200
|
94
|
+
encrypted_string = "0109C4E535EDA2CCE8CD69E50C179F5950CC4A2A898504F951C995B6BCEAFE1DFAB02894854B96B9D11C23E25DB5FB03"
|
95
|
+
assert_equal "false/nil/#{encrypted_string}", response.body
|
96
|
+
|
97
|
+
# {:remember} deserialized
|
98
|
+
# "submit confirm_delete"
|
99
|
+
# We're expecting serialized data and don't serialize anything.
|
100
|
+
post "/songs/serialize3/", params: {encrypted_resume_data: encrypted_string} # {:remember} serialized
|
101
|
+
assert_response 200
|
102
|
+
assert_equal "false/{\"remember\"=>\"#<OpenStruct id=1>\", \"id\"=>9}/", response.body
|
103
|
+
|
104
|
+
# retrieve process_model via id in {:resume_data}
|
105
|
+
# we can see {endpoint_ctx[:process_model_id]}
|
106
|
+
# We're expecting serialized data and don't serialize anything.
|
107
|
+
post "/songs/serialize4/", params: {encrypted_resume_data: encrypted_string}
|
108
|
+
assert_response 200
|
109
|
+
assert_equal "9/{\"remember\"=>\"#<OpenStruct id=1>\", \"id\"=>9}/", response.body
|
110
|
+
|
111
|
+
# retrieve process_model via {:resume_data}'s serialized id.
|
112
|
+
# We're expecting serialized data and don't serialize anything.
|
113
|
+
post "/songs/serialize5/", params: {encrypted_resume_data: encrypted_string}
|
114
|
+
assert_response 200
|
115
|
+
assert_equal "#<struct Song id=9>/{\"remember\"=>\"#<OpenStruct id=1>\", \"id\"=>9}/", response.body
|
116
|
+
# model not found
|
117
|
+
post "/songs/serialize5/", params: {encrypted_resume_data: "36C61CCE30E6CFDE637DF0DA9257CC49"}
|
118
|
+
assert_response 404
|
119
|
+
assert_equal "", response.body
|
120
|
+
|
121
|
+
# find model without serialize from params, no serialized stuff passed
|
122
|
+
# DISCUSS: this could be a different test
|
123
|
+
post "/songs/serialize6/", params: {id: 1}
|
124
|
+
assert_response 200
|
125
|
+
assert_equal "#<struct Song id=\"1\">/#<struct Song id=\"1\">/", response.body
|
126
|
+
# model not found
|
127
|
+
post "/songs/serialize6/", params: {id: nil}
|
128
|
+
assert_response 404
|
129
|
+
assert_equal "", response.body
|
130
|
+
|
131
|
+
# {find_process_model: true} set on controller level, for all endpoints.
|
132
|
+
post "/songs/serialize7/", params: {id: 1}
|
133
|
+
assert_response 200
|
134
|
+
assert_equal "#<struct Song id=\"1\">/#<struct Song id=\"1\">/", response.body
|
135
|
+
# {find_process_model: false} overrides controller setting
|
136
|
+
post "/songs/serialize72/", params: {id: 1}
|
137
|
+
assert_response 200
|
138
|
+
assert_equal "false/nil/", response.body
|
139
|
+
|
140
|
+
end
|
141
|
+
|
73
142
|
test "sign_in" do
|
74
143
|
# wrong credentials
|
75
144
|
post "/auth/sign_in", params: {}
|
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
|
-
spec.add_dependency "trailblazer-activity-dsl-linear", ">= 0.3.
|
20
|
+
spec.add_dependency "trailblazer-activity-dsl-linear", ">= 0.3.4", "< 0.4.0"
|
21
21
|
|
22
22
|
spec.add_development_dependency "bundler"
|
23
23
|
spec.add_development_dependency "rake"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trailblazer-endpoint
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-12-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: trailblazer-activity-dsl-linear
|
@@ -16,7 +16,7 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.3.
|
19
|
+
version: 0.3.4
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
22
|
version: 0.4.0
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 0.3.
|
29
|
+
version: 0.3.4
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: 0.4.0
|
@@ -109,6 +109,9 @@ files:
|
|
109
109
|
- lib/trailblazer/endpoint/dsl.rb
|
110
110
|
- lib/trailblazer/endpoint/options.rb
|
111
111
|
- lib/trailblazer/endpoint/protocol.rb
|
112
|
+
- lib/trailblazer/endpoint/protocol/cipher.rb
|
113
|
+
- lib/trailblazer/endpoint/protocol/controller.rb
|
114
|
+
- lib/trailblazer/endpoint/protocol/find_process_model.rb
|
112
115
|
- lib/trailblazer/endpoint/rails.rb
|
113
116
|
- lib/trailblazer/endpoint/version.rb
|
114
117
|
- test/adapter/api_test.rb
|