servus 0.1.4 → 0.1.6

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/check-docs.md +1 -0
  3. data/.claude/commands/consistency-check.md +1 -0
  4. data/.claude/commands/fine-tooth-comb.md +1 -0
  5. data/.claude/commands/red-green-refactor.md +5 -0
  6. data/.claude/settings.json +15 -0
  7. data/.rubocop.yml +18 -2
  8. data/CHANGELOG.md +46 -0
  9. data/CLAUDE.md +10 -0
  10. data/IDEAS.md +1 -1
  11. data/READme.md +153 -5
  12. data/builds/servus-0.1.4.gem +0 -0
  13. data/builds/servus-0.1.5.gem +0 -0
  14. data/docs/core/2_architecture.md +32 -4
  15. data/docs/current_focus.md +569 -0
  16. data/docs/features/5_event_bus.md +244 -0
  17. data/docs/integration/1_configuration.md +60 -7
  18. data/docs/integration/2_testing.md +123 -0
  19. data/lib/generators/servus/event_handler/event_handler_generator.rb +59 -0
  20. data/lib/generators/servus/event_handler/templates/handler.rb.erb +86 -0
  21. data/lib/generators/servus/event_handler/templates/handler_spec.rb.erb +48 -0
  22. data/lib/generators/servus/service/service_generator.rb +4 -0
  23. data/lib/generators/servus/service/templates/arguments.json.erb +19 -10
  24. data/lib/generators/servus/service/templates/result.json.erb +8 -2
  25. data/lib/generators/servus/service/templates/service.rb.erb +101 -4
  26. data/lib/generators/servus/service/templates/service_spec.rb.erb +67 -6
  27. data/lib/servus/base.rb +21 -5
  28. data/lib/servus/config.rb +34 -14
  29. data/lib/servus/event_handler.rb +275 -0
  30. data/lib/servus/events/bus.rb +137 -0
  31. data/lib/servus/events/emitter.rb +162 -0
  32. data/lib/servus/events/errors.rb +10 -0
  33. data/lib/servus/railtie.rb +16 -0
  34. data/lib/servus/support/validator.rb +27 -0
  35. data/lib/servus/testing/matchers.rb +88 -0
  36. data/lib/servus/testing.rb +2 -0
  37. data/lib/servus/version.rb +1 -1
  38. data/lib/servus.rb +6 -0
  39. metadata +19 -1
