Skip to content

Check the operand of and, xor, zip and zip_with, and release 0.9.2 - #83

Closed
nz wants to merge 13 commits into
mainfrom
2026-09-10-errgonomic-0.9.x-triage/and-xor-zip
Closed

nz wants to merge 13 commits into
mainfrom
2026-09-10-errgonomic-0.9.x-triage/and-xor-zip

Conversation

@nz

@nz nz commented Sep 10, 2026 •

Copy link
Copy Markdown
Member

Errgonomic 0.9.2 makes Option#and, #xor, #zip and #zip_with check their operand the way or already did. 0.9.x let Some(2).and(3) hand back the bare 3, let a None receiver accept any operand silently, and let Some(1).zip(2) and Some(:l).xor(:r) fall into a bare NoMethodError. This pull request stacks on the 0.9.1 branch (#81) and targets main, so until #81 merges its diff also shows the earlier releases' commits; it merges after that one, and the next release in the series stacks on it.

Closes #78.

What changes

  • The four combinators raise Errgonomic::ArgumentError (other must be an Option, was Integer) on a bare operand, on a None receiver as well as a Some, before the receiver's variant is consulted. Code that passed a bare value to and and read it back has to wrap it.
  • Version 0.9.2, with a changelog section and an upgrading note.

How it works

lib/errgonomic/option.rb: one private option_operand! runs first in or, and, xor, zip and zip_with, so the five carry one message. zip_with checks its operand only; its block returns a bare value the method wraps, as Rust's does. Every reproduction line in #78 is a doctest on the method it exercises. CHANGELOG.md carries the 0.9.2 section.

Evidence

Run locally on 2026-09-10, aarch64-darwin, inside the flake devshell:

  • bundle exec rubocop: 24 files inspected, 6 offenses, all six pre-existing on main (Metrics on method_missing in option.rb and result.rb, Metrics on optional_dig, Style/Documentation on Errgonomic::Rails). None is in code this branch touches.
  • bundle exec yard doctest: 174 runs, 445 assertions, 0 failures, 0 errors.
  • bundle exec rake test: 132 runs, 464 assertions, 0 failures, 0 errors.
  • bundle exec rake test:strict: 132 runs, 464 assertions, 0 failures, 0 errors.
  • bundle exec rake: the same three counts.
  • nix build .#errgonomic: built errgonomic-0.9.2, with rake in its check phase.
  • nix flake check --all-systems: all checks passed.

CI runs yard doctest, rake test and rake test:strict on Ruby 3.4 on ubuntu-latest.

What is left open

