#I'm struggling with type intersections - creating subset types. (playground enclosed)
1 messages · Page 1 of 1 (latest)
Preview:```ts
interface DriverOptions {
uri?: string
host?: string
username?: string
password?: string
}
type CleanDriverOptions = Omit<
DriverOptions,
"password"
let opts: DriverOptions
opts = {
uri: "https://user:[email protected]/database",
host: "somedbserver.aws.com",
username: "user",
password: "pass",
}
...```
@proud wedge TS is just a type-system - if you actually want to remove passwordfrom your opts object, then you need a runtime mechanism for that, like destructuring.
there's no way to clone the original opts to a type that doesn't have it and exclude that way?
as in Typescript is just a type-assertion: it tells TS to 'dangerously' assume that a value is a certain shape, it doesn't have any effect on the runtime value.
gotcha. yeah, that's sorta the understanding of what i was getting to. just didn't like the destructuring approach and had hoped that Omit etc would do what i wanted
Yeah, no, you'll need something at runtime.
There's an option for no-unused-vars that allows unused destructured values to be used to support this sort of pattern.
i'll look into that. still, it would be nice to have a way to declare what was 'clean/safe' vs not, and have that type be enforceable
Does ESLint allow _/prefixing with _ to signal discard?
it does
If so, then you could do const { password: _, ...rest } = ....
but _password doesn't exist on opts and therefore can't be destructured
hmm, i wonder if that'd work
Yeah, or password: _password
OK, pivoting the q slightly -- is there a way to express a type that MUST NOT contain a given prop? because i kinda wanted to explore the idea of being able to assert that i have opts and cleanOpts
essentially i'm trying to build pii exclusion filtering, and having that type enforcement feels ... right?
No, but that should not be the approach you take.
TS as a language does not care about excess properties, opts is assignable to optsWithoutPassword by definition.
The typical approach is to validate/sanitize when data is crossing the TS boundary. That means when data is coming into TS world (eg when you fetch from a request, read from a file, etc) you do validation/sanitization, and when data is going out of the TS world (eg when you return an API response, write to a file, etc) you do validation/sanitization.
hmm
is there a decent pattern for validating a type? the original code is something like :
let dbConnectionOpts: DBConnectionOptsType;
dbConnectionOpts = parseDbConnectionURI(inputURI);
given what y'all have said, i assume my next line would be something like
validateDbConnectionOpts(dbConnectionOpts);
What does parseDbConnectionURI return? If it returns DBConnectionOptsType, then it should already have done validation.
Yeah, it constructs one of those types
Otherwise you have to keep remembering "oh yeah btw always call another function after this function if you don't then a bug might appear"
right, that'd be lame
Yeah in this case inputURI is data crossing the TS boundary, and the parseDbConnectionURI should do validation/sanitization.
That way you know "a variable of type DBConnectionOptsType is always good to use" rather than "sometimes it's good to use sometimes I need to pass that through a validation function first"
so here's the real code basically....
i understand what you're saying there @astral kernel , but to bring it together, what i'm trying to figure out now is how to ensure that cleanOpts is 'valid' -- i.e. there are not properties on it that shouldn't be there. right now i'm putting a lot of trust in that destructure, i guess
The trick is, you don't 😄
don't validate, don't trust? 😛
You only do that at the boundary crossing point, what are you using cleanOpts for that you must ensure the password is removed? Are you returning it in an API response, are you writing it to file, are you displaying it in the console, or something else?
Then the thing that calls the logger is responsible for only logging everything else but the password. That's crossing the TS boundary: it goes from inside TS to outside into the logs.
yeah, that's a good point
Before that, you should not write any code to prevent password from existing, because you are essentially fighting against the language.
better to have a filter that e.g. is prepareForLogging(opts) or similar, as part of the logger interface.
Maybe a function that's just logOpts(opts) (or a method on the logger, whatever) and that thing will be responsible for making sure password does not get logged.
Just making sure I'm not misunderstanding, is prepareForLogging(opts) supposed to return a new object and then you log the returned object?
Because that essentially brings back the "oh don't forget to call a function before you log it or else something bad might happen" problem.
Yes. that's because the opts really should be passed as a context to a log entry; so to help contextify what's going on with whatever is going on that's being logged.
i hear you on the call-a-function problem, but it's probably better than extending the logger interface right now and injecting the clean behavior
because that'll make the logger (used in a bunch of different places) have some inconsistent behavior
though it might be interesting to declare a callback to cleanup context, which could then be defined within each given project ... or something like that. anyhow, something to figure out
Yeah you have more context on what works for your project, but was just bringing up a concern.
You can also go the branded type route and mark values as "safe to log."
yeah, i appreciate it . i'm also balancing rewriting one util libary rather than two at once 😉
oooh, how would i do that?
Preview:```ts
declare const safeToLog: unique symbol
type SafeToLog = {
[safeToLog]: true
}
declare const log: (data: SafeToLog) => void
type Opts = {
foo: string
bar: number
password: string
}
const prepareOptsForLogging = (
opts: Opts
):
...```
interesting! i'll have to dig more into how that works. that's kinda cool though