Skip to content

Commit

Permalink
feat add optional type
Browse files Browse the repository at this point in the history
  • Loading branch information
lifeiscontent authored Dec 17, 2022
1 parent 9103e88 commit 269bd19
Show file tree
Hide file tree
Showing 19 changed files with 234 additions and 0 deletions.
5 changes: 5 additions & 0 deletions dtslint/ts3.5/Encoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export type OfTestOutput = E.OutputOf<typeof OfTest> // $ExpectType { a: string;
//
E.nullable(NumberToString) // $ExpectType Encoder<string | null, number | null>

//
// optional
//
E.optional(NumberToString) // $ExpectType Encoder<string | undefined, number | undefined>

//
// struct
//
Expand Down
6 changes: 6 additions & 0 deletions dtslint/ts3.5/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ make((S) => S.boolean) // $ExpectType Schema<boolean>

make((S) => S.nullable(S.string)) // $ExpectType Schema<string | null>

//
// optional
//

make((S) => S.optional(S.string)) // $ExpectType Schema<string | undefined>

//
// struct
//
Expand Down
8 changes: 8 additions & 0 deletions src/Codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,14 @@ export function nullable<I, O, A>(or: Codec<I, O, A>): Codec<null | I, null | O,
return make(D.nullable(or), E.nullable(or))
}

/**
* @category combinators
* @since 2.3.0
*/
export function optional<I, O, A>(or: Codec<I, O, A>): Codec<undefined | I, undefined | O, undefined | A> {
return make(D.optional(or), E.optional(or))
}

/**
* @category combinators
* @since 2.2.15
Expand Down
9 changes: 9 additions & 0 deletions src/Decoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,14 @@ export const nullable: <I, A>(or: Decoder<I, A>) => Decoder<null | I, null | A>

/**
* @category combinators
* @since 2.3.0
*/
export const optional: <I, A>(or: Decoder<I, A>) => Decoder<undefined | I, undefined | A> =
/*#__PURE__*/
K.optional(M)((u, e) => FS.concat(FS.of(DE.member(0, error(u, 'undefined'))), FS.of(DE.member(1, e))))

