Navigating Assertions within Functional Code

When working with functional programming, I often encounter situations where my knowledge exceeds the type system of the language. Take for example this TypeScript scenario where I parse a UUID and display its embedded fields to the user. The program initially validates the input using io-ts to ensure it adheres to the UUID specification. However, after splitting the input, the program struggles to confirm that the split UUID consists of five parts, leading me to use an fp-ts Option. To overcome this, I resort to an assert false from getOrElse in order to eliminate the Option. Are there more idiomatic ways in functional programming to handle assertions? Notifying the end user about this error doesn't seem useful as it signifies an issue in the assumptions made by the programmer rather than something the end user can resolve.

#!/usr/bin/env ts-node

import { append, intersperse, map, prepend } from 'fp-ts/lib/Array';
import { isRight } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/function';
import { IO } from 'fp-ts/lib/IO';
import { fromPredicate, getOrElse } from 'fp-ts/lib/Option';
import { empty } from 'fp-ts/lib/string';
import * as t from 'io-ts';

type Tuple5<A, B, C, D, E> = [A, B, C, D, E];
const length = 5;
const fromArray = fromPredicate(
  <A>(as: Array<A>): as is Tuple5<A, A, A, A, A> => as.length === length,
);
const Tuple5_ = {
  length,
  fromArray,
};

const separator = '-';

const hex = (n: number): string => `[A-Fa-f0-9]{${n}}`;
const fields: Tuple5<number, number, number, number, number> = [8, 4, 4, 4, 12];
const regexp = pipe(
  fields,
  map(hex),
  intersperse(separator),
  prepend('^'),
  append('$'),
).join(empty);

export type Uuid = t.Branded<string, UuidBrand>;
export type UuidC = t.BrandC<t.StringC, UuidBrand>;
export const Uuid: UuidC = t.brand(
  t.string,
  (x): x is t.Branded<string, UuidBrand> => x.match(RegExp(regexp)) !== null,
  'Uuid',
);
export type UuidBrand = {
  readonly Uuid: unique symbol;
};

export type TimeLow = string;
export type TimeMid = string;
export type TimeHiAndVersion = string;
export type ClockSeq = string;
export type Node = string;

export type Groups = Tuple5<TimeLow, TimeMid, TimeHiAndVersion, ClockSeq, Node>;

export const groups = (uuid: Uuid): Groups =>
  pipe(
    uuid.split(separator),
    Tuple5_.fromArray,
    getOrElse((): Groups => {
      // eslint-disable-next-line
      throw new Error('Assert false! Uuid invalid despite validation.');
    }),
  );

const main: IO<void> = () => {
  const [_node, _script, input] = process.argv;
  const result = Uuid.decode(input);
  if (isRight(result)) {
    const uuid: Uuid = result.right;
    const [timeLow, timeMid, timeHiAndVersion, clockSeq, node] = groups(uuid);
    console.log({ timeLow, timeMid, timeHiAndVersion, clockSeq, node });
  } else {
    console.error('Invalid input!');
  }
};

main();

Answer №1

Code parsing is better than validation.

type UuidPart1 = string & { readonly UuidPart1: unique symbol }
type UuidPart2 = string & { readonly UuidPart2: unique symbol }
type UuidPart3 = string & { readonly UuidPart3: unique symbol }
type UuidPart4 = string & { readonly UuidPart4: unique symbol }
type UuidPart5 = string & { readonly UuidPart5: unique symbol }
type SplitUuid = [UuidPart1, UuidPart2, UuidPart3, UuidPart4, UuidPart5]

declare const parseUuid: (a: Uuid) => Option<SplitUuid>

declare const recombineUuid: (a: SplitUuid) => Uuid

The function above splits the Uuid into 5 parts and validates each part to match the format of a Uuid. If all parts are valid, it returns Some containing the SplitUuid type (which is a 5-tuple). Otherwise, it returns None.

In your code, use SplitUuid when dealing with split Uuids instead of regular Uuids.

If no splitting is needed, convert back to Uuid without loss and provide the function with a Uuid parameter.

No need for manual validation anymore. Just ensure your code handles the correct types and eliminate runtime validation steps.

If you have a scenario where either a Uuid or SplitUuid can be passed, utilize a type guard:

type AnyUuid = Uuid | SplitUuid
function isSplitUuid(a: AnyUuid): a is SplitUuid {
  return typeof a === 'object'
}

declare const logSplitUuid = (a: SplitUuid) => console.log('this is split!', a)

const example: (a: AnyUuid) => void = a => pipe(
  O.fromPredicate(isSplitUuid),
  O.getOrElse(() => parseUuid(a)),
  logSplitUuid
)

This function can handle both split and non-split Uuids, automatically splitting them if necessary and logging the split result. Fully typed and safe.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Error encountered in the auth service Observable for the Metronic Angular 8 login page. The issue persists when utilizing the same source code in the default component, where it functions

Issue with Observable in Metronic Theme Angular 8 I have encountered an error while using the auth service in another component of the Metronic Theme for Angular 8. The same service works without any errors in the default logic component. this.auth ...

Attempting to render the application results in an error message stating: "Actions must be plain objects. Custom middleware should be used for asynchronous actions."

