Skip to content

Commit

Permalink
once again add examples to repo
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanhofer committed Feb 17, 2023
1 parent 9510a06 commit 77d431f
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 3 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
lib
examples
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,10 @@ console.info(result().toPrecision(2)) // => e.g. '0.57'

## Examples

- [input validation](https://www.typescriptlang.org/play?noUncheckedIndexedAccess=true&noUnusedLocals=true&noUnusedParameters=true&target=99&jsx=0&useUnknownInCatchVariables=true&noImplicitOverride=true&noFallthroughCasesInSwitch=true&exactOptionalPropertyTypes=true&pretty=true#code/JYWwDg9gTgLgBAbzgUwB4GNlhsCA7AGjgGcBXdTY4uAXzgDMoIQ4ByNTbXPAQwBs+AT1YAoEQHpxcALSy58hYqXKVqteo2a1Y9PmLwAbv2AATHjGRwAvHAA8AEXM8AfAAozMHgC44jz0SM+U3NoH3cnHz8eAEprZzgYKFJLAB8SROA8AHNYq3iEEQBIXTx9OGBiAHkAa2s4QOCYaHDPaKLgejhXCprrKxtE5NioZBhSKDwSckpiFpixQpGxiZQMLBx8bqrqtpoxSRktI+OT07PVMRhBMEsABQgymwLCnlIYAAtQ9KhMrKKcGB8ZA+fQ-bJFEoWPAwEEZcF7EQlMqQfQANWMHmgdVcKJhcHu+ly+SKByW40mIGQPDwv3opD4KCgTCgcEpVB4WWQ1AA7u9kJNiMxRu9fuVqJkGiZ2p1XABCXEAOleH2gw1G5LYACJlZ8oJrWRViL9RNKuvKHjAFQCgWrlpNWJrrch9SBDcbTTiLVbgIDkAqgdkPnB4gBGABMAAZbRqHU6XaQygAjSzEXUWFkfalwcMRuDod48KA8dDp4gmwodM2KyH8mDRlYOmvQl1u7KiCsyxVO-38rJB2xwACsEajcDJDc1TZg8bKkJ4mTg5jgQJ4ZWHufzheLpfLpPVKwABoNkAe4Lz+fUMeZuHAwKviFyiuPJseRAikfBiDwDMgCfAbKugh4OgXS4j4f5EogEJ6IYV4bHgABKXL0v+l5BB4yCevoRC4ui6EhFAbQdl0krXvgSFkHwloVAAomsXD4PWkykfBFEoXABx3lQDDQNyhYmKsnDwQsBxfgYoofJYHg8GefLMXBN4FtQXEPlKxQweUAk2CGHFSDwfE+iQ36-haAAqEBRImq6YbibRPvuArTFysymLs+xSOcnled5PnSGIaCQLAeYaUkkwAcQQEga4kHPB+Rk-n+bFUXU+nzp+xl-q4zwvG8uo+KwABSEDvJM9gQMgrAEEUhROvlNFMliBZ4CYQTZCQnz0gJyYoKuwDIFAlXVVO+UADLQMgLDAGAZAsCYEB8NARBGvAPCUvASLICW+6LiYU0VMA6CisgQSWnAACCa1EAAjqQwAKoNhQ0HZxGuGJJn6El1HEHRQncLEHxMNycB4MgQP1cyr0ZRan3Rc9SLzX6mT0BArgHiDQO4vFyAmD4AAkCBvYlyFUdFNAHm5IihdFIhAA)
- [fetching data](https://www.typescriptlang.org/play?target=99&moduleResolution=99&module=100&noUncheckedIndexedAccess=true&noUnusedLocals=true&noUnusedParameters=true&jsx=0&useUnknownInCatchVariables=true&noImplicitOverride=true&noFallthroughCasesInSwitch=true&exactOptionalPropertyTypes=true&pretty=true#code/JYWwDg9gTgLgBAbzgUwB4GNlhsCA7AGjgGcBXdTY4o4YgUQyx3wEMAbNgTwCVky34AXzgAzKBBBwA5GkzZcedlykAoUJFiI4LKslgBlcpWJxhYidNlMFSzgHodxPTFUqYnMMjgApYvjgAvD5+eACCUFAsnHAAPsH4APIARgBWyOjwcb74AApQoMA4AG7IKm4eXtlhEVGB8XgA2gC6Ze6e9clpGXUIKgCQDQDScMB4JDD5eADmTQD8AFz1KoKtFfV5BcVeQUkQEGzILGNxeKQcsXCnIEl6F8QTo1NldnZwALQfn1-fP79--wDAUDgSDAc9XgATZAiUZeCHAEQiPTIPDwBhyZhjdBsRx8UTQbTnADWowhcAgIhQjHk+GIKhecAAcgkACp0RZ+EDIAAWEAA7nAWRV9Oh8tg4OgjlJ4PD7o9SLRuXAbjA+cgUXB4Yjkai4ABhHG6EwwbksGV6YAlUTiSQAAzoEWgtoAdPTXnAPZ7QiYWHA+dAiSxxKQ8GS1RKjiRkPBfWBxJ5YNFRsRgFC4CazRLDcaIHAQCwiV5RkioCjMH6A9opixRq7sY4mdH-VAiejrP40DAURCTA7xFBEP0GQABGDEN7AKZ4aBeE1eHI4zhTYOhzUQPHT+AJlP3dPcry26cAVVOTghABkIJK2MRbRL8DCnn1SywIfguHAAMSpur6TjXfYAAopEZJsAzbGk8CkABKZYVHrKg4AAEXSCAoSqCDMSpLtQ17R0B16PoRzHCcpxnPd50XZcIBDMk3w3CAtz0Hd4DnOBDwgE9SDPS9r1ve88EffoXzfPAP2-Mkgj-AC2GAlD0DQ5AMOpTEYLghCTAACRgGAwEwhRsO7PD+0HIjXlHcdJ2nUsKLgBcomo2i1wYpioBY2yOK4nir3YfiFMEydhMOUTxJ-KT-12WSpG03T9PwNSVg0uA6HAdwkLNHRozisZOyM5L8NM4jLLImy2PspcVzo9cTE3OBt1oVj93Y49T2QC8fJvO9-KE59gvfaIJN-CKgKkFLsE4dKYEymBsoS8E-X3MYkRgdBuUeTUMqIX0kQFE1HhMSUxlNMBPDwZ1nQWjVOWjNbplEGsbxoeBaDgKY9jJGBcyJacBT5U14FNY18imKZkQ+praDIUp-N3ZbVsm30gh0Tg8HQOAAB5eBgUgoDwIV2ly3D6gAPkA7tIFGGAOQeaZoMCYnTJh+A4e5Xh+HgJG+RrZno1WsnQwp1FYL6PpnUlFbuTJ+mqQxBRALwZABVA1VwJUuXkGgzW4AZNinCgEoBwUs4yRuOAQxYIoHpYJIDjgc7XT6BE4EA2hstsNmzhgQCWY9gQtdLbHcdEXnWb4T3tdeMAGxEaAuagMkrEgsozLt87bNLABHUg+HgI22DJEQHr9QolV9e4zW4hS0wAFlQVByQHABWOvU4dp3AIAQh9sOBGdCAiTpgOcZytX8HlxW4BivTR7wQDbQAEgQbv2edcvseIQRFkX5fPdXqb15ZTtBFtTXk6Zjapt9jntC5wpg4lq-nRSEJAOF0W51niEMulr+pu0EwsbD3xhrfootxZ822AzROmJx4CnkopZSssx4ay1gye2DdbJR04GwCAr57zGwEu5CkIw8CWzYD+bw+gEiMlbv0dursZ7ux7l7X+LAr7+2jMPC+bDmERzqtHWOQYE4z2Tu3cIkRODOloOIqIgFWHsLgAAMkUXADu8jmHOgONME0dNCK9UDiPJBs8FYCjGmlDKTgZoz1fnTNBacCT5k4KbNiZDUxmgMsQtirD8H53usANgqd+grCCgYkgRg+DEDkRlBRdiLpHALqMWw99VrrW8XnMkvlcymzIBQCJIgzhwTdO8UEJTSllPKRUgEqx2hHj1j0OhEJqaTCfIoLkTTHiFPPqDGAtTmJ1GRqjZ2dMAgM0IufFmvS3JX36bfHmEsEbo0mc0UmUhuQ6TAMQeYLxrpvBYGAYAYsJB2G4sxNSQ5Xh-RRMgA2fovDdOVCwdARJtBwFLOzIg4ZiC8gIatdIzyY4DmgQoEwMI3IwDoZSb2IdJnEEfgwoxujQHn1kHUCZetYXMNfqAhkppQy219JgWANYxhtC8MQoF-hSSWlTKQWwoD24ouTFNVGyBiHK2bK2GeiKRb6K4RS2eUg9Q0V8bVFmZt0UXQXIcJw6YoDRBYNWUYcAcRdigM6NSItgkiwZOGUS0pXmHA4NESUNlrY0UageMxE0LFZRnnefcpYiB+FuQa0JkZkCpXlTUTg9LIWMrwOXFlxCrUI0sdlblIsh5BxycYQCzQ35apTkqpQ5I5yG0ytQF1Uaxi+lBgrSIAS9AmS5FQBVpRI2cKDvy4CJ40CeAyG1FA+FJUHEygJKa3RdakBOtAGAbwuwsBAOq4WWqdZNTeeHQ6KAS63GyeEqg+SAnxPbcS8kONxV9IJKbTxax7SetDbaoxtp+jnxOW5VF0L0VXyxectcJAJA3XWnyEutlWEntpPsZAUjBIQDnjGvJZwPws0bYvM9xBNEoimCaYQYGT7J2zWE3JVBAJgdsZHARUA450QyoUhklT8MEcI0Rt4ZQunRlCBwSZrS8RI2ICjNGr9pZjNpPAMD0zObc1etGGFWKU7XI1CwEQqq8ye2AGAW2Ct7iNvyajLC14bwuqnfmQsYSbJ-UzCmyAVBgA2zJaQFaD6DqRhuBC52bGNHwvbHgCNDJMxsW7OSSkNEBxxggMuQdogcG-S8DCRQRq9ziF2k1ItBIMzwC-VMC63SpAmFlOJqIjbPq2TPaAk0gXLgTz7NAFDV7MWnz6KOi5XglMFi8GQUq-0XWmitOA7kMgMnnH5XSFOwCRRingM+84Bx4CcBonAb6-IRiUl66QfEUB7lJdxRCfFhirO3omNEJLpYQAQCtGxchCs9x2e5IF40TUssDjNVaZdI2pDnCcF4d1BUHXltxAYedkTzPs1gre7bXgHPhgQ147+z6TTmr9JEE661ChOtzHfKdpsYakC5GSZNYxuK0oCVzH1vKg5Pc9q-Z0+YwCAUAkgajphhkM2o7BYJeHiMU8p1T74pGWOvJDP0ujgzGMjMZnTs91GTAcbvt0ijbAqODr4LxmGn7v0xxy3oTnpOyhQBDFioAA)
> You need to clone this repository locally and open it in your IDE (VS Code) to see typesafety in action. (StackBlitz)[https://stackblitz.com/] and [github.dev](https://github.dev/github/dev) won't show TypeScript errors. Also the [TypeScript Playground](https://www.typescriptlang.org/play) is not able to show it correctly unless you set some specific options.
- [input validation](https://github.com/ivanhofer/exceptionally/blob/main/examples/input-validation.ts)
- [fetching data](https://github.com/ivanhofer/exceptionally/blob/main/examples/fetching-data.ts)

<!---------------------------------------------------------------------------------------------------------->

Expand Down
125 changes: 125 additions & 0 deletions examples/fetching-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// ! NOTE: You need to clone this repository locally and open it in your IDE (VS Code) to see typesafety in action.

import { exception, isExceptionallyResult, success } from 'exceptionally'
import { assertSuccess, guardSuccess } from 'exceptionally/assert'

type Json = JsonArray | JsonObject | JsonPrimitive

type JsonArray = Json[]

type JsonObject = {
[K in string]?: Json
}

type JsonPrimitive = boolean | null | number | string

// --------------------------------------------------------------------------------------------------------------------

// define different Exception classes for all kind of exceptions
// NOTE: somehow TypeScript can't distinguish between different Classes that derive from `Error`.
// As a workaround we can set a property inside that class to make inference work again.
class NetworkException extends Error {
// @ts-ignore the Playground does not persist the `noUnusedLocals` config
readonly #id = Symbol('NetworkException')
}
class DecodeJsonException extends Error {
// @ts-ignore the Playground does not persist the `noUnusedLocals` config
readonly #id = Symbol('DecodeJsonException')
}
class HttpException extends Error {
// @ts-ignore the Playground does not persist the `noUnusedLocals` config
readonly #id = Symbol('HttpException')
}
class EmptyDatasetException extends Error {
// @ts-ignore the Playground does not persist the `noUnusedLocals` config
readonly #id = Symbol('EmptyDatasetException')
}

// when fetching data, a few things can happen.. when something fails, it is good to know what has triggered the issue
const fetchData = async <ReturnType extends Json>(endpoint: string) => {
const fetchResult = await fetch(endpoint)
.catch(e => exception(new NetworkException(e))) // the server could be unavailable ...
if (isExceptionallyResult(fetchResult)) return fetchResult // pass forward exception

// ... the request could fail with a statuscode 4xx or 5xx ...
if (!fetchResult.ok) return exception(new HttpException(`${fetchResult.status}: ${fetchResult.statusText}`))

const dataResult = await fetchResult.json()
.then(data => data as ReturnType)
.catch(e => exception(new DecodeJsonException(e))) // ... or the payload could consist of invalid JSON ...
if (isExceptionallyResult(dataResult)) return dataResult // pass forward exception

if (Array.isArray(dataResult) && !dataResult.length) {
return exception(new EmptyDatasetException()) // ... or maybe the validation of the data could fail ..
}

return success(dataResult) // ... and finally fetching data could also be successful
}

// --------------------------------------------------------------------------------------------------------------------

type User = {
id: string
name: string
}

const getUsers = async () => {
const fetchUsersResult = await fetchData<User[]>('https://some-api.com/users')

// whenever we get back a result, we should check for exceptions first
if (fetchUsersResult.isException) {
const exc = fetchUsersResult()
// handle a certain type of exception individually
if (exc instanceof NetworkException) {
return exception('Could not fetch users. Please try again later.')
}

// we don't really care about the `EmptyDatasetException` here, so we return an empty array
if (exc instanceof EmptyDatasetException) {
return success([])
}

// in all other cases, we return a general error message
return exception('Unexpected error. Please contact the support-team.')
}

// the result can either be successful and contain our users or be of type `EmptyDatasetException`
const users = fetchUsersResult()

// do something with the data
console.info(`successfully fetched ${users.length} users`)

return success(users) // pass forward data
}

// --------------------------------------------------------------------------------------------------------------------

const getAllUsernames = async () => {
const usersResult = await getUsers()
// even after multiple nested function calls we can make sure what all possible outcomes can be
if (usersResult.isException) {
// at the end of our program flow we finally throw the error that e.g. get's displayed to the user
throw new Error(usersResult())
}

// we can make sure that we have catch'ed all exceptions
// TypeScript will let you know if you forget to handle an exception
// try to remove the line that throws the Error above and you'll see an error here
guardSuccess(usersResult) // only type-safety

// if you don't trust TypeScript ton warn you about unhandled exceptions, you can use the following line
// when this line get's executed on runtime, it will throw an error
assertSuccess(usersResult) // type-safety with additional runtime-safety

// at the end we return the data without wrapping it, so it can be consumed in an usual way
return usersResult().map(({ name }) => name)
}

// --------------------------------------------------------------------------------------------------------------------

const run = async () => {
const usernames = await getAllUsernames()
console.info(usernames)
}

run()
60 changes: 60 additions & 0 deletions examples/input-validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// ! NOTE: You need to clone this repository locally and open it in your IDE (VS Code) to see typesafety in action.

import { exception, success } from 'exceptionally'

// --------------------------------------------------------------------------------------------------------------------

const validate = <Data>(data: Data, validator: (data: Data) => true | string) => {
const isOk = validator(data)
if (isOk === true) return success(data)

return exception(isOk)
}

// --------------------------------------------------------------------------------------------------------------------

type Post = {
author: string
title: string
content: string
}

const postValidator = (post: Post) => {
// return meaningful error messages when something is invalid
if (!post.author) return '"author" missing'

if (!post.title) return '"title" missing'
if (post.title.length > 120) return '"title" must be shorter than 120 characters'

if (!post.content) return '"content" missing'
if (post.title.length < 500) return '"content" must contain at least 500 characters'

// return `true` when validation passes
return true
}

const savePost = async (post: Post) => {
const validationResult = validate(post, postValidator)
if (validationResult.isException) return validationResult // pass forward exception

// saving the data when validation has passed
const id = 1 // await savePostToDatabase(post)

return success(id)
}

// --------------------------------------------------------------------------------------------------------------------

export const run = async () => {
const savePostResult = await savePost({
author: 'John Doe',
title: 'Error handling should be easier',
content: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Amet, qui.',
})

if (savePostResult.isException) throw new Error(savePostResult())

console.info(`new post saved: ${savePostResult()}`)
}

run()
4 changes: 3 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"paths": {
"exceptionally": ["./src/index.ts"]
"exceptionally": ["./src/index.ts"],
"exceptionally/assert": ["./src/assert/index.ts"],
"exceptionally/utils": ["./src/utils/index.ts"]
}
},
"include": [
Expand Down

0 comments on commit 77d431f

Please sign in to comment.