@@ -0,0 +1,244 @@
1
+ # @title Features / 5. Event Bus
2
+
3
+ # Event Bus
4
+
5
+ Servus includes an event-driven architecture for decoupling service logic from side effects. Services emit events, and EventHandlers subscribe to them and invoke downstream services.
6
+
7
+ ## Overview
8
+
9
+ The Event Bus provides:
10
+ - **Emitters**: Services declare events they emit on success/failure
11
+ - **EventHandlers**: Subscribe to events and invoke services in response
12
+ - **Event Bus**: Routes events to registered handlers via ActiveSupport::Notifications
13
+ - **Payload Validation**: Optional JSON Schema validation for event payloads
14
+
15
+ ## Service Event Emission
16
+
17
+ Services can emit events when they succeed or fail using the `emits` DSL:
18
+
19
+ ```ruby
20
+ class CreateUser::Service < Servus::Base
21
+ emits :user_created, on: :success
22
+ emits :user_creation_failed, on: :failure
23
+
24
+ def initialize(email:, name:)
25
+ @email = email
26
+ @name = name
27
+ end
28
+
29
+ def call
30
+ user = User.create!(email: @email, name: @name)
31
+ success(user: user)
32
+ rescue ActiveRecord::RecordInvalid => e
33
+ failure(e.message)
34
+ end
35
+ end
36
+ ```
37
+
38
+ ### Custom Payloads
39
+
40
+ By default, success events receive `result.data` and failure events receive `result.error`. Customize with a block or method:
41
+
42
+ ```ruby
43
+ class CreateUser::Service < Servus::Base
44
+ # Block-based payload
45
+ emits :user_created, on: :success do |result|
46
+ { user_id: result.data[:user].id, email: result.data[:user].email }
47
+ end
48
+
49
+ # Method-based payload
50
+ emits :user_stats_updated, on: :success, with: :stats_payload
51
+
52
+ private
53
+
54
+ def stats_payload(result)
55
+ { user_count: User.count, latest_user_id: result.data[:user].id }
56
+ end
57
+ end
58
+ ```
59
+
60
+ ### Trigger Types
61
+
62
+ - `:success` - Fires when service returns `success(...)`
63
+ - `:failure` - Fires when service returns `failure(...)`
64
+ - `:error!` - Fires when service calls `error!(...)` (before exception is raised)
65
+
66
+ ## Event Handlers
67
+
68
+ EventHandlers live in `app/events/` and subscribe to events using a declarative DSL:
69
+
70
+ ```ruby
71
+ # app/events/user_created_handler.rb
72
+ class UserCreatedHandler < Servus::EventHandler
73
+ handles :user_created
74
+
75
+ invoke SendWelcomeEmail::Service, async: true do |payload|
76
+ { user_id: payload[:user_id], email: payload[:email] }
77
+ end
78
+
79
+ invoke TrackAnalytics::Service, async: true do |payload|
80
+ { event: 'user_created', user_id: payload[:user_id] }
81
+ end
82
+ end
83
+ ```
84
+
85
+ ### Generator
86
+
87
+ Generate handlers with the Rails generator:
88
+
89
+ ```bash
90
+ rails g servus:event_handler user_created
91
+ # Creates:
92
+ # app/events/user_created_handler.rb
93
+ # spec/events/user_created_handler_spec.rb
94
+ ```
95
+
96
+ ### Invocation Options
97
+
98
+ ```ruby
99
+ class UserCreatedHandler < Servus::EventHandler
100
+ handles :user_created
101
+
102
+ # Synchronous invocation (default)
103
+ invoke NotifyAdmin::Service do |payload|
104
+ { message: "New user: #{payload[:email]}" }
105
+ end
106
+
107
+ # Async via ActiveJob
108
+ invoke SendWelcomeEmail::Service, async: true do |payload|
109
+ { user_id: payload[:user_id] }
110
+ end
111
+
112
+ # Async with specific queue
113
+ invoke SendWelcomeEmail::Service, async: true, queue: :mailers do |payload|
114
+ { user_id: payload[:user_id] }
115
+ end
116
+
117
+ # Conditional invocation
118
+ invoke GrantPremiumRewards::Service, if: ->(p) { p[:premium] } do |payload|
119
+ { user_id: payload[:user_id] }
120
+ end
121
+
122
+ invoke SkipForPremium::Service, unless: ->(p) { p[:premium] } do |payload|
123
+ { user_id: payload[:user_id] }
124
+ end
125
+ end
126
+ ```
127
+
128
+ ## Emitting Events Directly
129
+
130
+ EventHandlers provide an `emit` class method for emitting events from controllers, jobs, or other code without a service:
131
+
132
+ ```ruby
133
+ class UsersController < ApplicationController
134
+ def create
135
+ user = User.create!(user_params)
136
+ UserCreatedHandler.emit({ user_id: user.id, email: user.email })
137
+ redirect_to user
138
+ end
139
+ end
140
+ ```
141
+
142
+ This is useful when the event source isn't a Servus service.
143
+
144
+ ## Payload Schema Validation
145
+
146
+ Define JSON schemas to validate event payloads:
147
+
148
+ ```ruby
149
+ class UserCreatedHandler < Servus::EventHandler
150
+ handles :user_created
151
+
152
+ schema payload: {
153
+ type: 'object',
154
+ required: ['user_id', 'email'],
155
+ properties: {
156
+ user_id: { type: 'integer' },
157
+ email: { type: 'string', format: 'email' }
158
+ }
159
+ }
160
+
161
+ invoke SendWelcomeEmail::Service, async: true do |payload|
162
+ { user_id: payload[:user_id], email: payload[:email] }
163
+ end
164
+ end
165
+ ```
166
+
167
+ When `emit` is called, the payload is validated against the schema before the event is dispatched.
168
+
169
+ ## Handler Validation
170
+
171
+ Enable strict validation to catch handlers subscribing to non-existent events:
172
+
173
+ ```ruby
174
+ # config/initializers/servus.rb
175
+ Servus.configure do |config|
176
+ config.strict_event_validation = true # Default: true
177
+ end
178
+
179
+ # Then manually validate (typically in a rake task or CI)
180
+ Servus::EventHandler.validate_all_handlers!
181
+ ```
182
+
183
+ This helps catch typos and orphaned handlers during development and CI.
184
+
185
+ ## Best Practices
186
+
187
+ ### Single Event Per Service
188
+
189
+ Services should emit one event per trigger representing their core concern:
190
+
191
+ ```ruby
192
+ # Good - one event, handler coordinates reactions
193
+ class CreateUser::Service < Servus::Base
194
+ emits :user_created, on: :success
195
+ end
196
+
197
+ class UserCreatedHandler < Servus::EventHandler
198
+ handles :user_created
199
+
200
+ invoke SendWelcomeEmail::Service, async: true
201
+ invoke TrackAnalytics::Service, async: true
202
+ invoke NotifySlack::Service, async: true
203
+ end
204
+
205
+ # Avoid - service doing too much coordination
206
+ class CreateUser::Service < Servus::Base
207
+ emits :send_welcome_email, on: :success
208
+ emits :track_user_analytics, on: :success
209
+ emits :notify_slack, on: :success
210
+ end
211
+ ```
212
+
213
+ ### Naming Conventions
214
+
215
+ - Events: Past tense describing what happened (`user_created`, `payment_processed`)
216
+ - Handlers: Event name + "Handler" suffix (`UserCreatedHandler`)
217
+
218
+ ### Handler Location
219
+
220
+ Handlers live in `app/events/` and are auto-loaded by the Railtie:
221
+
222
+ ```
223
+ app/events/
224
+ ├── user_created_handler.rb
225
+ ├── payment_processed_handler.rb
226
+ └── order_completed_handler.rb
227
+ ```
228
+
229
+ ## Instrumentation
230
+
231
+ Events are instrumented via ActiveSupport::Notifications and appear in Rails logs:
232
+
233
+ ```
234
+ servus.events.user_created (1.2ms) {:user_id=>123, :email=>"user@example.com"}
235
+ ```
236
+
237
+ Subscribe to events programmatically:
238
+
239
+ ```ruby
240
+ ActiveSupport::Notifications.subscribe(/^servus\.events\./) do |name, *args|
241
+ event_name = name.sub('servus.events.', '')
242
+ Rails.logger.info "Event emitted: #{event_name}"
243
+ end
244
+ ```
@@ -2,22 +2,27 @@
2
2
 
