superstruct icon indicating copy to clipboard operation
superstruct copied to clipboard

Coercing a refined struct doesn't trigger refinement

Open yeoffrey opened this issue 5 months ago • 0 comments

Consider this example, where we have a refiner and a coercer:

import { coerce, create, number, refine, string } from 'superstruct'

const BigNumber = refine(number(), 'Big Number', (value) => {
    return value > 100 ? true : "Number isn't big enough!"
})

const StringBigNumber = coerce(string(), BigNumber, (value) => {
    return `${value}`
})

Based on the documentation on coercing data:

The second argument to coerce is a struct narrowing the types of input values you want to try coercion. In the example above, the coercion function will only ever be called when the input is a string—booleans would ignore coercion and fail normally.

Expected

This implies to me that the coercer will only run if the number is a valid BigNumber. If it fails, then I should expect the error message to contain the message from the refiner.

// ... above code
create(30, StringBigNumber) // Fails, "Number isn't big enough!"
create('hello', StringBigNumber) // Fails, "Expected a number, but received: "hello""

Actual

The coercer skips the refiner entirely, ignoring its validation and just checking that it

// ... above code
create(30, StringBigNumber) // Fails, "Expected a string, but received: 30"
create('Hello!', StringBigNumber) // Pass, not even a number

Is this intentional? To me the documentation says it should first pass the second struct, and then do coercion, then pass the first struct.

yeoffrey avatar Sep 12 '24 19:09 yeoffrey