errgonomic 0.10.1 → 0.10.2
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 +14 -0
- data/README.md +4 -0
- data/lib/errgonomic/option.rb +13 -10
- data/lib/errgonomic/result.rb +46 -45
- data/lib/errgonomic/version.rb +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: 124c029eafbcbb48377836a8facaaea281559a922924d544bd7ce3a6b47d0ad8
|
|
4
|
+
data.tar.gz: '089786b6eb3ce3be3068803da318543f50dd33878d2b453d22e68296888d7a85'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b7d3e991e2d9981f08fc6ea8437f2052b1ae6ba08e20831867c48f6cdc37cb8907697f33a7fee3c4ab3cb91bf6e8768c1a551a7892e0070172b49d6220deeed1
|
|
7
|
+
data.tar.gz: e0c8fb408cdae888b7475c15a91ca597f4f57463997101cabbca8fe91318f912b3fa490689ef4caa05858d68ad3e0477a45ad723142f38941e5d7ba12ee54cfb
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.10.2] - 2026-09-10
|
|
4
|
+
|
|
5
|
+
This release makes every `Err` carry an error and removes `Option#ok`, the one method that built an `Err` without one.
|
|
6
|
+
|
|
7
|
+
### Upgrading from 0.10.1
|
|
8
|
+
|
|
9
|
+
Replace `Err()` with `Err(reason)`, and a pattern `in Err()` with `in Err` or `in Err(_)`: `Err()` and `Errgonomic::Result::Err.new` with no argument raise `ArgumentError`, and `in Err()` no longer matches any `Err`. Replace `opt.ok` with `opt.ok_or(reason)`; `ok` on an Option now raises `Errgonomic::UnwrappedAccessError`, which names `ok_or` and `ok_or_else`.
|
|
10
|
+
|
|
11
|
+
### Changes
|
|
12
|
+
|
|
13
|
+
- [Behavior change] `Err()` and `Errgonomic::Result::Err.new` with no argument raise `ArgumentError`. A value-less `Err` held an internal placeholder, `Errgonomic::Result::Err::Arbitrary`, which `unwrap_err!` handed back to the caller, a gem-internal value leaking into application data. The placeholder is deleted along with the special case that made `Err().deconstruct` answer `[]`, so `Err(e).deconstruct` is always `[e]`, `in Err(e)` and `in Err(_)` match every `Err`, and `in Err()` matches none. `Err(nil)` is unchanged: it carries `nil`
|
|
14
|
+
- `Option#ok` is removed. `None().ok` was the only method that returned a value-less `Err`, and Rust has no `Option::ok`; `ok_or` and `ok_or_else` make the caller name the error
|
|
15
|
+
- [Docs] The README's Known limitations section names a converted model's `belongs_to ..., optional: true, touch: true`, which raises `Errgonomic::UnwrappedAccessError` on a save or a destroy with the association absent because a `None` does not delegate `persisted?`, and gives `errgonomic_optional_except` as the way around it. The limitation predates 0.10.2
|
|
16
|
+
|
|
3
17
|
## [0.10.1] - 2026-09-10
|
|
4
18
|
|
|
5
19
|
This release makes cross-type equality raise unconditionally and removes the switch that used to turn it on.
|
data/README.md
CHANGED
|
@@ -423,6 +423,10 @@ This is the register of where the gem leaves the Rust idiom, and why. ActiveReco
|
|
|
423
423
|
|
|
424
424
|
The set is closed. If a future integration appears to need a sixth compromise, that is a signal ActiveRecord is pushing back somewhere unmapped, and it warrants a design discussion rather than a quiet patch. `errgonomic_optional_except` and `errgonomic_serialize_none` are deliberately not on the list: they are configuration, an escape hatch that softens the all-or-nothing include for whatever conflict shows up next and a choice of how an absent value is written, rather than semantic exceptions.
|
|
425
425
|
|
|
426
|
+
#### Known limitations
|
|
427
|
+
|
|
428
|
+
A converted model with `belongs_to :writer, optional: true, touch: true` raises `Errgonomic::UnwrappedAccessError` (``undefined method `persisted?' for None``) when it saves or is destroyed with the association absent: on `create`, on any `update`, and on `destroy`. ActiveRecord's touch callback reads the association back through the public reader and asks `record && record.persisted?`, and a `None` is truthy and does not delegate `persisted?` the way a `Some` does. Leave the association unwrapped with `errgonomic_optional_except :writer`, which keeps `touch: true` working and hands back `nil` for an absent record.
|
|
429
|
+
|
|
426
430
|
## Development
|
|
427
431
|
|
|
428
432
|
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment. The repository is a self-contained Nix flake; with direnv, `direnv allow` puts the right toolchain on your path.
|
data/lib/errgonomic/option.rb
CHANGED
|
@@ -626,21 +626,24 @@ module Errgonomic
|
|
|
626
626
|
block.call(value)
|
|
627
627
|
end
|
|
628
628
|
|
|
629
|
-
# convert the option into a result where Some is Ok and None is Err
|
|
630
|
-
# @example
|
|
631
|
-
# None().ok # => Err()
|
|
632
|
-
# Some(1).ok # => Ok(1)
|
|
633
|
-
def ok
|
|
634
|
-
return Errgonomic::Result::Ok.new(value) if some?
|
|
635
|
-
|
|
636
|
-
Errgonomic::Result::Err.new
|
|
637
|
-
end
|
|
638
|
-
|
|
639
629
|
# Transforms the option into a result, mapping Some(v) to Ok(v) and None to Err(err)
|
|
640
630
|
#
|
|
641
631
|
# @example
|
|
642
632
|
# None().ok_or("wow") # => Err("wow")
|
|
643
633
|
# Some(1).ok_or("such err") # => Ok(1)
|
|
634
|
+
#
|
|
635
|
+
# @example there is no bare ok: an Err always names its error
|
|
636
|
+
# begin
|
|
637
|
+
# None().ok
|
|
638
|
+
# rescue NoMethodError => e
|
|
639
|
+
# e.class
|
|
640
|
+
# end # => Errgonomic::UnwrappedAccessError
|
|
641
|
+
# begin
|
|
642
|
+
# Some(1).ok
|
|
643
|
+
# rescue NoMethodError => e
|
|
644
|
+
# e.class
|
|
645
|
+
# end # => Errgonomic::UnwrappedAccessError
|
|
646
|
+
# Some(1).respond_to?(:ok) # => false
|
|
644
647
|
def ok_or(err)
|
|
645
648
|
return Errgonomic::Result::Ok.new(value) if some?
|
|
646
649
|
|
data/lib/errgonomic/result.rb
CHANGED
|
@@ -5,8 +5,8 @@ require_relative 'variant_name'
|
|
|
5
5
|
module Errgonomic
|
|
6
6
|
module Result
|
|
7
7
|
# The base class for Result's Ok and Err class variants. We implement as
|
|
8
|
-
# much logic as possible here, and let Ok and
|
|
9
|
-
#
|
|
8
|
+
# much logic as possible here, including construction, and let Ok and
|
|
9
|
+
# Err handle only their self identification.
|
|
10
10
|
class Any
|
|
11
11
|
include Comparable
|
|
12
12
|
|
|
@@ -27,7 +27,7 @@ module Errgonomic
|
|
|
27
27
|
# end # => Errgonomic::UnwrappedAccessError
|
|
28
28
|
# Ok(1).respond_to?(:value) # => false
|
|
29
29
|
# Ok(1).frozen? # => true
|
|
30
|
-
# Err().frozen? # => true
|
|
30
|
+
# Err(:x).frozen? # => true
|
|
31
31
|
def initialize(value)
|
|
32
32
|
@value = value
|
|
33
33
|
freeze
|
|
@@ -124,7 +124,7 @@ module Errgonomic
|
|
|
124
124
|
# Ok(1) == 1 # => raise Errgonomic::TypeMismatchError, "Errgonomic::Result::Ok == Integer, which strict equality refuses.\nCompare Results (res == Ok(1)), test the inner value (res.ok_and? { |v| v == 1 }), or unwrap_or a fallback first."
|
|
125
125
|
# Ok(1) != 1 # => raise Errgonomic::TypeMismatchError, "Errgonomic::Result::Ok != Integer, which strict equality refuses.\nCompare Results (res == Ok(1)), test the inner value (res.ok_and? { |v| v == 1 }), or unwrap_or a fallback first."
|
|
126
126
|
# Ok(1) === 1 # => raise Errgonomic::TypeMismatchError, "Errgonomic::Result::Ok === Integer, which strict equality refuses.\nCompare Results (res == Ok(1)), test the inner value (res.ok_and? { |v| v == 1 }), or unwrap_or a fallback first."
|
|
127
|
-
# Err() == nil # => raise Errgonomic::TypeMismatchError, "Errgonomic::Result::Err == NilClass, which strict equality refuses.\nCompare Results (res == Ok(nil)), test the inner value (res.ok_and? { |v| v == nil }), or unwrap_or a fallback first."
|
|
127
|
+
# Err(:x) == nil # => raise Errgonomic::TypeMismatchError, "Errgonomic::Result::Err == NilClass, which strict equality refuses.\nCompare Results (res == Ok(nil)), test the inner value (res.ok_and? { |v| v == nil }), or unwrap_or a fallback first."
|
|
128
128
|
# Ok(1) === Ok(1) # => true
|
|
129
129
|
# begin
|
|
130
130
|
# [Ok(1)].include?(1)
|
|
@@ -132,7 +132,7 @@ module Errgonomic
|
|
|
132
132
|
# e.class
|
|
133
133
|
# end # => Errgonomic::TypeMismatchError
|
|
134
134
|
# { Ok(1) => :v }[1] # => nil
|
|
135
|
-
# nil == Err() # => false
|
|
135
|
+
# nil == Err(:x) # => false
|
|
136
136
|
#
|
|
137
137
|
# @example an Option is another container, not another Result
|
|
138
138
|
# Ok(1) == Some(1) # => raise Errgonomic::TypeMismatchError, "Errgonomic::Result::Ok == Errgonomic::Option::Some, which strict equality refuses.\nA Result and an Option are different containers, and neither is the other.\nUnwrap the one you meant (res.unwrap_or(nil) == opt.unwrap_or(nil))."
|
|
@@ -468,35 +468,37 @@ module Errgonomic
|
|
|
468
468
|
end
|
|
469
469
|
|
|
470
470
|
# The Rust shape: each variant deconstructs to its one payload, so
|
|
471
|
-
# `in Ok(v)` binds the value and `in Err(e)` binds the error.
|
|
472
|
-
# value-less Err deconstructs to nothing: the sentinel behind it is
|
|
473
|
-
# internal and must never bind to a pattern variable.
|
|
471
|
+
# `in Ok(v)` binds the value and `in Err(e)` binds the error.
|
|
474
472
|
#
|
|
475
473
|
# @example
|
|
476
474
|
# Ok(1).deconstruct # => [1]
|
|
477
|
-
# Err(:
|
|
478
|
-
# Err().deconstruct # => []
|
|
475
|
+
# Err(:x).deconstruct # => [:x]
|
|
479
476
|
# Ok(1).respond_to?(:deconstruct_keys) # => false
|
|
480
477
|
#
|
|
481
|
-
# @example
|
|
482
|
-
# case Err()
|
|
483
|
-
# in Err(e) then e
|
|
484
|
-
# in Err then :no_value
|
|
485
|
-
# end # => :no_value
|
|
486
|
-
# case Err()
|
|
487
|
-
# in Err() then :no_value
|
|
488
|
-
# end # => :no_value
|
|
478
|
+
# @example every Err carries an error, so `in Err()` matches none of them
|
|
489
479
|
# case Err(:x)
|
|
480
|
+
# in Err() then :empty
|
|
490
481
|
# in Err(e) then e
|
|
491
|
-
# in Err then :no_value
|
|
492
482
|
# end # => :x
|
|
493
|
-
#
|
|
494
|
-
#
|
|
495
|
-
#
|
|
496
|
-
#
|
|
497
|
-
#
|
|
498
|
-
#
|
|
499
|
-
#
|
|
483
|
+
# case Err(:x)
|
|
484
|
+
# in Err then :any_err
|
|
485
|
+
# end # => :any_err
|
|
486
|
+
# case Err(:x)
|
|
487
|
+
# in Err(_) then :any_err
|
|
488
|
+
# end # => :any_err
|
|
489
|
+
#
|
|
490
|
+
# @example an Err nested in an Ok matches through the Ok's payload
|
|
491
|
+
# case Ok(Err(:boom))
|
|
492
|
+
# in Ok(Err(e)) then e
|
|
493
|
+
# end # => :boom
|
|
494
|
+
# case Ok(Err(:boom))
|
|
495
|
+
# in Ok(Err()) then :empty
|
|
496
|
+
# in Ok(Err(_)) then :err_inside
|
|
497
|
+
# end # => :err_inside
|
|
498
|
+
# case Ok(Err(:boom))
|
|
499
|
+
# in Ok(Ok(value)) then value
|
|
500
|
+
# in Ok(Err) then :err_inside
|
|
501
|
+
# end # => :err_inside
|
|
500
502
|
#
|
|
501
503
|
# @example a two-branch case/in with no else is exhaustive
|
|
502
504
|
# case Ok(1)
|
|
@@ -537,8 +539,6 @@ module Errgonomic
|
|
|
537
539
|
# "Measurement produced an exception -- #{e.class}: #{e}"
|
|
538
540
|
# end # => "Measurement produced an exception -- StandardError: nope"
|
|
539
541
|
def deconstruct
|
|
540
|
-
return [] if value.equal?(Err::Arbitrary)
|
|
541
|
-
|
|
542
542
|
[value]
|
|
543
543
|
end
|
|
544
544
|
|
|
@@ -608,19 +608,23 @@ module Errgonomic
|
|
|
608
608
|
end
|
|
609
609
|
end
|
|
610
610
|
|
|
611
|
-
# The Err variant.
|
|
611
|
+
# The Err variant. It always carries an error, so unwrap_err! and a
|
|
612
|
+
# pattern variable bind what the caller put there.
|
|
613
|
+
#
|
|
614
|
+
# @example an Err without an error raises
|
|
615
|
+
# Err(:e).unwrap_err! # => :e
|
|
616
|
+
# Err() # => raise ArgumentError, "wrong number of arguments (given 0, expected 1)"
|
|
617
|
+
# Errgonomic::Result::Err.new # => raise ArgumentError, "wrong number of arguments (given 0, expected 1)"
|
|
618
|
+
#
|
|
619
|
+
# @example Err(nil) is an ordinary Err that holds nil
|
|
620
|
+
# Err(nil).inspect # => "Err(nil)"
|
|
621
|
+
# Err(nil).unwrap_err! # => nil
|
|
622
|
+
# Err(nil).deconstruct # => [nil]
|
|
623
|
+
# case Err(nil)
|
|
624
|
+
# in Ok(value) then [:ok, value]
|
|
625
|
+
# in Err(e) then [:err, e]
|
|
626
|
+
# end # => [:err, nil]
|
|
612
627
|
class Err < Any
|
|
613
|
-
class Arbitrary; end
|
|
614
|
-
|
|
615
|
-
# Err may be constructed without a value, if you want.
|
|
616
|
-
#
|
|
617
|
-
# @example
|
|
618
|
-
# Err(:y).unwrap_err! # => :y
|
|
619
|
-
# Err().unwrap_err! # => Arbitrary
|
|
620
|
-
def initialize(value = Arbitrary)
|
|
621
|
-
super(value)
|
|
622
|
-
end
|
|
623
|
-
|
|
624
628
|
# Err is always err
|
|
625
629
|
#
|
|
626
630
|
# @example
|
|
@@ -637,15 +641,12 @@ module Errgonomic
|
|
|
637
641
|
false
|
|
638
642
|
end
|
|
639
643
|
|
|
640
|
-
# Render like Rust's Debug
|
|
644
|
+
# Render like Rust's Debug, delegating to the inner value's inspect.
|
|
641
645
|
#
|
|
642
646
|
# @example
|
|
643
647
|
# Err(:nope).inspect # => "Err(:nope)"
|
|
644
|
-
# Err().inspect # => "Err()"
|
|
645
648
|
# Err(Some(1)).inspect # => "Err(Some(1))"
|
|
646
649
|
def inspect
|
|
647
|
-
return 'Err()' if value.equal?(Arbitrary)
|
|
648
|
-
|
|
649
650
|
"Err(#{value.inspect})"
|
|
650
651
|
end
|
|
651
652
|
end
|
|
@@ -687,7 +688,7 @@ def Ok(value)
|
|
|
687
688
|
end
|
|
688
689
|
|
|
689
690
|
# Global convenience method for constructing an Err result.
|
|
690
|
-
def Err(value
|
|
691
|
+
def Err(value)
|
|
691
692
|
Errgonomic::Result::Err.new(value)
|
|
692
693
|
end
|
|
693
694
|
|
data/lib/errgonomic/version.rb
CHANGED