Skip to content

Commit

Permalink
Fixed a case where transform to hash values wouldn't allow new keys
Browse files Browse the repository at this point in the history
  • Loading branch information
EugZol committed Oct 8, 2024
1 parent 4533da6 commit bc516dc
Show file tree
Hide file tree
Showing 17 changed files with 49 additions and 96 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,7 @@ I18n keys:

#### `transform_to_value(value)`

Always returns ValidResult. The value is transformed to provided argument (disregarding the original value).
Always returns ValidResult. The value is transformed to provided argument (disregarding the original value). If the resultant value is a Hash, all its keys are marked as validated and will survive `Datacaster.schema { ... }` call.

Returned value is deeply frozen with [`Ractor::make_shareable`](https://docs.ruby-lang.org/en/master/Ractor.html#method-c-make_shareable) to prevent application bugs due to modification of unintentionally shared value. If that effect is undesired, use [`transform { value }`](#transform--value--) instead.

Expand Down Expand Up @@ -1070,6 +1070,8 @@ Useful to perform some side-effect such as raising an exception, making a log en

Always returns ValidResult. Transforms the value: returns whatever the block has returned.

If the resultant value is a Hash, all its keys are marked as validated and will survive `Datacaster.schema { ... }` call.

```ruby
city =
Datacaster.schema do
Expand All @@ -1087,6 +1089,8 @@ city.(name: "Denver", distance: "2.5") # => Datacaster::ValidResult({:name=>"Den

Always returns ValidResult. If the value is `Datacaster.absent`, then `Datacaster.absent` is returned (the block isn't called). Otherwise, works like [`transform`](#transform--value).

If the resultant value is a Hash, all its keys are marked as validated and will survive `Datacaster.schema { ... }` call.

### Array schemas

To define compound data type, array of 'something', use `array_schema(something)` (or the alias `array_of(something)`). There is no built-in way to define an array wherein each element is of a different type.
Expand Down
5 changes: 3 additions & 2 deletions lib/datacaster/hash_mapper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ def cast(object, runtime:)
errors = {}
result = {}

runtime.will_check!

@fields.each do |key, validator|
new_value = runtime.ignore_checks! { validator.with_runtime(runtime).(object) }

Expand Down Expand Up @@ -52,6 +50,9 @@ def cast(object, runtime:)
end
end

runtime.will_check!
result.keys.each { |key| runtime.checked_key!(key) }

errors.delete_if { |_, v| v.empty? }

if errors.empty?
Expand Down
2 changes: 1 addition & 1 deletion lib/datacaster/predefined.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def optional(base, on: nil)
end

def pass
transform(&:itself)
cast { |v| Datacaster::ValidResult(v) }
end

def pass_if(base)
Expand Down
1 change: 1 addition & 0 deletions lib/datacaster/runtimes/structure_cleaner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def checked_key!(key)
end

def will_check!
return if @ignore
@should_check_stack[-1] = true
end

Expand Down
6 changes: 5 additions & 1 deletion lib/datacaster/transformer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ def initialize(&block)
end

def cast(object, runtime:)
Datacaster.ValidResult(Runtimes::Base.(runtime, @transform, object))
result = Runtimes::Base.(runtime, @transform, object)
if runtime.respond_to?(:checked_key!) && result.is_a?(Hash)
result.keys.each { |key| runtime.checked_key!(key) }
end
Datacaster::ValidResult(result)
end

def inspect
Expand Down
2 changes: 1 addition & 1 deletion lib/datacaster/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Datacaster
VERSION = "3.3.1"
VERSION = "3.3.2"
end
9 changes: 0 additions & 9 deletions spec/active_model_validations_spec.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
RSpec.describe Datacaster do
include Dry::Monads[:result]

before(:all) do
@i18n_module = Datacaster::Config.i18n_module
Datacaster::Config.i18n_module = I18n
end

after(:all) do
Datacaster::Config.i18n_module = @i18n_module
end

describe 'active model validations' do
subject do
described_class.schema { integer & validate(numericality: {greater_than_or_equal_to: 18}) }
Expand Down
9 changes: 0 additions & 9 deletions spec/around_spec.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
RSpec.describe Datacaster do
include Dry::Monads[:result]

before(:all) do
@i18n_module = Datacaster::Config.i18n_module
Datacaster::Config.i18n_module = Datacaster::SubstituteI18n
end

after(:all) do
Datacaster::Config.i18n_module = @i18n_module
end

describe "around casters" do
it "casts around with #cast_around" do
side_effect = []
Expand Down
9 changes: 0 additions & 9 deletions spec/datacaster_spec.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
RSpec.describe Datacaster do
include Dry::Monads[:result]

before(:all) do
@i18n_module = Datacaster::Config.i18n_module
Datacaster::Config.i18n_module = Datacaster::SubstituteI18n
end

after(:all) do
Datacaster::Config.i18n_module = @i18n_module
end

describe "absent typecasting" do
subject { described_class.schema { absent } }

Expand Down
9 changes: 0 additions & 9 deletions spec/errors_caster_spec.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
RSpec.describe Datacaster do
include Dry::Monads[:result]

before(:all) do
@i18n_module = Datacaster::Config.i18n_module
Datacaster::Config.i18n_module = Datacaster::SubstituteI18n
end

after(:all) do
Datacaster::Config.i18n_module = @i18n_module
end

describe "cast_errors" do
context "remaps errors with #cast_errors" do
it "with transform_to_hash" do
Expand Down
9 changes: 0 additions & 9 deletions spec/i18n_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,6 @@
Key = Datacaster::I18nValues::Key
Scope = Datacaster::I18nValues::Scope

before(:all) do
@i18n_module = Datacaster::Config.i18n_module
Datacaster::Config.i18n_module = I18n
end

after(:all) do
Datacaster::Config.i18n_module = @i18n_module
end

describe 'i18n' do
it 'returns default errors' do
schema = Datacaster.schema { check { false } }
Expand Down
9 changes: 0 additions & 9 deletions spec/object_context_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,6 @@
RSpec.describe Datacaster do
include Dry::Monads[:result]

before(:all) do
@i18n_module = Datacaster::Config.i18n_module
Datacaster::Config.i18n_module = Datacaster::SubstituteI18n
end

after(:all) do
Datacaster::Config.i18n_module = @i18n_module
end

describe "using context node (#with_object_context)" do
it "allows access to instance variables" do
object = Object.new
Expand Down
9 changes: 0 additions & 9 deletions spec/partial_schemas_spec.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
RSpec.describe Datacaster do
include Dry::Monads[:result]

before(:all) do
@i18n_module = Datacaster::Config.i18n_module
Datacaster::Config.i18n_module = Datacaster::SubstituteI18n
end

after(:all) do
Datacaster::Config.i18n_module = @i18n_module
end

describe 'partial schema' do
it 'shares check context with full schema in hash checks' do
common_validator =
Expand Down
33 changes: 33 additions & 0 deletions spec/structure_cleaner_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
RSpec.describe Datacaster do
include Dry::Monads[:result]

it 'transform marks hashes as cleaned' do
caster =
Datacaster.schema do
hash_schema(a: string) & transform { { b: 'value' } }
end
expect(caster.(a: 'value').to_dry_result).to eq Success(b: 'value')
end

it 'defaults marks hashes as cleaned' do
caster =
Datacaster.schema do
hash_schema(a: string) & remove & default({b: 'value'})
end
expect(caster.(a: 'value').to_dry_result).to eq Success(b: 'value')
end

it 'hash_value is reopened for check with hash_schema' do
caster =
Datacaster.schema do
hash_schema(a: string) & hash_value
end
expect(caster.(a: 'value', b: 'value').to_dry_result).to eq Failure(b: ["must be absent"])

caster =
Datacaster.schema do
hash_value & hash_schema(a: string)
end
expect(caster.(a: 'value', b: 'value').to_dry_result).to eq Failure(b: ["must be absent"])
end
end
9 changes: 0 additions & 9 deletions spec/switch_node_spec.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
RSpec.describe Datacaster do
include Dry::Monads[:result]

before(:all) do
@i18n_module = Datacaster::Config.i18n_module
Datacaster::Config.i18n_module = Datacaster::SubstituteI18n
end

after(:all) do
Datacaster::Config.i18n_module = @i18n_module
end

describe 'switch node' do
it 'performs switching with shortcuts' do
caster =
Expand Down
9 changes: 0 additions & 9 deletions spec/transaction_spec.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
RSpec.describe Datacaster::Transaction do
include Dry::Monads[:result]

before(:all) do
@i18n_module = Datacaster::Config.i18n_module
Datacaster::Config.i18n_module = Datacaster::SubstituteI18n
end

after(:all) do
Datacaster::Config.i18n_module = @i18n_module
end

it "returns true for Datacaster.instance?" do
tx =
Class.new do
Expand Down
9 changes: 0 additions & 9 deletions spec/user_context_spec.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
RSpec.describe Datacaster do
include Dry::Monads[:result]

before(:all) do
@i18n_module = Datacaster::Config.i18n_module
Datacaster::Config.i18n_module = Datacaster::SubstituteI18n
end

after(:all) do
Datacaster::Config.i18n_module = @i18n_module
end

describe "using context node (#with_context)" do
it "allows to access context.something in runtime" do
type = described_class.schema { check { |x| x == context.test } }
Expand Down

0 comments on commit bc516dc

Please sign in to comment.