trailblazer-endpoint 0.0.2 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +16 -0
  3. data/Appraisals +5 -0
  4. data/CHANGES.md +28 -0
  5. data/README.md +40 -5
  6. data/Rakefile +7 -1
  7. data/gemfiles/rails_app.gemfile +12 -0
  8. data/lib/trailblazer/endpoint.rb +55 -16
  9. data/lib/trailblazer/endpoint/adapter.rb +30 -121
  10. data/lib/trailblazer/endpoint/builder.rb +1 -1
  11. data/lib/trailblazer/endpoint/controller.rb +223 -0
  12. data/lib/trailblazer/endpoint/dsl.rb +31 -0
  13. data/lib/trailblazer/endpoint/options.rb +92 -0
  14. data/lib/trailblazer/endpoint/protocol.rb +7 -11
  15. data/lib/trailblazer/endpoint/protocol/cipher.rb +27 -0
  16. data/lib/trailblazer/endpoint/protocol/controller.rb +93 -0
  17. data/lib/trailblazer/endpoint/protocol/find_process_model.rb +15 -0
  18. data/lib/trailblazer/endpoint/version.rb +1 -1
  19. data/test/adapter/api_test.rb +6 -11
  20. data/test/adapter/web_test.rb +2 -5
  21. data/test/config_test.rb +128 -0
  22. data/test/docs/controller_test.rb +340 -58
  23. data/test/endpoint_test.rb +1 -1
  24. data/test/rails-app/.gitignore +8 -2
  25. data/test/rails-app/.ruby-version +1 -0
  26. data/test/rails-app/Gemfile +21 -9
  27. data/test/rails-app/Gemfile.lock +173 -118
  28. data/test/rails-app/app/concepts/app/api/v1/representer/errors.rb +16 -0
  29. data/test/rails-app/app/concepts/auth/jwt.rb +35 -0
  30. data/test/rails-app/app/concepts/auth/operation/authenticate.rb +32 -0
  31. data/test/rails-app/app/concepts/auth/operation/policy.rb +9 -0
  32. data/test/rails-app/app/concepts/song/cell/create.rb +4 -0
  33. data/test/rails-app/app/concepts/song/cell/new.rb +4 -0
  34. data/test/rails-app/app/concepts/song/operation/create.rb +17 -0
  35. data/test/rails-app/app/concepts/song/operation/show.rb +10 -0
  36. data/test/rails-app/app/concepts/song/representer.rb +5 -0
  37. data/test/rails-app/app/concepts/song/view/create.erb +1 -0
  38. data/test/rails-app/app/concepts/song/view/new.erb +1 -0
  39. data/test/rails-app/app/controllers/api/v1/songs_controller.rb +41 -0
  40. data/test/rails-app/app/controllers/application_controller.rb +8 -1
  41. data/test/rails-app/app/controllers/application_controller/api.rb +107 -0
  42. data/test/rails-app/app/controllers/application_controller/web.rb +45 -0
  43. data/test/rails-app/app/controllers/auth_controller.rb +44 -0
  44. data/test/rails-app/app/controllers/home_controller.rb +5 -0
  45. data/test/rails-app/app/controllers/songs_controller.rb +245 -13
  46. data/test/rails-app/app/models/song.rb +6 -0
  47. data/test/rails-app/app/models/user.rb +7 -0
  48. data/test/rails-app/bin/bundle +114 -0
  49. data/test/rails-app/bin/rails +4 -0
  50. data/test/rails-app/bin/rake +4 -0
  51. data/test/rails-app/bin/setup +33 -0
  52. data/test/rails-app/config/application.rb +26 -3
  53. data/test/rails-app/config/credentials.yml.enc +1 -0
  54. data/test/rails-app/config/database.yml +2 -2
  55. data/test/rails-app/config/environments/development.rb +7 -17
  56. data/test/rails-app/config/environments/production.rb +28 -23
  57. data/test/rails-app/config/environments/test.rb +10 -12
  58. data/test/rails-app/config/initializers/application_controller_renderer.rb +6 -4
  59. data/test/rails-app/config/initializers/cors.rb +16 -0
  60. data/test/rails-app/config/initializers/trailblazer.rb +2 -0
  61. data/test/rails-app/config/locales/en.yml +11 -1
  62. data/test/rails-app/config/master.key +1 -0
  63. data/test/rails-app/config/routes.rb +26 -4
  64. data/test/rails-app/db/schema.rb +15 -0
  65. data/test/rails-app/test/controllers/api_songs_controller_test.rb +87 -0
  66. data/test/rails-app/test/controllers/songs_controller_test.rb +146 -144
  67. data/test/rails-app/test/test_helper.rb +7 -1
  68. data/test/test_helper.rb +0 -2
  69. data/trailblazer-endpoint.gemspec +2 -1
  70. metadata +73 -22
  71. data/test/rails-app/config/initializers/cookies_serializer.rb +0 -5
  72. data/test/rails-app/config/initializers/new_framework_defaults.rb +0 -24
  73. data/test/rails-app/config/initializers/session_store.rb +0 -3
  74. data/test/rails-app/config/secrets.yml +0 -22
  75. data/test/rails-app/test/helpers/.keep +0 -0
  76. data/test/rails-app/test/integration/.keep +0 -0
  77. data/test/rails-app/test/mailers/.keep +0 -0
  78. data/test/rails-app/vendor/assets/javascripts/.keep +0 -0
  79. data/test/rails-app/vendor/assets/stylesheets/.keep +0 -0
