-
Notifications
You must be signed in to change notification settings - Fork 0
Proposal: struct.extend and struct.exact #2
Description
This proposal is two-part because the use of extend ties in with the use of exact. We will see why below.
*1) struct.exact
At the moment, struct only validates loose objects; when there are more props than required, they're simply ignored. This is probably safe for most use-cases, but some times you might want a strict mode where additional props are disallowed.
The proposed API for this is: struct(S).exact(x) -> x is exact S
There will be no change in the type guard. At compile time, exact is similar to loose validation, because any x will always be accepted as input, and it's unsafe to use un-validated props either way. It's only a runtime change where exact fails if there were more props than expected.
Example
const Message = struct({ from: string, to: string, content: string });
const x = { from, to, content };
if (Message.exact(x)) {
// x is valid
}
const y = { from, to, content, isForward };
if (Message.exact(y)) {
// y is not valid Message, since it has an unexpected prop `isForward`
}*2) struct.extend
Currently, extend is its own Higher Order Predicate. Its behaviour is such that it accepts a struct predicate as first param, and a struct as second param. The definition of the first struct is enforced at compile-time; you cannot extend from an arbitrary type (use and for that). x must satisfy both structs, while the second struct may not contradict the first; only extend it.
Problem
Because struct.exact as defined previously is also a predicate, extend will have to disallow exact structs as a parameter, because its runtime validation will fail before being extensible.
Because extend is very tied in to struct, moving it to a method makes extend and exact mutually exclusive. I'd also expect extend to be used as an extension of a previously defined struct, rather than inline definition like extend(struct(...), ...). Making it a method makes a lot of sense.
Example
Existing:
const Message = struct({ from: string, to: string, content: string });
// .. elsewhere
const ForwardedMessage = extend(Message, { isForward: true });Proposed:
const ForwardedMessage = Message.extend({ isForward: bool });Unaddressed issues
There are still some concerns to be worried about, namely deeply strict structs, like this one:
const MessageContent = struct({
type: oneOf("html", "markdown"),
value: string
}).exact; // notice that MessageContent has been exact-ed
const Message = struct({
from: string,
to: string,
content: MessageContent,
});Say Message is extended like so:
const MessageWithAttachment = Message.extend({
content: {
attachment: string,
},
});This check fails to behave as expected at runtime because MessageWithAttachment will always fail as the original struct's content is exacted. This could be solved by having a way to extract the non-exact predicate out of an exact predicate, but we're yet to decide whether implicitly un-exacting structs when extending them is the right way to go.