trailblazer-endpoint 0.0.4 → 0.0.5

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +16 -0
  3. data/Appraisals +5 -0
  4. data/CHANGES.md +6 -0
  5. data/Rakefile +7 -1
  6. data/gemfiles/rails_app.gemfile +12 -0
  7. data/lib/trailblazer/endpoint.rb +20 -5
  8. data/lib/trailblazer/endpoint/adapter.rb +30 -121
  9. data/lib/trailblazer/endpoint/builder.rb +1 -1
  10. data/lib/trailblazer/endpoint/controller.rb +203 -1
  11. data/lib/trailblazer/endpoint/dsl.rb +6 -3
  12. data/lib/trailblazer/endpoint/options.rb +13 -44
  13. data/lib/trailblazer/endpoint/protocol.rb +5 -8
  14. data/lib/trailblazer/endpoint/version.rb +1 -1
  15. data/test/adapter/api_test.rb +6 -11
  16. data/test/adapter/web_test.rb +2 -5
  17. data/test/config_test.rb +25 -0
  18. data/test/docs/controller_test.rb +160 -73
  19. data/test/endpoint_test.rb +1 -1
  20. data/test/rails-app/.gitignore +8 -2
  21. data/test/rails-app/.ruby-version +1 -0
  22. data/test/rails-app/Gemfile +11 -9
  23. data/test/rails-app/Gemfile.lock +137 -121
  24. data/test/rails-app/app/concepts/app/api/v1/representer/errors.rb +16 -0
  25. data/test/rails-app/app/concepts/auth/jwt.rb +35 -0
  26. data/test/rails-app/app/concepts/auth/operation/authenticate.rb +32 -0
  27. data/test/rails-app/app/concepts/auth/operation/policy.rb +9 -0
  28. data/test/rails-app/app/concepts/song/operation/create.rb +13 -0
  29. data/test/rails-app/app/concepts/song/operation/show.rb +10 -0
  30. data/test/rails-app/app/concepts/song/representer.rb +5 -0
  31. data/test/rails-app/app/controllers/api/v1/songs_controller.rb +35 -0
  32. data/test/rails-app/app/controllers/application_controller.rb +6 -1
  33. data/test/rails-app/app/controllers/application_controller/api.rb +105 -0
  34. data/test/rails-app/app/controllers/application_controller/web.rb +30 -0
  35. data/test/rails-app/app/controllers/songs_controller.rb +15 -17
  36. data/test/rails-app/app/models/song.rb +3 -0
  37. data/test/rails-app/app/models/user.rb +5 -0
  38. data/test/rails-app/bin/bundle +114 -0
  39. data/test/rails-app/bin/rails +4 -0
  40. data/test/rails-app/bin/rake +4 -0
  41. data/test/rails-app/bin/setup +33 -0
  42. data/test/rails-app/config/application.rb +26 -3
  43. data/test/rails-app/config/credentials.yml.enc +1 -0
  44. data/test/rails-app/config/database.yml +2 -2
  45. data/test/rails-app/config/environments/development.rb +7 -17
  46. data/test/rails-app/config/environments/production.rb +28 -23
  47. data/test/rails-app/config/environments/test.rb +8 -12
  48. data/test/rails-app/config/initializers/application_controller_renderer.rb +6 -4
  49. data/test/rails-app/config/initializers/cors.rb +16 -0
  50. data/test/rails-app/config/initializers/trailblazer.rb +2 -0
  51. data/test/rails-app/config/locales/en.yml +11 -1
  52. data/test/rails-app/config/master.key +1 -0
  53. data/test/rails-app/config/routes.rb +8 -3
  54. data/test/rails-app/db/schema.rb +15 -0
  55. data/test/rails-app/test/controllers/api_songs_controller_test.rb +83 -0
  56. data/test/rails-app/test/controllers/songs_controller_test.rb +36 -144
  57. data/test/rails-app/test/test_helper.rb +7 -1
  58. data/test/test_helper.rb +0 -2
  59. data/trailblazer-endpoint.gemspec +1 -0
  60. metadata +52 -21
  61. data/test/rails-app/config/initializers/cookies_serializer.rb +0 -5
  62. data/test/rails-app/config/initializers/new_framework_defaults.rb +0 -24
  63. data/test/rails-app/config/initializers/session_store.rb +0 -3
  64. data/test/rails-app/config/secrets.yml +0 -22
  65. data/test/rails-app/test/helpers/.keep +0 -0
  66. data/test/rails-app/test/integration/.keep +0 -0
  67. data/test/rails-app/test/mailers/.keep +0 -0
  68. data/test/rails-app/vendor/assets/javascripts/.keep +0 -0
  69. data/test/rails-app/vendor/assets/stylesheets/.keep +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2333c41e1962d218d94a14572f7c8c955e5510b294ca0ea27ac2828898719c9
