trailblazer-endpoint 0.0.4 → 0.0.5

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