-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
66 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
// A nominal type system means that each type is unique | ||
// and even if types have the same data you cannot assign | ||
// across types. | ||
|
||
// TypeScript's type system is structural, which means | ||
// if the type is shaped like a duck, it's a duck. If a | ||
// goose has all the same attributes as a duck, then it also | ||
// is a duck. | ||
|
||
// This can have drawbacks, for example there are cases | ||
// where a string or number can have special context and you | ||
// don't want to ever make the values transferrable. For | ||
// example: | ||
// | ||
// - User Input Strings (unsafe) | ||
// - Translation Strings | ||
// - User Identification Numbers | ||
// - Access Tokens | ||
|
||
// We can we get most of the value from a nominal type | ||
// system with a little bit of extra code. | ||
|
||
// We're going to use an intersectional type, with a unique | ||
// constraint in the form of a property called __brand (this | ||
// is convention) which makes it impossible to assign a | ||
// normal string to a ValidatedInputString. | ||
|
||
type ValidatedInputString = string & { __brand: "User Input Post Validation" } | ||
|
||
// We will will use a function to transform a string to | ||
// a ValidatedInputString - but the point worth noting | ||
// is that we're just _telling_ TypeScript that it's true. | ||
|
||
const validateUserInput = (input: string) => { | ||
const simpleValidatedInput = input.replace(/\</g, "≤") | ||
return simpleValidatedInput as ValidatedInputString | ||
} | ||
|
||
// Now we can create functions which will only accept | ||
// our new nominal type, and not the general string type. | ||
|
||
const printName = (name: ValidatedInputString) => { | ||
console.log(name) | ||
} | ||
|
||
// For example, here's some unsafe input from a user, going | ||
// through the validator and then being able print: | ||
|
||
const input = "\n<script>alert('bobby tables')</script>" | ||
const validatedInput = validateUserInput(input) | ||
printName(validatedInput) | ||
|
||
// On the other-hand passing the un-validated string to | ||
// printName will raise a compiler error: | ||
|
||
printName(input) | ||
|
||
// You can read a comprehensive overview of the | ||
// different ways to create nominal types, and their | ||
// trade-offs in this 400 comment long GitHub issue | ||
// | ||
// https://github.com/Microsoft/TypeScript/issues/202 | ||
// | ||
// and this post is a great summary | ||
// https://michalzalecki.com/nominal-typing-in-typescript/ | ||
// |