gcanti / io-ts-types

A collection of codecs and combinators for use with io-ts
https://gcanti.github.io/io-ts-types/
MIT License
311 stars 40 forks source link

Help with usage of UUID #125

Closed NicoleRauch closed 4 years ago

NicoleRauch commented 4 years ago

Hello,

I'd like to use UUID in my codebase as I will be working with GUID's. But if I include the const into my structure:

const IOPhasengruppenId = t.interface({
    PhasengruppenId: UUID
});

and generate a type from this:

export type IPhasengruppenId = t.TypeOf<typeof IOPhasengruppenId>;

then I need to be able to generate elements of type UUID in my codebase whenever I construct an IPhasengruppenId. But the provided UUID implementation only seems to support the validation part? So my question is: How can I use UUID while at the same time being able to generate UUIDs in my code?

Thanks for any insights!

mlegenhausen commented 4 years ago

UUID is just a Brand type so it is still a simple string where we enforce a certain structure on that string. The check is just in the typesystem.

So to generate a UUID in your code you can use

const myId = '6e9c5587-a342-4b63-a901-87b31fa2ffa3' as UUID

or if you want to be more save and prevent programming errors you can use decode.

const myId = pipe(
  UUID.decode('6e9c5587-a342-4b63-a901-87b31fa2ffa3'),
  E.getOrElse(errors => { throw new Error(failure(errors).join('\n')) })
)

I normally call this function for conversions in any branded type cast.

NicoleRauch commented 4 years ago

Oh thanks! It was not clear to me that decode() actually creates an UUID - I thought it just validates. This clarifies things a lot!

Now to the technical details of your code snippet:

Sorry for these beginner questions; while I know functional programming in general, the io-fs ecosystem is new to me, so I'm struggling a lot with issues like these...

mlegenhausen commented 4 years ago

Sorry when working with fp-ts for to long you lose the need for explaining where this stuff is coming from. Here a complete example.

import { pipe } from 'fp-ts/lib/pipeable'
import * as E from 'fp-ts-/lib/Either'
import { UUID } from 'io-ts-types/lib/UUID'
import * as t from 'io-ts'
import { failure } from 'io-ts/lib/PathReporter'

export function cast<I, A>(codec: t.Decoder<I, A>): (value: I) => A {
  return value => pipe(
    value,
    codec.decode,
    E.getOrElse<t.Errors, A>(errors => {
      throw new Error(failure(errors).join('\n'))
    })
  )
}

const myId = cast(UUID)('6e9c5587-a342-4b63-a901-87b31fa2ffa3')

All functions exported from the modules directly are normally curried and data last functions. Thats why you can use them in pipe.

There are some conventions on how you name the imports from certain modules. For example import * as O from 'fp-ts/lib/Option or import * as TE from 'fp-ts/lib/TaskEither' and so on.

If you need further help you should take a look in the FP-Slack and join the #typescript channel there are many fp-ts users.

NicoleRauch commented 4 years ago

Thank you very much, that helps me a lot! Also, I will direct further questions to the FP Slack, thanks for the hint.