Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ in Errgonomic::Option::None
end
```

An unhandled Option refuses to leak into your output: `to_s` and `to_json` raise `Errgonomic::SerializeError`, so you handle the inner value deliberately rather than shipping `#<Errgonomic::Option::Some...>` to a user.
An unhandled Option refuses to leak into your output: `to_s`, `to_json`, and `as_json` raise `Errgonomic::SerializeError`, so you handle the inner value deliberately rather than shipping `#<Errgonomic::Option::Some...>` to a user. The refusal covers `as_json` because Hash and Array serialization recurses through that method, and an Option nested in a payload would otherwise serialize as `{"value": ...}`.

`unwrap!` and `expect!` are for tests and consoles, not application code: they raise on `None`, which is exactly the ambiguous failure the type exists to prevent. Application code should always have a combinator or pattern match that handles the `None` branch explicitly; if none fits, that is a gap worth an issue rather than a reason to unwrap.

Expand Down Expand Up @@ -148,7 +148,7 @@ in Errgonomic::Result::Err, Exception => e
end
```

Like Options, unwrapped Results refuse `to_s` and `to_json`. And `Object#result?` / `Object#assert_result!` help enforce at runtime that a value is a Result.
Like Options, unwrapped Results refuse `to_s`, `to_json`, and `as_json`. And `Object#result?` / `Object#assert_result!` help enforce at runtime that a value is a Result.

### Optional collections

Expand Down
9 changes: 9 additions & 0 deletions lib/errgonomic/option.rb
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,15 @@ def to_json(*_args)
raise Errgonomic::SerializeError, 'cannot serialize an unwrapped Option'
end

# ActiveSupport's Hash#as_json and Array#as_json recurse through their
# members with as_json rather than to_json, so an Option nested in a
# payload reaches Object#as_json and serializes as its instance
# variables. Refuse there too, and the guard holds wherever an Option
# travels.
def as_json(*_args)
raise Errgonomic::SerializeError, 'cannot serialize an unwrapped Option'
end

# pp uses its own object dump unless told otherwise; keep it consistent
# with inspect.
def pretty_print(pp)
Expand Down
9 changes: 9 additions & 0 deletions lib/errgonomic/result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,15 @@ def to_json(*_args)
raise Errgonomic::SerializeError, 'cannot serialize an unwrapped Result'
end

# ActiveSupport's Hash#as_json and Array#as_json recurse through their
# members with as_json rather than to_json, so a Result nested in a
# payload reaches Object#as_json and serializes as its instance
# variables. Refuse there too, and the guard holds wherever a Result
# travels.
def as_json(*_args)
raise Errgonomic::SerializeError, 'cannot serialize an unwrapped Result'
end

# pp uses its own object dump unless told otherwise; keep it consistent
# with inspect.
def pretty_print(pp)
Expand Down
12 changes: 12 additions & 0 deletions test/rails_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,18 @@ def test_where_with_an_array_of_options
refute_includes relation.to_sql, 'NULL'
end

# Hash and Array serialization recurses with as_json, never to_json, so
# the refusal has to sit on as_json to survive nesting.
def test_nested_options_and_results_refuse_to_serialize
assert_raises(Errgonomic::SerializeError) { Some(5).as_json }
assert_raises(Errgonomic::SerializeError) { { a: Some(5) }.to_json }
assert_raises(Errgonomic::SerializeError) { { a: None() }.to_json }
assert_raises(Errgonomic::SerializeError) { [Some(5)].to_json }
assert_raises(Errgonomic::SerializeError) { Ok(5).as_json }
assert_raises(Errgonomic::SerializeError) { { a: Err(5) }.to_json }
assert_raises(Errgonomic::SerializeError) { [Ok(5)].to_json }
end

def test_delegate_optional
author = Author.create!(name: 'Cixin Liu')
book = author.books.create!(title: 'Death\'s End')
Expand Down
Loading