3
3
  # Configuration
4
4
 
5
- Servus works without configuration. One optional setting exists for schema file location.
5
+ Servus works without configuration. Optional settings exist for customizing directories and event validation.
6
6
 
7
- ## Schema Root
7
+ ## Directory Configuration
8
8
 
9
- By default, Servus looks for schema JSON files in `Rails.root/app/schemas/services` (or `./app/schemas/services` in non-Rails apps).
10
-
11
- Change the location if needed:
9
+ Configure where Servus looks for schemas, services, and event handlers:
12
10
 
13
11
  ```ruby
14
12
  # config/initializers/servus.rb
15
13
  Servus.configure do |config|
16
- config.schema_root = Rails.root.join('config/schemas')
14
+ # Default: 'app/schemas'
15
+ config.schemas_dir = 'app/schemas'
16
+
17
+ # Default: 'app/services'
18
+ config.services_dir = 'app/services'
19
+
20
+ # Default: 'app/events'
21
+ config.events_dir = 'app/events'
17
22
  end
18
23
  ```
19
24
 
20
- This affects legacy file-based schemas only - schemas defined via the `schema` DSL method do not use files.
25
+ These affect legacy file-based schemas and handler auto-loading. Schemas defined via the `schema` DSL method do not use files.
21
26
 
