dry-rb / dry-rb/dry-schema

`validate_keys` is not respected within nested hashes

Open
#511 4 comments 1 reaction 0 assignees View on GitHub
bug help wanted
Dominant language
Ruby
Stars
492
Forks
123
PR merge metrics
No merged PRs in 30d

Description

## Describe the bug

When defining a schema whose keys should be validated with the exception of one field, modifying the `config.validate_keys` value within the child field seems to apply the change to the root schema as well.

Additionally, depending on the order in which you define the sub fields, you may encounter a cryptic `FrozenConfigError`.

## To Reproduce

```rb
# Keys in this schema should be validated
schema = Dry::Schema.JSON do
config.validate_keys = true

# EXCEPT for keys under the `unvalidated` key
required(:unvalidated).hash do
config.validate_keys = false
end

required(:validated).hash do
required(:foo).filled(:string)
end
end

# This results in no errors even though `validated.biz` is not valid according to the schema
validation = schema.call({
unvalidated: { foo: "bar" },

validated: {
foo: "bar",
biz: "baz",
},
})
```

Additionally, if you reorder the `hash` blocks, then you get a `FrozenConfigError`. For example:

```rb
schema = Dry::Schema.JSON do
config.validate_keys = true

required(:validated).hash do
required(:foo).filled(:string)
end

# EXCEPT for keys under the `unvalidated` key
required(:unvalidated).hash do
# Frozen config error here
config.validate_keys = false
end
end
```

Slightly different, but related. If you define a separate stand-alone schema for the unvalidated key, the `config.validate_keys = false` is completely ignored. This is again at best surprising and at worst a serious bug.

```rb
UnvalidatedSchema = Dry::Schema.JSON do
config.validate_keys = false
end

# This behaves as if `config.validate_keys = true` for `UnvalidatedSchema`
Schema = Dry::Schema.JSON do
config.validate_keys = true

required(:unvalidated).hash(UnvalidatedSchema)
end

# Has error for unknown keys
validation = Schema.call({ unvalidated: { foo: "bar" }})
```

## Expected behavior

Either:

1. The `config` should be scoped to the current schema definition and not implicitly modify parent configurations at a minimum. Configuration implicitly propagating down is reasonable, but child configs modifying parents is surprising.
2. Make modifying configuration in children impossible regardless of order. If you don't want this to be configurable, it shouldn't allow the use to do this at all.

In my opinion, #2 is a no-go, because it is extra confusing when you compose schemas as shown in the last example above. The configuration in the composed schema is just completely ignored.

## My environment

- Ruby version:
```
$ ruby --version
ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [arm64-darwin25]
```

- OS:
```
$ sw_vers
ProductName: macOS
ProductVersion: 26.2
BuildVersion: 25C56
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.