@@ -0,0 +1,32 @@
1
+ # FIXME: this is not final, yet!
2
+ class Auth::Operation::Authenticate < Trailblazer::Activity::Railway
3
+ step :verify_token
4
+ step :is_token_expired?
5
+ step :set_current_user
6
+
7
+ def verify_token(ctx, request:, **)
8
+ auth_header = request.headers['Authorization'] || ""
9
+ jwt_encoded_token = auth_header.split(' ').last
10
+ ctx[:encoded_jwt_token] = jwt_encoded_token
11
+
12
+ # FIXME
13
+ #raise StandardError if JwtService.expired?(jwt_encoded_token)
14
+ ctx[:decoded_jwt_token] = Auth::Jwt.decode(jwt_encoded_token)
15
+ end
16
+
17
+ def is_token_expired?(ctx, decoded_jwt_token:, **)
18
+ expiration_integer = decoded_jwt_token.first['exp']
19
+ return false if expiration_integer.nil?
20
+ return false if (expiration_integer - DateTime.now.to_i) <= 0
21
+ return true
22
+ end
23
+
24
+ def set_current_user(ctx, decoded_jwt_token:, **)
25
+ user_id = decoded_jwt_token.first['user_id']
26
+
27
+ ctx[:current_user] = User.find_by(
28
+ id: user_id
29
+ )
30
+ true # FIXME
31
+ end
32
+ end
@@ -0,0 +1,9 @@
1
+ module Auth
2
+ module Operation
3
+ class Policy
4
+ def self.call(ctx, domain_ctx:, **)
5
+ domain_ctx[:params][:policy] == false ? false : true
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ module Song::Cell
2
+ class Create < Trailblazer::Cell
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Song::Cell
2
+ class New < Trailblazer::Cell
3
+ end
4
+ end
@@ -0,0 +1,17 @@
1
+ module Song::Operation
2
+ class Create < Trailblazer::Operation
3
+ step :contract
4
+ step :model
5
+ # step :validate
6
+ # step :save
7
+
8
+ def model(ctx, params:, **)
9
+ return unless params[:id]
10
+ ctx[:model] = Song.new(params[:id])
11
+ end
12
+
13
+ def contract(ctx, **)
14
+ ctx[:contract] = Struct.new(:errors).new()
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ module Song::Operation
2
+ class Show < Trailblazer::Operation
3
+ step :model, Output(:failure) => End(:not_found)
4
+
5
+ def model(ctx, params:, **)
6
+ return unless params[:id] == "1"
7
+ ctx[:model] = Song.new(params[:id])
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ class Song::Representer < Representable::Decorator
2
+ include Representable::JSON
3
+
4
+ property :id
5
+ end
@@ -0,0 +1 @@
1
+ <div><%= model %><%= options[:current_user] %></div>
@@ -0,0 +1 @@
1
+ <div><%= model %></div>
@@ -0,0 +1,41 @@
1
+ #:controller
2
+ module Api
3
+ module V1
4
+ #:endpoint
5
+ class SongsController < ApplicationController::Api
6
+ endpoint Song::Operation::Create
7
+ endpoint Song::Operation::Show do
8
+ {Output(:not_found) => Track(:not_found)} # add additional wiring to {domain_activity}
9
+ end
10
+ #:endpoint end
11
+
12
+ #:create
13
+ def create
14
+ endpoint Song::Operation::Create, representer_class: Song::Representer
15
+ end
16
+ #:create end
17
+
18
+ #~empty
19
+ def show
20
+ endpoint Song::Operation::Show, representer_class: Song::Representer
21
+ end
22
+
23
+ def show_with_options
24
+ endpoint Song::Operation::Show, representer_class: Song::Representer, protocol_failure_block: ->(ctx, endpoint_ctx:, **) { head endpoint_ctx[:status] + 1 }
25
+ end
26
+
27
+ class WithOptionsController < ApplicationController::Api
28
+ endpoint Song::Operation::Show do {Output(:not_found) => Track(:not_found)} end
29
+
30
+ #:show-options
31
+ def show
32
+ endpoint Song::Operation::Show, representer_class: Song::Representer,
33
+ protocol_failure_block: ->(ctx, endpoint_ctx:, **) { head endpoint_ctx[:status] + 1 }
34
+ end
35
+ #:show-options end
36
+ end
37
+ #~empty end
38
+ end
39
+ end
40
+ end
41
+ #:controller end
@@ -1,3 +1,10 @@
1
+ require "trailblazer/endpoint/controller"
2
+
1
3
  class ApplicationController < ActionController::Base
