#I'm struggling with type intersections - creating subset types. (playground enclosed)

1 messages · Page 1 of 1 (latest)

proud wedge
#

I'm trying to create a 'subset' type -- i.e. all the elements of type abc EXCEPT property xyz. I know about the destructuring way to do this, but i don't love it. I thought that Omit might be the way to succeed here, but no luck. What am i not understanding?

thanks all.

finite lintelBOT
#
imajes#0

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",
}
...```

ancient monolith
#

@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.

proud wedge
#

there's no way to clone the original opts to a type that doesn't have it and exclude that way?

ancient monolith
#

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.

proud wedge
#

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

ancient monolith
#

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.

proud wedge
#

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

astral kernel
#

Does ESLint allow _/prefixing with _ to signal discard?

proud wedge
#

it does

astral kernel
#

If so, then you could do const { password: _, ...rest } = ....

proud wedge
#

but _password doesn't exist on opts and therefore can't be destructured

#

hmm, i wonder if that'd work

ancient monolith
#

Yeah, or password: _password

proud wedge
#

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?

astral kernel
#

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.

proud wedge
#

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);

astral kernel
#

What does parseDbConnectionURI return? If it returns DBConnectionOptsType, then it should already have done validation.

proud wedge
#

Yeah, it constructs one of those types

astral kernel
#

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"

proud wedge
#

right, that'd be lame

astral kernel
#

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"

proud wedge
#

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

astral kernel
#

The trick is, you don't 😄

proud wedge
#

don't validate, don't trust? 😛

astral kernel
#

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?

proud wedge
#

logging it

#

instrumenting for SRE etc

astral kernel
#

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.

proud wedge
#

yeah, that's a good point

astral kernel
#

Before that, you should not write any code to prevent password from existing, because you are essentially fighting against the language.

proud wedge
#

better to have a filter that e.g. is prepareForLogging(opts) or similar, as part of the logger interface.

astral kernel
#

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.

proud wedge
#

yeah, we're saying the same thing 🙂

#

thanks, that's helpful.

astral kernel
#

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.

proud wedge
#

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

astral kernel
#

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."

proud wedge
#

yeah, i appreciate it . i'm also balancing rewriting one util libary rather than two at once 😉

proud wedge
finite lintelBOT
#
nonspicyburrito#0

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
):
...```

proud wedge
#

interesting! i'll have to dig more into how that works. that's kinda cool though