22
27
  ## Schema Cache
23
28
 
@@ -49,3 +54,51 @@ config.active_job.default_queue_name = :default
49
54
  ```
50
55
 
51
56
  Servus respects ActiveJob queue configuration - no Servus-specific setup needed.
57
+
58
+ ## Event Bus Configuration
59
+
60
+ ### Strict Event Validation
61
+
62
+ Enable strict validation to catch handlers subscribing to events that aren't emitted by any service:
63
+
64
+ ```ruby
65
+ # config/initializers/servus.rb
66
+ Servus.configure do |config|
67
+ # Default: true
68
+ config.strict_event_validation = true
69
+ end
70
+ ```
71
+
72
+ When enabled, you can validate handlers at boot or in CI:
73
+
74
+ ```ruby
75
+ # In a rake task or initializer
76
+ Servus::EventHandler.validate_all_handlers!
77
+ ```
78
+
79
+ This raises `Servus::Events::OrphanedHandlerError` if any handler subscribes to a non-existent event.
80
+
81
+ ### Handler Auto-Loading
82
+
83
+ In Rails, handlers in `app/events/` are automatically loaded. The Railtie:
84
+ - Clears the event bus on reload in development
85
+ - Eager-loads all `*_handler.rb` files from `config.events_dir`
86
+
87
+ ```
88
+ app/events/
89
+ ├── user_created_handler.rb
90
+ ├── payment_processed_handler.rb
91
+ └── order_completed_handler.rb
92
+ ```
93
+
94
+ ### Event Instrumentation
95
+
96
+ Events are instrumented via ActiveSupport::Notifications with the prefix `servus.events.`:
97
+
98
+ ```ruby
99
+ # Subscribe to all Servus events
100
+ ActiveSupport::Notifications.subscribe(/^servus\.events\./) do |name, *args|
101
+ event_name = name.sub('servus.events.', '')
102
+ Rails.logger.info "Event: #{event_name}"
103
+ end
104
+ ```
@@ -162,3 +162,126 @@ it "validates required fields" do
162
162
  }.to raise_error(Servus::Support::Errors::ValidationError, /required/)
163
163
  end
164
164
  ```
165
+
166
+ ## Testing Event Emission
167
+
168
+ Servus provides RSpec matchers for testing that services emit events.
169
+
170
+ ### Setup
171
+
172
+ Include the matchers in your test suite:
173
+
174
+ ```ruby
175
+ # spec/spec_helper.rb
176
+ require 'servus/testing'
177
+ ```
178
+
179
+ ### emit_event Matcher
180
+
181
+ Assert that a block emits an event:
182
+
183
+ ```ruby
184
+ RSpec.describe CreateUser::Service do
185
+ it 'emits user_created event on success' do
186
+ expect {
187
+ described_class.call(email: 'test@example.com', name: 'Test')
188
+ }.to emit_event(:user_created)
189
+ end
190
+
191
+ it 'emits event with expected payload' do
192
+ expect {
193
+ described_class.call(email: 'test@example.com', name: 'Test')
194
+ }.to emit_event(:user_created).with(hash_including(email: 'test@example.com'))
195
+ end
196
+
197
+ # Using handler class instead of symbol
198
+ it 'emits to UserCreatedHandler' do
199
+ expect {
200
+ described_class.call(email: 'test@example.com', name: 'Test')
201
+ }.to emit_event(UserCreatedHandler)
202
+ end
203
+ end
204
+ ```
205
+
206
+ ### call_service Matcher
207
+
208
+ Assert that a handler invokes a service:
209
+
210
+ ```ruby
211
+ RSpec.describe UserCreatedHandler do
212
+ let(:payload) { { user_id: 123, email: 'test@example.com' } }
213
+
214
+ it 'invokes SendWelcomeEmail::Service' do
215
+ expect {
216
+ described_class.handle(payload)
217
+ }.to call_service(SendWelcomeEmail::Service).with(user_id: 123)
218
+ end
219
+
220
+ it 'invokes service asynchronously' do
221
+ expect {
222
+ described_class.handle(payload)
223
+ }.to call_service(SendWelcomeEmail::Service).async
224
+ end
225
+ end
226
+ ```
227
+
228
+ ### Testing EventHandler Directly
229
+
230
+ Test handlers by calling their `handle` class method:
231
+
232
+ ```ruby
233
+ RSpec.describe UserCreatedHandler do
234
+ let(:payload) { { user_id: 123, email: 'test@example.com' } }
235
+
236
+ before do
237
+ allow(SendWelcomeEmail::Service).to receive(:call_async)
238
+ allow(TrackAnalytics::Service).to receive(:call_async)
239
+ end
240
+
241
+ it 'invokes SendWelcomeEmail with mapped arguments' do
242
+ described_class.handle(payload)
243
+
244
+ expect(SendWelcomeEmail::Service)
245
+ .to have_received(:call_async)
246
+ .with(user_id: 123, email: 'test@example.com')
247
+ end
248
+
249
+ it 'invokes TrackAnalytics with event data' do
250
+ described_class.handle(payload)
251
+
252
+ expect(TrackAnalytics::Service)
253
+ .to have_received(:call_async)
254
+ .with(event: 'user_created', user_id: 123)
255
+ end
256
+
257
+ context 'with conditional invocation' do
258
+ it 'skips premium rewards for non-premium users' do
259
+ allow(GrantPremiumRewards::Service).to receive(:call)
260
+
261
+ described_class.handle(payload.merge(premium: false))
262
+
263
+ expect(GrantPremiumRewards::Service).not_to have_received(:call)
264
+ end
265
+ end
266
+ end
267
+ ```
268
+
269
+ ### Testing Event Emission from Handler
270
+
271
+ Test the `emit` class method:
272
+
273
+ ```ruby
274
+ RSpec.describe UserCreatedHandler do
275
+ it 'emits the user_created event' do
276
+ expect {
277
+ described_class.emit({ user_id: 123, email: 'test@example.com' })
278
+ }.to emit_event(:user_created)
279
+ end
280
+
281
+ it 'validates payload against schema' do
282
+ expect {
283
+ described_class.emit({ invalid: 'payload' })
284
+ }.to raise_error(Servus::Support::Errors::ValidationError)
285
+ end
286
+ end
287
+ ```
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servus
4
+ module Generators
5
+ # Rails generator for creating Servus event handlers.
6
+ #
7
+ # Generates an event handler class and spec file.
8
+ #
9
+ # @example Generate an event handler
10
+ # rails g servus:event_handler user_created
11
+ #
12
+ # @example Generated files
13
+ # app/events/user_created_handler.rb
14
+ # spec/app/events/user_created_handler_spec.rb
15
+ #
16
+ # @see https://guides.rubyonrails.org/generators.html
17
+ class EventHandlerGenerator < Rails::Generators::NamedBase
18
+ source_root File.expand_path('templates', __dir__)
19
+
20
+ class_option :no_docs, type: :boolean,
21
+ default: false,
22
+ desc: 'Skip documentation comments in generated files'
23
+
24
+ # Creates the event handler and spec files.
25
+ #
26
+ # @return [void]
27
+ def create_handler_file
28
+ template 'handler.rb.erb', handler_path
29
+ template 'handler_spec.rb.erb', handler_spec_path
30
+ end
31
+
32
+ private
33
+
34
+ # Returns the path for the handler file.
35
+ #
36
+ # @return [String] handler file path
37
+ # @api private
38
+ def handler_path
39
+ File.join(Servus.config.events_dir, "#{file_name}_handler.rb")
40
+ end
41
+
42
+ # Returns the path for the handler spec file.
43
+ #
44
+ # @return [String] spec file path
45
+ # @api private
46
+ def handler_spec_path
47
+ File.join('spec', Servus.config.events_dir, "#{file_name}_handler_spec.rb")
48
+ end
49
+
50
+ # Returns the handler class name.
51
+ #
52
+ # @return [String] handler class name
53
+ # @api private
54
+ def handler_class_name
55
+ "#{class_name}Handler"
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ <%- unless options[:no_docs] -%>
4
+ # Handles the :<%= file_name %> event by invoking configured services.
5
+ #
6
+ # EventHandlers subscribe to a single event and declaratively map it to one or
7
+ # more service invocations. This provides clean separation between event emission
8
+ # (what happened) and event handling (what to do about it).
9
+ #
10
+ # @example Basic usage
11
+ # class <%= handler_class_name %> < Servus::EventHandler
12
+ # handles :<%= file_name %>
13
+ #
14
+ # # Invoke a service when this event fires
15
+ # invoke SendEmail::Service, async: true do |payload|
16
+ # { user_id: payload[:user_id], email: payload[:email] }
17
+ # end
18
+ # end
19
+ #
20
+ # @example Multiple service invocations
21
+ # invoke SendWelcomeEmail::Service, async: true, queue: :mailers do |payload|
22
+ # { user_id: payload[:user_id] }
23
+ # end
24
+ #
25
+ # invoke TrackAnalytics::Service, async: true do |payload|
26
+ # { event: '<%= file_name %>', user_id: payload[:user_id] }
27
+ # end
28
+ #
29
+ # @example Conditional invocation
30
+ # invoke GrantRewards::Service, if: ->(payload) { payload[:premium] } do |payload|
31
+ # { user_id: payload[:user_id] }
32
+ # end
33
+ #
34
+ # @example With payload schema validation
35
+ # schema payload: {
36
+ # type: 'object',
37
+ # required: ['user_id'],
38
+ # properties: {
39
+ # user_id: { type: 'integer' },
40
+ # email: { type: 'string', format: 'email' }
41
+ # }
42
+ # }
43
+ #
44
+ # @example Emit this event from anywhere
45
+ # # From controllers, jobs, rake tasks, etc.
46
+ # <%= handler_class_name %>.emit({ user_id: 123, email: 'user@example.com' })
47
+ #
48
+ # Available options for `invoke`:
49
+ # - async: true - Invoke service asynchronously via ActiveJob
50
+ # - queue: :queue_name - Specify ActiveJob queue (requires async: true)
51
+ # - if: ->(payload) {} - Condition that must be true to invoke
52
+ # - unless: ->(payload) {} - Condition that must be false to invoke
53
+ #
54
+ # @see Servus::EventHandler
55
+ # @see Servus::Events::Bus
56
+ <%- end -%>
57
+ class <%= handler_class_name %> < Servus::EventHandler
58
+ handles :<%= file_name %>
59
+
60
+ <%- unless options[:no_docs] -%>
61
+ # TODO: Define payload schema (optional but recommended)
62
+ # schema payload: {
63
+ # type: 'object',
64
+ # required: ['required_field'],
65
+ # properties: {
66
+ # required_field: { type: 'string' }
67
+ # }
68
+ # }
69
+
70
+ # TODO: Add service invocations
71
+ # invoke YourService, async: true do |payload|
72
+ # {
73
+ # # Map event payload to service arguments
74
+ # argument_name: payload[:field_name]
75
+ # }
76
+ # end
77
+ <%- end -%>
78
+ schema payload: {
79
+ type: 'object',
80
+ description: 'JSON schema for the <%= handler_class_name %> event payload',
81
+ }
82
+
83
+ # invoke ExampleService, async: true do |payload|
84
+ # { example_arg: payload[:example_field] }
85
+ # end
86
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ RSpec.describe <%= handler_class_name %> do
6
+ <%- unless options[:no_docs] -%>
7
+ # Test that the handler invokes the correct services with properly mapped arguments.
8
+ #
9
+ # Example test pattern:
10
+ # it 'invokes YourService with correct arguments' do
11
+ # expect(YourService).to receive(:call_async).with(user_id: 123)
12
+ # described_class.handle({ user_id: 123, email: 'test@example.com' })
13
+ # end
14
+ #
15
+ # For testing event emission from controllers/jobs:
16
+ # include Servus::Testing::EventHelpers
17
+ #
18
+ # it 'emits <%= file_name %> event' do
19
+ # servus_expect_event(:<%= file_name %>)
20
+ # .with_payload(hash_including(user_id: 123))
21
+ # .when { YourController.new.create }
22
+ # end
23
+
24
+ <%- end -%>
25
+ let(:payload) do
26
+ {
27
+ # TODO: Add sample payload fields
28
+ # user_id: 123,
29
+ # email: 'test@example.com'
30
+ }
31
+ end
32
+
33
+ <%- unless options[:no_docs] -%>
34
+ # TODO: Add tests for service invocations
35
+ # it 'invokes YourService with mapped arguments' do
36
+ # expect(YourService).to receive(:call_async).with(user_id: payload[:user_id])
37
+ # described_class.handle(payload)
38
+ # end
39
+
40
+ # TODO: Test conditional logic if using :if or :unless
41
+ # it 'skips invocation when condition is false' do
42
+ # expect(YourService).not_to receive(:call_async)
43
+ # described_class.handle(payload.merge(premium: false))
44
+ # end
45
+ <%- else -%>
46
+ # Add your tests here
47
+ <%- end -%>
48
+ end
@@ -24,6 +24,10 @@ module Servus
24
24
 
25
25
  argument :parameters, type: :array, default: [], banner: 'parameter'
26
26
 
27
+ class_option :no_docs, type: :boolean,
28
+ default: false,
29
+ desc: 'Skip documentation comments in generated files'
30
+
27
31
  # Creates all service-related files.
28
32
  #
29
33
  # Generates the service class, spec file, and schema files from templates.
@@ -1,15 +1,24 @@
1
1
  {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "<%= service_class_name %> Arguments",
4
+ "description": "JSON Schema for validating <%= service_class_name %> input arguments",
2
5
  "type": "object",
3
6
  "properties": {
4
- <%- parameters.each do |param| %>
5
- "<%= param %>": {
6
- "type": "UNDECLARED_TYPE"
7
- }
8
- <% end %>
7
+ <%- parameters.each_with_index do |param, index| -%>
8
+ "<%= param %>": {
9
+ "type": "string",
10
+ "description": "TODO: Describe the <%= param %> parameter"
11
+ }<%= index < parameters.length - 1 ? ',' : '' %>
12
+ <%- end -%>
13
+ <%- if parameters.empty? -%>
9
14
  },
15
+ <%- else -%>
16
+ },
17
+ <%- end -%>
10
18
  "required": [
11
- <%- parameters.each do |param| %>
12
- "<%= param %>",
13
- <% end %>
14
- ]
15
- }
19
+ <%- parameters.each_with_index do |param, index| -%>
20
+ "<%= param %>"<%= index < parameters.length - 1 ? ',' : '' %>
21
+ <%- end -%>
22
+ ],
23
+ "additionalProperties": false
24
+ }