senro_usecaser 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +23 -0
- data/README.md +195 -0
- data/lib/senro_usecaser/base.rb +80 -11
- data/lib/senro_usecaser/provider.rb +1 -1
- data/lib/senro_usecaser/version.rb +1 -1
- data/sig/generated/senro_usecaser/base.rbs +36 -7
- data/sig/generated/senro_usecaser/provider.rbs +1 -1
- data/sig/overrides.rbs +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cbfc07ede41cc295ceb0a019774a480744b226e88edc46f72a6447d59375f4e4
|
|
4
|
+
data.tar.gz: b86aaade8ccc9f2479f18d0f3bd5204203718ce26740eb3a51b5cccf1ed06798
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2ce95671ffefc951140c3625db940f1b077fe8bf40138d790b25067ab010a087d9f96b6c2e31bab2819ea89f0b2e6e905c70288f9409e24782cd73a0bca832cd
|
|
7
|
+
data.tar.gz: aa3b27b6eff694929f8cfc33ca47e5d7fb450eeb4ac5da9fecba2023cc70393a2edfa1b033e89d0fe0f0e1abe5cda3817841e8b92e04294062c0cd4a02ef8879
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.0] - 2026-01-31
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Runtime type validation for input**
|
|
13
|
+
- `input` now accepts Module(s) for interface-based validation
|
|
14
|
+
- Single module: `input HasUserId` - validates that input's class includes the module
|
|
15
|
+
- Multiple modules: `input HasUserId, HasEmail` - validates that input's class includes all modules
|
|
16
|
+
- Class validation remains supported for backwards compatibility
|
|
17
|
+
- **Runtime type validation for output**
|
|
18
|
+
- When `output` is declared with a Class, the success result's value is validated
|
|
19
|
+
- Raises `TypeError` if the output value is not an instance of the declared class
|
|
20
|
+
- Hash schema (`output({ key: Type })`) skips validation for backwards compatibility
|
|
21
|
+
- **New class methods**
|
|
22
|
+
- `input_types` - Returns an array of declared input types (Module/Class)
|
|
23
|
+
- `input_class` - Backwards compatible method, returns Class if specified or first type
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Type validation errors raise exceptions with `.call` and return `Result.failure` with `.call!`
|
|
28
|
+
- Input validation: `ArgumentError`
|
|
29
|
+
- Output validation: `TypeError`
|
|
30
|
+
|
|
8
31
|
## [0.2.0] - 2026-01-31
|
|
9
32
|
|
|
10
33
|
### Added
|
data/README.md
CHANGED
|
@@ -442,6 +442,179 @@ end
|
|
|
442
442
|
|
|
443
443
|
The `**_rest` parameter in Input's initialize allows extra fields to be passed through pipeline steps without errors.
|
|
444
444
|
|
|
445
|
+
### Runtime Type Validation
|
|
446
|
+
|
|
447
|
+
In addition to static type checking with RBS, SenroUsecaser provides runtime type validation for Input and Output. This ensures that the actual values passed at runtime match the expected types.
|
|
448
|
+
|
|
449
|
+
#### Input Type Validation
|
|
450
|
+
|
|
451
|
+
The `input` declaration supports three patterns:
|
|
452
|
+
|
|
453
|
+
##### 1. Class Validation (Traditional)
|
|
454
|
+
|
|
455
|
+
When a Class is specified, input must be an instance of that class:
|
|
456
|
+
|
|
457
|
+
```ruby
|
|
458
|
+
class CreateUserUseCase < SenroUsecaser::Base
|
|
459
|
+
input CreateUserInput # Class
|
|
460
|
+
|
|
461
|
+
def call(input)
|
|
462
|
+
# input must be a CreateUserInput instance
|
|
463
|
+
success(input.name)
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# OK
|
|
468
|
+
CreateUserUseCase.call(CreateUserInput.new(name: "Taro"))
|
|
469
|
+
|
|
470
|
+
# ArgumentError: Input must be an instance of CreateUserInput, got String
|
|
471
|
+
CreateUserUseCase.call("invalid")
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
##### 2. Interface Validation (Single Module)
|
|
475
|
+
|
|
476
|
+
When a Module is specified, input's class must include that module. This enables duck-typing with explicit interface contracts:
|
|
477
|
+
|
|
478
|
+
```ruby
|
|
479
|
+
# Define interface
|
|
480
|
+
module HasUserId
|
|
481
|
+
def user_id
|
|
482
|
+
raise NotImplementedError
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# UseCase expects input that includes HasUserId
|
|
487
|
+
class FindUserUseCase < SenroUsecaser::Base
|
|
488
|
+
input HasUserId
|
|
489
|
+
|
|
490
|
+
#: (HasUserId) -> SenroUsecaser::Result[User]
|
|
491
|
+
def call(input)
|
|
492
|
+
user = User.find(input.user_id)
|
|
493
|
+
success(user)
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# Input class that implements the interface
|
|
498
|
+
class UserQuery
|
|
499
|
+
include HasUserId
|
|
500
|
+
|
|
501
|
+
attr_reader :user_id
|
|
502
|
+
|
|
503
|
+
def initialize(user_id:)
|
|
504
|
+
@user_id = user_id
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
# OK - UserQuery includes HasUserId
|
|
509
|
+
FindUserUseCase.call(UserQuery.new(user_id: 123))
|
|
510
|
+
|
|
511
|
+
# ArgumentError: Input UserQuery must include HasUserId
|
|
512
|
+
class InvalidInput
|
|
513
|
+
attr_reader :user_id
|
|
514
|
+
def initialize(user_id:) = @user_id = user_id
|
|
515
|
+
end
|
|
516
|
+
FindUserUseCase.call(InvalidInput.new(user_id: 123))
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
##### 3. Multiple Interfaces Validation
|
|
520
|
+
|
|
521
|
+
Multiple Modules can be specified. The input must include ALL of them:
|
|
522
|
+
|
|
523
|
+
```ruby
|
|
524
|
+
module HasUserId
|
|
525
|
+
def user_id = raise NotImplementedError
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
module HasEmail
|
|
529
|
+
def email = raise NotImplementedError
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# UseCase requires both interfaces
|
|
533
|
+
class NotifyUserUseCase < SenroUsecaser::Base
|
|
534
|
+
input HasUserId, HasEmail
|
|
535
|
+
|
|
536
|
+
#: ((HasUserId & HasEmail)) -> SenroUsecaser::Result[bool]
|
|
537
|
+
def call(input)
|
|
538
|
+
notify(input.user_id, input.email)
|
|
539
|
+
success(true)
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# Input class must include both modules
|
|
544
|
+
class NotificationRequest
|
|
545
|
+
include HasUserId
|
|
546
|
+
include HasEmail
|
|
547
|
+
|
|
548
|
+
attr_reader :user_id, :email
|
|
549
|
+
|
|
550
|
+
def initialize(user_id:, email:)
|
|
551
|
+
@user_id = user_id
|
|
552
|
+
@email = email
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
# OK
|
|
557
|
+
NotifyUserUseCase.call(NotificationRequest.new(user_id: 123, email: "test@example.com"))
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
##### Interface Pattern in Pipelines
|
|
561
|
+
|
|
562
|
+
Interface validation is especially useful for sub-UseCases in pipelines. A parent UseCase's Input can include multiple interfaces, and each step only requires the interfaces it needs:
|
|
563
|
+
|
|
564
|
+
```ruby
|
|
565
|
+
# Parent UseCase - Input includes both interfaces
|
|
566
|
+
class ProcessOrderUseCase < SenroUsecaser::Base
|
|
567
|
+
class Input
|
|
568
|
+
include HasUserId
|
|
569
|
+
include HasEmail
|
|
570
|
+
|
|
571
|
+
attr_reader :user_id, :email, :order_items
|
|
572
|
+
|
|
573
|
+
def initialize(user_id:, email:, order_items:)
|
|
574
|
+
@user_id = user_id
|
|
575
|
+
@email = email
|
|
576
|
+
@order_items = order_items
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
input Input
|
|
581
|
+
|
|
582
|
+
organize do
|
|
583
|
+
step FindUserUseCase # Only needs HasUserId
|
|
584
|
+
step NotifyUserUseCase # Needs HasUserId and HasEmail
|
|
585
|
+
step CreateOrderUseCase
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
#### Output Type Validation
|
|
591
|
+
|
|
592
|
+
When `output` is declared with a Class, the success result's value is validated:
|
|
593
|
+
|
|
594
|
+
```ruby
|
|
595
|
+
class UserOutput
|
|
596
|
+
attr_reader :user
|
|
597
|
+
def initialize(user:) = @user = user
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
class FindUserUseCase < SenroUsecaser::Base
|
|
601
|
+
input FindUserInput
|
|
602
|
+
output UserOutput # Class declaration enables validation
|
|
603
|
+
|
|
604
|
+
def call(input)
|
|
605
|
+
user = User.find(input.user_id)
|
|
606
|
+
success(UserOutput.new(user: user)) # OK
|
|
607
|
+
|
|
608
|
+
# TypeError: Output must be an instance of UserOutput, got User
|
|
609
|
+
# success(user) # Wrong! Must wrap in UserOutput
|
|
610
|
+
end
|
|
611
|
+
end
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**Note:** When `output` is a Hash schema (e.g., `output({ user: User })`), validation is skipped for backwards compatibility.
|
|
615
|
+
|
|
616
|
+
**Note:** Type validation errors raise exceptions (`ArgumentError` for input, `TypeError` for output). See [`.call` vs `.call!`](#call-vs-call-1) for how exceptions are handled.
|
|
617
|
+
|
|
445
618
|
### Simplicity
|
|
446
619
|
|
|
447
620
|
Define UseCases with minimal boilerplate. Avoids over-abstraction and provides an intuitive API.
|
|
@@ -1007,6 +1180,28 @@ end
|
|
|
1007
1180
|
|
|
1008
1181
|
Use `.call!` when you want to ensure all exceptions are captured as `Result.failure` without explicit rescue blocks in your UseCase.
|
|
1009
1182
|
|
|
1183
|
+
**Type validation errors** (from `input` and `output` declarations) also follow this pattern:
|
|
1184
|
+
|
|
1185
|
+
```ruby
|
|
1186
|
+
# With .call - type validation errors raise exceptions
|
|
1187
|
+
begin
|
|
1188
|
+
UseCase.call(invalid_input)
|
|
1189
|
+
rescue ArgumentError => e
|
|
1190
|
+
puts e.message # "Input SomeClass must include HasUserId"
|
|
1191
|
+
end
|
|
1192
|
+
|
|
1193
|
+
# With .call! - type validation errors become Result.failure
|
|
1194
|
+
result = UseCase.call!(invalid_input)
|
|
1195
|
+
result.failure? # => true
|
|
1196
|
+
result.errors.first.code # => :exception
|
|
1197
|
+
result.errors.first.message # => "Input SomeClass must include HasUserId"
|
|
1198
|
+
```
|
|
1199
|
+
|
|
1200
|
+
| Validation | Exception type | With `.call` | With `.call!` |
|
|
1201
|
+
|------------|---------------|--------------|---------------|
|
|
1202
|
+
| Input type | `ArgumentError` | Raises | `Result.failure` |
|
|
1203
|
+
| Output type | `TypeError` | Raises | `Result.failure` |
|
|
1204
|
+
|
|
1010
1205
|
#### Exception Handling in Pipelines
|
|
1011
1206
|
|
|
1012
1207
|
When using `.call!` with `organize` pipelines, the exception capture behavior is **chained** to all steps. This is especially useful with `on_failure: :collect`:
|
data/lib/senro_usecaser/base.rb
CHANGED
|
@@ -276,17 +276,41 @@ module SenroUsecaser
|
|
|
276
276
|
@around_hooks ||= []
|
|
277
277
|
end
|
|
278
278
|
|
|
279
|
-
# Declares the expected input type for this UseCase
|
|
279
|
+
# Declares the expected input type(s) for this UseCase
|
|
280
|
+
# Accepts a Class or one or more Modules that input must include
|
|
280
281
|
#
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
282
|
+
# @example Single class
|
|
283
|
+
# input UserInput
|
|
284
|
+
#
|
|
285
|
+
# @example Single module (interface)
|
|
286
|
+
# input HasUserId
|
|
287
|
+
#
|
|
288
|
+
# @example Multiple modules (interfaces)
|
|
289
|
+
# input HasUserId, HasEmail
|
|
290
|
+
#
|
|
291
|
+
#: (*Module) -> void
|
|
292
|
+
def input(*types)
|
|
293
|
+
@input_types = types
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Returns the input types as an array
|
|
297
|
+
#
|
|
298
|
+
#: () -> Array[Module]
|
|
299
|
+
def input_types
|
|
300
|
+
@input_types || []
|
|
284
301
|
end
|
|
285
302
|
|
|
286
|
-
# Returns the input class
|
|
303
|
+
# Returns the input class (for backwards compatibility)
|
|
304
|
+
# If a Class is specified, returns it. Otherwise returns the first type.
|
|
287
305
|
#
|
|
288
|
-
#: () ->
|
|
289
|
-
|
|
306
|
+
#: () -> Module?
|
|
307
|
+
def input_class
|
|
308
|
+
types = input_types
|
|
309
|
+
return nil if types.empty?
|
|
310
|
+
|
|
311
|
+
# Class があればそれを返す(単一 Class 指定の後方互換)
|
|
312
|
+
types.find { |t| t.is_a?(Class) } || types.first
|
|
313
|
+
end
|
|
290
314
|
|
|
291
315
|
# Declares the expected output type for this UseCase
|
|
292
316
|
#
|
|
@@ -340,7 +364,7 @@ module SenroUsecaser
|
|
|
340
364
|
subclass.instance_variable_set(:@use_case_namespace, @use_case_namespace)
|
|
341
365
|
subclass.instance_variable_set(:@organized_steps, @organized_steps&.dup)
|
|
342
366
|
subclass.instance_variable_set(:@on_failure_strategy, @on_failure_strategy)
|
|
343
|
-
subclass.instance_variable_set(:@
|
|
367
|
+
subclass.instance_variable_set(:@input_types, @input_types&.dup)
|
|
344
368
|
subclass.instance_variable_set(:@output_schema, @output_schema)
|
|
345
369
|
end
|
|
346
370
|
|
|
@@ -372,6 +396,8 @@ module SenroUsecaser
|
|
|
372
396
|
raise ArgumentError, "#{self.class.name} must define `input` class"
|
|
373
397
|
end
|
|
374
398
|
|
|
399
|
+
validate_input!(input)
|
|
400
|
+
|
|
375
401
|
execute_with_hooks(input) do
|
|
376
402
|
call(input)
|
|
377
403
|
end
|
|
@@ -416,6 +442,48 @@ module SenroUsecaser
|
|
|
416
442
|
Result.capture(*exception_classes, code: code, &)
|
|
417
443
|
end
|
|
418
444
|
|
|
445
|
+
# Validates that input satisfies all declared input types
|
|
446
|
+
# For Modules: checks if input's class includes the module
|
|
447
|
+
# For Classes: checks if input is an instance of the class
|
|
448
|
+
#
|
|
449
|
+
#: (untyped) -> void
|
|
450
|
+
def validate_input!(input)
|
|
451
|
+
types = self.class.input_types
|
|
452
|
+
return if types.empty?
|
|
453
|
+
|
|
454
|
+
types.each do |expected_type|
|
|
455
|
+
if expected_type.is_a?(Module) && !expected_type.is_a?(Class)
|
|
456
|
+
# Module の場合: include しているかを検査
|
|
457
|
+
unless input.class.include?(expected_type)
|
|
458
|
+
raise ArgumentError,
|
|
459
|
+
"Input #{input.class} must include #{expected_type}"
|
|
460
|
+
end
|
|
461
|
+
elsif !input.is_a?(expected_type)
|
|
462
|
+
# Class の場合: インスタンスかを検査
|
|
463
|
+
raise ArgumentError,
|
|
464
|
+
"Input must be an instance of #{expected_type}, got #{input.class}"
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
# Validates that the result's value satisfies the declared output type
|
|
470
|
+
# Only validates if result is success and output_schema is a Class
|
|
471
|
+
#
|
|
472
|
+
#: (Result[untyped]) -> void
|
|
473
|
+
def validate_output!(result)
|
|
474
|
+
return unless result.success?
|
|
475
|
+
|
|
476
|
+
expected_type = self.class.output_schema
|
|
477
|
+
return if expected_type.nil?
|
|
478
|
+
return unless expected_type.is_a?(Class)
|
|
479
|
+
|
|
480
|
+
value = result.value
|
|
481
|
+
return if value.is_a?(expected_type)
|
|
482
|
+
|
|
483
|
+
raise TypeError,
|
|
484
|
+
"Output must be an instance of #{expected_type}, got #{value.class}"
|
|
485
|
+
end
|
|
486
|
+
|
|
419
487
|
# Executes the core logic with before/after/around hooks
|
|
420
488
|
#
|
|
421
489
|
#: (untyped) { () -> Result[untyped] } -> Result[untyped]
|
|
@@ -423,6 +491,7 @@ module SenroUsecaser
|
|
|
423
491
|
execution = build_around_chain(input, core_block)
|
|
424
492
|
run_before_hooks(input)
|
|
425
493
|
result = execution.call
|
|
494
|
+
validate_output!(result)
|
|
426
495
|
run_after_hooks(input, result)
|
|
427
496
|
result
|
|
428
497
|
end
|
|
@@ -692,12 +761,12 @@ module SenroUsecaser
|
|
|
692
761
|
end
|
|
693
762
|
|
|
694
763
|
# Calls a single UseCase in the pipeline
|
|
695
|
-
# Requires
|
|
764
|
+
# Requires input type(s) to be defined for pipeline steps
|
|
696
765
|
#
|
|
697
766
|
#: (singleton(Base), untyped) -> Result[untyped]
|
|
698
767
|
def call_use_case(use_case_class, input)
|
|
699
|
-
|
|
700
|
-
raise ArgumentError, "#{use_case_class.name} must define `input`
|
|
768
|
+
if use_case_class.input_types.empty?
|
|
769
|
+
raise ArgumentError, "#{use_case_class.name} must define `input` type(s) to be used in a pipeline"
|
|
701
770
|
end
|
|
702
771
|
|
|
703
772
|
call_method = @_capture_exceptions || false ? :call! : :call #: Symbol
|
|
@@ -188,15 +188,31 @@ module SenroUsecaser
|
|
|
188
188
|
# : () -> Array[Proc]
|
|
189
189
|
def self.around_hooks: () -> Array[Proc]
|
|
190
190
|
|
|
191
|
-
# Declares the expected input type for this UseCase
|
|
191
|
+
# Declares the expected input type(s) for this UseCase
|
|
192
|
+
# Accepts a Class or one or more Modules that input must include
|
|
192
193
|
#
|
|
193
|
-
#
|
|
194
|
-
|
|
194
|
+
# @example Single class
|
|
195
|
+
# input UserInput
|
|
196
|
+
#
|
|
197
|
+
# @example Single module (interface)
|
|
198
|
+
# input HasUserId
|
|
199
|
+
#
|
|
200
|
+
# @example Multiple modules (interfaces)
|
|
201
|
+
# input HasUserId, HasEmail
|
|
202
|
+
#
|
|
203
|
+
# : (*Module) -> void
|
|
204
|
+
def self.input: (*Module) -> void
|
|
205
|
+
|
|
206
|
+
# Returns the input types as an array
|
|
207
|
+
#
|
|
208
|
+
# : () -> Array[Module]
|
|
209
|
+
def self.input_types: () -> Array[Module]
|
|
195
210
|
|
|
196
|
-
# Returns the input class
|
|
211
|
+
# Returns the input class (for backwards compatibility)
|
|
212
|
+
# If a Class is specified, returns it. Otherwise returns the first type.
|
|
197
213
|
#
|
|
198
|
-
# : () ->
|
|
199
|
-
|
|
214
|
+
# : () -> Module?
|
|
215
|
+
def self.input_class: () -> Module?
|
|
200
216
|
|
|
201
217
|
# Declares the expected output type for this UseCase
|
|
202
218
|
#
|
|
@@ -267,6 +283,19 @@ module SenroUsecaser
|
|
|
267
283
|
# : [T] (*Class, ?code: Symbol) { () -> T } -> Result[T]
|
|
268
284
|
def capture: [T] (*Class, ?code: Symbol) { () -> T } -> Result[T]
|
|
269
285
|
|
|
286
|
+
# Validates that input satisfies all declared input types
|
|
287
|
+
# For Modules: checks if input's class includes the module
|
|
288
|
+
# For Classes: checks if input is an instance of the class
|
|
289
|
+
#
|
|
290
|
+
# : (untyped) -> void
|
|
291
|
+
def validate_input!: (untyped) -> void
|
|
292
|
+
|
|
293
|
+
# Validates that the result's value satisfies the declared output type
|
|
294
|
+
# Only validates if result is success and output_schema is a Class
|
|
295
|
+
#
|
|
296
|
+
# : (Result[untyped]) -> void
|
|
297
|
+
def validate_output!: (Result[untyped]) -> void
|
|
298
|
+
|
|
270
299
|
# Executes the core logic with before/after/around hooks
|
|
271
300
|
#
|
|
272
301
|
# : (untyped) { () -> Result[untyped] } -> Result[untyped]
|
|
@@ -378,7 +407,7 @@ module SenroUsecaser
|
|
|
378
407
|
def step_should_stop?: (Step) -> bool
|
|
379
408
|
|
|
380
409
|
# Calls a single UseCase in the pipeline
|
|
381
|
-
# Requires
|
|
410
|
+
# Requires input type(s) to be defined for pipeline steps
|
|
382
411
|
#
|
|
383
412
|
# : (singleton(Base), untyped) -> Result[untyped]
|
|
384
413
|
def call_use_case: (singleton(Base), untyped) -> Result[untyped]
|
data/sig/overrides.rbs
CHANGED
|
@@ -11,6 +11,6 @@ module SenroUsecaser
|
|
|
11
11
|
# Class methods that rbs-inline doesn't generate correctly
|
|
12
12
|
def self.organized_steps: () -> Array[Step]?
|
|
13
13
|
def self.use_case_namespace: () -> (Symbol | String)?
|
|
14
|
-
def self.
|
|
14
|
+
def self.output_schema: () -> (Class | Hash[Symbol, Class])?
|
|
15
15
|
end
|
|
16
16
|
end
|