4
- data.tar.gz: 0a4fc49084a5e359ec3a0780d4f63a839a25fd0d63006c02c462224ee65d3f71
3
+ metadata.gz: 5856f0724650869732af67c5b45eee354e68a606b97d86153dffa3271adc9ee1
4
+ data.tar.gz: 01504dc413db4e2dce833908d1687ba0492f157cea3a3bca58853d9615d597d5
5
5
  SHA512:
6
- metadata.gz: 606002eb9724eee10e09459b4d47e0802ba84287a822283fcb1145f8b2097580e5d99942f7702d79145556226c2524112a1007f3dfa5201f8453dd5dd3ba704a
7
- data.tar.gz: 3916c8bfe99dbba20f8b6f9aed7a3d2c300b7f2bcc868ab751c00f22cd6db949ea6b77977a511366cdd89a04409ae84a96a92c9987133e454cf6e500a3720a45
6
+ metadata.gz: 9c6322150c0dad7b568a77c8dc8b4af4a48c40e6358d18b16cf57e50f3f9515bae3e38a1875f3eb48e420192dbe978a5f7631b5b35f7c1e532e58e52d3f2043a
7
+ data.tar.gz: 3ff1847a6e2e59fba1d8b3713b7069d58ea7e89be250ab79d5d87b795a5141c6a2c5dd1cc653257191d07906c66c4700fd12408b03200bdc89d87b312451cf96
@@ -0,0 +1,16 @@
1
+ .bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /log
10
+ tmp
11
+ .DS_Store
12
+ log/
13
+ *.iml
14
+ .idea
15
+ *.swp
16
+ gemfiles/*.lock
@@ -0,0 +1,5 @@
1
+ appraise 'rails-app' do
2
+ gem 'rails', '6.0.3.1'
3
+ gem 'sqlite3', '~> 1.4'
4
+ gem "trailblazer-operation"
5
+ end
data/CHANGES.md CHANGED
@@ -1,7 +1,13 @@
1
+ # 0.0.5
2
+
3
+ * Removed `Protocol::Failure`. Until we have `Railway::End::Failure`, use a normal `Activity::End` everywhere instead of introducing our own.
4
+ * Default `with_or_etc:invoke` is `TaskWrap.invoke`.
5
+
1
6
  # 0.0.4
2
7
 
3
8
  * Use new `context-0.3.1`.
4
9
  * Don't use `wtf?`.
10
+ * Don't create a `Context` anymore in `Endpoint.arguments_for`.
5
11
 
6
12
  # 0.0.3
7
13
 
data/Rakefile CHANGED
@@ -5,6 +5,12 @@ task :default => [:test]
5
5
 
6
6
  Rake::TestTask.new(:test) do |test|
7
7
  test.libs << 'test'
8
- test.test_files = FileList['test/endpoint_test.rb', 'test/docs/*_test.rb', "test/adapter/*_test.rb"]
8
+ test.test_files = FileList['test/endpoint_test.rb', 'test/docs/*_test.rb', "test/adapter/*_test.rb", "test/config_test.rb"]
9
+ test.verbose = true
10
+ end
11
+
12
+ Rake::TestTask.new('test-rails-app') do |test|
13
+ test.libs << 'test'
14
+ test.test_files = FileList['test/rails-app/test/test_helper.rb', 'test/rails-app/test/**/*.rb']
9
15
  test.verbose = true
10
16
  end