2
- protect_from_forgery with: :exception
4
+ def self.current_user_in_domain_ctx
5
+ ->(_ctx, ((ctx, a), b)) { ctx[:domain_ctx][:current_user] = ctx[:current_user]; [_ctx, [[ctx, a], b]] } # FIXME: extract to lib?
6
+ end
3
7
  end
8
+
9
+
10
+ # directive
@@ -0,0 +1,107 @@
1
+ #:app-controller
2
+ #:app-include
3
+ class ApplicationController::Api < ApplicationController
4
+ include Trailblazer::Endpoint::Controller.module(api: true, application_controller: true)
5
+ #:app-include end
6
+
7
+ def self.options_for_block_options(ctx, controller:, **)
8
+ response_block = ->(ctx, endpoint_ctx:, **) do
9
+ controller.render json: endpoint_ctx[:representer], status: endpoint_ctx[:status]
10
+ end
11
+
12
+ {
13
+ success_block: response_block,
14
+ failure_block: response_block,
15
+ protocol_failure_block: response_block
16
+ }
17
+ end
18
+
19
+ directive :options_for_block_options, method(:options_for_block_options)
20
+ #:app-controller end
21
+
22
+ #:options_for_endpoint
23
+ def self.options_for_endpoint(ctx, controller:, **)
24
+ {
25
+ request: controller.request,
26
+ errors_representer_class: App::Api::V1::Representer::Errors,
27
+ errors: Trailblazer::Endpoint::Adapter::API::Errors.new,
28
+ }
29
+ end
30
+
31
+ directive :options_for_endpoint, method(:options_for_endpoint)
32
+ #:options_for_endpoint end
33
+
34
+ #:options_for_domain_ctx
35
+ def self.options_for_domain_ctx(ctx, controller:, **) # TODO: move to ApplicationController
36
+ {
37
+ params: controller.params,
38
+ }
39
+ end
40
+
41
+ directive :options_for_domain_ctx, method(:options_for_domain_ctx)
42
+ #:options_for_domain_ctx end
43
+
44
+ #:protocol
45
+ class Protocol < Trailblazer::Endpoint::Protocol
46
+ step Auth::Operation::Policy, inherit: true, id: :policy, replace: :policy
47
+ step Subprocess(Auth::Operation::Authenticate), inherit: true, id: :authenticate, replace: :authenticate
48
+ end
49
+ #:protocol end
50
+
51
+ #:adapter
52
+ module Adapter
53
+ class Representable < Trailblazer::Endpoint::Adapter::API
54
+ step :render # added before End.success
55
+ step :render_errors, after: :_422_status, magnetic_to: :failure, Output(:success) => Track(:failure)
56
+ step :render_errors, after: :protocol_failure, magnetic_to: :fail_fast, Output(:success) => Track(:fail_fast), id: :render_protocol_failure_errors
57
+
58
+ def render(ctx, domain_ctx:, representer_class:, **) # this is what usually happens in your {Responder}.
59
+ ctx[:representer] = representer_class.new(domain_ctx[:model] || raise("no model found!"))
60
+ end
61
+
62
+ def render_errors(ctx, errors:, errors_representer_class:, **) # TODO: extract with {render}
63
+ ctx[:representer] = errors_representer_class.new(errors)
64
+ end
65
+
66
+ Trailblazer::Endpoint::Adapter::API.insert_error_handler_steps!(self)
67
+ include Trailblazer::Endpoint::Adapter::API::Errors::Handlers # handler methods to set an error message.
68
+ end # Representable
69
+ end
70
+ #:adapter end
71
+
72
+ puts Trailblazer::Developer.render(Adapter::Representable)
73
+
74
+ #:endpoint
75
+ # app/controllers/application_controller/api.rb
76
+ endpoint protocol: Protocol, adapter: Adapter::Representable do
77
+ # {Output(:not_found) => Track(:not_found)}
78
+ {}
79
+ end
80
+ #:endpoint end
81
+ end
82
+
83
+
84
+ # header 'Authorization', "Bearer #{result['jwt_token']}" if result['jwt_token']
85
+
86
+
87
+ # ΓΞΞ Protocol ΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞ˥
88
+ # | (Start)--->[Authenticate]-->[Policy]-->[Show]----------->((success)) |
89
+ # | | | | L------------->((failure)) |
90
+ # | | | L------------->((not_found)) |
91
+ # | | L------------------->((not_authorized)) |
92
+ # | L----------------------------->((not_authenticated)) |
93
+ # | ((invalid_data)) |
94
+ # LΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞ˩
95
+ #--˥˩ ˪
96
+
97
+ # ΓΞΞ Adapter::Representable ΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞ˥
98
+ # | |
99
+ # | (Start)---> ΓΞΞ Protocol ΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞ˥ |
100
+ # | (Start)--->[Authenticate]-->[Policy]-->[Show]----------->((success)) |--->[_200_status]--->[render]---------------------------------------------------------------->((success)) |
101
+ # | | | | L------------->((failure)) |-˥ |
102
+ # | | | L------------->((not_found)) |-|------------------------------->[_404_status]-----------v |
103
+ # | | L------------------->((not_authorized)) |-|->[handle_not_authorized]------>[_403_status]--->[protocol_failure]--->[render_errors]--->((fail_fast)) |
104
+ # | L----------------------------->((not_authenticated)) |-|->[handle_not_authenticated]--->[_401_status]-----------^ |
105
+ # | ((invalid_data)) |-┴->[handle_invalid_data]-------->[_422_status]--->[render_errors]--------------------------->((failure)) |
106
+ # LΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞ˩ |
107
+ # LΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞ˩
@@ -0,0 +1,45 @@
1
+ #:app-include
2
+ #:options
3
+ #:protocol
4
+ #:generic
5
+ class ApplicationController::Web < ApplicationController
6
+ #~pskip
7
+ #~gskip
8
+ include Trailblazer::Endpoint::Controller.module(dsl: true, application_controller: true)
9
+ #:app-include end
10
+
11
+ def self.options_for_endpoint(ctx, controller:, **)
12
+ {
13
+ session: controller.session,
14
+ }
15
+ end
16
+
17
+ directive :options_for_endpoint, method(:options_for_endpoint)
18
+ #:options end
19
+
20
+ # directive :options_for_flow_options, method(:options_for_flow_options)
21
+ # directive :options_for_block_options, method(:options_for_block_options)
22
+ #~pskip end
23
+ class Protocol < Trailblazer::Endpoint::Protocol
24
+ # provide method for {step :authenticate}
25
+ def authenticate(ctx, session:, **)
26
+ ctx[:current_user] = User.find_by(id: session[:user_id])
27
+ end
28
+
29
+ # provide method for {step :policy}
30
+ def policy(ctx, domain_ctx:, **)
31
+ Policy.(domain_ctx)
32
+ end
33
+
34
+ Trailblazer::Endpoint::Protocol::Controller.insert_copy_to_domain_ctx!(self, :current_user => :current_user)
35
+ end
36
+ #:protocol end
37
+ Policy = ->(domain_ctx) { domain_ctx[:params][:policy] == "false" ? false : true }
38
+ #~gskip end
39
+ endpoint protocol: Protocol, adapter: Trailblazer::Endpoint::Adapter::Web
40
+ end
41
+ #:generic end
42
+
43
+ # do
44
+ # {Output(:not_found) => Track(:not_found)}
45
+ # end
@@ -0,0 +1,44 @@
1
+ class AuthController < ApplicationController::Web
2
+ # We could use a fully-fledged operation here, with a contract and whatnot.
3
+ def self.authenticate(ctx, request:, params:, **)
4
+ if params[:username] == "yogi@trb.to" && params[:password] == "secret"
5
+ ctx[:current_user] = User.find_by(email: params[:username])
6
+
7
+ return true
8
+ else
9
+ return false # let's be extra explicit!
10
+ end
11
+ end
12
+
13
+ endpoint("sign_in", domain_activity: Class.new(Trailblazer::Activity::Railway)) do
14
+ # step nil, delete: :domain_activity
15
+ step nil, delete: :policy
16
+ step AuthController.method(:authenticate), replace: :authenticate, inherit: true, id: :authenticate
17
+
18
+ {}
19
+ end
20
+
21
+ def self.options_for_endpoint(ctx, controller:, **)
22
+ {
23
+ params: controller.params,
24
+ request: controller.request,
25
+ }
26
+ end
27
+
28
+ directive :options_for_endpoint, method(:options_for_endpoint)
29
+
30
+ def sign_in
31
+ endpoint "sign_in" do |ctx, current_user:, **|
32
+ session[:user_id] = current_user.id # Working on {session} is HTTP-specific and done in the controller.
33
+
34
+ redirect_to dashboard_path
35
+ # render html: "Yes!"
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def dashboard_path
42
+ "/"
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ class HomeController < ApplicationController
2
+ def dashboard
3
+ render html: "Yo!"
4
+ end
5
+ end
@@ -1,26 +1,258 @@
1
- class SongsController < ApplicationController
2
- require "trailblazer/endpoint/rails"
3
- include Trailblazer::Endpoint::Controller
1
+ #:endpoint
2
+ #:or
3
+ #:create
4
+ class SongsController < ApplicationController::Web
5
+ endpoint Song::Operation::Create
4
6
 
