u-case 5.4.0 → 5.5.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: 0dd8852d56fd022780ae18b61d849649c84080c4fe2a681aca031d3b0c43b139
4
- data.tar.gz: 8d9196aee650c25efc8bedc701cbcc1f190e14ea4c8ca97ea6f2f22b3c09259c
3
+ metadata.gz: 4e57208798d170fbccde7b54f5beb6220cecbfd70c3983fedcb701070a73c7bc
4
+ data.tar.gz: e4a0c472d1fd6edacf6ac3495d902ca851a88f5420360b708bdf2f204818cc1a
5
5
  SHA512:
6
- metadata.gz: a7b9beee649d540efdbae0553139f09fc4a9053390db61c98c5fd0a3a81569ea7ab60a6db0218f7842f38eabd90f2f0bdec86c717ee473f5692143a1ae44746d
7
- data.tar.gz: 1ade9a482e232dfdd5a1976aeb2e47f0b2a5f129bdb965911f7cfda233b2f99f0e34fae97a3402770700bb367a32ded882b1a4b7727c5aae2d7ad0680d863c32
6
+ metadata.gz: 154ce36f3efefa81e8e0dcc191cfd25396a7ad0b431f6d2a8190974036ed21f70faa617cc6834569f7fe02855b712abb33dca1da44285913c578ff73cc7cdfcc
7
+ data.tar.gz: 20f5e74caf9b58ae935bb39dc9780fd7bcfe21e68f4adbec162cff797284939067b2e254e790a2eb17ac8de3ce7a30460af5908170ba136730fa94b784f84a87
data/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  > **Note:** This gem was originally published as `u-service` (versions 0.1.0 – 1.0.0) and renamed to `u-case` starting with `u-case 1.0.0` on 2019-09-15.
9
9
 
10
+ ## [5.5.0] - 2026-05-24
11
+ ### Added
12
+ - `Micro::Case.results { |on| ... }` macro to declare a results contract — the allowed `Success`/`Failure` types and the result keys each one requires. `Success(...)` / `Failure(...)` calls that use an undeclared type now raise `Micro::Case::Error::UnexpectedResultType`; calls missing a declared required key raise `Micro::Case::Error::MissingResultKeys`. Use cases without a `results` block keep their previous unrestricted behavior. The check routes through `Micro::Case::Check#results_contract!`, so it is also bypassed when `config.disable_runtime_checks = true` (closes #22). Carve-outs so contracts don't break neighbouring features:
13
+ - Framework-generated `__failure_from_attributes_errors` (the auto-failure produced when `accept:`/`reject:` or ActiveModel validation rejects an input) bypasses the contract — it goes directly to `__set__` rather than through `Failure(...)` — so combining `results` with attribute validation no longer requires declaring `:invalid_attributes`.
14
+ - Rescued exceptions in `Micro::Case::Safe` (which produce `Failure(result: exception)`) bypass the contract.
15
+ - Result hashes with `String` keys are matched against the contract's symbolised required keys — `Success(result: { 'value' => 1 })` satisfies `result: [:value]`, mirroring `Result`'s own tolerance for either key type.
16
+ - Non-`Hash` / non-`Symbol` `result:` arguments fall through to the existing `Micro::Case::Error::InvalidResult` ("must be a Hash") instead of being misreported as missing keys.
17
+ - Non-`Symbol` `type` arguments fall through to `Micro::Case::Error::InvalidResultType` instead of being misreported as undeclared.
18
+ - `Micro::Case.results` raises `ArgumentError` when called on the abstract base class itself, so a stray declaration cannot leak a contract to every subclass in the process.
19
+
10
20
  ## [5.4.0] - 2026-05-24
11
21
  ### Added
12
22
  - `Micro::Case.config.disable_runtime_checks` config (default `false`) to skip the gem's internal argument/contract checks for better performance in production. All checks are consolidated in `Micro::Case::Check::Enabled` (the default) and `Micro::Case::Check::Disabled` (no-ops with the same signature); the active module is swapped via `Micro::Case.check`. Measured throughput win is JIT-dependent: within noise on stock Ruby (no JIT), ~3–5% on Ruby 3.2 +YJIT, ~4–7% on Ruby 4.0 +PRISM (see `benchmarks/perfomance/runtime_checks/compare.rb`). Closes #45.
@@ -468,6 +478,7 @@ First release under the `u-case` name (renamed from `u-service`).
468
478
  - `Micro::Service::Result` with `Success`/`Failure` factories and helper methods for returning typed results from services.
469
479
  - Runtime dependency on `u-attributes` for service input declaration.
470
480
 
481
+ [5.5.0]: https://github.com/serradura/u-case/compare/v5.4.0...v5.5.0
471
482
  [5.4.0]: https://github.com/serradura/u-case/compare/v5.3.1...v5.4.0
472
483
  [5.3.1]: https://github.com/serradura/u-case/compare/v5.3.0...v5.3.1
473
484
  [5.3.0]: https://github.com/serradura/u-case/compare/v5.2.1...v5.3.0
data/CLAUDE.md CHANGED
@@ -90,8 +90,17 @@ Both files are user-facing — keep them in sync with the code:
90
90
  - **`README.md` and `README.pt-BR.md`**: the **Documentation** table and the
91
91
  **Compatibility** table at the top reference the latest released version
92
92
  and its dependency bounds. Update both files together — they are
93
- translations of each other and must stay in lockstep. If you change a
94
- documented API, update both READMEs in the same commit.
93
+ translations of each other and must stay in lockstep. Any user-visible
94
+ API change requires a README update in the same commit:
95
+ - **New public API** (new macro, new module-level method, new public
96
+ instance method, new error class users can rescue, new config option) —
97
+ add or extend the relevant section in both READMEs with an example.
98
+ - **Changed documented API** — update the existing section in both
99
+ READMEs to match the new behavior.
100
+ - **Removed/deprecated API** — remove or mark the section in both
101
+ READMEs.
102
+ - Pure internal refactors, CI tweaks, and test-only changes don't need
103
+ README updates.
95
104
 
96
105
  ## Internal argument checks live in `Micro::Case::Check`
97
106
 
data/README.md CHANGED
@@ -27,7 +27,7 @@ The main project goals are:
27
27
  Version | Documentation
28
28
  --------- | -------------
29
29
  unreleased| https://github.com/serradura/u-case/blob/main/README.md
30
- 5.4.0 | https://github.com/serradura/u-case/blob/v5.x/README.md
30
+ 5.5.0 | https://github.com/serradura/u-case/blob/v5.x/README.md
31
31
  4.5.1 | https://github.com/serradura/u-case/blob/v4.x/README.md
32
32
 
33
33
  > **Note:** Você entende português? 🇧🇷 🇵🇹 Verifique o [README traduzido em pt-BR](https://github.com/serradura/u-case/blob/main/README.pt-BR.md).
@@ -42,6 +42,7 @@ unreleased| https://github.com/serradura/u-case/blob/main/README.md
42
42
  - [What are the default result types?](#what-are-the-default-result-types)
43
43
  - [How to define custom result types?](#how-to-define-custom-result-types)
44
44
  - [Is it possible to define a custom type without a result data?](#is-it-possible-to-define-a-custom-type-without-a-result-data)
45
+ - [How to declare a results contract?](#how-to-declare-a-results-contract)
45
46
  - [How to use the result hooks?](#how-to-use-the-result-hooks)
46
47
  - [Why the hook usage without a defined type exposes the result itself?](#why-the-hook-usage-without-a-defined-type-exposes-the-result-itself)
47
48
  - [Using decomposition to access the result data and type](#using-decomposition-to-access-the-result-data-and-type)
@@ -90,7 +91,7 @@ unreleased| https://github.com/serradura/u-case/blob/main/README.md
90
91
  | u-case | branch | ruby | activemodel | u-attributes |
91
92
  | ---------------- | ------ | -------- | -------------- | -------------- |
92
93
  | unreleased | main | >= 2.7 | >= 6.0 | >= 2.8, < 4.0 |
93
- | 5.4.0 | v5.x | >= 2.7 | >= 6.0 | >= 2.8, < 4.0 |
94
+ | 5.5.0 | v5.x | >= 2.7 | >= 6.0 | >= 2.8, < 4.0 |
94
95
  | 5.1.0 | v5.x | >= 2.7 | >= 6.0 | >= 2.7, < 4.0 |
95
96
  | 4.5.1 | v4.x | >= 2.2.0 | >= 3.2, <= 8.1 | >= 2.7, < 3.0 |
96
97
 
@@ -335,6 +336,58 @@ result.use_case.attributes # {"a"=>2, "b"=>"2"}
335
336
 
336
337
  [⬆️ Back to Top](#table-of-contents-)
337
338
 
339
+ #### How to declare a results contract?
340
+
341
+ Answer: Use the `results do |on| ... end` macro to declare which result types your use case can return, and which keys each one requires. When a contract is declared, `Success(...)` / `Failure(...)` calls that use an undeclared type raise `Micro::Case::Error::UnexpectedResultType`, and calls that omit a declared required key raise `Micro::Case::Error::MissingResultKeys`.
342
+
343
+ ```ruby
344
+ class Divide < Micro::Case
345
+ attributes :a, :b
346
+
347
+ results do |on|
348
+ on.failure(:attributes_must_be_numbers)
349
+ on.failure(:division_by_zero)
350
+
351
+ on.success(result: [:division])
352
+ end
353
+
354
+ def call!
355
+ return Failure(:attributes_must_be_numbers) unless Kind.of?(Numeric, a, b)
356
+ return Failure(:division_by_zero) if b == 0
357
+
358
+ Success result: { division: a / b }
359
+ end
360
+ end
361
+
362
+ Divide.call(a: 10, b: 2).data # => { division: 5 }
363
+ Divide.call(a: 10, b: 0).type # => :division_by_zero
364
+ Divide.call(a: 'x', b: 2).type # => :attributes_must_be_numbers
365
+ ```
366
+
367
+ A type passed to `on.success` / `on.failure` without a `result:` argument declares the type with no required keys (any payload — including the implicit `{ type => true }` from `Failure(:my_type)` — is accepted). When `result: [:key1, :key2]` is given, those keys must be present in the result hash; extra keys are allowed.
368
+
369
+ ```ruby
370
+ class Wrong < Micro::Case
371
+ results do |on|
372
+ on.success(result: [:value])
373
+ on.failure(:known)
374
+ end
375
+
376
+ def call!
377
+ Success(:other, result: { value: 1 }) # raises Micro::Case::Error::UnexpectedResultType
378
+ # Success(result: { wrong: 1 }) # raises Micro::Case::Error::MissingResultKeys
379
+ # Failure(:other) # raises Micro::Case::Error::UnexpectedResultType
380
+ end
381
+ end
382
+ ```
383
+
384
+ Notes:
385
+ - Use cases without a `results` block keep their previous unrestricted behavior — the contract is opt-in.
386
+ - Subclasses inherit the parent's contract.
387
+ - Rescued exceptions in `Micro::Case::Safe` (which produce `Failure(result: exception)` automatically) bypass the contract.
388
+
389
+ [⬆️ Back to Top](#table-of-contents-)
390
+
338
391
  #### How to use the result hooks?
339
392
 
340
393
  As [mentioned earlier](#microcaseresult---what-is-a-use-case-result), the `Micro::Case::Result` has two methods to improve the application flow control. They are: `#on_success`, `on_failure`.
data/README.pt-BR.md CHANGED
@@ -27,7 +27,7 @@ Principais objetivos deste projeto:
27
27
  Versão | Documentação
28
28
  --------- | -------------
29
29
  unreleased| https://github.com/serradura/u-case/blob/main/README.md
30
- 5.4.0 | https://github.com/serradura/u-case/blob/v5.x/README.md
30
+ 5.5.0 | https://github.com/serradura/u-case/blob/v5.x/README.md
31
31
  4.5.1 | https://github.com/serradura/u-case/blob/v4.x/README.md
32
32
 
33
33
  ## Índice <!-- omit in toc -->
@@ -40,6 +40,7 @@ unreleased| https://github.com/serradura/u-case/blob/main/README.md
40
40
  - [O que são os tipos de resultados?](#o-que-são-os-tipos-de-resultados)
41
41
  - [Como definir tipos customizados de resultados?](#como-definir-tipos-customizados-de-resultados)
42
42
  - [É possível definir um tipo sem definir os dados do resultado?](#é-possível-definir-um-tipo-sem-definir-os-dados-do-resultado)
43
+ - [Como declarar um contrato de resultados?](#como-declarar-um-contrato-de-resultados)
43
44
  - [Como utilizar os hooks dos resultados?](#como-utilizar-os-hooks-dos-resultados)
44
45
  - [Por que o hook sem um tipo definido expõe o próprio resultado?](#por-que-o-hook-sem-um-tipo-definido-expõe-o-próprio-resultado)
45
46
  - [Usando decomposição para acessar os dados e tipo do resultado](#usando-decomposição-para-acessar-os-dados-e-tipo-do-resultado)
@@ -88,7 +89,7 @@ unreleased| https://github.com/serradura/u-case/blob/main/README.md
88
89
  | u-case | branch | ruby | activemodel | u-attributes |
89
90
  | ---------------- | ------ | -------- | -------------- | -------------- |
90
91
  | unreleased | main | >= 2.7 | >= 6.0 | >= 2.8, < 4.0 |
91
- | 5.4.0 | v5.x | >= 2.7 | >= 6.0 | >= 2.8, < 4.0 |
92
+ | 5.5.0 | v5.x | >= 2.7 | >= 6.0 | >= 2.8, < 4.0 |
92
93
  | 5.1.0 | v5.x | >= 2.7 | >= 6.0 | >= 2.7, < 4.0 |
93
94
  | 4.5.1 | v4.x | >= 2.2.0 | >= 3.2, <= 8.1 | >= 2.7, < 3.0 |
94
95
 
@@ -333,6 +334,58 @@ result.use_case.attributes # {"a"=>2, "b"=>"2"}
333
334
 
334
335
  [⬆️ Voltar para o índice](#índice-)
335
336
 
337
+ #### Como declarar um contrato de resultados?
338
+
339
+ Resposta: Utilize a macro `results do |on| ... end` para declarar quais tipos de resultado o caso de uso pode retornar e quais chaves cada um exige. Quando há um contrato declarado, chamadas a `Success(...)` / `Failure(...)` que usem um tipo não declarado levantam `Micro::Case::Error::UnexpectedResultType`, e chamadas que omitam uma chave obrigatória declarada levantam `Micro::Case::Error::MissingResultKeys`.
340
+
341
+ ```ruby
342
+ class Divide < Micro::Case
343
+ attributes :a, :b
344
+
345
+ results do |on|
346
+ on.failure(:attributes_must_be_numbers)
347
+ on.failure(:division_by_zero)
348
+
349
+ on.success(result: [:division])
350
+ end
351
+
352
+ def call!
353
+ return Failure(:attributes_must_be_numbers) unless Kind.of?(Numeric, a, b)
354
+ return Failure(:division_by_zero) if b == 0
355
+
356
+ Success result: { division: a / b }
357
+ end
358
+ end
359
+
360
+ Divide.call(a: 10, b: 2).data # => { division: 5 }
361
+ Divide.call(a: 10, b: 0).type # => :division_by_zero
362
+ Divide.call(a: 'x', b: 2).type # => :attributes_must_be_numbers
363
+ ```
364
+
365
+ Um tipo declarado em `on.success` / `on.failure` sem o argumento `result:` é aceito sem chaves obrigatórias (qualquer payload — inclusive o implícito `{ tipo => true }` de `Failure(:meu_tipo)` — é aceito). Quando `result: [:chave_1, :chave_2]` é informado, essas chaves precisam estar presentes no hash de resultado; chaves extras são permitidas.
366
+
367
+ ```ruby
368
+ class Wrong < Micro::Case
369
+ results do |on|
370
+ on.success(result: [:value])
371
+ on.failure(:known)
372
+ end
373
+
374
+ def call!
375
+ Success(:other, result: { value: 1 }) # levanta Micro::Case::Error::UnexpectedResultType
376
+ # Success(result: { wrong: 1 }) # levanta Micro::Case::Error::MissingResultKeys
377
+ # Failure(:other) # levanta Micro::Case::Error::UnexpectedResultType
378
+ end
379
+ end
380
+ ```
381
+
382
+ Notas:
383
+ - Casos de uso sem o bloco `results` mantêm o comportamento anterior sem restrições — o contrato é opt-in.
384
+ - Subclasses herdam o contrato declarado na classe pai.
385
+ - Exceções capturadas em `Micro::Case::Safe` (que geram `Failure(result: exception)` automaticamente) são exemptas do contrato.
386
+
387
+ [⬆️ Voltar para o índice](#índice-)
388
+
336
389
  #### Como utilizar os hooks dos resultados?
337
390
 
338
391
  Como [mencionando anteriormente](#microcaseresult---o-que-é-o-resultado-de-um-caso-de-uso), o `Micro::Case::Result` tem dois métodos para melhorar o controle do fluxo da aplicação. São eles:
@@ -59,6 +59,38 @@ module Micro
59
59
  def hash!(arg)
60
60
  Kind::Hash[arg]
61
61
  end
62
+
63
+ def results_contract!(use_case_class, kind, type, value)
64
+ contract = use_case_class.__results_contract__
65
+ return unless contract
66
+ return unless type.is_a?(Symbol)
67
+ return if value.is_a?(Exception)
68
+
69
+ if kind == :success
70
+ declared = contract.success_declared?(type)
71
+ declared_types = contract.successes.keys
72
+ required = contract.success_keys(type) if declared
73
+ else
74
+ declared = contract.failure_declared?(type)
75
+ declared_types = contract.failures.keys
76
+ required = contract.failure_keys(type) if declared
77
+ end
78
+
79
+ raise Error::UnexpectedResultType.new(use_case_class, kind, type, declared_types) unless declared
80
+ return if required.nil? || required.empty?
81
+
82
+ if value.is_a?(Hash)
83
+ data_keys = value.keys.map { |k| k.is_a?(String) ? k.to_sym : k }
84
+ elsif value.is_a?(Symbol)
85
+ data_keys = [type]
86
+ else
87
+ return
88
+ end
89
+
90
+ missing = required - data_keys
91
+
92
+ raise Error::MissingResultKeys.new(use_case_class, kind, type, missing) unless missing.empty?
93
+ end
62
94
  end
63
95
 
64
96
  module Disabled
@@ -76,6 +108,7 @@ module Micro
76
108
  def flow_use_cases!(_use_cases); end
77
109
  def map_args!(_args); end
78
110
  def hash!(arg); arg; end
111
+ def results_contract!(_use_case_class, _kind, _type, _value); end
79
112
  end
80
113
  end
81
114
  end
@@ -60,9 +60,32 @@ module Micro
60
60
  end
61
61
  end
62
62
 
63
+ class UnexpectedResultType < TypeError
64
+ def initialize(use_case_class, kind, type, declared_types)
65
+ declared_list = declared_types.map { |t| ":#{t}" }.join(', ')
66
+ declared_list = '(none)' if declared_list.empty?
67
+
68
+ super(
69
+ "#{use_case_class.name} declared a results contract — " \
70
+ "#{kind} type :#{type} is not declared. Declared #{kind} types: #{declared_list}."
71
+ )
72
+ end
73
+ end
74
+
75
+ class MissingResultKeys < ArgumentError
76
+ def initialize(use_case_class, kind, type, missing_keys)
77
+ missing_list = missing_keys.map { |k| ":#{k}" }.join(', ')
78
+
79
+ super(
80
+ "#{use_case_class.name} declared a results contract — " \
81
+ "#{kind} :#{type} is missing required result keys: #{missing_list}."
82
+ )
83
+ end
84
+ end
85
+
63
86
  def self.by_wrong_usage?(exception)
64
87
  case exception
65
- when Kind::Error, ArgumentError, InvalidResult, UnexpectedResult then true
88
+ when Kind::Error, ArgumentError, InvalidResult, UnexpectedResult, UnexpectedResultType then true
66
89
  else false
67
90
  end
68
91
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ class Case
5
+ class Result
6
+ class Contract
7
+ attr_reader :successes, :failures
8
+
9
+ def self.define(&block)
10
+ contract = new
11
+ block.call(Definition.new(contract))
12
+ contract
13
+ end
14
+
15
+ def initialize
16
+ @successes = {}
17
+ @failures = {}
18
+ end
19
+
20
+ def add_success(type, keys)
21
+ @successes[type] = Array(keys).map(&:to_sym)
22
+ end
23
+
24
+ def add_failure(type, keys)
25
+ @failures[type] = Array(keys).map(&:to_sym)
26
+ end
27
+
28
+ def success_declared?(type)
29
+ @successes.key?(type)
30
+ end
31
+
32
+ def failure_declared?(type)
33
+ @failures.key?(type)
34
+ end
35
+
36
+ def success_keys(type)
37
+ @successes[type]
38
+ end
39
+
40
+ def failure_keys(type)
41
+ @failures[type]
42
+ end
43
+
44
+ class Definition
45
+ def initialize(contract)
46
+ @contract = contract
47
+ end
48
+
49
+ def success(type = :ok, result: nil)
50
+ @contract.add_success(Kind::Symbol[type], result)
51
+ end
52
+
53
+ def failure(type = :error, result: nil)
54
+ @contract.add_failure(Kind::Symbol[type], result)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Micro
4
4
  class Case
5
- VERSION = '5.4.0'.freeze
5
+ VERSION = '5.5.0'.freeze
6
6
  end
7
7
  end
data/lib/micro/case.rb CHANGED
@@ -11,6 +11,7 @@ module Micro
11
11
  require 'micro/case/utils'
12
12
  require 'micro/case/error'
13
13
  require 'micro/case/result'
14
+ require 'micro/case/result/contract'
14
15
  require 'micro/case/check'
15
16
  require 'micro/case/config'
16
17
  require 'micro/case/safe'
@@ -66,6 +67,20 @@ module Micro
66
67
  @__flow_use_cases = Cases::Utils.map_use_cases(args)
67
68
  end
68
69
 
70
+ def self.results(&block)
71
+ raise ArgumentError, 'a block is required'.freeze unless block
72
+ raise ArgumentError, 'must be called on a Micro::Case subclass, not on Micro::Case itself'.freeze if self == ::Micro::Case
73
+
74
+ @__results_contract = Result::Contract.define(&block)
75
+ end
76
+
77
+ def self.__results_contract__
78
+ return @__results_contract if defined?(@__results_contract)
79
+
80
+ parent = superclass
81
+ parent.respond_to?(:__results_contract__) ? parent.__results_contract__ : nil
82
+ end
83
+
69
84
  class << self
70
85
  alias __call__ call
71
86
 
@@ -227,9 +242,10 @@ module Micro
227
242
  end
228
243
 
229
244
  def __failure_from_attributes_errors
230
- Failure(
231
- Config.instance.activemodel_validation_errors_failure,
232
- result: { errors: attributes_errors }
245
+ __get_result(
246
+ false,
247
+ { errors: attributes_errors },
248
+ Config.instance.activemodel_validation_errors_failure
233
249
  )
234
250
  end
235
251
 
@@ -244,6 +260,8 @@ module Micro
244
260
  def Success(type = :ok, result: nil)
245
261
  value = result || type
246
262
 
263
+ ::Micro::Case.check.results_contract!(self.class, :success, type, value)
264
+
247
265
  __get_result(true, value, type)
248
266
  end
249
267
 
@@ -260,10 +278,11 @@ module Micro
260
278
 
261
279
  type = MapFailureType.call(value, type)
262
280
 
281
+ ::Micro::Case.check.results_contract!(self.class, :failure, type, value)
282
+
263
283
  __get_result(false, value, type)
264
284
  end
265
285
 
266
-
267
286
  def Check(type = nil, result: nil, on: Kind::Empty::HASH)
268
287
  result_key = type || :check
269
288
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: u-case
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.4.0
4
+ version: 5.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Serradura
@@ -122,6 +122,7 @@ files:
122
122
  - lib/micro/case/config.rb
123
123
  - lib/micro/case/error.rb
124
124
  - lib/micro/case/result.rb
125
+ - lib/micro/case/result/contract.rb
125
126
  - lib/micro/case/result/transitions.rb
126
127
  - lib/micro/case/result/wrapper.rb
127
128
  - lib/micro/case/safe.rb