Branded Types give you stronger input validation

  Рет қаралды 16,644

Andrew Burgess

Andrew Burgess

Күн бұрын

Thanks for watching, be sure to like and subscribe! For more, go to shaky.sh

Пікірлер: 70
@samroelants
@samroelants Жыл бұрын
Really wish typescript had a built-in way of doing this kind of nominal typing, rather than having to hack it using type intersections. Really well explained! Looking forward to more tips!
@noxiifoxi
@noxiifoxi Жыл бұрын
yep, I won't use this method because it's ridiculous, I hope they add something like that in a normal way to ts in the future.
@khai96x
@khai96x Жыл бұрын
New type pattern: Create a wrapper object type with a unique private symbol as property. Unlike his 'Branded Types', this wrapper type will match its runtime object.
@DemanaJaire
@DemanaJaire Жыл бұрын
@@khai96x Runtime fetishists.
@vaap
@vaap Жыл бұрын
they already have the "unique" keyword, i can easily imageine "type EmailAddress = unique string"
@herzog0
@herzog0 Жыл бұрын
This just saved my freaking life. We had like 30 interfaces that extended Record and their attributes had to be changed. We ended up with dozens of calls to non-existing attributes throughout our code that would not throw a compilation error. I saw this video a long time ago and remembered it now. Great explanation!
@toddymikey
@toddymikey Жыл бұрын
Alternatively, (heavy approach) repackage a string email address as a class and pass that on past the point of initial checks ... thereby always being sure whenever it is reused by any function that handles email addresses that it is an actual email address.
@KadenCartwright
@KadenCartwright Жыл бұрын
The downside of this is the way that typescript recognizes whether something fulfills the requirements of a type when that type is really a class is based off the public api of an instance of the class so it’s fairly easy to just construct an object literal that looks similar enough for the compiler not to complain but has bypassed the validation, especially when it’s a simple object with one field like `const str: SpecialString={value : “some special string”}` The intellisence could actually guide you down the wrong path pretty easily that way
@KirkWaiblinger
@KirkWaiblinger Жыл бұрын
​@@KadenCartwright actually classes are treated more or less nominally in TS as long as they have at least one non-public member. It's wonky. So if you have class Clazz { private x: number = 3 } declare function f(x: Clazz): void; Then f({}) and f({ x: 3}) will both be errors. Even this will fail class ImposterClazz { private x: number = 3 } f(new ImposterClazz ()) So yeah wrapping validated objects in classes (with a non public member) would probably successfully prevent false negatives in most circumstances in TS. Not necessarily recommending it but just saying you can't actually lie to it that easily with structurally similar objects. One of those things that the TS docs are extremely misleading about.
@hyperprotagonist
@hyperprotagonist Жыл бұрын
How have I only just discovered you? Love your approach to explaining stuff. Really, really helpful! Keep it up!
@martinemanuel8239
@martinemanuel8239 Жыл бұрын
The last one was awesome, thanks for sharing!!
@sam.kendrick
@sam.kendrick Жыл бұрын
Thanks for your effort! I will use this at work!
@webstuffzak
@webstuffzak Жыл бұрын
Great content Andrew, don't know why KZbin recommended you to me but, it sure was right. Subbed 👍
@natanaelaitonean3867
@natanaelaitonean3867 Жыл бұрын
Super helpful explanation! Thanks!
@user-em9wo8gu2p
@user-em9wo8gu2p Жыл бұрын
Great video! Thanks Andrew!
@hut_fire1490
@hut_fire1490 10 ай бұрын
I stumbled upon a goldmine, thank you Andrew !
@tezza48
@tezza48 Жыл бұрын
that's pretty sweet, I didn't know you could coerce types like that using `foo is bar`. another tool for the belt!
@magicjuand
@magicjuand Жыл бұрын
this is interesting for strings which you need a function to validate. for a lot of strings, especially IDs and such, template literal types are probably the way to go
@PatricioHondagneuRoig
@PatricioHondagneuRoig Жыл бұрын
You sir just earned a subscriber
@rahimco-su3sc
@rahimco-su3sc Жыл бұрын
your videos are really helpfull | thanks a lot for your efforts
@bobobo1673
@bobobo1673 Жыл бұрын
Thank you
@edgarabgaryan8989
@edgarabgaryan8989 Жыл бұрын
you are the best
@nickolaizein7465
@nickolaizein7465 6 ай бұрын
Great!
@antonpieper
@antonpieper Жыл бұрын
You could also use a template literal `${string}@{string}.${string}`
@paulholsters7932
@paulholsters7932 8 ай бұрын
Thx
@tezza48
@tezza48 Жыл бұрын
you could even use the assert on a function that mutates the input into the new type if that's your jam I guess. like something adding some component or entry to the argument.
@andrew-burgess
@andrew-burgess Жыл бұрын
Hmm, yeah, I guess that would work! Personally, I’d be more likely to just have it return a new type, but you’ve got to love the flexibility of TS!
@Q_20
@Q_20 Жыл бұрын
Specifying type check in return type automatically promotes type in function usage
@spead
@spead Жыл бұрын
nice
@mahadevovnl
@mahadevovnl Жыл бұрын
But why do you need that object with __brand at all? After validating or asserting it, it should be good enough to just keep the type as a string, no?
@DavidAguileraMoncusi
@DavidAguileraMoncusi Жыл бұрын
The __brand object only exists at a type level. Your code will only contain strings. If you didn't include the object in the type definitions, string and EmailAddress are synonyms. Sure, the safeguard function would let YOU know that the variable is an email, but at runtime and not at the type level (it'd be equivalent of the function simply returned a boolean).
@Azoraqua
@Azoraqua Жыл бұрын
What about string templates? Like "type Email = `${name}@${domain}.${tld}`" That will only accept strings that are in that exact format. Any usecase you think?
@vukkulvar9769
@vukkulvar9769 Жыл бұрын
But how would it knows that name/domain/tld must not contains @ ?
@Azoraqua
@Azoraqua Жыл бұрын
@@vukkulvar9769 it doesn’t, any strong suffices, I don’t think you can prevent that. Although you might be able to provide a type that removes all invalid characters.
@robertotonino2916
@robertotonino2916 Жыл бұрын
Interesting approach! Why is the “& { … }” necessary? Is it because the type alias would be recognised as a string even after checking isEmailAddress? Also, what about string literal types? Are they too strict for this use case?
@vytah
@vytah Жыл бұрын
type EmailAddress = string means that EmailAddress is the exact same type as string, so it's just a clunky alias. All strings would be EmailAddresses in that situation.
@echobucket
@echobucket Жыл бұрын
Is the function mutating the type of the email string? Is there a way to do this without mutating? Like make isEmailAddress return an Optional EmailAddress?
@andrew-burgess
@andrew-burgess Жыл бұрын
The function casts the string to a new type. You could wrap it in an Optional, but if you want the type EmailAddress, you will need to cast a string to that type.
@jethrolarson
@jethrolarson Жыл бұрын
They can't have my brand, I have special eyes!
@zahash1045
@zahash1045 Жыл бұрын
Why not just have a class "EmailAddress" that has a private constructor and a static factory method that takes a string as input and returns Option as output. That way, the only way to get an "EmailAddress" object is to call the static factory that does all the checks. So, it's a guarantee that you checked the string if you have an "EmailAddress" object If we do it your way then there it is possible to just do "'asdf' as EmailAddress". So, even if you have a branded type, there is no guarantee that it came from the validator function.
@aarondewindt
@aarondewindt Жыл бұрын
The branded type doesn't actually change the underlying type of the variable. It's still a string, so you can use it as any other string. All string methods will be available (eg. replace, split, etc) and you can pass it to functions expecting strings. The object type you're intersecting will never instantiated, and the string is never copied or wrapped. The only thing you're doing is checking if the value of the string is valid, and if it is, you tell the static type checker that after this point, I have a string with a valid email in it.
@zahash1045
@zahash1045 Жыл бұрын
@@aarondewindt I can do the same after unwrapping the inner value with a getter to get access to the string and call all the methods I want But what I want you to focus on is If we do it your way then it is possible to just do "'asdf' as EmailAddress" (maybe vscode even suggests it as a "quick fix"). So, even if you have a branded type, there is no guarantee that it came from the validator
@andrew-burgess
@andrew-burgess Жыл бұрын
I kinda like your approach here, but I don't think it solves the `as` problem. This bit of TypeScript is working for me: class EmailAddress { } const a = "test" as EmailAddress; If you look at the type of `a`, it's `EmailAddress`.
@redcrafterlppa303
@redcrafterlppa303 Жыл бұрын
@@andrew-burgess but I think thats just the downside of supporting the underlying dynamic type system of js. If you forbid the as casting you would also lose the is casting. The new type pattern works without casting values by simply guaranteeing the correctness of the object by being of a certain type. This technique is heavily used in type oriented languages like rust.
@ChrisAthanas
@ChrisAthanas Жыл бұрын
This is useful to know and I would suggest reducing the jargon and verbosity and simplifying the explanation a bit more
@Danielo515
@Danielo515 Жыл бұрын
This is the only way to have properly safe types. Too bad most people don’t ever see beyond number, Boolean and string
@stanstrum
@stanstrum Жыл бұрын
Why not use a "unique symbol" e.g. & { __brand: unique symbol }; ?
@formyeve
@formyeve 9 ай бұрын
I don't get why one needs to do this when there are things called classes 😂 oop solved these problems a long time ago
@vanish3408
@vanish3408 Жыл бұрын
I read this as "Braindead types"
@andrew-burgess
@andrew-burgess Жыл бұрын
😂🤣
@pwall
@pwall Жыл бұрын
Amazing content, undervalued by the algorithm even though it got on my feed.
@ricardodasilva9241
@ricardodasilva9241 Жыл бұрын
Genuine doubt here, why you would call runtime code with emails, only place I can see it would help are on tests or writing libraries maybe? How is this better than a schema validator? Are there other use cases for this?
@ricardodasilva9241
@ricardodasilva9241 Жыл бұрын
ah, I see. You are just explaining what you can do with the typing. But I think this is a bad use case.
@andrew-burgess
@andrew-burgess Жыл бұрын
Yeah, this pattern would make sense within a schema validation library. But also, there are cases (like one off bulk processing jobs) where adding a validation dependency is a little too heavy, and I just want something lighter.
@DavidAguileraMoncusi
@DavidAguileraMoncusi Жыл бұрын
​@@ricardodasilva9241 I can think of other use cases where this is extremely useful like. For example, imagine I have an API that returns a list of objects with an attribute name "slug." If I defined type ObjectSlug = string, I'd be able to pass any string as an object slug. With branded types, however, only* slug attributes extracted from objects retrieved via the API would be valid. An error messages would also be more helpful. * That's probably not 100% sure, but you get the point
@anatolydyatlov963
@anatolydyatlov963 Жыл бұрын
Or... just use Zod
@andrew-burgess
@andrew-burgess Жыл бұрын
Oh, I’ve been meaning to give Zod a try! Thanks for the push :)
@DavidAguileraMoncusi
@DavidAguileraMoncusi Жыл бұрын
Would Zod help, though? Would I really be able to have an EmailAddress type in my app that, if I get an attribute of said type, I know for sure* (provided there aren't any explicit casts) it's an email address?
@anatolydyatlov963
@anatolydyatlov963 Жыл бұрын
@@DavidAguileraMoncusi Yes, that's one of the main features of Zod - it can parse untyped entities and throw errors when they don't meet the specification. For example: ``` const TEmail = zod.string().email(); const untypedUnknownString = "..."; const possibleEmail = TEmail.safeParse(untypedUnknownString); // if possibleEmailString.success is true, then possibleEmail [dot] data (links aren't allowed in the comments) will be a typed email address previously stored in untypedUnknownString. The type will be: TEmail // if possibleEmailString.success is false, then possibleEmail [dot] error will contain a parsing error message ``` This is an example of a predefined type feature of Zod (string.email), but of course, you can use your own type-checking logic, regexes and primitive types. You can also create Zod object types where each property has its own type parser. In this case, parsing the whole object will handle all the properties recursively, assuring that the object meets the schema.
@mbehboodian
@mbehboodian Жыл бұрын
There are still things to improve in content of your videos, but subscribed. Keep it up 🙂
@andrew-burgess
@andrew-burgess Жыл бұрын
Thanks! Would love to hear what I can improve!
@gosnooky
@gosnooky 11 ай бұрын
It's not much, but it's an honest hack.
@RM-bg5cd
@RM-bg5cd Жыл бұрын
Aren’t these called type guards?
@loko1944
@loko1944 Жыл бұрын
typeguards are jus the tool here to use branded type. You can't use branded type without typeguards. Thats the point of using branded types - safety. If that is no sufficient...idk, watch again
@RM-bg5cd
@RM-bg5cd Жыл бұрын
@@loko1944 That literally is not what I'm asking. Maybe try actually reading properly? Branded types as shown in the video are documented as typed guards in their docs.
@andrew-burgess
@andrew-burgess Жыл бұрын
I think Loko is right here, actually. Type guards, according to the TS docs, and functions that narrow the type of the argument they accept. So you’re right, we do use type guards here. The branded type is the type that the guard function will narrow its argument to. The core idea here is that the only way to get a value of a branded type is via the associated guard function. There’s no other was to get a value of that type, apart from explicitly casting it via ‘as’.
@redcrafterlppa303
@redcrafterlppa303 Жыл бұрын
This seems similar to "if (o instanceof Foo f) {}" casting in java but implicit on callsite and not really using the type system. Why not create a simple type EmailAddress with a constructor or factory function that validates the email address? This way you have a real type representing an email and not just a botched string that's implicitly casted by the check function.
@nomadshiba
@nomadshiba Жыл бұрын
actually brands are not that useful with Emails. also you can define emails like this. type Email = `${string}@${string}.${string[0]}${string}` i would use brand for things, such as Ids or similar things and i would define a brand like this const enum UserId { _ = "" } export type { UserId } const enum PostId { _ = "" } export type { PostId } unlike & { __brand ... } pattern, by using enum you dont have to find a name for your brand
@praktycznewskazowki6733
@praktycznewskazowki6733 7 ай бұрын
I still don't understand a use case for this... What can goes wrong with simple isEmail: (email) => boolean ??
@tak68tak
@tak68tak Жыл бұрын
Zod validation is easier like z.string().email
How to use generics in TypeScript
11:46
Andrew Burgess
Рет қаралды 36 М.
How to use TypeScript Enums and why not to, maybe
12:43
Andrew Burgess
Рет қаралды 18 М.
Final muy inesperado 🥹
00:48
Juan De Dios Pantoja
Рет қаралды 17 МЛН
I’m just a kid 🥹🥰 LeoNata family #shorts
00:12
LeoNata Family
Рет қаралды 4,7 МЛН
Which one is the best? #katebrush #shorts
00:12
Kate Brush
Рет қаралды 24 МЛН
will i never understand this? unknown.
12:05
Andrew Burgess
Рет қаралды 3,1 М.
how to get better at typescript (feat. kysely)
16:57
Andrew Burgess
Рет қаралды 7 М.
TypeScript Wizardry: Recursive Template Literals
14:47
Tech Talks with Simon
Рет қаралды 36 М.
any vs unknown vs never: TypeScript demystified
8:01
Andrew Burgess
Рет қаралды 20 М.
Rust Functions Are Weird (But Be Glad)
19:52
Logan Smith
Рет қаралды 127 М.
Type Narrowing in TypeScript
11:51
Andrew Burgess
Рет қаралды 8 М.
Infer is easier than you think
13:38
Matt Pocock
Рет қаралды 85 М.
this regex identifies prime numbers (reaction)
10:23
Andrew Burgess
Рет қаралды 113 М.
Are your TypeScript Unions broken? | Advanced TypeScript
7:36
Andrew Burgess
Рет қаралды 7 М.
All Rust features explained
21:30
Let's Get Rusty
Рет қаралды 285 М.
Main filter..
0:15
CikoYt
Рет қаралды 8 МЛН
MacBook Air Японский Прикол!
0:42
Sergey Delaisy
Рет қаралды 569 М.