trailblazer-endpoint 0.0.2 → 0.0.7

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