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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 543390fc5f85546ffde4785cfc41951400800094506078271cfe32535e9d784a
4
- data.tar.gz: 8d5cdf061dbf7026eaab5ba8cab628368c600943699dbad04235c9d1d9bd3f5a
3
+ metadata.gz: 124c029eafbcbb48377836a8facaaea281559a922924d544bd7ce3a6b47d0ad8
4
+ data.tar.gz: '089786b6eb3ce3be3068803da318543f50dd33878d2b453d22e68296888d7a85'
5
5
  SHA512:
6
- metadata.gz: ad7cbe42d7f1d65549323c026a3f4486d4e64a9b5274023fc3eebb0a576ceb09c030151d176512220e13ef8d6e1bc3c2bff78162783ecb286bbb6805ac16c8c9
7
- data.tar.gz: 4588909f84b800a1c497882d030fd88f23606146fe64b7ae02ecc8b7f4406ba2c5dd749d0c709c7004717de80a9a69e73d6247b2ce11013446fa25db66271c3d
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.
@@ -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
 
@@ -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 Err handle their
9
- # initialization and self identification.
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. A
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(:e).deconstruct # => [:e]
478
- # Err().deconstruct # => []
475
+ # Err(:x).deconstruct # => [:x]
479
476
  # Ok(1).respond_to?(:deconstruct_keys) # => false
480
477
  #
481
- # @example a value-less Err matches `in Err` and `in Err()`, never `in Err(e)`
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
- # begin
494
- # case Err()
495
- # in Err(e) then e
496
- # end
497
- # rescue NoMatchingPatternError => e
498
- # e.class
499
- # end # => NoMatchingPatternError
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; a value-less Err renders bare.
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 = Errgonomic::Result::Err::Arbitrary)
691
+ def Err(value)
691
692
  Errgonomic::Result::Err.new(value)
692
693
  end
693
694
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Errgonomic
4
- VERSION = '0.10.1'
4
+ VERSION = '0.10.2'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: errgonomic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.1
4
+ version: 0.10.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Zadrozny