trailblazer-endpoint 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES.md +3 -0
  3. data/Gemfile +16 -0
  4. data/README.md +11 -0
  5. data/Rakefile +10 -0
  6. data/lib/trailblazer-endpoint.rb +1 -0
  7. data/lib/trailblazer/endpoint.rb +133 -0
  8. data/lib/trailblazer/endpoint/adapter.rb +197 -0
  9. data/lib/trailblazer/endpoint/builder.rb +56 -0
  10. data/lib/trailblazer/endpoint/protocol.rb +122 -0
  11. data/lib/trailblazer/endpoint/rails.rb +27 -0
  12. data/lib/trailblazer/endpoint/version.rb +5 -0
  13. data/test/adapter/api_test.rb +78 -0
  14. data/test/adapter/representable_test.rb +7 -0
  15. data/test/adapter/web_test.rb +40 -0
  16. data/test/benchmark/skill_resolver_benchmark.rb +43 -0
  17. data/test/docs/controller_test.rb +92 -0
  18. data/test/docs/endpoint_test.rb +54 -0
  19. data/test/endpoint_test.rb +908 -0
  20. data/test/rails-app/.gitignore +21 -0
  21. data/test/rails-app/Gemfile +17 -0
  22. data/test/rails-app/Gemfile.lock +157 -0
  23. data/test/rails-app/README.md +24 -0
  24. data/test/rails-app/Rakefile +6 -0
  25. data/test/rails-app/app/controllers/application_controller.rb +3 -0
  26. data/test/rails-app/app/controllers/songs_controller.rb +26 -0
  27. data/test/rails-app/app/models/application_record.rb +3 -0
  28. data/test/rails-app/app/models/concerns/.keep +0 -0
  29. data/test/rails-app/config.ru +5 -0
  30. data/test/rails-app/config/application.rb +15 -0
  31. data/test/rails-app/config/boot.rb +3 -0
  32. data/test/rails-app/config/database.yml +25 -0
  33. data/test/rails-app/config/environment.rb +5 -0
  34. data/test/rails-app/config/environments/development.rb +54 -0
  35. data/test/rails-app/config/environments/production.rb +86 -0
  36. data/test/rails-app/config/environments/test.rb +42 -0
  37. data/test/rails-app/config/initializers/application_controller_renderer.rb +6 -0
  38. data/test/rails-app/config/initializers/backtrace_silencers.rb +7 -0
  39. data/test/rails-app/config/initializers/cookies_serializer.rb +5 -0
  40. data/test/rails-app/config/initializers/filter_parameter_logging.rb +4 -0
  41. data/test/rails-app/config/initializers/inflections.rb +16 -0
  42. data/test/rails-app/config/initializers/mime_types.rb +4 -0
  43. data/test/rails-app/config/initializers/new_framework_defaults.rb +24 -0
  44. data/test/rails-app/config/initializers/session_store.rb +3 -0
  45. data/test/rails-app/config/initializers/wrap_parameters.rb +14 -0
  46. data/test/rails-app/config/locales/en.yml +23 -0
  47. data/test/rails-app/config/routes.rb +6 -0
  48. data/test/rails-app/config/secrets.yml +22 -0
  49. data/test/rails-app/db/seeds.rb +7 -0
  50. data/test/rails-app/log/.keep +0 -0
  51. data/test/rails-app/test/controllers/.keep +0 -0
  52. data/test/rails-app/test/controllers/songs_controller_test.rb +156 -0
  53. data/test/rails-app/test/fixtures/.keep +0 -0
  54. data/test/rails-app/test/fixtures/files/.keep +0 -0
  55. data/test/rails-app/test/helpers/.keep +0 -0
  56. data/test/rails-app/test/integration/.keep +0 -0
  57. data/test/rails-app/test/mailers/.keep +0 -0
  58. data/test/rails-app/test/models/.keep +0 -0
  59. data/test/rails-app/test/test_helper.rb +10 -0
  60. data/test/rails-app/tmp/.keep +0 -0
  61. data/test/rails-app/vendor/assets/javascripts/.keep +0 -0
  62. data/test/rails-app/vendor/assets/stylesheets/.keep +0 -0
  63. data/test/test_helper.rb +34 -0
  64. data/trailblazer-endpoint.gemspec +26 -0
  65. metadata +236 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a13cc4ea9b50a656f620578a75ccd11aa01106bc22be8975e8758a660d1a13aa
