javascriptangulartypescriptfunctional-programmingfp-ts

How to pipe and chain an operation that uses multiple `Eithers` and `Promises` from `fp-ts`


i'm new on fp-ts, i'm trying to create a functional-like method that:

  1. Parse a bearer token
  2. Check if the user is valid using the parsed token
import { Request } from 'express';
import { either } from 'fp-ts';
import { pipe } from 'fp-ts/lib/function';

// * Parse the token
declare const parseRawToken: (rawToken: string | undefined) => either.Either<Error, string>;

// * Interface of the validate method
type MakeIsRequestAuthenticated = (
  validateUserWithToken: (data: string) => Promise<either.Either<Error, void>>,
) => (request: Request) => Promise<either.Either<Error, void>>;

I want to chain these validations inside a pipe, so I tried to implement the validation by:

export const makeIsRequestAuthenticated: MakeIsRequestAuthenticated = validateUserWithToken => {
  // * Validate the request
  return async request => {
    return pipe(
      parseRawToken(request.header('Authorization')),
      either.chain(validateUserWithToken)
      );
  };
};

but it gives my the following error:

Argument of type '(data: string) => Promise<Either<Error, void>>' is not assignable to parameter of type '(a: string) => Either<Error, void>'.
  Type 'Promise<Either<Error, void>>' is not assignable to type 'Either<Error, void>'.ts(2345)

i had try replace the Promise by a TaskEither and some other solutions but none of them worked

I want to use the chain or may other some method to be able to execute all these operations inside the pipe


Solution

  • Hope this can help:

    import { Request } from 'express';
    import { pipe } from 'fp-ts/lib/function';
    import * as TE from "fp-ts/TaskEither";
    import * as E from "fp-ts/Either";
    import { assert } from 'console';
    
    // * Parse the token (dummy implementation)
    const parseRawToken = (rawToken: string | undefined) =>
        rawToken ? E.right(rawToken) : E.left(new Error("Invalid token"));
    
    // * Interface of the validate method (changed return type to TastEither<Error, Request>)
    type MakeIsRequestAuthenticated = (
        validateUserWithToken: (data: string) => TE.TaskEither<Error, void>,
    ) => (request: Request) => TE.TaskEither<Error, Request>;
    
    //Actual implementation
    export const makeIsRequestAuthenticated: MakeIsRequestAuthenticated =
        validateUser => (request: Request) =>
            pipe(
                request.header("Authorization"), //returns string | undefined
                parseRawToken, //returns  Either<Error, string>
                TE.fromEither, //To TaskEither
                TE.chain(validateUser), //validate token 
                TE.map(() => request) //If no errors return request
            )
    
    //Mock request
    const mockRequest = {
        header: (name: string) => "fake token",
        body: {
            userName: "MsFake",
        }
    } as Request;
    
    //Dummy implementations for tokenValidators: sucess and fail
    const mockValidToken: (token: string) => TE.TaskEither<Error, void> =
        (token: string) => TE.right(void 0);
    const mockInValidToken: (token: string) => TE.TaskEither<Error, void> =
        (token: string) => TE.left(new Error("Token not valid"));
    
    //Test
    const fail = makeIsRequestAuthenticated(mockInValidToken)(mockRequest);
    const sucess = makeIsRequestAuthenticated(mockValidToken)(mockRequest);
    
    sucess().then(d => assert(d._tag === "Right"));
    fail().then(d => assert(d._tag === "Left"));
    
    

    Now the method MakeIsRequestAuthenticated returns a TaskEither<Error, Request> and you can chain it with any method taking a Request as a parameter. From now on you may deal with taskEither instead of promises, but it shouldn't be a problem.