[QUESTION] Best way to validate non-enumerable properties
albertodiazdorado opened this issue ยท 1 comments
albertodiazdorado commented
๐ Feature request
Current Behavior
Validating non-enumerable properties is cumbersome, since they are not taken into account by io-ts
codecs (to my knowledge):
import * as t from "io-ts";
import { isLeft } from "fp-ts/Either";
const e = new Error("ENOENT");
const validation = t.type({ message: t.string }).decode(e);
console.log(isLeft(validation)); // true
console.log(e.message); // ENOENT
Desired Behavior
Validating non-enumerable properties is easy.
Suggested Solution
I am not sure whether the solution is...
- To have
io-ts
codecs also check non-enumerable properties by default - To have an additional wrapper that explicitely checks non-enumerable properties, a la
const errorCodec = t.nonEnumerable(t.type({ message: t.string }));
- To install a third party library to turn non-enumerable properties into enumerable properties or to code said function yourself:
const getOwnProperties = (e: unknown) => Object.getOwnPropertyNames(e).reduce( (error, property) => ({ ..error, [property]: Object.getOwnPropertyDescriptor(e, property)?.value }), {} );
- To include said function in
fp-ts
Who does this impact? Who is this for?
Advance TS users who do not want to use any
in catch
blocks
Describe alternatives you've considered
Alternative 1: Disable TypeScript
declare function throws(): never;
declare function handleEnoent(): void;
declare function handleGenericError(): void;
try {
throws();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.code === "ENOENT") {
handleEnoent();
} else {
handleGenericError();
}
}
Alternative 2: Manual validation
declare function throws(): never;
declare function handleEnoent(): void;
declare function handleGenericError(): void;
class ErrorWithCode extends Error {
constructor(msg: string, readonly code: unknown) {
super(msg);
}
}
const isErrorWithCode = (e: unknown): e is ErrorWithCode =>
typeof e === "object" && e !== null && "code" in e;
try {
throws();
} catch (error: unknown) {
if (isErrorWithCode(error) && error.code === "ENOENT") {
handleEnoent();
} else {
handleGenericError();
}
}
Alternative 3: io-ts cumbersome validation
import * as t from "io-ts";
declare function throws(): never;
declare function handleEnoent(): void;
declare function handleGenericError(): void;
const getOwnProperties = (e: unknown) =>
Object.getOwnPropertyNames(e).reduce(
(error, property) => ({
...error,
[property]: Object.getOwnPropertyDescriptor(e, property)?.value
}),
{}
);
const errorCodec = t.type({ message: t.string, code: t.string });
try {
throws();
} catch (error: unknown) {
const validation = errorCodec.decode(getOwnProperties(error));
if (validation._tag === "Right" && validation.right.code === "ENOENT") {
handleEnoent();
} else {
handleGenericError();
}
}
Additional context
Hey @gcanti , huge fan here! I love fp-ts :D
I would be willing to implement this feature if you would like to have it in io-ts
or fp-ts
Your environment
As of October 28th 2022:
Software | Version(s) |
---|---|
io-ts | latest |
fp-ts | latest |
TypeScript | latest |
silasdavis commented
Also a problem I have come across with dynamically defined properties that happen to be part of some interfaces