/**
* @category combinators
* @since 2.2.15
*/
export const fromStruct = <P extends Record<string, Decoder<any, any>>>(
Expand Down Expand Up @@ -488,6 +496,7 @@ export const Schemable: S.Schemable2C<URI, unknown> = {
number,
boolean,
nullable,
optional,
type,
struct,
partial,
Expand Down
10 changes: 10 additions & 0 deletions src/Encoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ export function nullable<O, A>(or: Encoder<O, A>): Encoder<null | O, null | A> {
}
}

/**
* @category combinators
* @since 2.3.0
*/
export function optional<O, A>(or: Encoder<O, A>): Encoder<undefined | O, undefined | A> {
return {
encode: (a) => (a === undefined ? undefined : or.encode(a))
}
}

/**
* @category combinators
* @since 2.2.15
Expand Down
11 changes: 11 additions & 0 deletions src/Eq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ export function nullable<A>(or: Eq<A>): Eq<null | A> {
}
}

/**
* @category combinators
* @since 2.3.0
*/
export function optional<A>(or: Eq<A>): Eq<undefined | A> {
return {
equals: (x, y) => (x === undefined || y === undefined ? x === y : or.equals(x, y))
}
}

/**
* @category combinators
* @since 2.2.15
Expand Down Expand Up @@ -204,6 +214,7 @@ export const Schemable: Schemable1<E.URI> = {
number,
boolean,
nullable,
optional,
type,
struct,
partial,
Expand Down
9 changes: 9 additions & 0 deletions src/Guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ export const nullable = <I, A extends I>(or: Guard<I, A>): Guard<null | I, null
is: (i): i is null | A => i === null || or.is(i)
})

/**
* @category combinators
* @since 2.3.0
*/
export const optional = <I, A extends I>(or: Guard<I, A>): Guard<undefined | I, undefined | A> => ({
is: (i): i is undefined | A => i === undefined || or.is(i)
})

/**
* @category combinators
* @since 2.2.15
Expand Down Expand Up @@ -325,6 +333,7 @@ export const Schemable: S.Schemable1<URI> = {
number,
boolean,
nullable,
optional,
type,
struct,
partial,
Expand Down
22 changes: 22 additions & 0 deletions src/Kleisli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,28 @@ export function nullable<M extends URIS2, E>(
})
}

/**
* @category combinators
* @since 2.3.0
*/
export function optional<M extends URIS2, E>(
M: Applicative2C<M, E> & Bifunctor2<M>
): <I>(
onError: (i: I, e: E) => E
) => <A>(or: Kleisli<M, I, E, A>) => Kleisli<M, undefined | I, E, undefined | A> {
return <I>(onError: (i: I, e: E) => E) =>
<A>(or: Kleisli<M, I, E, A>): Kleisli<M, undefined | I, E, undefined | A> => ({
decode: (i) =>
i === undefined
? M.of<undefined | A>(undefined)
: M.bimap(
or.decode(i),
(e) => onError(i, e),
(a): A | undefined => a
),
});
}

/**
* @category combinators
* @since 2.2.15
Expand Down
3 changes: 3 additions & 0 deletions src/Schemable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface Schemable<S> {
readonly number: HKT<S, number>
readonly boolean: HKT<S, boolean>
readonly nullable: <A>(or: HKT<S, A>) => HKT<S, null | A>
readonly optional: <A>(or: HKT<S, A>) => HKT<S, undefined | A>
/** @deprecated */
readonly type: <A>(properties: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, { [K in keyof A]: A[K] }>
readonly struct: <A>(properties: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, { [K in keyof A]: A[K] }>
Expand Down Expand Up @@ -55,6 +56,7 @@ export interface Schemable1<S extends URIS> {
readonly number: Kind<S, number>
readonly boolean: Kind<S, boolean>
readonly nullable: <A>(or: Kind<S, A>) => Kind<S, null | A>
readonly optional: <A>(or: Kind<S, A>) => Kind<S, undefined | A>
/** @deprecated */
readonly type: <A>(properties: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, { [K in keyof A]: A[K] }>
readonly struct: <A>(properties: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, { [K in keyof A]: A[K] }>
Expand Down Expand Up @@ -82,6 +84,7 @@ export interface Schemable2C<S extends URIS2, E> {
readonly number: Kind2<S, E, number>
readonly boolean: Kind2<S, E, boolean>
readonly nullable: <A>(or: Kind2<S, E, A>) => Kind2<S, E, null | A>
readonly optional: <A>(or: Kind2<S, E, A>) => Kind2<S, E, undefined | A>
/** @deprecated */
readonly type: <A>(properties: { [K in keyof A]: Kind2<S, E, A[K]> }) => Kind2<S, E, { [K in keyof A]: A[K] }>
readonly struct: <A>(properties: { [K in keyof A]: Kind2<S, E, A[K]> }) => Kind2<S, E, { [K in keyof A]: A[K] }>
Expand Down
9 changes: 9 additions & 0 deletions src/TaskDecoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,14 @@ export const nullable: <I, A>(or: TaskDecoder<I, A>) => TaskDecoder<null | I, nu

/**
* @category combinators
* @since 2.2.7
*/
export const optional: <I, A>(or: TaskDecoder<I, A>) => TaskDecoder<undefined | I, undefined | A> =
/*#__PURE__*/
K.optional(M)((u, e) => FS.concat(FS.of(DE.member(0, error(u, 'undefined'))), FS.of(DE.member(1, e))))

/**
* @category combinators
* @since 2.2.15
*/
export const fromStruct = <P extends Record<string, TaskDecoder<any, any>>>(
Expand Down Expand Up @@ -494,6 +502,7 @@ export const Schemable: S.Schemable2C<URI, unknown> = {
number,
boolean,
nullable,
optional,
type,
struct,
partial,
Expand Down
7 changes: 7 additions & 0 deletions src/Type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ export const refine = <A, B extends A>(refinement: Refinement<A, B>, id: string)
*/
export const nullable = <A>(or: Type<A>): Type<null | A> => t.union([t.null, or])

/**
* @category combinators
* @since 2.3.0
*/
export const optional = <A>(or: Type<A>): Type<undefined | A> => t.union([t.undefined, or])

/**
* @category combinators
* @since 2.2.15
Expand Down Expand Up @@ -206,6 +212,7 @@ export const Schemable: S.Schemable1<URI> = {
number,
boolean,
nullable,
optional,
type,
struct,
partial,
Expand Down
5 changes: 5 additions & 0 deletions test/Arbitrary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ export function nullable<A>(or: Arbitrary<A>): Arbitrary<null | A> {
return fc.oneof(fc.constant(null), or)
}

export function optional<A>(or: Arbitrary<A>): Arbitrary<undefined | A> {
return fc.oneof(fc.constant(undefined), or);
}

export function struct<A>(properties: { [K in keyof A]: Arbitrary<A[K]> }): Arbitrary<A> {
return fc.record(properties)
}
Expand Down Expand Up @@ -125,6 +129,7 @@ export const Schemable: S.Schemable1<URI> & S.WithUnknownContainers1<URI> & S.Wi
number,
boolean,
nullable,
optional,
type: struct,
struct,
partial,
Expand Down
40 changes: 40 additions & 0 deletions test/Codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,46 @@ describe('Codec', () => {
})
})

describe("optional", () => {
describe("decode", () => {
it("should decode a valid input", () => {
const codec = _.optional(codecNumber);
assert.deepStrictEqual(codec.decode(undefined), D.success(undefined));
assert.deepStrictEqual(codec.decode("1"), D.success(1));
});

it("should reject an invalid input", () => {
const codec = _.optional(codecNumber);
assert.deepStrictEqual(
codec.decode(undefined),
E.left(
FS.concat(
FS.of(DE.member(0, FS.of(DE.leaf(undefined, "undefined")))),
FS.of(DE.member(1, FS.of(DE.leaf(undefined, "string"))))
)
)
);
assert.deepStrictEqual(
codec.decode("a"),
E.left(
FS.concat(
FS.of(DE.member(0, FS.of(DE.leaf("a", "undefined")))),
FS.of(DE.member(1, FS.of(DE.leaf("a", "parsable to a number"))))
)
)
);
});
});

describe("encode", () => {
it("should encode a value", () => {
const codec = _.optional(codecNumber);
assert.strictEqual(codec.encode(undefined), undefined);
assert.strictEqual(codec.encode(1), "1");
});
});
});

describe('struct', () => {
describe('decode', () => {
it('should decode a valid input', () => {
Expand Down
30 changes: 30 additions & 0 deletions test/Decoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,36 @@ describe('Decoder', () => {
})
})

describe("optional", () => {
it("should decode a valid input", () => {
const decoder = _.optional(H.decoderNumberFromUnknownString);
assert.deepStrictEqual(decoder.decode(undefined), _.success(undefined));
assert.deepStrictEqual(decoder.decode("1"), _.success(1));
});

it("should reject an invalid input", () => {
const decoder = _.optional(H.decoderNumberFromUnknownString);
assert.deepStrictEqual(
decoder.decode(undefined),
E.left(
FS.concat(
FS.of(DE.member(0, FS.of(DE.leaf(undefined, "undefined")))),
FS.of(DE.member(1, FS.of(DE.leaf(undefined, "string"))))
)
)
);
assert.deepStrictEqual(
decoder.decode("a"),
E.left(
FS.concat(
FS.of(DE.member(0, FS.of(DE.leaf("a", "undefined")))),
FS.of(DE.member(1, FS.of(DE.leaf("a", "parsable to a number"))))
)
)
);
});
});

describe('struct', () => {
it('should decode a valid input', async () => {
const decoder = _.struct({
Expand Down
6 changes: 6 additions & 0 deletions test/Encoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ describe('Encoder', () => {
assert.deepStrictEqual(encoder.encode(null), null)
})

it("optional", () => {
const encoder = E.optional(H.encoderNumberToString);
assert.deepStrictEqual(encoder.encode(1), "1");
assert.deepStrictEqual(encoder.encode(undefined), undefined);
});

it('struct', () => {
const encoder = E.struct({ a: H.encoderNumberToString, b: H.encoderBooleanToNumber })
assert.deepStrictEqual(encoder.encode({ a: 1, b: true }), { a: '1', b: 1 })
Expand Down
13 changes: 13 additions & 0 deletions test/Guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,19 @@ describe('Guard', () => {
})
})

describe("optional", () => {
it("should accept valid inputs", () => {
const guard = G.optional(G.string);
assert.strictEqual(guard.is(undefined), true)
assert.strictEqual(guard.is("a"), true)
})

it("should reject invalid inputs", () => {
const guard = G.optional(G.string);
assert.strictEqual(guard.is(1), false)
})
})

describe('struct', () => {
it('should accept valid inputs', () => {
const guard = G.struct({ a: G.string, b: G.number })
Expand Down
4 changes: 4 additions & 0 deletions test/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ describe('Schema', () => {
check(make((S) => S.nullable(S.string)))
})

it("optional", () => {
check(make((S) => S.optional(S.string)))
})

it('struct', () => {
check(
make((S) =>
Expand Down
Loading

0 comments on commit 269bd19

Please sign in to comment.