Nothing in this release. The remaining three issues from the same report (#76, #77, #79) each get their own release, stacked on this branch in order.

Background

Project charter: cowork/projects/2026-09-10-errgonomic-0.9.x-triage/README.md.

After review

The #81 review fixes are merged forward into this branch; this release's own content is unchanged. Gates after the merge, 2026-09-10: rubocop 6 offenses, the same six; yard doctest 174 runs, 447 assertions; rake test and rake test:strict 136 runs, 480 assertions each; nix build and nix flake check --all-systems pass. CI passed on 1a089dc.

nz added 7 commits September 10, 2026 10:25
0.9.0 made Option#to_s render as inspect does, to keep a raise out of
the rescue block that interpolates a wrapper into its log line. In a
mass conversion of every model in an application, every site that
built a string from a reader then wrote wrapper text into data with no
exception: a UNIQUE identity column, a hostname, a hashed auth token
and a customer-facing page. Each of those raised under 0.8.3.

The raise comes back as the same Errgonomic::SerializeError, so an
existing rescue still matches, and its message now carries the remedy
the log-line case needs: the value via the bounded inspect, that to_s
is refused, and inspect for a log line or unwrap_or / expect! for the
value. The bounded inspect moves into its own helper so the to_json
refusal and this one name the value the same way.
The same reversal as for Option, with the same message shape: the
value via a bounded inspect, that to_s is refused, and the spellings
to reach for. Result had no bounded inspect helper, so one is added
privately for the to_s refusal; the to_json and as_json refusals keep
their fixed message.
Each reproduction from the 0.9.0 field reports is a test: join,
interpolation, format and String() for both containers; a bare ERB
<%= %> through ActionView, whose template error carries the refusal as
its cause; and an Option or Result in a Hash key, which both the json
gem and ActiveSupport's as_json stringify through to_s, so the raising
to_s closes the key position that 0.9.0 opened.

A None in a bare ERB tag renders as nothing rather than raising: the
Rails integration's None answers nil?, and ActionView's output buffer
skips a nil before it asks for to_s. The test records that as it is.
The changelog names the to_s reversal as a behavior change with an
upgrading note for anyone who wrote against 0.9.0's rendering. The
0.9.0 section stays as history.
or already refused a bare operand with Errgonomic::ArgumentError.
and, xor, zip and zip_with did not: and handed the bare operand back
out of the Option chain, xor and zip fell into a NoMethodError on
some? or none?, and whether anything fired at all depended on the
receiver's variant, so a None-heavy path never learned its operand was
wrong. The check now runs before the discriminant is consulted, on all
five combinators, with or's message.
nz added 6 commits September 10, 2026 12:27
unwrap_err! passed the Ok's value as the exception message, so the
message was the value's to_s. With to_s refused on an Option or a
Result, Ok(Some(1)).unwrap_err! printed only the class name and its
message raised. The message is now the value's inspect, bounded as the
to_s refusal bounds it, and the error carries the value as unwrap!'s
does.
The join test covered the same shape with different values, and the
group_by test grouped bare Options rather than a converted model's
reader. Both lines now appear as #75 writes them: a join of a Some and
a None, and a group_by over a converted model's optional association
on its way to JSON.
With ActiveSupport loaded, Hash#to_json reaches a key through as_json,
which refused an Option under 0.9.0 as well, so the two to_json
assertions in the hash-key test passed before the to_s change and did
not test it. They move to a child process that loads the json gem and
errgonomic alone, where 0.9.0 rendered {"Some(1)":2} and a raising to_s
refuses.
A case/in with several branches and none matching raises
NoMatchingPatternError with the subject itself as its message, so on
an Option or a Result the message refuses. The top-level printer then
shows only the class name, and Minitest's reporter crashes before it
names the test. The README says so and that the fix is the missing
branch, and a test pins each of the three behaviors so the text cannot
drift from them. The json-gem child test shares the new run_ruby
helper.
Logger's own formatter inspects a message that is not a String, so
logger.info(opt) renders. ActiveSupport::TaggedLogging interpolates the
message once a tag is set, and Rails tags every production request with
its request_id, so the same call passes in tests and raises in
production. The README and the 0.9.1 upgrading note say to write
logger.info(opt.inspect), and a test pins both loggers' behavior.
@nz
nz force-pushed the 2026-09-10-errgonomic-0.9.x-triage/and-xor-zip branch from 1a089dc to 94ac512 Compare September 10, 2026 18:31
@nz
nz marked this pull request as ready for review September 10, 2026 18:32
@nz
nz enabled auto-merge September 10, 2026 18:32
lutzcc1
lutzcc1 previously approved these changes Sep 10, 2026
@nz
nz dismissed lutzcc1’s stale review September 10, 2026 19:40

The merge-base changed after approval.

@lutzcc1
lutzcc1 self-requested a review September 10, 2026 19:44
lutzcc1
lutzcc1 previously approved these changes Sep 10, 2026
@nz
nz dismissed lutzcc1’s stale review September 10, 2026 19:47

The merge-base changed after approval.

lutzcc1
lutzcc1 previously approved these changes Sep 10, 2026
@nz
nz dismissed lutzcc1’s stale review September 10, 2026 20:00

The merge-base changed after approval.

@nz
nz disabled auto-merge September 10, 2026 20:15
@nz

nz commented Sep 10, 2026

Copy link
Copy Markdown
Member Author

Superseded by #87, which landed this release's commits on main as a linear history (merged 2026-09-10).

@nz nz closed this Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

and/xor/zip/zip_with skip the pedantic operand check; Some(x).and(y) returns the bare operand

2 participants