7
+ #:endpoint end
5
8
  def create
6
- endpoint Create, path: songs_path, args: [ params, { "user.current" => ::Module } ]
9
+ endpoint Song::Operation::Create do |ctx, current_user:, model:, **|
10
+ render html: cell(Song::Cell::Create, model, current_user: current_user)
11
+ end.Or do |ctx, contract:, **| # validation failure
12
+ render html: cell(Song::Cell::New, contract)
13
+ end
14
+ end
15
+ #:create end
16
+
17
+ #~oskip
18
+ class CreateOrController < SongsController
19
+ #~oskip end
20
+ def create
21
+ endpoint Song::Operation::Create do |ctx, current_user:, model:, **|
22
+ render html: cell(Song::Cell::Create, model, current_user: current_user)
23
+ end.Or do |ctx, contract:, **| # validation failure
24
+ render json: contract.errors, status: 422
25
+ end
26
+ end
27
+ end
28
+ #:or end
29
+
30
+ def create_without_block
31
+ endpoint Song::Operation::Create
32
+ end
33
+
34
+ class CreateWithOptionsController < SongsController
35
+ #:create-options
36
+ def create
37
+ endpoint Song::Operation::Create, session: {user_id: 2} do |ctx, current_user:, model:, **|
38
+ render html: cell(Song::Cell::Create, model, current_user: current_user)
39
+ end
40
+ end
41
+ #:create-options end
7
42
  end
