Skip to content

Commit

Permalink
Datacaster::Transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
EugZol committed Oct 5, 2023
1 parent 3911c64 commit c209552
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 12 deletions.
18 changes: 10 additions & 8 deletions lib/datacaster/and_node.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
module Datacaster
class AndNode < Base
def initialize(left, right)
@left = left
@right = right
def initialize(*casters)
@casters = casters
end

def cast(object, runtime:)
left_result = @left.with_runtime(runtime).(object)
return left_result unless left_result.valid?

@right.with_runtime(runtime).(left_result.value)
Datacaster.ValidResult(
@casters.reduce(object) do |result, caster|
caster_result = caster.with_runtime(runtime).(result)
return caster_result unless caster_result.valid?
caster_result.value
end
)
end

def inspect
"#<Datacaster::AndNode L: #{@left.inspect} R: #{@right.inspect}>"
"#<Datacaster::AndNode casters: #{@casters.inspect}>"
end
end
end
2 changes: 1 addition & 1 deletion lib/datacaster/context_nodes/structure_cleaner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def initialize(base, strategy = :fail)
super(base)

unless %i[fail remove pass].include?(strategy)
raise ArgumentError.new("Strategy should be :fail (return error on extra keys), :remove (remove extra keys) or :pass (ignore presence of extra keys)")
raise ArgumentError.new("Strategy should be :fail (return error on extra keys), :remove (remove extra keys) or :pass (ignore presence of extra keys), instead got #{strategy.inspect}")
end

@strategy = strategy
Expand Down
14 changes: 11 additions & 3 deletions lib/datacaster/predefined.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ def hash_schema(fields, error_key = nil)
)
end

def strict_hash_schema(fields, error_key = nil)
schema(hash_schema(fields, error_key))
end

def choosy_hash_schema(fields, error_key = nil)
choosy_schema(hash_schema(fields, error_key))
end

def transform_to_hash(fields)
HashMapper.new(fields.transform_values { |x| DefinitionDSL.expand(x) })
end
Expand All @@ -54,15 +62,15 @@ def validate(active_model_validations)
end

def schema(base)
ContextNodes::StructureCleaner.new(base, strategy: :fail)
ContextNodes::StructureCleaner.new(base, :fail)
end

def choosy_schema(base)
ContextNodes::StructureCleaner.new(base, strategy: :remove)
ContextNodes::StructureCleaner.new(base, :remove)
end

def partial_schema(base)
ContextNodes::StructureCleaner.new(base, strategy: :pass)
ContextNodes::StructureCleaner.new(base, :pass)
end

# 'Meta' types
Expand Down
33 changes: 33 additions & 0 deletions lib/datacaster/transaction.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module Datacaster
module Transaction
module ClassMethods
def perform(caster)
@_caster = ContextNodes::StructureCleaner.new(caster, :fail)
end

def perform_partial(caster)
@_caster = ContextNodes::StructureCleaner.new(caster, :pass)
end

def perform_choosy(caster)
@_caster = ContextNodes::StructureCleaner.new(caster, :remove)
end

def call(*args, **kwargs)
new.call(*args, **kwargs)
end
end

def self.included(base)
base.extend Datacaster::Predefined
base.extend ClassMethods
base.include Datacaster::Mixin
end

def cast(object, runtime:)
self.class.instance_variable_get(:@_caster).
with_runtime(runtime).
(object)
end
end
end
28 changes: 28 additions & 0 deletions spec/datacaster_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,34 @@
end
end

describe "and node with .steps(...)" do
subject do
described_class.schema do
steps(
string,
check { |x| x.length <= 5 },
compare("test")
)
end
end

it "returns Success when all are valid" do
expect(subject.("test").to_dry_result).to eq Success("test")
end

it "returns first ErrorResult, when first is ErrorResult" do
expect(subject.(:not_even_string).to_dry_result).to eq Failure(["is not a string"])
end

it "returns second ErrorResult, when second is ErrorResult" do
expect(subject.("too long").to_dry_result).to eq Failure(["is invalid"])
end

it "returns the last ErrorResult, when the last is ErrorResult" do
expect(subject.("tset").to_dry_result).to eq Failure(['does not equal "test"'])
end
end

describe "or (|) node" do
subject do
described_class.schema { string | integer }
Expand Down
57 changes: 57 additions & 0 deletions spec/transaction_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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
include Datacaster::Transaction
end

expect(Datacaster.instance?(tx.new)).to eq true
end

it "performs basic casting with Datacaster::Predefined casters" do
tx =
Class.new do
include Datacaster::Transaction

perform integer
end

expect(tx.new.(5).to_dry_result).to eq Success(5)
expect(tx.(5).to_dry_result).to eq Success(5)
end

it "performs multi-step casting" do
tx =
Class.new do
include Datacaster::Transaction

unwrap = transform { |x| x.to_h }
typecast = hash_schema(name: string, email: string)
send_email = transform do |email|
{address: email, result: true}
end

perform steps(
unwrap,
typecast,
transform_to_hash(email: pick(:email) & send_email)
)
end

expect(tx.(name: 'John', email: '[email protected]').to_dry_result).to eq Success(
name: 'John',
email: {address: '[email protected]', result: true}
)
end
end

0 comments on commit c209552

Please sign in to comment.