Skip to content

Commit

Permalink
Update to 0.2.0 (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
faramozzayw authored Jan 11, 2021
1 parent 833b40a commit 364b611
Show file tree
Hide file tree
Showing 11 changed files with 429 additions and 19 deletions.
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## 0.2.0 (January 11, 2021)

### `Option`

- add new methods:
- static `makeDefault`
- `unsafe_insert`
- `okOr`
- `okOrElse`
- `match`
- override `toString`

### `Result`

- add new features:

- static `makeDefault`
- `expect`
- `expectErr`
- `match`
- override `toString`

- update docs

## 0.1.0 (December 30, 2020)

Initial release
61 changes: 61 additions & 0 deletions __test__/option.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { None, Some, Option, Ok, Err, Result } from "./../src";

describe("Option", () => {
it("toString", () => {
expect(None().toString()).toEqual("None");

expect(Some(5).toString()).toEqual("Some(5)");
expect(Some(Some(5)).toString()).toEqual("Some(Some(5))");

expect(Some({ code: 15 }).toString()).toEqual("Some([object Object])");

expect(`${None()}`).toEqual("None");
expect(`${Some(5)}`).toEqual("Some(5)");
});

it("is_some", () => {
const some = Some(5);
expect(some.isSome()).toBeTruthy();
Expand All @@ -25,6 +37,38 @@ describe("Option", () => {
expect(some.expect("some")).toEqual(5);
});

it("match", () => {
expect(
Some("ok").match({
some: (some) => some.length,
none: () => "error",
}),
).toEqual(2);

expect(
Some({
text: "Lorem lorem",
user: "@user",
}).match({
some: (some) => some.user,
}),
).toEqual("@user");

expect(
None().match({
some: (_) => "some",
none: () => "Something bad wrong",
}),
).toEqual("Something bad wrong");

expect(
None().match({
some: (_) => 200,
none: () => 404,
}),
).toEqual(404);
});

it("unwrap on `Some`", () => {
const [some1, some2, some3] = [
Some(5),
Expand Down Expand Up @@ -77,6 +121,11 @@ describe("Option", () => {
expect(none.unwrapOrElse(() => "NONE")).toEqual("NONE");
});

it("unsafe_insert", () => {
expect(None().unsafe_insert(5)).toEqual(Some(5));
expect(Some(0).unsafe_insert(65)).toEqual(Some(65));
});

it("`map` on `Some`", () => {
const some = Some({ isSome: true });

Expand Down Expand Up @@ -119,6 +168,18 @@ describe("Option", () => {
expect(mappedNone).toEqual(500);
});

it("okOr", () => {
expect(Some(5).okOr("Failed")).toEqual(Ok(5));
expect(None().okOr("Failed")).toEqual(Err("Failed"));
});

it("okOrElse", () => {
const failFn = () => "Failed";

expect(Some(5).okOrElse(failFn)).toEqual(Ok(5));
expect(None().okOrElse(failFn)).toEqual(Err("Failed"));
});

it("andThen", () => {
const some = Some(25);
const sq = (x: number) => Some(x * x);
Expand Down
71 changes: 71 additions & 0 deletions __test__/result.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { Err, Ok, Result, Some, None, Option } from "./../src";

describe("Result", () => {
it("toString", () => {
expect(Err(5).toString()).toEqual(`Err(5)`);
expect(Err(Err("Error")).toString()).toEqual(`Err(Err(Error))`);

expect(Ok(5).toString()).toEqual("Ok(5)");
expect(Ok(Ok(5)).toString()).toEqual("Ok(Ok(5))");

expect(Err({ code: 15 }).toString()).toEqual("Err([object Object])");
});

it("isOk", () => {
const ok = Ok("ok");
expect(ok.isOk()).toBeTruthy();
Expand All @@ -17,6 +27,28 @@ describe("Result", () => {
expect(ok.isErr()).toBeFalsy();
});

it("expect", () => {
expect(Ok("ok").expect("Testing expect")).toEqual("ok");

try {
Err("fail result").expect("Testing expect");
} catch (e: unknown) {
expect((e as Error).message).toMatch(/Testing expect/gi);
}
});

it("expectErr", () => {
expect(Err("fail result").expectErr("Testing expect")).toEqual(
"fail result",
);

try {
Ok("ok result").expectErr("Testing expect");
} catch (e: unknown) {
expect((e as Error).message).toMatch(/Testing expect/gi);
}
});

it("ok", () => {
const ok = Ok("ok");
expect(ok.ok()).toEqual(Some("ok"));
Expand All @@ -33,6 +65,45 @@ describe("Result", () => {
expect(err.err()).toEqual(Some("err"));
});

it("match", () => {
expect(
Ok("ok").match({
ok: (some) => some.length,
err: () => "error",
}),
).toEqual(2);

expect(
Ok({
text: "Lorem lorem",
user: "@user",
}).match({
ok: (ok) => ok.user,
}),
).toEqual("@user");

expect(
Err("error").match({
ok: (_) => "ok",
err: (_) => "Something bad wrong",
}),
).toEqual("Something bad wrong");

expect(
Err({
code: 404,
}).match({
err: (err) => err.code,
}),
).toEqual(404);

expect(
Ok("nice").match({
err: (_) => "not nice",
}),
).toBeNull();
});

it("unwrap", () => {
expect(Ok(5).unwrap()).toEqual(5);
expect(Ok([1, 3, 4]).unwrap()).toEqual([1, 3, 4]);
Expand Down
143 changes: 138 additions & 5 deletions src/Option/Option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,26 @@ import { OptionType } from "../types";
import { Some, None } from "./values";

import { Ok, Err, Result } from "../Result";
import { Options } from "prettier";
import { unwrapFailed } from "../utils";

interface OptionMatch<T, ReturnSome, ReturnNone> {
some?: (some: T) => ReturnSome;
none?: () => ReturnNone;
}

/**
* Type `Option` represents an optional value: every `Option` is either `Some` and contains a value, or `None`, and does not.
* `Option` types are very common in Rust code, as they have a number of uses:
*
* - Initial values
* - Return value for otherwise reporting simple errors, where `None` is returned on error
* - Optional struct fields
* - Optional function arguments
* - Nullable values
* - Swapping things out of difficult situations
*
* @category Option
*/
export class Option<T> {
/** @ignore */
private data: OptionType<T>;
Expand All @@ -20,9 +38,44 @@ export class Option<T> {
return clone(this.data);
}

/** @ignore */
private unwrapFailed(msg: string, error: T): never {
throw new Error(`${msg}: ${JSON.stringify(error)}`);
/**
* Returns the "default value" for a Option<T> => `None`.
*/
public static makeDefault() {
return None();
}

/**
* Pattern match to retrieve the value
*
* @template Some - return type of the `Some` branch
* @template None - return type of the `None` branch
*
* ### Example
* ```ts
* expect(Some("ok").match({
* some: some => some.length,
* none: () => "error",
* })).toEqual(2);
*
* expect(None().match({
* some: _ => "some",
* none: () => "Something bad wrong",
* })).toEqual("Something bad wrong")
*
* expect(None().match({
* some: _ => 200,
* none: () => 404,
* })).toEqual(404)
* ```
*/
public match<Some, None>({
some,
none,
}: OptionMatch<T, Some, None>): Some | None | null {
if (this.isNone()) return none ? none() : null;

return some ? some(this.clone()) : null;
}

/** Returns `true` if the option is a `Some` value. */
Expand Down Expand Up @@ -50,6 +103,25 @@ export class Option<T> {
return this.clone();
}

/**
* Inserts value into the option
*
* If the option already contains a value, the old value is dropped.
*
* @unsafe
*
* ### Example
* ```ts
* expect(None().unsafe_insert(5)).toEqual(Some(5));
* expect(Some(0).unsafe_insert(65)).toEqual(Some(65));
* ```
*/
public unsafe_insert(val: T): Option<T> {
this.data = val;

return this;
}

/**
* Returns the contained `Some` value, consuming the self value.
*
Expand Down Expand Up @@ -163,6 +235,45 @@ export class Option<T> {
return fn(this.clone());
}

/**
* Transforms the `Option<T>` into a `Result<T, E>`, mapping `Some(v)` to `Ok(v)` and `None` to `Err(err)`.
*
* Arguments passed to `okOr` are eagerly evaluated; if you are passing the result of a function
* call, it is recommended to use `okOrElse`, which is lazily evaluated.
*
* ### Example
* ```ts
* expect(Some(5).okOr("Failed")).toEqual(Ok(5));
* expect(None().okOr("Failed")).toEqual(Err("Failed"));
* ```
*/
public okOr<E>(err: E): Result<T, E> {
if (this.isSome()) {
return Ok(this.clone());
}

return Err(err);
}

/**
* Transforms the `Option<T>` into a `Result<T, E>`, mapping `Some(v)` to `Ok(v)` and `None` to `Err(err())`.
*
* ### Example
* ```ts
* const failFn = () => "Failed";
*
* expect(Some(5).okOrElse(failFn)).toEqual(Ok(5));
* expect(None().okOrElse(failFn)).toEqual(Err("Failed"));
* ```
*/
public okOrElse<E, F extends () => E>(fn: F) {
if (this.isNone()) {
return Err(fn());
}

return Ok(this.clone());
}

/**
* Returns None if the option is `None`, otherwise calls f with the wrapped
* value and returns the result.
Expand Down Expand Up @@ -283,7 +394,7 @@ export class Option<T> {
const innerError = this.data.unwrap();
return Err<E>(innerError);
} else {
this.unwrapFailed(
unwrapFailed(
"called `Option::transpose()` on an `Some` value where `self` is not an `Result`",
this.data,
);
Expand Down Expand Up @@ -315,6 +426,28 @@ export class Option<T> {
return Some(this.data);
}

/**
* Returns a string representation of an object.
*
* @override
*
* ### Example
* ```ts
* expect(None().toString()).toEqual("None");
*
* expect(Some(5).toString()).toEqual("Some(5)");
* expect(Some(Some(5)).toString()).toEqual("Some(Some(5))");
*
* // BUT
* expect(Some({ code: 15 }).toString()).toEqual("Some([object Object])");
* ```
*/
public toString() {
if (this.isNone()) return "None";

return `Some(${this.data.toString()})`;
}

/** Returns `None` if the option is `None`, otherwise returns `optb`. */
private and<U>(optb: Option<U>): Option<U> {
if (this.isNone()) return None();
Expand Down
Loading

1 comment on commit 364b611

@vercel
Copy link

@vercel vercel bot commented on 364b611 Jan 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.