errgonomic 0.10.1 → 0.11.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 +27 -0
- data/README.md +5 -1
- data/doctest_helper.rb +5 -0
- data/lib/errgonomic/option.rb +13 -10
- data/lib/errgonomic/rails/active_record_delegate_optional.rb +20 -1
- data/lib/errgonomic/rails/active_record_optional.rb +29 -4
- 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: 94eb90cd1a2a6e07d42a5bc9318fc34cea84cff058c6d78299c2aa1673279b3e
|
|
4
|
+
data.tar.gz: c8516b096821a7a1902805687a51b7f07f1443adeccf50f3b85659006f431b61
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a3803504c4789e6bf2df90f28348e90f72089121e2a2b06f2ba8ec28db7082286447ff07ca6c2c23cd5a41e38d86e31b454237ae3d2299ed4d1f97d0b57518a
|
|
7
|
+
data.tar.gz: c868b2d29da869091801aac2b5295b5a69a67ac405a9a14d38e62021048ff5133dfd288d61caadbfb783f512da3cd409fda19453c55a13610a8525af6a2b46fb
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.11.0] - 2026-09-16
|
|
4
|
+
|
|
5
|
+
This release makes a converted model's query methods and a delegated predicate answer the verdict a boolean caller asked for, rather than something an `if` reads as true.
|
|
6
|
+
|
|
7
|
+
### Upgrading from 0.10.2
|
|
8
|
+
|
|
9
|
+
An application that prepended its own `query_attribute` unwrap to work around a wrapped column's `attr?` answering true for `false`, `0` or `""` can delete it: the concern does it now, and a prepended copy is redundant rather than harmful. A `delegate_optional` whose method name ends in `?` returns `true` or `false` where it returned an Option, so `some?`, `unwrap_or` and the other combinators on that result are a `NoMethodError`. Read the boolean instead. The one loss is a `?` method that answers a value by design, which now answers `true`: declare it without the `?`, or write `some_and?` by hand. The seam to watch is `Object#to_option`, which lifts whatever it is handed, so `record.delegated?.to_option` is `Some(false)` for an absent target where it was `None`.
|
|
10
|
+
|
|
11
|
+
### Changes
|
|
12
|
+
|
|
13
|
+
- [Bug fix] A converted model's `query_attribute`, its private `attribute?` alias and the `attr?` methods Rails generates from them answer what the unconverted model answers for a wrapped column, so `note.pinned?` is `false` for an explicit `false` and `note.rank?` is `false` for a `0`. The concern overrides `query_attribute` to unwrap the reader's Option before handing the value to Rails' own cast. Previously the cast saw a `Some`, missed its `true` and `false, nil` arms and fell through to `!blank?`, where `Option#blank?` is `none?`, so every present value answered true ([#88](https://github.com/omc/errgonomic/issues/88))
|
|
14
|
+
- [Behavior change] `delegate_optional` with a method name ending in `?` answers `true` or `false` through `some_and?`, reading an absent target as `false`, where it answered `Some(true)`, `Some(false)` and `None` before. A `?` name asks for a verdict and no Option can serve as one, because every Option is truthy: `Some(false)` and `None` both took the true branch of an `if`, which is the silent wrong branch that `delegate ..., allow_nil: true` does not have. Every other name is lifted into an Option as before, and there is no opt-out keyword ([#89](https://github.com/omc/errgonomic/issues/89))
|
|
15
|
+
|
|
16
|
+
## [0.10.2] - 2026-09-10
|
|
17
|
+
|
|
18
|
+
This release makes every `Err` carry an error and removes `Option#ok`, the one method that built an `Err` without one.
|
|
19
|
+
|
|
20
|
+
### Upgrading from 0.10.1
|
|
21
|
+
|
|
22
|
+
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`.
|
|
23
|
+
|
|
24
|
+
### Changes
|
|
25
|
+
|
|
26
|
+
- [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`
|
|
27
|
+
- `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
|
|
28
|
+
- [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
|
|
29
|
+
|
|
3
30
|
## [0.10.1] - 2026-09-10
|
|
4
31
|
|
|
5
32
|
This release makes cross-type equality raise unconditionally and removes the switch that used to turn it on.
|
data/README.md
CHANGED
|
@@ -403,7 +403,7 @@ The declaration reads as well above the include as below it, as `errgonomic_opti
|
|
|
403
403
|
|
|
404
404
|
`Model.errgonomic_optionals` reports which readers a model wrapped, including nullable foreign-key columns, so `book.author_id` is `Some(1)` alongside `book.author`. That is how to check that a conversion did what it meant to. A subclass reports what it inherited alongside anything it wrapped itself, so the report names every wrapped reader the class responds to; `Model.errgonomic_optional_names` is the set that class wrapped on its own.
|
|
405
405
|
|
|
406
|
-
`delegate_optional` is Rails' `delegate` with `allow_nil`, where the absent case is a `None` rather than a `nil`: `delegate_optional :name, to: :author` gives `book.name # => Some('Cixin Liu')`, and `None()` where there is no author. The prefix forms are Rails': `prefix: true` names the reader after the target (`author_name`), and `prefix: :writer` names it `writer_name`. `private: true` works as it does there. The reader forwards whatever it was called with, arguments and block alike. `allow_nil: true` is accepted and says nothing new, so a `delegate` declaration swaps over unchanged unless it delegates a writer. `delegate_optional :name=, to: :author` raises instead: an assignment through an absent target has nowhere to put the value, and dropping it silently is what the type is there to prevent. `allow_nil: false` asks for a reader that raises on absence, which this does not have, so it raises `ArgumentError` where it is written, as a declaration with no `to:` does.
|
|
406
|
+
`delegate_optional` is Rails' `delegate` with `allow_nil`, where the absent case is a `None` rather than a `nil`: `delegate_optional :name, to: :author` gives `book.name # => Some('Cixin Liu')`, and `None()` where there is no author. The prefix forms are Rails': `prefix: true` names the reader after the target (`author_name`), and `prefix: :writer` names it `writer_name`. `private: true` works as it does there. The reader forwards whatever it was called with, arguments and block alike. `allow_nil: true` is accepted and says nothing new, so a `delegate` declaration swaps over unchanged unless it delegates a writer or a predicate. A name ending in `?` asks for a verdict, and no Option can serve as one, because every Option is truthy. So a predicate answers a bare `true` or `false` through `some_and?`: `delegate_optional :prolific?, to: :author` gives `book.prolific? # => false` for an unprolific author, and `false` again where there is no author, which branches an `if` the way Rails' `allow_nil: true` does. The one thing that costs is a `?` method that answers a value by design, which now answers `true`; declare it without the `?`, or write `some_and?` by hand. `delegate_optional :name=, to: :author` raises instead: an assignment through an absent target has nowhere to put the value, and dropping it silently is what the type is there to prevent. `allow_nil: false` asks for a reader that raises on absence, which this does not have, so it raises `ArgumentError` where it is written, as a declaration with no `to:` does.
|
|
407
407
|
|
|
408
408
|
It is available on every model, converted or not, because it lifts both ends one layer. The target is lifted, so a plain record reads as `Some` and a `nil` as `None`. What the delegated call returns is lifted too, so a delegated reader that is itself an Option comes back as one Option rather than two.
|
|
409
409
|
|
|
@@ -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/doctest_helper.rb
CHANGED
|
@@ -76,6 +76,10 @@ class Writer < ActiveRecord::Base
|
|
|
76
76
|
def styled_name
|
|
77
77
|
yield(name)
|
|
78
78
|
end
|
|
79
|
+
|
|
80
|
+
def credited?
|
|
81
|
+
!bio.nil?
|
|
82
|
+
end
|
|
79
83
|
end
|
|
80
84
|
|
|
81
85
|
# A converted model reads its association as an Option.
|
|
@@ -86,6 +90,7 @@ class Article < ActiveRecord::Base
|
|
|
86
90
|
delegate_optional :name, to: :writer, prefix: :author
|
|
87
91
|
delegate_optional :bio, to: :writer
|
|
88
92
|
delegate_optional :greeting, :styled_name, to: :writer, prefix: true
|
|
93
|
+
delegate_optional :credited?, to: :writer
|
|
89
94
|
delegate_optional :table_name, to: :class
|
|
90
95
|
end
|
|
91
96
|
|
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
|
|
|
@@ -27,6 +27,11 @@ module Errgonomic
|
|
|
27
27
|
# @!method delegate_optional(*methods, to: nil, prefix: nil, private: nil, allow_nil: nil)
|
|
28
28
|
# @!scope class
|
|
29
29
|
# Delegates to an optional target, answering an Option: None where the target is absent.
|
|
30
|
+
# A name ending in `?` is the exception: it answers a bare boolean, false for an absent target.
|
|
31
|
+
# @example a predicate answers a verdict rather than an Option, since every Option is truthy
|
|
32
|
+
# Article.create!(title: 'Omelas', writer: Writer.create!(name: 'Ursula', bio: 'writes')).credited? # => true
|
|
33
|
+
# Article.create!(title: 'Omelas', writer: Writer.create!(name: 'Ursula')).credited? # => false
|
|
34
|
+
# Article.create!(title: 'Untitled').credited? # => false
|
|
30
35
|
# @example prefix forms name the reader, as they do for Rails' delegate
|
|
31
36
|
# article = Article.create!(title: 'Omelas', writer: Writer.create!(name: 'Ursula', bio: 'writes'))
|
|
32
37
|
# article.writer_name # => Some('Ursula')
|
|
@@ -158,11 +163,25 @@ module Errgonomic
|
|
|
158
163
|
def define_optional_delegation(receiver, method_name, reader, declared_at)
|
|
159
164
|
class_eval <<-RUBY, declared_at.path, declared_at.lineno # rubocop:disable Style/EvalWithLocation
|
|
160
165
|
def #{reader}(...)
|
|
161
|
-
#{receiver
|
|
166
|
+
#{delegation_body(receiver, method_name)}
|
|
162
167
|
end
|
|
163
168
|
RUBY
|
|
164
169
|
end
|
|
165
170
|
|
|
171
|
+
# A ? name asks for a verdict, and an Option is not usable as one:
|
|
172
|
+
# every Option is truthy, so Some(false) and None both take the true
|
|
173
|
+
# branch. A predicate therefore answers a bare boolean, reading an
|
|
174
|
+
# absent target as false, which branches the way nil does.
|
|
175
|
+
def delegation_body(receiver, method_name)
|
|
176
|
+
return "#{receiver}.to_option.some_and? { |target| target.#{method_name}(...) }" if predicate?(method_name)
|
|
177
|
+
|
|
178
|
+
"#{receiver}.to_option.and_then { |target| target.#{method_name}(...).to_option }"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def predicate?(method_name)
|
|
182
|
+
method_name.to_s.end_with?('?')
|
|
183
|
+
end
|
|
184
|
+
|
|
166
185
|
# A target named for a Ruby keyword reads as the keyword in the body
|
|
167
186
|
# it is written into, so it needs an explicit receiver. Rails answers
|
|
168
187
|
# the same question for delegate, and answers it for the same names.
|
|
@@ -38,10 +38,12 @@ module Errgonomic
|
|
|
38
38
|
# Validation unwraps at read_attribute_for_validation, the seam every
|
|
39
39
|
# EachValidator fetches an attribute through; serialization at
|
|
40
40
|
# read_attribute_for_serialization, the seam every attribute in a
|
|
41
|
-
# payload is fetched through;
|
|
42
|
-
# value, the seam every field reads its record through
|
|
43
|
-
#
|
|
44
|
-
#
|
|
41
|
+
# payload is fetched through; a form helper at ActionView's tag
|
|
42
|
+
# value, the seam every field reads its record through; and a query
|
|
43
|
+
# method at query_attribute, the seam every generated attr? is cast
|
|
44
|
+
# through. So a standard validator weighs the value, a payload carries
|
|
45
|
+
# it, a form renders it and a query method answers for it, rather than
|
|
46
|
+
# for the wrapper. A singular association with nested
|
|
45
47
|
# attributes goes further and keeps its plain reader: nested attributes
|
|
46
48
|
# are assigned through the reader, and ActiveRecord asks whatever it
|
|
47
49
|
# finds there whether it is a new record. So does a reader a framework
|
|
@@ -115,6 +117,29 @@ module Errgonomic
|
|
|
115
117
|
errgonomic_omit_absent_keys(hash)
|
|
116
118
|
end
|
|
117
119
|
|
|
120
|
+
# A query method reads the record through the public reader and matches
|
|
121
|
+
# what it finds against true, then false and nil, before falling through
|
|
122
|
+
# to blankness. An Option is none of those, and its blankness is its
|
|
123
|
+
# discriminant rather than its value, so an explicit false and a stored
|
|
124
|
+
# zero would answer true. Unwrapping the read is what keeps the answer
|
|
125
|
+
# the one an unconverted model gives.
|
|
126
|
+
#
|
|
127
|
+
# @example
|
|
128
|
+
# Note.new(pinned: false).pinned? # => false
|
|
129
|
+
# Note.new(rank: 0).rank? # => false
|
|
130
|
+
# Note.new(title: '').title? # => false
|
|
131
|
+
# Note.new(pinned: true).pinned? # => true
|
|
132
|
+
# Note.new(rank: 3).rank? # => true
|
|
133
|
+
def query_attribute(attr_name)
|
|
134
|
+
query_cast_attribute(attr_name, Errgonomic::Rails.unwrap_option(public_send(attr_name)))
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Rails copies query_attribute into attribute? at the alias, and the
|
|
138
|
+
# attr? methods it generates call that copy, so an override of the one
|
|
139
|
+
# never reaches them.
|
|
140
|
+
alias attribute? query_attribute
|
|
141
|
+
private :attribute?
|
|
142
|
+
|
|
118
143
|
# YARD does not see through a concern's class_methods block, so the
|
|
119
144
|
# method it documents is declared rather than read.
|
|
120
145
|
#
|
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