I am experiencing an issue while testing my vite + typescript + redux application to render the App component using vitest for testing. I am utilizing redux@toolkit and encountering a problem when trying to implement async thunk in the app component: Error ...

The process of combining objects is greatly influenced by the specific typed values allowed in a function

In the process of refactoring some code, I encountered a scenario where a function returns different types of content based on the key passed in. Now, I am looking to create a function signature that can accept various types of content depending on the pro ...

I'm having trouble locating a declaration file for the module 'vue-prism-component'

Currently utilizing Vue 3 (Composition API), Vite, and Typescript but encountering a missing module issue with vue-prism-component. <script lang="ts" setup> import 'prismjs' import 'prismjs/themes/prism-tomorrow.css' imp ...

Utilizing "regression-js" within an Angular 2 project: A comprehensive guide

I have integrated the Regression npm module https://www.npmjs.com/package/regression into my Angular 2 application to utilize the Linear Regression functionality. I installed the package using "npm install regression". However, I encountered errors while a ...

Implementing a GIF loader in your webpack configuration for a Typescript/React/Next.js application

Upon inserting a .gif file in my Typescript React app, an error message has surfaced. ./src/gif/moving.gif 1:6 Module parse failed: Unexpected token (1:6) You may need an appropriate loader to handle this file type, currently no loaders are configured to p ...

The Lenis smooth scrolling feature (GSAP) is not functioning properly

I have encountered an issue with the smooth scrolling feature of gsap causing a delay on my website. This problem is only resolved when I manually go into the browser settings and disable smooth scrolling by navigating to chrome://flags/#smooth-scrolling ...

Ava tests hitting a snag with TypeScript ("Oops! Unexpected identifier found")

Currently delving into the realms of TypeScript, I decided to venture into creating a TypeScript React application using create-react-app. This application involves a separate TypeScript file called logic.ts, which in turn imports a JSON file. import past ...

Utilize decorators for enhancing interface properties with metadata information

Can decorators be utilized to add custom information to specific properties within an interface? An example can help clarify this: Interface for App state: export interface AppState { @persist userData: UserData, @persist selectedCompany: UserCo ...

Instructions for developing a tailored database solution State Storage Adapter for the Microsoft Bot Framework

As I work on creating a custom adapter for integrating a specific database with the Microsoft Bot framework, my approach is to develop something similar to the cosmosDBPartitionedStorage class within the bot framework. From my analysis, it seems that ther ...

Having trouble getting the React form validation to work using Material UI TextField and TypeScript

I'm having trouble implementing validation on a "sign up" page using the MUI library in React with TypeScript. I've added the "required" attribute to each TextField tag, but the validation doesn't seem to be working upon submission. I'v ...

What happens when a typed Array in Typescript has an undefined property?

I've encountered an issue with a seemingly simple problem that's causing me quite the headache. The code snippet in question is provided below: interface IFoo{ ReturnFirstBarObject1(): string; FillBarArray(array: Array<Bar>): void; } ...

Can a universal type be designed for application across various types?

I've got this function: function stackPlayer(stack){ } The stack parameter can have one of the following forms only: a function that takes req, res, and next as arguments. a function that takes req, res, and next as arguments, and returns a functio ...

Utilizing React with Typescript to create JSX text translation files

My task involves translating text stored in a file... ///translations.txt const TEXT: { [x: string]: { [y: string]: string } } = { en: { joinNow: <React.Fragment>Join <b>Now<b/></React.Fragment>, signUp: <React.Fragmen ...

Issues arise when attempting to use recursive types in combination with optional properties

In my code, I've created a type definition that allows me to traverse an object using an array of strings or indices representing the keys of the object or nested arrays: export type PredicateFunction<ArrayType> = (array: ArrayType, index?: numb ...

Generating an iFrame in Angular with real-time data from Observable sources

I am looking to integrate multiple YouTube videos into my Angular application using iframes. The video URLs are stored in a database, and I need to fetch the 3 most recent ones on each visit. To achieve this, the "youtube" component makes a request to a ...

What is the reason behind the TypeScript HttpClient attempting to interpret a plain text string as JSON data?

When utilizing Angular TypeScript, I have implemented the following approach to send a POST request. This request requires a string parameter and returns a string as the response. sendPostRequest(postData: string): Observable<string> { let header: ...

Challenges of Mocking Functions in Different Files When Testing with Jest

I'm currently facing a challenge with writing tests for the CreateVendor function using Jest and Supertest. My issue lies in how to effectively mock the dependencies (generateSalt, hashPassword) in order to correctly test the behavior of the function. ...

retrieve a nested object's property using a dynamic string

Here is the object model I am working with: export class FrcCapacity { constructor( public id?: number, public frcId?: number, public capGroupId?: number, public capGroup?: CapGroup, public salesProductId?: number, public p1?: num ...

Ensuring Type Safety with Setter-Only Properties in TypeScript

I found it quite surprising that this piece of code compiles without any issues in TypeScript, but throws an error at runtime: class Y { set writeOnlyProp(value: number) { // perform some actions here } } const y = new Y() // runtime error ...