8
43
 
9
- def update
10
- endpoint Update, path: songs_path, args: [params]
44
+
45
+ class CreateWithOptionsForDomainCtxController < SongsController
46
+ #:domain_ctx
47
+ def create
48
+ endpoint Song::Operation::Create, options_for_domain_ctx: {params: {id: 999}} do |ctx, model:, **|
49
+ render html: cell(Song::Cell::Create, model)
50
+ end
51
+ end
52
+ #:domain_ctx end
11
53
  end
12
54
 
13
- def update_with_user
14
- endpoint Update, path: songs_path, args: [ params, { "user.current" => ::Module } ]
55
+ class CreateEndpointCtxController < SongsController
56
+ #:endpoint_ctx
57
+ def create
58
+ endpoint Song::Operation::Create do |ctx, endpoint_ctx:, **|
59
+ render html: "Created", status: endpoint_ctx[:status]
60
+ end.Or do |ctx, **| # validation failure
61
+ #~empty
62
+ #~empty end
63
+ end
64
+ end
65
+ #:endpoint_ctx end
15
66
  end
16
67
 
17
- def show
18
- endpoint Show, path: songs_path, args: [ params, { "user.current" => ::Module } ]
68
+ # end.Or do |ctx, endpoint_ctx:, **| # validation failure
69
+ # render json: endpoint_ctx.keys, status: 422
70
+ # end
71
+
72
+
73
+ class CreateWithProtocolFailureController < SongsController
74
+ #:protocol_failure
75
+ def create_with_protocol_failure
76
+ endpoint Song::Operation::Create do |ctx, **|
77
+ redirect_to dashboard_path
78
+ end.protocol_failure do |ctx, **|
79
+ render html: "wrong login, app crashed", status: 500
80
+ end
81
+ end
82
+ #:protocol_failure end
19
83
  end