@@ -0,0 +1,12 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "multi_json"
6
+ gem "minitest-line"
7
+ gem "dry-validation"
8
+ gem "rails", "6.0.3.1"
9
+ gem "sqlite3", "~> 1.4"
10
+ gem "trailblazer-operation"
11
+
12
+ gemspec path: "../"
@@ -2,8 +2,7 @@ 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, domain_ctx_filter: nil, &block)
6
-
5
+ def self.build(protocol:, adapter:, domain_activity:, scope_domain_ctx: true, domain_ctx_filter: nil, protocol_block: ->(*) { Hash.new })
7
6
  # special considerations around the {domain_activity} and its taskWrap:
8
7
  #
9
8
  # 1. domain_ctx_filter (e.g. to filter {current_user})
@@ -24,16 +23,21 @@ module Trailblazer
24
23
  domain_ctx_filter_callable = [[Trailblazer::Activity::TaskWrap::Pipeline.method(:insert_before), "task_wrap.call_task", ["endpoint.domain_ctx_filter", domain_ctx_filter]]]
25
24
  extensions_options[:extensions] << Trailblazer::Activity::TaskWrap::Extension(merge: domain_ctx_filter_callable) if domain_ctx_filter
26
25
 
26
+ # puts Trailblazer::Developer.render(protocol)
27
+ # puts
28
+
27
29
  app_protocol = Class.new(protocol) do
28
30
  step(Subprocess(domain_activity), {inherit: true, id: :domain_activity, replace: :domain_activity,
29
31
 
30
32
  # FIXME: where does this go?
31
33
  }.
32
34
  merge(extensions_options).
33
- merge(instance_exec(&block)) # the block is evaluated in the {Protocol} context.
35
+ merge(instance_exec(&protocol_block)) # the block is evaluated in the {Protocol} context.
34
36
  )
35
37
  end
36
38
 
39
+ # puts Trailblazer::Developer.render(app_protocol)
40
+
37
41
  Class.new(adapter) do
38
42
  step(Subprocess(app_protocol), {inherit: true, id: :protocol, replace: :protocol})
39
43
  end # app_adapter
@@ -47,9 +51,19 @@ module Trailblazer
47
51
  }
48
52
  end
49
53
 
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
+
50
64
  # Runtime
51
65
  # 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:, invoke: Trailblazer::Developer.method(:wtf?))
66
+ def self.with_or_etc(activity, args, failure_block:, success_block:, protocol_failure_block:, invoke: Trailblazer::Activity::TaskWrap.method(:invoke)) # invoke: Trailblazer::Developer.method(:wtf?)
53
67
  # args[1] = args[1].merge(focus_on: { variables: [:returned], steps: :invoke_workflow })
54
68
 
55
69
  # signal, (endpoint_ctx, _ ) = Trailblazer::Developer.wtf?(activity, args)
@@ -78,7 +92,8 @@ module Trailblazer
78
92
 
79
93
  #@ For WORKFLOW and operations. not sure this method will stay here.
80
94
  def self.arguments_for(domain_ctx:, flow_options:, circuit_options: {}, **endpoint_options)
81
- domain_ctx = Trailblazer::Context(domain_ctx, {}, flow_options[:context_options])
95
+ # we don't have to create the Ctx wrapping explicitly here. this is done via `:input`.
96
+ # domain_ctx = Trailblazer::Context::IndifferentAccess.build(domain_ctx, {}, [domain_ctx, flow_options], circuit_options)
82
97
 
