dry-struct 如何有条件地验证一个属性?
dry-struct How to conditionally validate one attribute?
我正在使用干式和干式结构,我想进行条件验证。
对于 class:
class Tax < Dry::Struct
attribute :tax_type, Types::String.constrained(min_size: 2, max_size: 3, included_in: %w[IVA IS NS])
attribute :tax_country_region, Types::String.constrained(max_size: 5)
attribute :tax_code, Types::String.constrained(max_size: 10)
attribute :description, Types::String.constrained(max_size: 255)
attribute :tax_percentage, Types::Integer
attribute :tax_ammount, Types::Integer.optional
end
我想将 tax_ammount
验证为整数和强制性 if `tax_type == 'IS'.
dry-struct
实际上是用于基本类型断言和强制转换。
如果您想要更复杂的验证,那么您可能还想实施 dry-validation
(根据 dry-rb
的建议)
请参阅 Validating data with dry-struct 其中指出
Please don’t. Structs are meant to work with valid input, it cannot generate error messages good enough for displaying them for a user etc. Use dry-validation for validating incoming data and then pass its output to structs.
使用 dry-validation
的条件验证类似于
TaxValidation = Dry::Validation.Schema do
# Could be:
# required(:tax_type).filled(:str?,
# size?: 2..3,
# included_in?: %w(IVA IS NS))
# but since we are validating against a list of Strings I figured the rest was implied
required(:tax_type).filled(included_in?: %w(IVA IS NS))
optional(:tax_amount).maybe(:int?)
# rule name is of your choosing and will be used
# as the errors key (i just chose `tax_amount` for consistency)
rule(tax_amount:[:tax_type, :tax_amount]) do |tax_type, tax_amount|
tax_type.eql?('IS').then(tax_amount.filled?)
end
end
- 这需要
tax_type
在 %w(IVA IS NS)
列表中;
- 允许
tax_amount
是可选的,但如果填写它必须是Integer
(int?
)和;
- 如果
tax_type == 'IS'
(eql?('IS')
) 则必须填写tax_amount
(这意味着根据上面的规则它必须是Integer
)。
显然您也可以验证您的其他输入,但为了简洁起见,我省略了这些输入。
示例:
TaxValidation.({}).success?
#=> false
TaxValidation.({}).errors
# => {:tax_type=>["is missing"]}
TaxValidation.({tax_type: 'NO'}).errors
#=> {:tax_type=>["must be one of: IVA, IS, NS"]}
TaxValidation.({tax_type: 'NS'}).errors
#=> {}
TaxValidation.({tax_type: 'IS'}).errors
#=> {:tax_amount=>["must be filled"]}
TaxValidation.({tax_type: 'IS',tax_amount:'NO'}).errors
#=> {:tax_amount=>["must be an integer"]}
TaxValidation.({tax_type: 'NS',tax_amount:12}).errors
#=> {}
TaxValidation.({tax_type: 'NS',tax_amount:12}).success?
#=> true
有一个替代解决方案 - 没有实质上重复结构属性的验证规则。
module TaxChore
class BaseTax < Dry::Struct
attribute :type, Types::String.enum('IVA','NS')
# ...
attribute? :amount, Types::Integer.default(0)
end
class MandatoryTax < BaseTax
attribute :type, Types::String.enum('IS')
attribute :amount, Types::Integer
end
Tax = BaseTax | MandatoryTax
def self.run
tax = Tax.(type: 'IVA')
p tax
tax = Tax.(type: 'IVA', amount: 21)
p tax
tax = Tax.(type: 'IS', amount: 42)
p tax
begin
tax = Tax.(type: 'IS')
p tax
rescue Dry::Struct::Error => e
puts e
end
end
run
end
输出:
#<TaxChore::BaseTax type="IVA" amount=0>
#<TaxChore::BaseTax type="NS" amount=21>
#<TaxChore::MandatoryTax type="IS" amount=42>
[TaxChore::MandatoryTax.new] :amount is missing in Hash input
(N.B:我删除了多余的 tax_
前缀,因为 tax.type
一样清晰但更短。)
我正在使用干式和干式结构,我想进行条件验证。
对于 class:
class Tax < Dry::Struct
attribute :tax_type, Types::String.constrained(min_size: 2, max_size: 3, included_in: %w[IVA IS NS])
attribute :tax_country_region, Types::String.constrained(max_size: 5)
attribute :tax_code, Types::String.constrained(max_size: 10)
attribute :description, Types::String.constrained(max_size: 255)
attribute :tax_percentage, Types::Integer
attribute :tax_ammount, Types::Integer.optional
end
我想将 tax_ammount
验证为整数和强制性 if `tax_type == 'IS'.
dry-struct
实际上是用于基本类型断言和强制转换。
如果您想要更复杂的验证,那么您可能还想实施 dry-validation
(根据 dry-rb
的建议)
请参阅 Validating data with dry-struct 其中指出
Please don’t. Structs are meant to work with valid input, it cannot generate error messages good enough for displaying them for a user etc. Use dry-validation for validating incoming data and then pass its output to structs.
使用 dry-validation
的条件验证类似于
TaxValidation = Dry::Validation.Schema do
# Could be:
# required(:tax_type).filled(:str?,
# size?: 2..3,
# included_in?: %w(IVA IS NS))
# but since we are validating against a list of Strings I figured the rest was implied
required(:tax_type).filled(included_in?: %w(IVA IS NS))
optional(:tax_amount).maybe(:int?)
# rule name is of your choosing and will be used
# as the errors key (i just chose `tax_amount` for consistency)
rule(tax_amount:[:tax_type, :tax_amount]) do |tax_type, tax_amount|
tax_type.eql?('IS').then(tax_amount.filled?)
end
end
- 这需要
tax_type
在%w(IVA IS NS)
列表中; - 允许
tax_amount
是可选的,但如果填写它必须是Integer
(int?
)和; - 如果
tax_type == 'IS'
(eql?('IS')
) 则必须填写tax_amount
(这意味着根据上面的规则它必须是Integer
)。
显然您也可以验证您的其他输入,但为了简洁起见,我省略了这些输入。
示例:
TaxValidation.({}).success?
#=> false
TaxValidation.({}).errors
# => {:tax_type=>["is missing"]}
TaxValidation.({tax_type: 'NO'}).errors
#=> {:tax_type=>["must be one of: IVA, IS, NS"]}
TaxValidation.({tax_type: 'NS'}).errors
#=> {}
TaxValidation.({tax_type: 'IS'}).errors
#=> {:tax_amount=>["must be filled"]}
TaxValidation.({tax_type: 'IS',tax_amount:'NO'}).errors
#=> {:tax_amount=>["must be an integer"]}
TaxValidation.({tax_type: 'NS',tax_amount:12}).errors
#=> {}
TaxValidation.({tax_type: 'NS',tax_amount:12}).success?
#=> true
有一个替代解决方案 - 没有实质上重复结构属性的验证规则。
module TaxChore
class BaseTax < Dry::Struct
attribute :type, Types::String.enum('IVA','NS')
# ...
attribute? :amount, Types::Integer.default(0)
end
class MandatoryTax < BaseTax
attribute :type, Types::String.enum('IS')
attribute :amount, Types::Integer
end
Tax = BaseTax | MandatoryTax
def self.run
tax = Tax.(type: 'IVA')
p tax
tax = Tax.(type: 'IVA', amount: 21)
p tax
tax = Tax.(type: 'IS', amount: 42)
p tax
begin
tax = Tax.(type: 'IS')
p tax
rescue Dry::Struct::Error => e
puts e
end
end
run
end
输出:
#<TaxChore::BaseTax type="IVA" amount=0>
#<TaxChore::BaseTax type="NS" amount=21>
#<TaxChore::MandatoryTax type="IS" amount=42>
[TaxChore::MandatoryTax.new] :amount is missing in Hash input
(N.B:我删除了多余的 tax_
前缀,因为 tax.type
一样清晰但更短。)