20
84
 
21
- def create_with_custom_handlers
22
- endpoint Create, path: songs_path, args: [ params, { "user.current" => ::Module } ] do |m|
23
- m.created { |result| render json: result["representer.serializer.class"].new(result["model"]), status: 999 }
85
+
86
+ # endpoint_ctx
87
+ # :resume_data
88
+ # domain_ctx
89
+ # :resume_data (copy)
90
+
91
+
92
+ # authenticate
93
+
94
+ # deserialize ==> {resume_data: {id: 1}}
95
+ # deserialize_process_model_id_from_resume_data
96
+
97
+ # find_process_model
98
+ # policy
99
+ # domain_activity
100
+
101
+ # serialize suspend_data and deserialize resume_data
102
+ class SerializeController < SongsController
103
+ endpoint Song::Operation::Create,
104
+ protocol: ApplicationController::Web::Protocol
105
+ # serialize: true
106
+
107
+ def self.options_for_block_options(ctx, **)
108
+ {
109
+ invoke: Trailblazer::Developer.method(:wtf?) # FIXME
110
+ }
111
+ end
112
+
113
+
114
+ def self.options_for_endpoint(ctx, controller:, **)
115
+ {
116
+ cipher_key: Rails.application.config.cipher_key,
117
+
118
+ encrypted_resume_data: controller.params[:encrypted_resume_data],
119
+ }
120
+ end
121
+
122
+ directive :options_for_block_options, method(:options_for_block_options)
123
+ directive :options_for_endpoint, method(:options_for_endpoint)
124
+
125
+ def create
126
+ encrypted_value = Trailblazer::Workflow::Cipher.encrypt_value({}, cipher_key: cipher_key, value: JSON.dump({id: "findings received", class: Object}))
127
+
128
+ endpoint Song::Operation::Create, encrypted_resume_data: encrypted_value, process_model_from_resume_data: false do |ctx, current_user:, endpoint_ctx:, **|
129
+ render html: cell(Song::Cell::Create, model, current_user: current_user)
130
+ end.Or do |ctx, contract:, **| # validation failure
131
+ render html: cell(Song::Cell::New, contract)
132
+ end
133
+ end
134
+ end
135
+
136
+ # TODO: not really a doc test.
137
+ # the entire deserialize cycle is skipped since we only use {:serialize}
138
+ class Serialize1Controller < SerializeController
139
+ class Create < Trailblazer::Operation
140
+ pass ->(ctx, **) { ctx[:model] = ctx.key?(:model) ? ctx[:model] : false }
141
+ end
142
+
143
+ endpoint "Create",
144
+ domain_activity: Create,
145
+ serialize: true,
146
+ deserialize: true
147
+
148
+ def create
149
+ # {:model} and {:memory} are from the domain_ctx.
150
+ # {:encrypted_suspend_data} from endpoint.
151
+ endpoint "Create" do |ctx, model:, endpoint_ctx:, **|
152
+ render html: "#{model.inspect}/#{ctx[:memory].inspect}/#{endpoint_ctx[:encrypted_suspend_data]}".html_safe
153
+ end.Or do |ctx, **| # validation failure
154
+ render html: "xxx", status: 500
155
+ end
156
+ end
157
+ end
158
+
159
+ # TODO: not really a doc test.
160
+ # ---------deserialize cycle is skipped.
161
+ # we serialize {:remember}.
162
+ class Serialize2Controller < Serialize1Controller # "render confirm page"
163
+ class Create < Trailblazer::Operation
164
+ pass ->(ctx, **) { ctx[:model] = ctx.key?(:model) ? ctx[:model] : false }
165
+ step ->(ctx, **) { ctx[:suspend_data] = {remember: OpenStruct.new(id: 1), id: 9} } # write to domain_ctx[:suspend_data]
166
+ end
167
+
168
+ endpoint "Create",
169
+ domain_activity: Create,
170
+ serialize: true
171
+ end
172
+
173
+ # we can read from {:resume_data}
174
+ class Serialize3Controller < Serialize1Controller # "process submitted confirm page"
175
+ class Create < Trailblazer::Operation
176
+ pass ->(ctx, **) { ctx[:model] = ctx.key?(:model) ? ctx[:model] : false }
177
+ pass ->(ctx, **) { ctx[:memory] = ctx[:resume_data] } # read/process the suspended data
178
+ end
179
+
180
+ endpoint "Create",
181
+ domain_activity: Create,
182
+ deserialize: true
183
+ end
184
+
185
+ # find process_model via id in suspend/resume data (used to be called {process_model_from_resume_data})
186
+ class Serialize4Controller < Serialize1Controller
187
+ class Create < Trailblazer::Operation
188
+ pass ->(ctx, **) { ctx[:model] = ctx.key?(:model) ? ctx[:model] : false }
189
+ pass ->(ctx, **) { ctx[:memory] = ctx[:resume_data] } # read/process the suspended data
190
+ end
191
+
192
+ endpoint "Create",
193
+ domain_activity: Create,
194
+ deserialize: true,
195
+ find_process_model: true,
196
+ deserialize_process_model_id_from_resume_data: true
197
+
198
+ def create
199
+ endpoint "Create", process_model_class: Song do |ctx, endpoint_ctx:, **|
200
+ render html: "#{endpoint_ctx[:process_model_id].inspect}/#{ctx[:memory].inspect}/#{endpoint_ctx[:encrypted_suspend_data]}".html_safe
201
+ end
202
+ end
203
+ end
204
+
205
+ # find process_model from resume
206
+ # FIXME: what is the diff to Controller4?
207
+ class Serialize5Controller < Serialize1Controller
208
+ endpoint "Create",
209
+ domain_activity: Serialize4Controller::Create,
210
+ deserialize: true,
211
+ find_process_model: true,
212
+ deserialize_process_model_id_from_resume_data: true
213
+
214
+ def create
215
+ endpoint "Create", find_process_model: true, process_model_class: Song, process_model_id: params[:id] do |ctx, model:, endpoint_ctx:, **|
216
+ render html: "#{model.inspect}/#{ctx[:memory].inspect}/#{endpoint_ctx[:encrypted_suspend_data]}".html_safe
217
+ end
218
+ end
219
+ end
220
+
221
+ # find process_model from action_options
222
+ class Serialize6Controller < Serialize1Controller
223
+ endpoint "Create",
224
+ domain_activity: Serialize4Controller::Create,
225
+ protocol: ApplicationController::Web::Protocol,
226
+ find_process_model: true
227
+
228
+ def create
229
+ endpoint "Create", find_process_model: true, process_model_class: Song, process_model_id: params[:id] do |ctx, model:, endpoint_ctx:, **|
230
+ render html: "#{model.inspect}/#{endpoint_ctx[:process_model].inspect}/#{endpoint_ctx[:encrypted_suspend_data]}".html_safe
231
+ end
232
+ end
233
+ end
234
+
235
+ # Configure only {:find_process_model} and {:protocol}.
236
+ class Serialize7Controller < Serialize1Controller
237
+ endpoint find_process_model: true # generic setting for all endpoints in this controller.
238
+
239
+ endpoint "Create", # no need to specify {:find_process_model}
240
+ domain_activity: Serialize4Controller::Create
241
+
242
+ endpoint "New",
243
+ find_process_model: false,
244
+ domain_activity: Serialize4Controller::Create
245
+
246
+ def create
247
+ endpoint "Create", process_model_class: Song, process_model_id: params[:id] do |ctx, model:, endpoint_ctx:, **|
248
+ render html: "#{model.inspect}/#{endpoint_ctx[:process_model].inspect}/#{endpoint_ctx[:encrypted_suspend_data]}".html_safe
249
+ end
250
+ end
251
+
252
+ def new
253
+ endpoint "New", process_model_class: Song, process_model_id: params[:id] do |ctx, model:, endpoint_ctx:, **|
254
+ render html: "#{model.inspect}/#{endpoint_ctx[:process_model].inspect}/#{endpoint_ctx[:encrypted_suspend_data]}".html_safe
255
+ end
24
256
  end
25
257
  end
26
258
  end