A
arktype14mo ago
Dimava

Context-sensitive validation / validation with parameters

interface Thing {
bars: string[];
foo: { baz: { bar: string }[]; }[];
}
interface Thing {
bars: string[];
foo: { baz: { bar: string }[]; }[];
}
What is the best/easiest way to write a validator which ensures that all bars are present in bars?
30 Replies
ssalbdivad
ssalbdivad14mo ago
Probably a narrow? Even if we implemented the relative path comparison syntax, it would likely only be for equality checks like password: "string", "repeatPassword: "./password" or whatever. This is definitely too complex for a dedicated syntax.
Dimava
Dimava14mo ago
I guess it would be possible with something like
const ThingBase = type({ bars: 'string[]' })
const Thing = ThingBase.morph((data, problems) => {
return scope({ bar: type.literal(...data.bars) }).type({
bars: 'bar[]';
foo: { baz: { bar: 'bar' }[]; }[];
})
})
const ThingBase = type({ bars: 'string[]' })
const Thing = ThingBase.morph((data, problems) => {
return scope({ bar: type.literal(...data.bars) }).type({
bars: 'bar[]';
foo: { baz: { bar: 'bar' }[]; }[];
})
})
or something , but would like to implement it like
const allowList = [];
const Thing = scope({
bar: narrow('string', s => allowList.includes(s))
}).narrow({ bars: 'string[]' }, (data) => {allowList = bars; return true})
.narrowToType({
bars: 'bar[]';
foo: { baz: { bar: 'bar' }[]; }[];
})
const allowList = [];
const Thing = scope({
bar: narrow('string', s => allowList.includes(s))
}).narrow({ bars: 'string[]' }, (data) => {allowList = bars; return true})
.narrowToType({
bars: 'bar[]';
foo: { baz: { bar: 'bar' }[]; }[];
})
So the thing I'm looking for is beforeValidate/afterValidate hooks to do smth like
const allowList = [];
const Thing = scope({
bar: narrow('string', s => allowList.includes(s))
}).type({
bars: 'bar[]';
foo: { baz: { bar: 'bar' }[]; }[];
}, {
beforeValidate: (data) => allowList = data.bars,
afterValidate: (data) => allowList = [],
})
const allowList = [];
const Thing = scope({
bar: narrow('string', s => allowList.includes(s))
}).type({
bars: 'bar[]';
foo: { baz: { bar: 'bar' }[]; }[];
}, {
beforeValidate: (data) => allowList = data.bars,
afterValidate: (data) => allowList = [],
})
ssalbdivad
ssalbdivad14mo ago
Oh maybe you were asking for the opposite bars bars a bit confusing haha
Dimava
Dimava14mo ago
Or even better, being able to use context like
const Thing = scope({
bar: narrow('string', (s, ctx) => ctx.allowList.includes(s))
}).type({
bars: 'bar[]';
foo: { baz: { bar: 'bar' }[]; }[];
}, {
beforeValidate: (data, ctx) => ctx.allowList = data.bars,
afterValidate: (data, ctx) => ctx.allowList = null,
})
const Thing = scope({
bar: narrow('string', (s, ctx) => ctx.allowList.includes(s))
}).type({
bars: 'bar[]';
foo: { baz: { bar: 'bar' }[]; }[];
}, {
beforeValidate: (data, ctx) => ctx.allowList = data.bars,
afterValidate: (data, ctx) => ctx.allowList = null,
})
ssalbdivad
ssalbdivad14mo ago
That might be achievable at some point with the path syntax because it would be something like ../../bars[number]
Dimava
Dimava14mo ago
I can't '..' because "I have no idea where the bar values are placed deep in the object"
ssalbdivad
ssalbdivad14mo ago
Maybe /bars[number] would be from the root of the type
Dimava
Dimava14mo ago
@ssalbdivad what is the most straightforward way to
const Chain =
type({ a: 'number' })
.narrow(type({ b: number }))
.narrow(type({ c: number }))
const Chain =
type({ a: 'number' })
.narrow(type({ b: number }))
.narrow(type({ c: number }))
(i.e. ordered validation) Or
const Chain = narrow('any', data => {
type({ a: number }).assert(data)
type({ b: number }).assert(data)
type({ c: number }).assert(data)
})
const Chain = narrow('any', data => {
type({ a: number }).assert(data)
type({ b: number }).assert(data)
type({ c: number }).assert(data)
})
or something
Unknown User
Unknown User14mo ago
Message Not Public
Sign In & Join Server To View
ssalbdivad
ssalbdivad14mo ago
What is the goal here? There's actually this related question I've been thinking about regarding preserving ordering through intersections of narrows. In general, narrows will run sequentially in the order they're defined, but they're not like morphs where the order itself is part of the type.
// as long as the narrows in l and r are individually safe to check // in the order they're specified, checking them in the order // resulting from this intersection should also be safe.
Dimava
Dimava14mo ago
can you return Out from narrow, again?
ssalbdivad
ssalbdivad14mo ago
No? Narrow is a constraint, not a morph If you mean can it be a type guard then yes, you use is like you normally would in TS
Dimava
Dimava14mo ago
function withHooks<T extends Type<unknown>>(
type1: T,
before: (data: T['infer'], problems: Problems) => Out<T['infer']> | void,
after: (data: T['infer'], problems: Problems) => Out<T['infer']> | void,
): T {
return narrow('any', (data, problems) => {
const a = before(data, problems);
if (a.problems) { problems.inherit(a.problems); return false; }
const b = type1(data, problems);
if (b.problems) { problems.inherit(b.problems); return false; }
const c = after(data, problems);
if (c.problems) { problems.inherit(c.problems); return false; }
return true;
})
}
function withHooks<T extends Type<unknown>>(
type1: T,
before: (data: T['infer'], problems: Problems) => Out<T['infer']> | void,
after: (data: T['infer'], problems: Problems) => Out<T['infer']> | void,
): T {
return narrow('any', (data, problems) => {
const a = before(data, problems);
if (a.problems) { problems.inherit(a.problems); return false; }
const b = type1(data, problems);
if (b.problems) { problems.inherit(b.problems); return false; }
const c = after(data, problems);
if (c.problems) { problems.inherit(c.problems); return false; }
return true;
})
}
erm I dunno what I wrote
ssalbdivad
ssalbdivad14mo ago
Couldn't you just use a morph?
Dimava
Dimava14mo ago
...I wrote not the thing I did want I guess I'm not sure, I huess I can
Want results from more Discord servers?
Add your server