trailblazer-endpoint 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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