/** * Represents strings that are UUIDs. * * This is heavily inspired by the `validator.js` module * [`isUUID`](https://github.com/validatorjs/validator.js/blob/master/src/lib/isUUID.js). * * @since 0.0.1 */ import { Kind, Kind2, URIS, URIS2, HKT2 } from 'fp-ts/HKT' import * as D from 'io-ts/Decoder' import * as Enc from 'io-ts/Encoder' import * as Eq_ from 'fp-ts/Eq' import * as G from 'io-ts/Guard' import * as Str from 'fp-ts/string' import * as TD from 'io-ts/TaskDecoder' import * as t from 'io-ts/Type' import { pipe } from 'fp-ts/function' import * as fc from 'fast-check' import * as Arb from '../internal/ArbitraryBase' /** * @since 0.0.1 * @internal */ interface UUIDBrand { readonly UUID: unique symbol } /** * See: https://github.com/validatorjs/validator.js/blob/master/src/lib/isUUID.js * * @since 0.0.1 * @internal */ const uuidRegex = { 1: /^[0-9A-F]{8}-[0-9A-F]{4}-1[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i, 2: /^[0-9A-F]{8}-[0-9A-F]{4}-2[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i, 3: /^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i, 4: /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, 5: /^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, all: /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i, } /** * Represents strings that are UUIDs. * * @since 0.0.1 * @category Model */ export type UUID = string & UUIDBrand /** * @since 0.0.1 * @category Model */ export type UUIDSchemableOptions = { version: 1 | 2 | 3 | 4 | 5 | 'all' } /** * @since 0.0.1 * @category Model */ export type SchemableParams = (options: UUIDSchemableOptions) => HKT2 /** * @since 0.0.1 * @category Model */ export type SchemableParams1 = ( options: UUIDSchemableOptions ) => Kind /** * @since 0.0.3 * @category Model */ export type SchemableParams2 = ( options: UUIDSchemableOptions ) => Kind2 /** * @since 0.0.1 * @category Model */ export type SchemableParams2C = ( options: UUIDSchemableOptions ) => Kind2 /** * @since 0.0.1 * @category Refinements */ export const isUUID = (options: UUIDSchemableOptions) => (s: string): s is UUID => typeof s === 'string' && uuidRegex[options.version].test(s) /** * @since 0.0.1 * @category Instances */ export const Decoder: SchemableParams2C = options => pipe(D.string, D.refine(isUUID(options), 'UUID')) /** * @since 0.0.1 * @category Instances */ export const Eq: SchemableParams1 = () => Str.Eq /** * @since 0.0.1 * @category Instances */ export const Guard: SchemableParams1 = options => pipe(G.string, G.refine(isUUID(options))) /** * @since 0.0.1 * @category Instances */ export const TaskDecoder: SchemableParams2C = options => pipe(TD.string, TD.refine(isUUID(options), 'UUID')) /** * @since 0.0.1 * @category Instances */ export const Type: SchemableParams1 = options => pipe(t.string, t.refine(isUUID(options), 'UUID')) /** * @since 0.0.3 * @category Instances */ export const Encoder: SchemableParams2 = () => Enc.id() /** * @since 0.0.3 * @category Instances */ export const Arbitrary: SchemableParams1 = options => fc.uuid().filter(isUUID(options))