Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: API for assertion messages #1

Open
MKRhere opened this issue Apr 15, 2020 · 3 comments
Open

Proposal: API for assertion messages #1

MKRhere opened this issue Apr 15, 2020 · 3 comments
Labels
enhancement New feature or request

Comments

@MKRhere
Copy link
Member

MKRhere commented Apr 15, 2020

As we know, runtype only tells you if an type-check passed or not, and doesn't tell why it failed. This proposal outlines the new API for validation messages.

Description

Currently, Predicate is a function that takes x and guards it against a type. This proposal recommends the addition of a validate method to Predicate which returns the following type:

{ ok: true, value: T } | { ok: false, error: ValidationError }

Where ValidationError extends Error with the properties expected and actual describing the corresponding types as string to produce a nicer error; and by default .message describing a generated error.

Example:

const Message = struct({ from: string, to: string, content: string });

// elsewhere
const res = Message.validate(msg);

if (res.ok) {
  // use msg
} else {
  // res.error
}

Downsides:

  • res.ok doesn't actually guard msg, and you get no static types like you normally would with the type guard.
@wojpawlik wojpawlik added the enhancement New feature or request label Apr 15, 2020
@wojpawlik

This comment has been minimized.

@trgwii
Copy link
Collaborator

trgwii commented Apr 20, 2020

type AnyFn = (...args: any[]) => any;

interface Cata<Ok extends AnyFn, Err extends AnyFn> {
	Ok: Ok,
	Err: Err
}

class Ok<Value> {
	constructor(private value: Value) {}
	cata<
		Ok extends (value: Value) => any,
		Err extends AnyFn
	>(x: Cata<Ok, Err>): ReturnType<Ok> {
		return x.Ok(this.value);
	}
}

class Err<Error> {
	constructor(private error: Error) {}
	cata<
		Ok extends AnyFn,
		Err extends (error: Error) => any
	>(x: Cata<Ok, Err>): ReturnType<Err> {
		return x.Err(this.error);
	}
}

export class Result<Value, Error> {
	static Ok<Value, Error = never>(value: Value) {
		return new Result<Value, Error>(new Ok(value));
	}
	static Err<Error, Value = never>(error: Error) {
		return new Result<Value, Error>(new Err(error));
	}
	constructor(private result: Ok<Value> | Err<Error>) {}
	cata<
		Ok extends (value: Value) => any,
		Err extends (error: Error) => any
	>(x: Cata<Ok, Err>): ReturnType<Ok> | ReturnType<Err> {
		return (this.result.cata as AnyFn)(x);
	}
	map<
		NextValue
	>(mapper: (value: Value) => NextValue): Result<NextValue, Error> {
		return this.bimap<NextValue, Error>(mapper, <T>(x: T) => x);
	}
	mapErr<
		NextError
	>(mapper: (error: Error) => NextError): Result<Value, NextError> {
		return this.bimap<Value, NextError>(<T>(x: T) => x, mapper);
	}
	bimap<
		NextValue,
		NextError
	>(
		mapValue: (value: Value) => NextValue,
		mapError: (error: Error) => NextError
	): Result<NextValue, NextError> {
		return new Result((this.result.cata as AnyFn)({
			Ok: (value: Value) => new Ok(mapValue(value)),
			Err: (error: Error) => new Err(mapError(error))
		}));
	}
}

Here's an alternative implementation of Result

@wojpawlik
Copy link
Collaborator

With @trgwii's suggestions:

type AnyFn = (x: never) => unknown;

interface Cata<OkFn extends AnyFn, ErrFn extends AnyFn> {
	Ok: OkFn
	Err: ErrFn
}

type CataOk<OkFn extends AnyFn> = Cata<OkFn, AnyFn>;
type CataErr<ErrFn extends AnyFn> = Cata<AnyFn, ErrFn>;

interface BaseResult<T, E> {
	readonly ok: boolean
	cata<Value, Error>(cata: Cata<(x: T) => Value, (x: E) => Error>): Value | Error
}

export namespace Result {
	export class Ok<T> implements BaseResult<T, never> {
		readonly ok = true as const;
		constructor(readonly value: T) { }
		cata<Value>(cata: CataOk<(value: T) => Value>) {
			return cata.Ok(this.value);
		}
	}
	export class Err<E> implements BaseResult<never, E> {
		readonly ok = false as const;
		constructor(readonly error: E) { }
		cata<Error>(cata: CataErr<(error: E) => Error>) {
			return cata.Err(this.error);
		}
	}
}

export type Result<T, E> = BaseResult<T, E> & (Result.Ok<T> | Result.Err<E>);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants