lite-command 2.0.2 → 2.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a841431a363de96b59d32abaa1bc733c04046a5d2f33b1c544e85b8b89cdcd9
4
- data.tar.gz: 11dd1cd8c8e19cc8d58a631f94f647eb4d7a6b2effe2591d04eaea4c693ecdee
3
+ metadata.gz: efd37a3a2fcbb8d90fef73088313f18bd281922d46594a4a57ee8dd34fcaf801
4
+ data.tar.gz: 4e88ca10b986095be52c3e454e70a6c8d7b3556bb4770ed51aca80d0b9beb894
5
5
  SHA512:
6
- metadata.gz: ae2440d1e33ddf601c42996a513f66771fb06af24a6f60534329b3337335bb714e3be2e9fd5b43d5f71e26ff8d2bc5bbe7bc9a07d275b259491858f6f8864e9b
7
- data.tar.gz: 873edb0d145a80033180e0a33072dd80da38512511d718af17e4e91ddc65d90118bd63d8a341d22278a1d026751b0cf867df708dafde21be54fc54ef0c821f47
6
+ metadata.gz: 4123ef0a3315a8b00796a58ca65b10bc2a51905dc59e663b1ffef2ec7f9becc700ae64216cb896024564993749979cd824087a68bac1e95532311dba29cd7aa0
7
+ data.tar.gz: dd4e22ead3d68f981319a372a0ce305f734f1745a31223daf725d93ff71cccbe1b4708e7b3d7ef4921f98f2e8df6c04939855b8a7e8d0e3292f5e7f3e0ec6c1a
data/.rubocop.yml CHANGED
@@ -51,6 +51,12 @@ RSpec/MultipleExpectations:
51
51
  Enabled: false
52
52
  RSpec/MultipleMemoizedHelpers:
53
53
  Enabled: false
54
+ RSpec/NestedGroups:
55
+ Enabled: false
56
+ RSpec/StringAsInstanceDoubleConstant:
57
+ Enabled: false
58
+ RSpec/VerifiedDoubleReference:
59
+ EnforcedStyle: string
54
60
  Style/ArgumentsForwarding:
55
61
  Enabled: false
56
62
  Style/Documentation:
data/CHANGELOG.md CHANGED
@@ -6,6 +6,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.1.0] - 2024-10-05
10
+ ### Added
11
+ - Added passing metadata to faults
12
+ - Added `on_success` callback
13
+ - Added `on_pending`, `on_executing`, `on_complete`, and `on_interrupted` callbacks
14
+ - Added attributes and attribute validations
15
+ - Added sequences
16
+ ### Changed
17
+ - Check error descendency instead of type
18
+ - Rename internal modules
19
+ - Make execute(!) methods private
20
+ ### Removed
21
+ - Remove predefined callback methods
22
+ - Remove non-bang fault methods
23
+
24
+ ## [2.0.3] - 2024-09-30
25
+ ### Changed
26
+ - Simplify error building
27
+ - Reduced recalling error since we can just throw it once
28
+ - Rename `fault_name` to `type`
29
+
9
30
  ## [2.0.2] - 2024-09-29
10
31
  ### Added
11
32
  - faultable module
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lite-command (2.0.2)
4
+ lite-command (2.1.0)
5
5
  ostruct
6
6
 
7
7
  GEM
@@ -114,7 +114,7 @@ GEM
114
114
  rspec-expectations (3.13.3)
115
115
  diff-lcs (>= 1.2.0, < 2.0)
116
116
  rspec-support (~> 3.13.0)
117
- rspec-mocks (3.13.1)
117
+ rspec-mocks (3.13.2)
118
118
  diff-lcs (>= 1.2.0, < 2.0)
119
119
  rspec-support (~> 3.13.0)
120
120
  rspec-support (3.13.1)
@@ -135,7 +135,7 @@ GEM
135
135
  rubocop-ast (>= 1.31.1, < 2.0)
136
136
  rubocop-rake (0.6.0)
137
137
  rubocop (~> 1.0)
138
- rubocop-rspec (3.0.5)
138
+ rubocop-rspec (3.1.0)
139
139
  rubocop (~> 1.61)
140
140
  ruby-progressbar (1.13.0)
141
141
  ruby_parser (3.21.1)
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # Lite::Command
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/lite-command.svg)](http://badge.fury.io/rb/lite-command)
4
- [![Build Status](https://travis-ci.org/drexed/lite-command.svg?branch=master)](https://travis-ci.org/drexed/lite-command)
5
4
 
6
5
  Lite::Command provides an API for building simple and complex command based service objects.
7
6
 
@@ -9,6 +8,10 @@ Lite::Command provides an API for building simple and complex command based serv
9
8
 
10
9
  Add this line to your application's Gemfile:
11
10
 
11
+ > [!NOTE]
12
+ > Gem versions `2.0.0`, `2.0.1`, `2.0.2`, and `2.0.3` are borked.
13
+ > Version `2.1.0` is the latest working version.
14
+
12
15
  ```ruby
13
16
  gem 'lite-command'
14
17
  ```
@@ -25,50 +28,112 @@ Or install it yourself as:
25
28
 
26
29
  * [Setup](#setup)
27
30
  * [Execution](#execution)
31
+ * [Dynamic Faults](#dynamic-faults)
28
32
  * [Context](#context)
29
- * [Internals](#Internals)
33
+ * [Attributes](#attributes)
34
+ * [States](#states)
35
+ * [Statuses](#statuses)
36
+ * [Callbacks](#callbacks)
37
+ * [State Hooks](#status-hooks)
38
+ * [Execution Hooks](#execution-hooks)
39
+ * [Status Hooks](#status-hooks)
40
+ * [Children](#children)
41
+ * [Throwing Faults](#throwing-faults)
42
+ * [Sequences](#sequences)
43
+ * [Results](#results)
44
+ * [Examples](#examples)
45
+ * [Disable Instance Calls](#disable-instance-calls)
46
+ * [ActiveModel Validations](#activemodel-validations)
30
47
  * [Generator](#generator)
31
48
 
32
49
  ## Setup
33
50
 
34
- Defining a command is as simple as adding a call method.
51
+ Defining a command is as simple as adding a call method to a command object (required).
35
52
 
36
53
  ```ruby
37
54
  class CalculatePower < Lite::Command::Base
38
55
 
39
56
  def call
40
- # TODO: implement calculator
57
+ if all_even_numbers?
58
+ context.result = ctx.a ** ctx.b
59
+ else
60
+ invalid!("All values must be even")
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def all_even_numbers?
67
+ # Some logic...
41
68
  end
42
69
 
43
70
  end
44
71
  ```
45
72
 
73
+ > [!TIP]
74
+ > You should make all of your domain logic private so that only the command API is exposed.
75
+
46
76
  ## Execution
47
77
 
48
78
  Executing a command can be done as an instance or class call.
49
- It returns the command instance in a forzen state.
79
+ It returns the command instance in a frozen state.
50
80
  These will never call will never raise an execption, but will
51
81
  be kept track of in its internal state.
52
82
 
53
- **NOTE:** Class calls is the prefered format due to its readability.
54
-
55
83
  ```ruby
56
- # Class call
57
- CalculatePower.call(..args)
58
-
59
- # Instance call
60
- caculator = CalculatePower.new(..args).call
84
+ CalculatePower.call(...)
85
+ # - or -
86
+ CalculatePower.new(...).call
61
87
 
88
+ # On success, fault and exception:
62
89
  #=> <CalculatePower ...>
63
90
  ```
64
91
 
92
+ > [!TIP]
93
+ > Class calls is the prefered format due to its readability.
94
+
65
95
  Commands can be called with a `!` bang method to raise a
66
96
  `Lite::Command::Fault` based exception or the original
67
97
  `StandardError` based exception.
68
98
 
69
99
  ```ruby
70
- CalculatePower.call!(..args)
100
+ CalculatePower.call!(...)
101
+ # - or -
102
+ CalculatePower.new(...).call!
103
+
104
+ # On success:
105
+ #=> <CalculatePower ...>
106
+
107
+ # On fault:
71
108
  #=> raises Lite::Command::Fault
109
+
110
+ # On exception:
111
+ #=> raises StandardError
112
+ ```
113
+
114
+ ### Dynamic Faults
115
+
116
+ You can enable dynamic faults named after your command. This is
117
+ especially helpful for catching + running custom logic or filtering
118
+ out specific errors from you APM service.
119
+
120
+ ```ruby
121
+ class CalculatePower < Lite::Command::Base
122
+
123
+ def call
124
+ fail!("Some failure")
125
+ end
126
+
127
+ private
128
+
129
+ def raise_dynamic_faults?
130
+ true
131
+ end
132
+
133
+ end
134
+
135
+ CalculatePower.call!(...)
136
+ #=> raises CalculatePower::Fault
72
137
  ```
73
138
 
74
139
  ## Context
@@ -81,7 +146,8 @@ of its children commands.
81
146
  class CalculatePower < Lite::Command::Base
82
147
 
83
148
  def call
84
- context.result = context.a ** context.b
149
+ # `ctx` is an alias to `context`
150
+ context.result = ctx.a ** ctx.b
85
151
  end
86
152
 
87
153
  end
@@ -90,50 +156,425 @@ command = CalculatePower.call(a: 2, b: 3)
90
156
  command.context.result #=> 8
91
157
  ```
92
158
 
93
- ## Internals
94
-
95
- #### States
96
- State represents the state of the executable code. Once `execute`
97
- is ran, it will always `complete` or `dnf` if a fault is thrown by a
98
- child command.
99
-
100
- - `pending`
101
- - Command objects that have been initialized.
102
- - `executing`
103
- - Command objects actively executing code.
104
- - `complete`
105
- - Command objects that executed to completion.
106
- - `dnf`
107
- - Command objects that could NOT be executed to completion.
108
- This could be as a result of a fault/exception on the
109
- object itself or one of its children.
110
-
111
- #### Statuses
112
-
113
- Status represents the state of the callable code. If no fault
114
- is thrown then a status of `success` is returned even if `call`
115
- has not been executed. The list of status include (by severity):
116
-
117
- - `success`
118
- - No fault or exception
119
- - `noop`
120
- - Noop represents skipping completion of call execution early
121
- an unsatisfied condition or logic check where there is no
122
- point on proceeding.
123
- - **eg:** account is sample: skip since its a non-alterable record
124
- - `invalid`
125
- - Invalid represents a stoppage of call execution due to
126
- missing, bad, or corrupt data.
127
- - **eg:** user not found: stop since rest of the call cant be executed
128
- - `failure`
129
- - Failure represents a stoppage of call execution due to
130
- an unsatisfied condition or logic check where it blocks
131
- proceeding any further.
132
- - **eg:** record not found: stop since there is nothing todo
133
- - `error`
134
- - Error represents a caught exception for a call execution
135
- that could not complete.
136
- - **eg:** ApiServerError: stop since there was a 3rd party issue
159
+ ### Attributes
160
+
161
+ Delegate methods for a cleaner command setup, type checking and
162
+ argument requirements. Setup a contract by using the `attribute`
163
+ method which automatically delegates to `context`.
164
+
165
+ | Options | Values | Default | Description |
166
+ | ---------- | ------ | ------- | ----------- |
167
+ | `from` | Symbol, String | `:context` | The object containing the attribute. |
168
+ | `types` | Symbol, String, Array, Proc | | The allowed class types of the attribute value. |
169
+ | `required` | Symbol, String, Boolean, Proc | `false` | The attribute must be passed to the context or delegatable (no matter the value). |
170
+ | `filled` | Symbol, String, Boolean, Proc | `false` | The attribute value must be not be `nil`. |
171
+
172
+ > [!NOTE]
173
+ > If optioned with some similar to `filled: true, types: [String, NilClass]`
174
+ > then `NilClass` for the `types` option will be removed automatically.
175
+
176
+ ```ruby
177
+ class CalculatePower < Lite::Command::Base
178
+
179
+ attribute :remote_storage, required: true, filled: true, types: RemoteStorage
180
+
181
+ attribute :a, :b
182
+ attribute :c, :d, from: :remote_storage, types: [Integer, Float]
183
+ attribute :x, :y, from: :local_storage, if: :signed_in?
184
+
185
+ def call
186
+ context.result =
187
+ (a.to_i ** b.to_i) +
188
+ (c.to_i + d.to_i) -
189
+ (x.to_i + y.to_i)
190
+ end
191
+
192
+ private
193
+
194
+ def local_storage
195
+ @local_storage ||= LocalStorage.new(x: 1, y: 1, z: 99)
196
+ end
197
+
198
+ def signed_in?
199
+ ctx.user.signed_in?
200
+ end
201
+
202
+ end
203
+
204
+ # With valid options:
205
+ storage = RemoteStorage.new(c: 2, d: 2, j: 99)
206
+ command = CalculatePower.call(a: 2, b: 2, remote_storage: storage)
207
+ command.status #=> "success"
208
+ command.context.result #=> 6
209
+
210
+ # With invalid options
211
+ command = CalculatePower.call
212
+ command.status #=> "invalid"
213
+ command.reason #=> "Invalid context attributes"
214
+ command.metadata #=> {
215
+ #=> context: ["a is required", "remote_storage must be filled"],
216
+ #=> remote_storage: ["d type invalid"]
217
+ #=> local_storage: ["is not defined or an attribute"]
218
+ #=> }
219
+ ```
220
+
221
+ ## States
222
+ `state` represents the condition of all the code command should execute.
223
+
224
+ | Status | Description |
225
+ | ------------- | ----------- |
226
+ | `pending` | Command objects that have been initialized. |
227
+ | `executing` | Command objects that are actively executing code. |
228
+ | `complete` | Command objects that executed to completion without fault/exception. |
229
+ | `interrupted` | Command objects that could **NOT** be executed to completion due to a fault/exception. |
230
+
231
+ > [!CAUTION]
232
+ > States are automatically transitioned and should **NEVER** be altered manually.
233
+
234
+ ```ruby
235
+ class CalculatePower < Lite::Command::Base
236
+
237
+ def call
238
+ # ...
239
+ end
240
+
241
+ end
242
+
243
+ command = CalculatePower.call(a: 1, b: 3)
244
+ command.state #=> "executed"
245
+ command.pending? #=> false
246
+ command.executed? #=> false
247
+ ```
248
+
249
+ ## Statuses
250
+
251
+ `status` represents the state of the domain logic executed via the `call` method.
252
+ A status of `success` is returned even if the command has **NOT** been executed.
253
+
254
+ | Status | Description |
255
+ | --------- | ----------- |
256
+ | `success` | Call execution completed without fault/exception. |
257
+ | `noop` | **Fault** to skip completion of call execution early for an unsatisfied condition where proceeding is pointless. |
258
+ | `invalid` | **Fault** to stop call execution due to missing, bad, or corrupt data. |
259
+ | `failure` | **Fault** to stop call execution due to an unsatisfied condition where it blocks proceeding any further. |
260
+ | `error` | **Fault** to stop call execution due to a thrown `StandardError` based exception. |
261
+
262
+ > [!IMPORTANT]
263
+ > Each **fault** status has a setter method ending in `!` that invokes a matching fault procedure.
264
+ > Metadata may also be passed to enrich your fault response.
265
+
266
+ ```ruby
267
+ class CalculatePower < Lite::Command::Base
268
+
269
+ def call
270
+ if ctx.a.nil? || ctx.b.nil?
271
+ invalid!("An a and b parameter must be passed")
272
+ elsif ctx.a < 1 || ctx.b < 1
273
+ failure!("Parameters must be >= 1")
274
+ elsif ctx.a == 1 || ctx.b == 1
275
+ noop!(
276
+ "Anything to the power of 1 is 1",
277
+ { i18n: "some.key" }
278
+ )
279
+ else
280
+ ctx.result = ctx.a ** ctx.b
281
+ end
282
+ rescue DivisionError => e
283
+ error!("Cathcing it myself")
284
+ end
285
+
286
+ end
287
+
288
+ command = CalculatePower.call(a: 1, b: 3)
289
+ command.ctx.result #=> nil
290
+ command.status #=> "noop"
291
+ command.reason #=> "Anything to the power of 1 is 1"
292
+ command.metadata #=> { i18n: "some.key" }
293
+ command.invalid? #=> false
294
+ command.noop? #=> true
295
+ command.noop?("Anything to the power of 1 is 1") #=> true
296
+ ```
297
+
298
+ ## Callbacks
299
+
300
+ Use callbacks to run arbituary code at transition points and
301
+ on finalized internals. The following is an example of the hooks
302
+ called for a failed command with a successful child command.
303
+
304
+ ```ruby
305
+ -> 1. FooCommand.on_pending
306
+ -> 2. FooCommand.on_before_execution
307
+ -> 3. FooCommand.on_executing
308
+ ---> 3a. BarCommand.on_pending
309
+ ---> 3b. BarCommand.on_before_execution
310
+ ---> 3c. BarCommand.on_executing
311
+ ---> 3d. BarCommand.on_after_execution
312
+ ---> 3e. BarCommand.on_success
313
+ ---> 3f. BarCommand.on_complete
314
+ -> 4. FooCommand.on_after_execution
315
+ -> 5. FooCommand.on_failure
316
+ -> 6. FooCommand.on_interrupted
317
+ ```
318
+
319
+ ### Status Hooks
320
+
321
+ Define one or more callbacks that are called during transitions
322
+ between states.
323
+
324
+ ```ruby
325
+ class CalculatePower < Lite::Command::Base
326
+
327
+ def call
328
+ # ...
329
+ end
330
+
331
+ private
332
+
333
+ def on_pending
334
+ # eg: Append additional contextual data
335
+ end
336
+
337
+ def on_executing
338
+ # eg: Insert inspection debugger
339
+ end
340
+
341
+ def on_complete
342
+ # eg: Log message for posterity
343
+ end
344
+
345
+ def on_interrupted
346
+ # eg: Report to APM with tags and metadata
347
+ end
348
+
349
+ end
350
+ ```
351
+
352
+ ### Execution Hooks
353
+
354
+ Define before and after callbacks to call around execution.
355
+
356
+ ```ruby
357
+ class CalculatePower < Lite::Command::Base
358
+
359
+ def call
360
+ # ...
361
+ end
362
+
363
+ private
364
+
365
+ def on_before_execution
366
+ # eg: Append additional contextual data
367
+ end
368
+
369
+ def on_after_execution
370
+ # eg: Store results to database
371
+ end
372
+
373
+ end
374
+ ```
375
+
376
+ ### Status Hooks
377
+
378
+ Define one or more callbacks that are called after execution for
379
+ specific statuses.
380
+
381
+ ```ruby
382
+ class CalculatePower < Lite::Command::Base
383
+
384
+ def call
385
+ # ...
386
+ end
387
+
388
+ private
389
+
390
+ def on_success
391
+ # eg: Increment KPI counter
392
+ end
393
+
394
+ def on_noop(fault)
395
+ # eg: Log message for posterity
396
+ end
397
+
398
+ def on_invalid(fault)
399
+ # eg: Send metadata errors to frontend
400
+ end
401
+
402
+ def on_failure(fault)
403
+ # eg: Rollback record changes
404
+ end
405
+
406
+ def on_error(fault_or_exception)
407
+ # eg: Report to APM with tags and metadata
408
+ end
409
+
410
+ end
411
+ ```
412
+
413
+ > [!NOTE]
414
+ > The `on_success` callback does **NOT** take any arguments.
415
+
416
+ ## Children
417
+
418
+ When building complex commands, its best that you pass the
419
+ parents context to the child command (unless neccessary) so
420
+ that it gains automated indexing and the parents `cmd_id`.
421
+
422
+ ```ruby
423
+ class CalculatePower < Lite::Command::Base
424
+
425
+ def call
426
+ CalculateSqrt.call(context.merge!(some_other: "required value"))
427
+ end
428
+
429
+ end
430
+ ```
431
+
432
+ ### Throwing Faults
433
+
434
+ Throwing faults allows you to bubble up child faults up to the parent.
435
+ Use it to create branches within your logic and create clean tracing
436
+ of your command results. You can use `throw!` as a catch-all or any
437
+ of the bang status method `failure!`.
438
+
439
+ ```ruby
440
+ class CalculatePower < Lite::Command::Base
441
+
442
+ def call
443
+ command = CalculateSqrt.call(context.merge!(some_other: "required value"))
444
+
445
+ if command.noop?("Sqrt of 1 is 1")
446
+ # Manually throw any fault you want
447
+ invalid!(command)
448
+ elsif command.fault?
449
+ # Automatically throws a matching fault type
450
+ throw!(command)
451
+ else
452
+ # Success, do nothing
453
+ end
454
+ end
455
+
456
+ end
457
+ ```
458
+
459
+ ## Sequences
460
+
461
+ A sequence is a command that calls commands in a linear fashion.
462
+ This is useful for composing multiple steps into one call.
463
+
464
+ > [!NOTE]
465
+ > Sequences only stop processing on `invalid`, `failure`, and `error`
466
+ > faults. This is due to the the idea the `noop` performs no work,
467
+ > so its no different than just passing the context forward. To change
468
+ > this behavior, just override the `ok?` method with you logic, eg: just `success`
469
+
470
+ ```ruby
471
+ class ProcessCheckout < Lite::Command::Sequence
472
+
473
+ attribute :user, required: true, filled: true
474
+
475
+ step FinalizeInvoice
476
+ step ChargeCard, if: :card_available?
477
+ step SendConfirmationEmail, SendConfirmationText
478
+ step NotifyWarehouse, unless: proc { ctx.invoice.fullfilled_by_amazon? }
479
+
480
+ # Do NOT set a call method.
481
+ # Its defined by Lite::Command::Sequence
482
+
483
+ private
484
+
485
+ def card_available?
486
+ user.has_card?
487
+ end
488
+
489
+ end
490
+
491
+ sequence = ProcessCheckout.call(...)
492
+ # <ProcessCheckout ...>
493
+ ```
494
+
495
+ ## Results
496
+
497
+ During any point in the lifecyle of a command, `to_hash` can be
498
+ called to dump out the current values. The `index` value is
499
+ auto-incremented and the `cmd_id` is static when its passed to
500
+ child commands. This helps with debugging and logging.
501
+
502
+ ```ruby
503
+ command = CalculatePower.call(...)
504
+ command.to_hash #=> {
505
+ #=> index: 1,
506
+ #=> cmd_id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
507
+ #=> command: "FailureCommand",
508
+ #=> outcome: "failure",
509
+ #=> state: "interrupted",
510
+ #=> status: "failure",
511
+ #=> reason: "[!] command stopped due to failure",
512
+ #=> metadata: {
513
+ #=> errors: { name: ["is too short"] },
514
+ #=> i18n_key: "command.failure"
515
+ #=> },
516
+ #=> caused_by: 1,
517
+ #=> thrown_by: 1,
518
+ #=> runtime: 0.0123
519
+ #=> }
520
+ ```
521
+
522
+ ## Examples
523
+
524
+ ### Disable Instance Calls
525
+
526
+ ```ruby
527
+ class CalculatePower < Lite::Command::Base
528
+
529
+ private_class_method :new
530
+
531
+ def call
532
+ # ...
533
+ end
534
+
535
+ end
536
+
537
+ CalculatePower.new(...).call
538
+ #=> raise NoMethodError
539
+ ```
540
+
541
+ ### ActiveModel Validations
542
+
543
+ ```ruby
544
+ class CalculatePower < Lite::Command::Base
545
+ include ActiveModel::Validations
546
+
547
+ validates :a, :b, presence: true
548
+
549
+ def call
550
+ # ...
551
+ end
552
+
553
+ def read_attribute_for_validation(key)
554
+ context.public_send(key)
555
+ end
556
+
557
+ private
558
+
559
+ def on_before_execution
560
+ return if valid?
561
+
562
+ invalid!(
563
+ errors.full_messages.to_sentence,
564
+ errors.to_hash
565
+ )
566
+ end
567
+
568
+ end
569
+
570
+ CalculatePower.call!
571
+
572
+ # With `validate!`
573
+ #=> raise ActiveRecord::RecordInvalid
574
+
575
+ # With `valid?`
576
+ #=> raise Lite::Command::Invalid
577
+ ```
137
578
 
138
579
  ## Generator
139
580