83
98
  [
84
99
  [
@@ -9,20 +9,32 @@ module Trailblazer
9
9
 
10
10
 
11
11
  module Adapter
12
- class Web <Trailblazer::Activity::FastTrack
12
+ class Web < Trailblazer::Activity::Path
13
13
  _404_path = ->(*) { step :_404_status }
14
14
  _401_path = ->(*) { step :_401_status }
15
15
  _403_path = ->(*) { step :_403_status }
16
16
  # _422_path = ->(*) { step :_422_status } # TODO: this is currently represented by the {failure} track.
17
17
 
18
+ # FIXME: is this really the only way to add an {End} to all this?
19
+ @state.update_sequence do |sequence:, **|
20
+ sequence = Activity::Path::DSL.append_end(sequence, task: Activity::End.new(semantic: :fail_fast), magnetic_to: :fail_fast, id: "End.fail_fast") # TODO: rename to {protocol_failure}
21
+ sequence = Activity::Path::DSL.append_end(sequence, task: Activity::End.new(semantic: :failure), magnetic_to: :failure, id: "End.failure")
22
+
23
+ recompile_activity!(sequence)
24
+
25
+ sequence
26
+ end
27
+
18
28
  step Subprocess(Protocol), # this will get replaced
19
29
  id: :protocol,
20
30
  Output(:not_authorized) => Path(track_color: :not_authorized, connect_to: Id(:protocol_failure), &_403_path),
21
31
  Output(:not_found) => Path(track_color: :not_found, connect_to: Id(:protocol_failure), &_404_path),
22
32
  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.
33
+ Output(:invalid_data) => Track(:failure), # application error, since it's usually a failed validation.
34
+ Output(:failure) => Track(:failure) # application error, since it's usually a failed validation.
35
+
36
+ step :protocol_failure, magnetic_to: nil, Output(:success) => Track(:fail_fast)#, Output(:failure) => Track(:fail_fast)
24
37
 
25
- step :protocol_failure, magnetic_to: nil, Output(:success) => Track(:fail_fast), Output(:failure) => Track(:fail_fast)
26
38
 
27
39
  def protocol_failure(ctx, **)
28
40
  true
@@ -41,28 +53,34 @@ module Trailblazer
41
53
  def _403_status(ctx, **)
42
54
  ctx[:status] = 403
43
55
  end
44
- end
56
+ end # Web
45
57
 
46
58
  class API < Web
47
59
  step :_200_status, after: :protocol
48
60
 
49
- def _200_status(ctx, **)
50
- ctx[:status] = 200
61
+ def _200_status(ctx, success_status: 200, **)
62
+ ctx[:status] = success_status
51
63
  end
52
64
 
53
- fail :_422_status, before: "End.failure"
65
+ step :_422_status, before: "End.failure", magnetic_to: :failure, Output(:success) => Track(:failure)
54
66
 
55
67
  def _422_status(ctx, **)
56
68
  ctx[:status] = 422
57
69
  end
58
70
 
59
71
 
60
- def self.insert_error_handler_steps(adapter)
72
+ def self.insert_error_handler_steps(adapter) # TODO: evaluate if needed?
61
73
  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
74
+ API.insert_error_handler_steps!(self)
75
+ end
76
+ end
77
+
78
+ def self.insert_error_handler_steps!(adapter)
79
+ adapter.instance_exec do
80
+ step :handle_not_authenticated, magnetic_to: :not_authenticated, Output(:success) => Track(:not_authenticated), before: :_401_status
81
+ step :handle_not_authorized, magnetic_to: :not_authorized, Output(:success) => Track(:not_authorized), before: :_403_status
64
82
  # step :handle_not_found, magnetic_to: :not_found, Output(:success) => Track(:not_found), Output(:failure) => Track(:not_found)
65
- fail :handle_invalid_data
83
+ step :handle_invalid_data, before: :_422_status, magnetic_to: :failure, Output(:success) => Track(:failure)
66
84
  end
67
85
  end
68
86
 
@@ -81,117 +99,8 @@ module Trailblazer
81
99
  end
82
100
  end
83
101
  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
102
+ end # API
190
103
 
191
- # def exec_success(ctx, success_block:, **)
192
- # success_block.call(ctx, **ctx.to_hash) # DISCUSS: use Nested(dynamic) ?
193
- # end
194
- end
195
104
  end
196
105
  end
197
106
  end
@@ -35,7 +35,7 @@ module Trailblazer
35
35
  def endpoint_for(id:, builder:, default_options:, **config)
36
36
  options = build_options_for(builder: builder, **config)
37
37
 
38
- return id, Trailblazer::Endpoint.build(default_options.merge(options[:options_for_build]), &options[:protocol_block])
38
+ return id, Trailblazer::Endpoint.build(default_options.merge(options[:options_for_build]).merge(protocol_block: options[:protocol_block]))
39
39
  end
40
40
 
41
41
  # {dsl_options} being something like
@@ -1,7 +1,209 @@
1
1
  module Trailblazer
2
2
  class Endpoint
3
3
  module Controller
4
+ def self.extended(extended)
5
+ extended.extend Trailblazer::Endpoint::Options::DSL
6
+ extended.extend Trailblazer::Endpoint::Options::DSL::Inherit
7
+ extended.extend Trailblazer::Endpoint::Options
8
+ extended.extend DSL::Endpoint
4
9
 
5
- end
10
+ extended.include InstanceMethods # {#endpoint_for}
11
+
12
+ # DISCUSS: hmm
13
+ extended.directive :generic_options, ->(*) { Hash.new } # for Controller::endpoint
14
+ extended.directive :options_for_flow_options, ->(*) { Hash.new }
15
+ extended.directive :options_for_endpoint, ->(*) { Hash.new }
16
+ extended.directive :options_for_domain_ctx, ->(*) { Hash.new }
17
+ end
18
+
19
+ # @experimental
20
+ def self.module(framework: :rails, api: false, dsl: false, application_controller: false)
21
+ if api
22
+ Module.new do
23
+ def self.included(includer)
24
+ includer.extend(Controller)
25
+ includer.include(InstanceMethods)
26
+ includer.include(InstanceMethods::API)
27
+ end
28
+ end
29
+ elsif dsl and !application_controller
30
+ Module.new do
31
+ def self.included(includer)
32
+ # includer.extend Trailblazer::Endpoint::Controller
33
+ includer.include Trailblazer::Endpoint::Controller::InstanceMethods::DSL
34
+ includer.include Trailblazer::Endpoint::Controller::Rails
35
+ includer.extend Trailblazer::Endpoint::Controller::Rails::DefaultBlocks
36
+ includer.extend Trailblazer::Endpoint::Controller::Rails::DefaultParams
37
+ includer.include Trailblazer::Endpoint::Controller::Rails::Process
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ module Rails
44
+ module Process
45
+ def send_action(action_name)
46
+ puts "@@@@@>>>>>>> #{action_name.inspect}"
47
+
48
+ dsl = send(action_name) # call the actual controller action.
49
+
50
+ options, block_options = dsl.to_args(self.class.options_for(:options_for_block_options, controller: self)) # {success_block:, failure_block:, protocol_failure_block:}
51
+ # now we know the authorative blocks
52
+
53
+ Controller.advance_endpoint_for_controller(**options, block_options: block_options, config_source: self.class, controller: self)
54
+ end
55
+
56
+ end # Process
57
+
58
+ # The three default handlers for {Endpoint::with_or_etc}
59
+ # @experimental
60
+ module DefaultBlocks
61
+ def self.extended(extended)
62
+ extended.directive :options_for_block_options, Controller.method(:options_for_block_options)
63
+ end
64
+ end
65
+ # @experimental
66
+ module DefaultParams
67
+ def self.extended(extended)
68
+ extended.directive :options_for_domain_ctx, ->(ctx, controller:, **) { {params: controller.params} }
69
+ end
70
+ end
71
+
72
+ end # Rails
73
+
74
+ module DSL
75
+ module Endpoint
76
+ def self.extended(extended)
77
+ extended.directive(:endpoints, ->(*) { {} })
78
+ end
79
+
80
+ def endpoint(name, **options, &block)
81
+ options = options.merge(protocol_block: block) if block_given?
82
+
83
+ return generic_endpoint_config(**name, **options) if name.is_a?(Hash)
84
+ endpoint_config(name, **options)
85
+ end
86
+
87
+ def generic_endpoint_config(protocol:, adapter:, **options)
88
+ self.singleton_class.define_method :generic_options do |ctx,**|
89
+ {
90
+ protocol: protocol,
91
+ adapter: adapter,
92
+ **options
93
+ }
94
+ end
95
+
96
+ directive :generic_options, method(:generic_options) # FIXME: do we need this?
97
+ end
98
+
99
+ def endpoint_config(name, **options)
100
+ puts "~~~~~~~~~~~~~~~config"
101
+ build_options = options_for(:generic_options, {}).merge(options) # DISCUSS: why don't we add this as another directive option/step?
102
+
103
+ endpoint = Trailblazer::Endpoint.build(build_options)
104
+
105
+ directive :endpoints, ->(*) { {name => endpoint} }
106
+ end
107
+
108
+ end
109
+ end
110
+
111
+ module InstanceMethods
112
+
113
+ def endpoint_for(name, config_source: self.class)
114
+ config_source.options_for(:endpoints, {}).fetch(name) # TODO: test non-existant endpoint
115
+ end
116
+
117
+ module DSL
118
+ def endpoint(name, **action_options, &block)
119
+ action_options = {controller: self}.merge(action_options) # FIXME: redundant with {DSL#endpoint}
120
+
121
+ endpoint = endpoint_for(name)
122
+
123
+ invoke_endpoint_with_dsl(endpoint: endpoint, **action_options, &block)
124
+ end
125
+
126
+ def invoke_endpoint_with_dsl(options, &block)
127
+ _dsl = Trailblazer::Endpoint::DSL::Runtime.new(options, block) # provides #Or etc, is returned to {Controller#call}
128
+ end
129
+ end
130
+
131
+ module API
132
+ def endpoint(name, config_source: self.class, **action_options)
133
+ endpoint = endpoint_for(name, config_source: config_source)
134
+
135
+ action_options = {controller: self}.merge(action_options) # FIXME: redundant with {DSL#endpoint}
136
+
137
+ block_options = config_source.options_for(:options_for_block_options, **action_options)
138
+ block_options = Trailblazer::Endpoint::Options.merge_with(action_options, block_options)
139
+
140
+ signal, (ctx, _) = Trailblazer::Endpoint::Controller.advance_endpoint_for_controller(
141
+ endpoint: endpoint,
142
+ block_options: block_options,
143
+ config_source: config_source,
144
+ **action_options
145
+ )
146
+
147
+ ctx
148
+ end
149
+ end # API
150
+ end
151
+
152
+
153
+ def self.advance_endpoint_for_controller(endpoint:, block_options:, **action_options)
154
+ domain_ctx, endpoint_options, flow_options = compile_options_for_controller(**action_options) # controller-specific, get from directives.
155
+
156
+ endpoint_options = endpoint_options.merge(action_options) # DISCUSS
157
+
158
+ Endpoint::Controller.advance_endpoint(
159
+ endpoint: endpoint,
160
+ block_options: block_options,
161
+
162
+ domain_ctx: domain_ctx,
163
+ endpoint_options: endpoint_options,
164
+ flow_options: flow_options,
165
+ )
166
+ end
167
+
168
+ def self.compile_options_for_controller(options_for_domain_ctx: nil, config_source:, **action_options)
169
+ flow_options = config_source.options_for(:options_for_flow_options, **action_options)
170
+ endpoint_options = config_source.options_for(:options_for_endpoint, **action_options) # "class level"
171
+ domain_ctx = options_for_domain_ctx || config_source.options_for(:options_for_domain_ctx, **action_options)
172
+
173
+ return domain_ctx, endpoint_options, flow_options
174
+ end
175
+
176
+ # Ultimate low-level entry point.
177
+ # Remember that you don't _have_ to use Endpoint.with_or_etc to invoke an endpoint.
178
+ def self.advance_endpoint(endpoint:, block_options:, domain_ctx:, endpoint_options:, flow_options:)
179
+
180
+ # build Context(ctx),
181
+ args, _ = Trailblazer::Endpoint.arguments_for(
182
+ domain_ctx: domain_ctx,
183
+ flow_options: flow_options,
184
+ **endpoint_options,
185
+ )
186
+
187
+ signal, (ctx, _ ) = Trailblazer::Endpoint.with_or_etc(
188
+ endpoint,
189
+ args, # [ctx, flow_options]
190
+
191
+ **block_options,
192
+ # success_block: success_block,
193
+ # failure_block: failure_block,
194
+ # protocol_failure_block: protocol_failure_block,
195
+ )
196
+ end
197
+
198
+ # Default blocks for the {Adapter}.
199
+ def self.options_for_block_options(ctx, controller:, **)
200
+ {
201
+ success_block: ->(ctx, endpoint_ctx:, **) { controller.head 200 },
202
+ failure_block: ->(ctx, **) { controller.head 422 },
203
+ protocol_failure_block: ->(ctx, endpoint_ctx:, **) { controller.head endpoint_ctx[:status] }
204
+ }
205
+ end
206
+
207
+ end # Controller
6
208
  end
7
209
  end