Skip to content

Commit

Permalink
Schema: make optional dual (#2353)
Browse files Browse the repository at this point in the history
Co-authored-by: gcanti <giulio.canti@gmail.com>
  • Loading branch information
tim-smart and gcanti authored Mar 18, 2024
1 parent a45a525 commit 5f5fcd9
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 79 deletions.
18 changes: 18 additions & 0 deletions .changeset/dry-experts-laugh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"@effect/schema": patch
---

make `optional` dual:

```ts
import * as S from "@effect/schema/Schema";

const schema = S.struct({
a: S.string.pipe(S.optional()),
});

// same as:
const schema2 = S.struct({
a: S.optional(S.string),
});
```
5 changes: 5 additions & 0 deletions .changeset/tidy-rocks-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

Types: add `Has` helper
16 changes: 16 additions & 0 deletions packages/effect/src/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ export type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends <
>() => T extends Y ? 1 : 2 ? true
: false

/**
* Determines if a record contains any of the given keys.
*
* @example
* import * as Types from "effect/Types"
*
* type Res1 = Types.Has<{ a: number }, "a" | "b"> // true
* type Res2 = Types.Has<{ c: number }, "a" | "b"> // false
*
* @since 2.0.0
* @category models
*/
export type Has<A, Key extends string> = (Key extends infer K ? K extends keyof A ? true : never : never) extends never
? false
: true

/**
* Merges two object where the keys of the left object take precedence in the case of a conflict.
*
Expand Down
66 changes: 62 additions & 4 deletions packages/schema/dtslint/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,8 +586,14 @@ S.asSchema(S.struct({ a: S.optional(S.never, { exact: true }) }))
// $ExpectType struct<{ a: PropertySignature<"?:", never, never, "?:", never, never>; }>
S.struct({ a: S.optional(S.never, { exact: true }) })

// $ExpectType Schema<{ readonly a?: string; }, { readonly a?: string; }, never>
S.asSchema(S.struct({ a: S.string.pipe(S.optional({ exact: true })) }))

// $ExpectType struct<{ a: PropertySignature<"?:", string, never, "?:", string, never>; }>
S.struct({ a: S.string.pipe(S.optional({ exact: true })) })

// ---------------------------------------------
// optional
// optional()
// ---------------------------------------------

// $ExpectType Schema<{ readonly a: string; readonly b: number; readonly c?: boolean | undefined; }, { readonly a: string; readonly b: number; readonly c?: boolean | undefined; }, never>
Expand All @@ -608,6 +614,12 @@ S.asSchema(S.struct({ a: S.optional(S.never) }))
// $ExpectType struct<{ a: PropertySignature<"?:", undefined, never, "?:", undefined, never>; }>
S.struct({ a: S.optional(S.never) })

// $ExpectType Schema<{ readonly a?: string | undefined; }, { readonly a?: string | undefined; }, never>
S.asSchema(S.struct({ a: S.string.pipe(S.optional()) }))

// $ExpectType struct<{ a: PropertySignature<"?:", string | undefined, never, "?:", string | undefined, never>; }>
S.struct({ a: S.string.pipe(S.optional()) })

// ---------------------------------------------
// optional { exact: true, default: () => A }
// ---------------------------------------------
Expand Down Expand Up @@ -640,9 +652,15 @@ S.struct({
c: S.optional(S.NumberFromString, { exact: true, default: () => 0 })
})

// @ts-expect-error
// $ExpectType struct<{ a: PropertySignature<":", "a" | "b", never, "?:", "a" | "b", never>; }>
S.struct({ a: S.optional(S.literal("a", "b"), { default: () => "a", exact: true }) })

// $ExpectType Schema<{ readonly a: "a" | "b"; }, { readonly a?: "a" | "b"; }, never>
S.asSchema(S.struct({ a: S.literal("a", "b").pipe(S.optional({ default: () => "a", exact: true })) }))

// $ExpectType struct<{ a: PropertySignature<":", "a" | "b", never, "?:", "a" | "b", never>; }>
S.struct({ a: S.literal("a", "b").pipe(S.optional({ default: () => "a", exact: true })) })

// ---------------------------------------------
// optional { default: () => A }
// ---------------------------------------------
Expand All @@ -659,9 +677,15 @@ S.asSchema(S.struct({ a: S.string, b: S.number, c: S.optional(S.NumberFromString
// $ExpectType struct<{ a: $string; b: $number; c: PropertySignature<":", number, never, "?:", string | undefined, never>; }>
S.struct({ a: S.string, b: S.number, c: S.optional(S.NumberFromString, { default: () => 0 }) })

// @ts-expect-error
// $ExpectType struct<{ a: PropertySignature<":", "a" | "b", never, "?:", "a" | "b" | undefined, never>; }>
S.struct({ a: S.optional(S.literal("a", "b"), { default: () => "a" }) })

// $ExpectType Schema<{ readonly a: "a" | "b"; }, { readonly a?: "a" | "b" | undefined; }, never>
S.asSchema(S.struct({ a: S.literal("a", "b").pipe(S.optional({ default: () => "a" as const })) }))

// $ExpectType struct<{ a: PropertySignature<":", "a" | "b", never, "?:", "a" | "b" | undefined, never>; }>
S.struct({ a: S.literal("a", "b").pipe(S.optional({ default: () => "a" as const })) })

// ---------------------------------------------
// optional { nullable: true, default: () => A }
// ---------------------------------------------
Expand All @@ -678,9 +702,15 @@ S.asSchema(S.struct({ a: S.optional(S.NumberFromString, { exact: true, nullable:
// $ExpectType struct<{ a: PropertySignature<":", number, never, "?:", string | null, never>; }>
S.struct({ a: S.optional(S.NumberFromString, { exact: true, nullable: true, default: () => 0 }) })

// @ts-expect-error
// $ExpectType struct<{ a: PropertySignature<":", "a" | "b", never, "?:", "a" | "b" | null | undefined, never>; }>
S.struct({ a: S.optional(S.literal("a", "b"), { default: () => "a", nullable: true }) })

// $ExpectType Schema<{ readonly a: "a" | "b"; }, { readonly a?: "a" | "b" | null | undefined; }, never>
S.asSchema(S.struct({ a: S.literal("a", "b").pipe(S.optional({ default: () => "a", nullable: true })) }))

// $ExpectType struct<{ a: PropertySignature<":", "a" | "b", never, "?:", "a" | "b" | null | undefined, never>; }>
S.struct({ a: S.literal("a", "b").pipe(S.optional({ default: () => "a", nullable: true })) })

// ---------------------------------------------
// optional { exact: true, as: "Option" }
// ---------------------------------------------
Expand All @@ -705,6 +735,12 @@ S.struct({
c: S.optional(S.NumberFromString, { exact: true, as: "Option" })
})

// $ExpectType Schema<{ readonly a: Option<string>; }, { readonly a?: string; }, never>
S.asSchema(S.struct({ a: S.string.pipe(S.optional({ exact: true, as: "Option" })) }))

// $ExpectType struct<{ a: PropertySignature<":", Option<string>, never, "?:", string, never>; }>
S.struct({ a: S.string.pipe(S.optional({ exact: true, as: "Option" })) })

// ---------------------------------------------
// optional { as: "Option" }
// ---------------------------------------------
Expand All @@ -721,6 +757,12 @@ S.asSchema(S.struct({ a: S.string, b: S.number, c: S.optional(S.NumberFromString
// $ExpectType struct<{ a: $string; b: $number; c: PropertySignature<":", Option<number>, never, "?:", string | undefined, never>; }>
S.struct({ a: S.string, b: S.number, c: S.optional(S.NumberFromString, { as: "Option" }) })

// $ExpectType Schema<{ readonly a: Option<string>; }, { readonly a?: string | undefined; }, never>
S.asSchema(S.struct({ a: S.string.pipe(S.optional({ as: "Option" })) }))

// $ExpectType struct<{ a: PropertySignature<":", Option<string>, never, "?:", string | undefined, never>; }>
S.struct({ a: S.string.pipe(S.optional({ as: "Option" })) })

// ---------------------------------------------
// optional { nullable: true, as: "Option" }
// ---------------------------------------------
Expand All @@ -731,12 +773,28 @@ S.asSchema(S.struct({ a: S.optional(S.NumberFromString, { nullable: true, as: "O
// $ExpectType struct<{ a: PropertySignature<":", Option<number>, never, "?:", string | null | undefined, never>; }>
S.struct({ a: S.optional(S.NumberFromString, { nullable: true, as: "Option" }) })

// $ExpectType Schema<{ readonly a: Option<string>; }, { readonly a?: string | null | undefined; }, never>
S.asSchema(S.struct({ a: S.string.pipe(S.optional({ nullable: true, as: "Option" })) }))

// $ExpectType struct<{ a: PropertySignature<":", Option<string>, never, "?:", string | null | undefined, never>; }>
S.struct({ a: S.string.pipe(S.optional({ nullable: true, as: "Option" })) })

// ---------------------------------------------
// optional { exact: true, nullable: true, as: "Option" }
// ---------------------------------------------

// $ExpectType Schema<{ readonly a: Option<number>; }, { readonly a?: string | null; }, never>
S.asSchema(S.struct({ a: S.optional(S.NumberFromString, { exact: true, nullable: true, as: "Option" }) }))

// $ExpectType struct<{ a: PropertySignature<":", Option<number>, never, "?:", string | null, never>; }>
S.struct({ a: S.optional(S.NumberFromString, { exact: true, nullable: true, as: "Option" }) })

// $ExpectType Schema<{ readonly a: Option<string>; }, { readonly a?: string | null; }, never>
S.asSchema(S.struct({ a: S.string.pipe(S.optional({ exact: true, nullable: true, as: "Option" })) }))

// $ExpectType struct<{ a: PropertySignature<":", Option<string>, never, "?:", string | null, never>; }>
S.struct({ a: S.string.pipe(S.optional({ exact: true, nullable: true, as: "Option" })) })

// ---------------------------------------------
// pick
// ---------------------------------------------
Expand Down
145 changes: 70 additions & 75 deletions packages/schema/src/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1571,85 +1571,80 @@ export const optionalToOptional = <FA, FI, FR, TA, TI, TR>(
* @since 1.0.0
*/
export const optional: {
<A, I, R>(
schema: Schema<A, I, R>,
options: {
readonly exact: true
readonly default: () => A
readonly nullable: true
}
): PropertySignature<":", A, never, "?:", I | null, R>
<A, I, R>(
schema: Schema<A, I, R>,
options: {
readonly exact: true
<
A,
const Options extends {
readonly exact?: true
readonly nullable?: true
} | {
readonly default: () => A
}
): PropertySignature<":", A, never, "?:", I, R>
<A, I, R>(
schema: Schema<A, I, R>,
options: {
readonly exact: true
readonly nullable: true
readonly as: "Option"
}
): PropertySignature<":", Option.Option<A>, never, "?:", I | null, R>
<A, I, R>(
schema: Schema<A, I, R>,
options: {
readonly exact: true
readonly exact?: true
readonly nullable?: true
} | {
readonly as: "Option"
}
): PropertySignature<":", Option.Option<A>, never, "?:", I, R>
<A, I, R>(
schema: Schema<A, I, R>,
options: {
readonly exact: true
readonly nullable: true
}
): PropertySignature<"?:", A, never, "?:", I | null, R>
<A, I, R>(
schema: Schema<A, I, R>,
options: {
readonly exact: true
}
): PropertySignature<"?:", A, never, "?:", I, R>
<A, I, R>(
schema: Schema<A, I, R>,
options: {
readonly exact?: true
readonly nullable?: true
} | undefined
>(
options?: Options
): <I, R>(schema: Schema<A, I, R>) => [undefined] extends [Options] ? PropertySignature<
"?:",
A | undefined,
never,
"?:",
I | undefined,
R
> :
PropertySignature<
Types.Has<Options, "as" | "default"> extends true ? ":" : "?:",
| (Types.Has<Options, "as"> extends true ? Option.Option<A> : A)
| (Types.Has<Options, "as" | "default" | "exact"> extends true ? never : undefined),
never,
"?:",
| I
| (Types.Has<Options, "nullable"> extends true ? null : never)
| (Types.Has<Options, "exact"> extends true ? never : undefined),
R
>
<
A,
I,
R,
const Options extends {
readonly exact?: true
readonly nullable?: true
} | {
readonly default: () => A
readonly nullable: true
}
): PropertySignature<":", A, never, "?:", I | null | undefined, R>
<A, I, R>(
schema: Schema<A, I, R>,
options: {
readonly nullable: true
readonly exact?: true
readonly nullable?: true
} | {
readonly as: "Option"
}
): PropertySignature<":", Option.Option<A>, never, "?:", I | null | undefined, R>
<A, I, R>(
schema: Schema<A, I, R>,
options: {
readonly as: "Option"
}
): PropertySignature<":", Option.Option<A>, never, "?:", I | undefined, R>
<A, I, R>(
schema: Schema<A, I, R>,
options: {
readonly default: () => A
}
): PropertySignature<":", A, never, "?:", I | undefined, R>
<A, I, R>(
readonly exact?: true
readonly nullable?: true
} | undefined
>(
schema: Schema<A, I, R>,
options: {
readonly nullable: true
}
): PropertySignature<"?:", A | undefined, never, "?:", I | null | undefined, R>
<A, I, R>(
schema: Schema<A, I, R>
): PropertySignature<"?:", A | undefined, never, "?:", I | undefined, R>
} = <A, I, R>(
options?: Options
): [undefined] extends [Options] ? PropertySignature<
"?:",
A | undefined,
never,
"?:",
I | undefined,
R
> :
PropertySignature<
Types.Has<Options, "as" | "default"> extends true ? ":" : "?:",
| (Types.Has<Options, "as"> extends true ? Option.Option<A> : A)
| (Types.Has<Options, "as" | "default" | "exact"> extends true ? never : undefined),
never,
"?:",
| I
| (Types.Has<Options, "nullable"> extends true ? null : never)
| (Types.Has<Options, "exact"> extends true ? never : undefined),
R
>
} = dual((args) => isSchema(args[0]), <A, I, R>(
schema: Schema<A, I, R>,
options?: {
readonly exact?: true
Expand Down Expand Up @@ -1756,7 +1751,7 @@ export const optional: {
}
}
}
}
})

/**
* @since 1.0.0
Expand Down

0 comments on commit 5f5fcd9

Please sign in to comment.