Is there a trick to making rules work when a rule path refers to a key in re-used schema?
AddressSchema = Dry::Schema.Params do
required(:country).value(:string)
required(:zipcode).value(:string)
required(:street).value(:string)
end
ContactSchema = Dry::Schema.Params do
required(:email).value(:string)
required(:mobile).value(:string)
end
class NewUserContract < Dry::Validation::Contract
params(AddressSchema, ContactSchema) do
required(:name).value(:string)
required(:age).value(:integer)
end
rule(:country) { key.failure('must be foo') unless value != 'foo' }
end
If I do something similar to above, I get a Dry::Validation::InvalidKeysError stating that the rule specifies keys that are not found in schema.
@solnic I have a better example/test which is similar to my schema/rule use case. The first test fails but the second passes fine. As I was trying to debug, I realized that the fix will require a change in dry-schema due to what #key_map returns in ClassInterface.
I really like this gem and would like to help and contribute for a fix but I am not sure 100% sure what would be the best course of action. Could you give me some guidance?
context 'when schema is nested and reused' do
let(:contract_class) do
Class.new(Dry::Validation::Contract) do
def self.name
"TestContract"
end
UserSchema = Dry::Schema.Params do
required(:email).filled(:string)
optional(:login).filled(:string)
optional(:details).hash do
optional(:address).hash do
required(:street).value(:string)
end
end
end
IdentifierSchema = Dry::Schema.Params do
required(:external_id).filled(:string)
optional(:aternate_id).filled(:string)
end
UserRequestSchema = Dry::Schema.Params do
required(:user).hash do
IdentifierSchema
UserSchema
end
end
params(UserRequestSchema)
end
end
context 'when the rule being applied to a key is in a reused nested schema' do
let(:request) { { user: { external_id: '12345abc', email: 'jane@doe.org', login: 'ab'} }}
before do
contract_class.rule(user: :login) do
key.failure("is too short") if values[user: :login].size < 3
end
end
it 'applies the rule when passed schema checks' do
expect(contract.(request).errors.to_h)
.to eql(user: { login: ["is too short"] })
end
end
context 'when schema has no rules' do
let(:request) { { user: { external_id: '12345abc', email: 'jane@doe.org' } } }
it 'validates as successful' do
expect(contract.(request).success?).to eq true
end
end
end
Update: I tried making the schema structure for UserRequestSchema to directly use the schema defined in IdentifiersSchema and UserSchema and I can make the tests that I wrote above pass.
UserRequestSchema = Dry::Schema.Params do
required(:user).hash do
required(:external_id).filled(:string)
optional(:aternate_id).filled(:string)
required(:email).filled(:string)
optional(:login).filled(:string)
optional(:details).hash do
optional(:address).hash do
required(:street).value(:string)
end
end
end
end
Ideally I would like to be able to combine schemas within another schema along with rules so that I can consolidate schemas that are shared across multiple requests. (IdentifiersSchema)