4
+ data.tar.gz: 4c006bbfeab10458615ee658e862cf152b42b17f1c0d8302f9d1308b8cdc1425
5
+ SHA512:
6
+ metadata.gz: 53dfeec3df63a69bbe4f481f58eaa6c71e32d55044066fda4eb7dc8579ef1b973079ff8fa6b7cbd7a6f4ce4ecdb767d25f37a6bf68eee99626557f1a1de2ab71
7
+ data.tar.gz: 99b9e753a78f182ad274c677aa9f1c33bc85fbd8ad4ce57c2574a4b4ef29d3400e3553c6484674e3d75cec41dcacea76f601db77aee7273fdd066ec3c41c17c7
@@ -0,0 +1,3 @@
1
+ # 0.0.1
2
+
3
+ * Provides very simple `Protocol` implementations for `Web` and `API`, same for `Adapter`.
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in trailblazer.gemspec
4
+ gemspec
5
+
6
+ gem "multi_json"
7
+
8
+ gem "minitest-line"
9
+
10
+ # gem "trailblazer-activity", path: "../trailblazer-activity"
11
+ # gem "trailblazer-activity-dsl-linear", path: "../trailblazer-activity-dsl-linear"
12
+ # gem "trailblazer-operation", path: "../operation"
13
+
14
+ gem "dry-validation"
15
+
16
+ # gem "trailblazer-developer", path: "../trailblazer-developer"
@@ -0,0 +1,11 @@
1
+ # Trailblazer::Endpoint
2
+
3
+ *Generic HTTP handlers for operation results.*
4
+
5
+ Decouple finding out *what happened* from *what to do*.
6
+
7
+ t test/controllers/songs_controller_test.rb --backtrace
8
+
9
+ ## TODO
10
+
11
+ * make travis build run `cd test/rails-app/ && rake`
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ task :default => [:test]
5
+
6
+ Rake::TestTask.new(:test) do |test|
7
+ test.libs << 'test'
8
+ test.test_files = FileList['test/endpoint_test.rb', 'test/docs/*_test.rb', "test/adapter/*_test.rb"]
9
+ test.verbose = true
10
+ end
@@ -0,0 +1 @@
1
+ require "trailblazer/endpoint"
@@ -0,0 +1,133 @@
1
+ module Trailblazer
2
+ class Endpoint
3
+ # Create an {Endpoint} class with the provided adapter and protocol.
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, domain_ctx_filter: nil, &block)
6
+
7
+ # special considerations around the {domain_activity} and its taskWrap:
8
+ #
9
+ # 1. domain_ctx_filter (e.g. to filter {current_user})
10
+ # 2. :input (scope {:domain_ctx})
11
+ # 3. call (domain_activity)
12
+ # 4. :output
13
+ # 5. save return signal
14
+
15
+
16
+ extensions_options = {
17
+ extensions: [Trailblazer::Activity::TaskWrap::Extension(merge: Trailblazer::Endpoint::Protocol::Domain.extension_for_terminus_handler)],
18
+ }
19
+
20
+ # scoping: {:domain_ctx} becomes ctx
21
+ extensions_options.merge!(Endpoint.options_for_scope_domain_ctx) if scope_domain_ctx # TODO: test flag
22
+
23
+
24
+ domain_ctx_filter_callable = [[Trailblazer::Activity::TaskWrap::Pipeline.method(:insert_before), "task_wrap.call_task", ["endpoint.domain_ctx_filter", domain_ctx_filter]]]
25
+ extensions_options[:extensions] << Trailblazer::Activity::TaskWrap::Extension(merge: domain_ctx_filter_callable) if domain_ctx_filter
26
+
27
+ app_protocol = Class.new(protocol) do
28
+ step(Subprocess(domain_activity), {inherit: true, id: :domain_activity, replace: :domain_activity,
29
+
30
+ # FIXME: where does this go?
31
+ }.
32
+ merge(extensions_options).
33
+ merge(instance_exec(&block)) # the block is evaluated in the {Protocol} context.
34
+ )
35
+ end
36
+
37
+ Class.new(adapter) do
38
+ step(Subprocess(app_protocol), {inherit: true, id: :protocol, replace: :protocol})
39
+ end # app_adapter
40
+
41
+ end
42
+
43
+ def self.options_for_scope_domain_ctx()
44
+ {
45
+ input: ->(ctx, **) { ctx[:domain_ctx] }, # gets automatically Context()'ed.
46
+ output: ->(domain_ctx, **) { {:domain_ctx => domain_ctx} }
47
+ }
48
+ end
49
+
50
+ # Runtime
51
+ # Invokes the endpoint for you and runs one of the three outcome blocks.
52
+ def self.with_or_etc(activity, args, failure_block:, success_block:, protocol_failure_block:)
53
+ # args[1] = args[1].merge(focus_on: { variables: [:returned], steps: :invoke_workflow })
54
+
55
+ signal, (endpoint_ctx, _ ) = Trailblazer::Developer.wtf?(activity, args)
56
+
57
+ # this ctx is passed to the controller block.
58
+ block_ctx = endpoint_ctx[:domain_ctx].merge(endpoint_ctx: endpoint_ctx, signal: signal, errors: endpoint_ctx[:errors]) # DISCUSS: errors? status?
59
+
60
+ # if signal < Trailblazer::Activity::End::Success
61
+ adapter_terminus_semantic = signal.to_h[:semantic]
62
+
63
+ executed_block =
64
+ if adapter_terminus_semantic == :success
65
+ success_block
66
+ elsif adapter_terminus_semantic == :fail_fast
67
+ protocol_failure_block
68
+ else
69
+ failure_block
70
+ end
71
+
72
+ executed_block.(block_ctx, **block_ctx)
73
+
74
+ # we return the original context???
75
+ return signal, [endpoint_ctx]
76
+ end
77
+
78
+ # def self.default_success_if(success_id)
79
+ # ->(signal:, graph:, **) { signal[:lane_positions][suspend_activity].last == graph.find(success_id).task }
80
+ # end
81
+
82
+ #@ For WORKFLOW and operations. not sure this method will stay here.
83
+ def self.arguments_for(domain_ctx:, collaboration:, dictionary: collaboration.to_h[:dictionary], flow_options:, circuit_options: {}, **options)
84
+ domain_ctx = Trailblazer::Context::IndifferentAccess.build(domain_ctx, {}, [domain_ctx, flow_options], circuit_options)
85
+
86
+ [
87
+ [
88
+ {
89
+ activity: collaboration,
90
+ domain_ctx: domain_ctx, # DISCUSS: is this where {:resume_data} comes in?
91
+ # process_model_class: process_model_class,
92
+ # process_model_from_resume_data: process_model_from_resume_data,
93
+ # find_process_model: find_process_model,
94
+ # encrypted_resume_data: encrypted_resume_data,
95
+
96
+ dictionary: dictionary,
97
+ # cipher_key: cipher_key,
98
+ **options,
99
+ },
100
+ flow_options
101
+ ],
102
+ circuit_options
103
+ ]
104
+ end
105
+
106
+ # FIXME: name will change! this is for controllers, only!
107
+ def self.advance_from_controller(endpoint, success_block:, failure_block:, protocol_failure_block: protocol_failure_block, **argument_options)
108
+ args = Trailblazer::Endpoint.arguments_for(argument_options)
109
+
110
+ signal, (ctx, _ ) = Trailblazer::Endpoint.with_or_etc(
111
+ endpoint,
112
+ args[0], # [ctx, flow_options]
113
+
114
+ success_block: success_block,
115
+ failure_block: failure_block,
116
+ protocol_failure_block: protocol_failure_block,
117
+ )
118
+
119
+ ctx
120
+ end
121
+ end
122
+ end
123
+ # created: Dry::Matcher::Case.new(
124
+ # match: ->(result) { result.success? && result["model.action"] == :new }, # the "model.action" doesn't mean you need Model.
125
+ # resolve: ->(result) { result }),
126
+ # not_found: Dry::Matcher::Case.new(
127
+ # match: ->(result) { result.failure? && result["result.model"] && result["result.model"].failure? },
128
+ # resolve: ->(result) { result }),
129
+ # # TODO: we could add unauthorized here.
130
+
131
+
132
+ require "trailblazer/endpoint/protocol"
133
+ require "trailblazer/endpoint/adapter"
@@ -0,0 +1,197 @@
1
+ module Trailblazer
2
+ class Endpoint
3
+
4
+ # The idea is to use the CreatePrototypeProtocol's outputs as some kind of protocol, outcomes that need special handling
5
+ # can be wired here, or merged into one (e.g. 401 and failure is failure).
6
+ # I am writing this class in the deep forests of the Algarve, hiding from the GNR.
7
+ # class Adapter < Trailblazer::Activity::FastTrack # TODO: naming. it's after the "application logic", more like Controller
8
+ # Currently reusing End.fail_fast as a "something went wrong, but it wasn't a real application error!"
9
+
10
+
11
+ module Adapter
12
+ class Web <Trailblazer::Activity::FastTrack
13
+ _404_path = ->(*) { step :_404_status }
14
+ _401_path = ->(*) { step :_401_status }
15
+ _403_path = ->(*) { step :_403_status }
16
+ # _422_path = ->(*) { step :_422_status } # TODO: this is currently represented by the {failure} track.
17
+
18
+ step Subprocess(Protocol), # this will get replaced
19
+ id: :protocol,
20
+ Output(:not_authorized) => Path(track_color: :not_authorized, connect_to: Id(:protocol_failure), &_403_path),
21
+ Output(:not_found) => Path(track_color: :not_found, connect_to: Id(:protocol_failure), &_404_path),
22
+ Output(:not_authenticated) => Path(track_color: :not_authenticated, connect_to: Id(:protocol_failure), &_401_path),
23
+ Output(:invalid_data) => Track(:failure) # application error, since it's usually a failed validation.
24
+
25
+ step :protocol_failure, magnetic_to: nil, Output(:success) => Track(:fail_fast), Output(:failure) => Track(:fail_fast)
26
+
27
+ def protocol_failure(ctx, **)
28
+ true
29
+ end
30
+
31
+
32
+ # FIXME:::::::
33
+ def _401_status(ctx, **)
34
+ ctx[:status] = 401
35
+ end
36
+
37
+ def _404_status(ctx, **)
38
+ ctx[:status] = 404
39
+ end
40
+
41
+ def _403_status(ctx, **)
42
+ ctx[:status] = 403
43
+ end
44
+ end
45
+
46
+ class API < Web
47
+ step :_200_status, after: :protocol
48
+
49
+ def _200_status(ctx, **)
50
+ ctx[:status] = 200
51
+ end
52
+
53
+ fail :_422_status, before: "End.failure"
54
+
55
+ def _422_status(ctx, **)
56
+ ctx[:status] = 422
57
+ end
58
+
59
+
60
+ def self.insert_error_handler_steps(adapter)
61
+ adapter = Class.new(adapter) do
62
+ step :handle_not_authenticated, magnetic_to: :not_authenticated, Output(:success) => Track(:not_authenticated), Output(:failure) => Track(:not_authenticated), before: :_401_status
63
+ step :handle_not_authorized, magnetic_to: :not_authorized, Output(:success) => Track(:not_authorized), Output(:failure) => Track(:not_authorized), before: :_403_status
64
+ # step :handle_not_found, magnetic_to: :not_found, Output(:success) => Track(:not_found), Output(:failure) => Track(:not_found)
65
+ fail :handle_invalid_data
66
+ end
67
+ end
68
+
69
+ class Errors < Struct.new(:message, :errors) # FIXME: extract
70
+ module Handlers
71
+ def handle_not_authenticated(ctx, errors:, **)
72
+ errors.message = "Authentication credentials were not provided or are invalid."
73
+ end
74
+
75
+ def handle_not_authorized(ctx, errors:, **)
76
+ errors.message = "Action not allowed due to a policy setting."
77
+ end
78
+
79
+ def handle_invalid_data(ctx, errors:, **)
80
+ errors.message = "The submitted data is invalid."
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ # Basic endpoint adapter for a HTTP document API.
87
+ # As always: "work in progress" ;)
88
+ #
89
+ # {End.fail_fast} currently implies a 4xx-able error.
90
+ class API_ < Trailblazer::Activity::FastTrack
91
+ _404_path = ->(*) { step :_404_status }
92
+ _401_path = ->(*) { step :_401_status; step :_401_error_message }
93
+ _403_path = ->(*) { step :_403_status }
94
+ # _422_path = ->(*) { step :_422_status } # TODO: this is currently represented by the {failure} track.
95
+
96
+ # The API Adapter automatically wires well-defined outputs for you to well-defined paths. :)
97
+ # FIXME
98
+
99
+ step Subprocess(Protocol), # this will get replaced
100
+ id: :protocol,
101
+ Output(:not_authorized) => Path(track_color: :_403, connect_to: Id(:render_protocol_failure_config), &_403_path),
102
+ Output(:not_found) => Path(track_color: :_404, connect_to: Id(:protocol_failure), &_404_path),
103
+ Output(:not_authenticated) => Path(track_color: :_401, connect_to: Id(:render_protocol_failure_config), &_401_path), # head(401), representer: Representer::Error, message: no token
104
+ Output(:invalid_data) => Track(:failure) # application error, since it's usually a failed validation.
105
+
106
+ # extensions: [Trailblazer::Activity::TaskWrap::Extension(merge: TERMINUS_HANDLER)]
107
+ # failure is automatically wired to failure, being an "application error" vs. a "protocol error (auth, etc)"
108
+
109
+
110
+ fail :failure_render_config
111
+ fail :failure_config_status
112
+ fail :render_failure
113
+
114
+ step :success_render_config
115
+ step :success_render_status
116
+ step :render_success
117
+
118
+
119
+ # DISCUSS: "protocol failure" and "application failure" should be the same path, probably?
120
+ step :render_protocol_failure_config, magnetic_to: nil, Output(:success) => Path(connect_to: Id("End.fail_fast")) do
121
+ step :render_protocol_failure
122
+ step :protocol_failure
123
+ end
124
+
125
+ =begin
126
+ render_protocol_failure_config # representer
127
+ render_protocol_failure # Representer.new
128
+ protocol_failure # true
129
+ =end
130
+
131
+ def success_render_status(ctx, **)
132
+ ctx[:status] = 200
133
+ end
134
+
135
+ def success_render_config(ctx, representer:, **)
136
+ true
137
+ end
138
+
139
+ def render_protocol_failure_config(*args)
140
+ failure_render_config(*args)
141
+ end
142
+
143
+ # ROAR
144
+ def render_success(ctx, representer:, domain_ctx:, **)
145
+ model = domain_ctx[:model]
146
+ ctx[:json] = representer.new(model).to_json # FIXME: use the same as render_failure.
147
+ end
148
+
149
+ def failure_render_config(ctx, error_representer:, **)
150
+ ctx[:representer] = error_representer
151
+ end
152
+
153
+ def failure_config_status(ctx, **)
154
+ ctx[:status] = 422
155
+ end
156
+
157
+ def protocol_failure(*args)
158
+ #failure_config(*args)
159
+ true
160
+ end
161
+ def render_protocol_failure(*args)
162
+ render_failure(*args)
163
+ end
164
+
165
+ # ROAR
166
+ def render_failure(ctx, error_representer:, errors:, **)
167
+ # render_success(*args)
168
+ ctx[:json] = error_representer.new(errors).to_json
169
+ end
170
+ # how/where would we configure each endpoint? (per action)
171
+ # class Endpoint
172
+ # representer ...
173
+ # message ...
174
+
175
+ def _401_status(ctx, **)
176
+ ctx[:status] = 401
177
+ end
178
+
179
+ def _404_status(ctx, **)
180
+ ctx[:status] = 404
181
+ end
182
+
183
+ def _403_status(ctx, **)
184
+ ctx[:status] = 403
185
+ end
186
+
187
+ def _401_error_message(ctx, **)
188
+ ctx[:error_message] = "Authentication credentials were not provided or invalid."
189
+ end
190
+
191
+ # def exec_success(ctx, success_block:, **)
192
+ # success_block.call(ctx, **ctx.to_hash) # DISCUSS: use Nested(dynamic) ?
193
+ # end
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,56 @@
1
+ module Trailblazer
2
+ class Endpoint
3
+ # you don't need this if you build your endpoints manually
4
+ class Builder < Trailblazer::Activity::Railway
5
+ # step :build_policy
6
+ step :build_protocol_block
7
+ step :normalize_tuple
8
+
9
+ # def build_policy(ctx, policies:, **)
10
+ # end
11
+
12
+ def build_protocol_block(ctx, policy:, **)
13
+ ctx[:protocol_block] = -> { step Subprocess(policy), id: :policy, replace: :policy, inherit: true; {} }
14
+ end
15
+
16
+ def normalize_tuple(ctx, protocol_block:, options_for_build: {}, **)
17
+ ctx[:build_options] = {
18
+ protocol_block: protocol_block,
19
+ options_for_build: options_for_build
20
+ }
21
+ end
22
+
23
+
24
+ module DSL
25
+ module_function
26
+ #
27
+ # @return endpoint_options
28
+
29
+ def build_options_for(builder:, **options)
30
+ signal, (ctx, _) = builder.([options])
31
+
32
+ ctx[:build_options] # ["web:submitted?", {protocol_block: ..., options_for_build: ...}]
33
+ end
34
+
35
+ def endpoint_for(id:, builder:, default_options:, **config)
36
+ options = build_options_for(builder: builder, **config)
37
+
38
+ return id, Trailblazer::Endpoint.build(default_options.merge(options[:options_for_build]), &options[:protocol_block])
39
+ end
40
+
41
+ # {dsl_options} being something like
42
+ #
43
+ # "api:Start.default" => {policies: []},
44
+ # "api:status?" => {policies: [:user_owns_diagram]},
45
+ # "api:download?" => {policies: [:user_owns_diagram]},
46
+ # "api:delete?" => {policies: [:user_owns_diagram]},
47
+ def endpoints_for(dsl_options, **options)
48
+ endpoints = dsl_options.collect do |id, config|
49
+ endpoint_for(id: id, **options, **config) # config is per endpoint, options are "global"
50
+ end.to_h
51
+ end
52
+ end
53
+ end # Builder
54
+
55
+ end
56
+ end
@@ -0,0 +1,122 @@
1
+ module Trailblazer
2
+ class Endpoint
3
+ # The {Protocol} implements auth*, and calls the domain OP/WF.
4
+ # You still have to implement handlers (like {#authorize} and {#handle_not_authorized}) yourself. This might change soon.
5
+ #
6
+ # Protocol must provide all ends for the Adapter (401,403 and 404 in particular), even if the ran op/workflow doesn't have it.
7
+ # Still thinking about how to do that best.
8
+
9
+ # Termini and their "pendants" in HTTP, which is unrelated to protocol!! Protocol is application-focused and doesn't know about HTTP.
10
+ # failure: 411
11
+ # success: 200
12
+ # not_found: 404
13
+ # not_authenticated: 401
14
+ # not_authorized: 403
15
+ class Protocol < Trailblazer::Activity::Railway
16
+ class Noop < Trailblazer::Activity::Railway
17
+ end
18
+
19
+ class Failure < Trailblazer::Activity::End # DISCUSS: move to Act::Railway?
20
+ # class Authentication < Failure
21
+ # end
22
+ end
23
+
24
+ def self._Path(semantic:, &block) # DISCUSS: the problem with Path currently is https://github.com/trailblazer/trailblazer-activity-dsl-linear/issues/27
25
+ Path(track_color: semantic, end_id: "End.#{semantic}", end_task: Failure.new(semantic: semantic), &block)
26
+ end
27
+
28
+ step :authenticate, Output(:failure) => _Path(semantic: :not_authenticated) do
29
+ # step :handle_not_authenticated
30
+ end
31
+
32
+ step :policy, Output(:failure) => _Path(semantic: :not_authorized) do # user from cookie, etc
33
+ # step :handle_not_authorized
34
+ end
35
+
36
+ # Here, we test a domain OP with ADDITIONAL explicit ends that get wired to the Adapter (vaidation_error => failure).
37
+ # We still need to test the other way round: wiring a "normal" failure to, say, not_found, by inspecting the ctx.
38
+ step Subprocess(Noop), id: :domain_activity
39
+
40
+
41
+
42
+ # add the {End.not_found} terminus to this Protocol. I'm not sure that's the final style, but since a {Protocol} needs to provide all
43
+ # termini for the Adapter this is the only way to get it working right now.
44
+ # FIXME: is this really the only way to add an {End} to all this?
45
+ @state.update_sequence do |sequence:, **|
46
+ sequence = Activity::Path::DSL.append_end(sequence, task: Failure.new(semantic: :not_found), magnetic_to: :not_found, id: "End.not_found")
47
+ sequence = Activity::Path::DSL.append_end(sequence, task: Failure.new(semantic: :invalid_data), magnetic_to: :invalid_data, id: "End.invalid_data")
48
+
49
+ recompile_activity!(sequence)
50
+
51
+ sequence
52
+ end
53
+
54
+ # Best-practices of useful routes and handlers that work with 2.1-OPs.
55
+ class Standard < Protocol
56
+ step :handle_not_authenticated, magnetic_to: :not_authenticated, Output(:success) => Track(:not_authenticated), Output(:failure) => Track(:not_authenticated)#, before: "End.not_authenticated"
57
+ step :handle_not_authorized, magnetic_to: :not_authorized, Output(:success) => Track(:not_authorized), Output(:failure) => Track(:not_authorized)
58
+ # step :handle_invalid_data, magnetic_to: :invalid_data, Output(:success) => Track(:invalid_data), Output(:failure) => Track(:invalid_data)
59
+
60
+
61
+ # TODO: allow translation.
62
+ module Handler
63
+ def handle_not_authorized(ctx, errors:, **)
64
+ errors.message = "Action not allowed due to a policy setting."
65
+ end
66
+
67
+ def handle_not_authenticated(ctx, errors:, **)
68
+ errors.message = "Authentication credentials were not provided or are invalid."
69
+ end
70
+ end
71
+
72
+ class Termini # FIXME: this means with invalid_data, not_found termini? 2.1?
73
+
74
+ end
75
+ end
76
+
77
+ module Bridge
78
+ # this "bridge" should be optional for "legacy operations" that don't have explicit ends.
79
+ # we have to inspect the ctx to find out what "really" happened (e.g. model empty ==> 404)
80
+ NotFound = Class.new(Trailblazer::Activity::Signal)
81
+ NotAuthorized = Class.new(Trailblazer::Activity::Signal)
82
+ NotAuthenticated = Class.new(Trailblazer::Activity::Signal)
83
+
84
+ def self.insert(protocol, **)
85
+ Class.new(protocol) do
86
+ fail :success?, after: :domain_activity,
87
+ # FIXME: how to add more signals/outcomes?
88
+ Output(NotFound, :not_found) => Track(:not_found),
89
+
90
+ # FIXME: Track(:not_authorized) is defined before this step, so the Forward search doesn't find it.
91
+ # solution would be to walk down sequence and find the first {:magnetic_to} "not_authorized"
92
+ Output(NotAuthorized, :not_authorized) => Track(:not_authorized) # FIXME: how to "insert into path"? => Track(:not_authorized) doesn't play!
93
+ end
94
+ end
95
+ end
96
+
97
+
98
+ module Domain
99
+ # taskWrap step that saves the return signal of the {domain_activity}.
100
+ # The taskWrap step is usually inserted after {task_wrap.output}.
101
+ def self.terminus_handler(wrap_ctx, original_args)
102
+
103
+ # Unrecognized Signal `"bla"` returned from EndpointTest::LegacyCreate. Registered signals are,
104
+ # - #<Trailblazer::Activity::End semantic=:failure>
105
+ # - #<Trailblazer::Activity::End semantic=:success>
106
+ # - #<Trailblazer::Activity::End semantic=:fromail_fast>
107
+
108
+ # {:return_args} is the original "endpoint ctx" that was returned from the {:output} filter.
109
+ wrap_ctx[:return_args][0][:domain_activity_return_signal] = wrap_ctx[:return_signal]
110
+
111
+ return wrap_ctx, original_args
112
+ end
113
+
114
+ def self.extension_for_terminus_handler
115
+ # this is called after {:output}.
116
+ [[Trailblazer::Activity::TaskWrap::Pipeline.method(:insert_after), "task_wrap.call_task", ["endpoint.end_signal", method(:terminus_handler)]]]
117
+ end
118
+ end
119
+
120
+ end
121
+ end
122
+ end