Skip to content

Commit

Permalink
Schema: refactor the declare signature to ensure that the decoding … (
Browse files Browse the repository at this point in the history
  • Loading branch information
gcanti authored Feb 14, 2024
1 parent 4e35089 commit e787a57
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 35 deletions.
20 changes: 20 additions & 0 deletions .changeset/spicy-houses-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
"@effect/schema": patch
---

Refactor the `declare` signature to ensure that the decoding and encoding functions do not utilize context.

As a result, we can relax the signature of the following functions to accept `R !== never`:

- `Parser.validateSync`
- `Parser.validateOption`
- `Parser.validateEither`
- `Parser.is`
- `Parser.asserts`
- `Schema.validateSync`
- `Schema.validateOption`
- `Schema.validateEither`
- `Schema.is`
- `Schema.asserts`

Additionally, the `Class` API no longer requires the optional argument `disableValidation` to be `true` when `R !== never`.
3 changes: 3 additions & 0 deletions packages/schema/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3186,6 +3186,9 @@ Error: ReadonlySet<NumberFromString>
*/
```

> [!WARNING]
> The decoding and encoding functions cannot use context (the `R` type parameter) and cannot use async effects.
## Adding Annotations

When you define a new data type, some compilers like `Arbitrary` or `Pretty` may not know how to handle the newly defined data. For instance:
Expand Down
12 changes: 6 additions & 6 deletions packages/schema/dtslint/Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,23 @@ Schema.declare(
}
)

// $ExpectType Schema<string, number, "a" | "b">
Schema.declare(
[aContext, bContext],
// @ts-expect-error
(_a, _b) => () => Taga.pipe(Effect.flatMap(ParseResult.succeed)),
(_a, _b) => () => ParseResult.succeed(1)
)

// $ExpectType Schema<string, number, "a" | "b">
Schema.declare(
[aContext, bContext],
(_a, _b) => () => ParseResult.succeed("a"),
// @ts-expect-error
(_a, _b) => () => Tagb.pipe(Effect.flatMap(ParseResult.succeed))
)

// $ExpectType Schema<string, number, "a" | "b">
Schema.declare(
[aContext, bContext],
// @ts-expect-error
(_a, _b) => () => Taga.pipe(Effect.flatMap(ParseResult.succeed)),
(_a, _b) => () => Tagb.pipe(Effect.flatMap(ParseResult.succeed))
)
Expand Down Expand Up @@ -346,7 +346,7 @@ export type MyClassContext = Schema.Schema.Context<typeof MyClass>
// $ExpectType Schema<{ readonly a: string; }, { readonly a: string; }, "a">
MyClass.struct

// $ExpectType [props: { readonly a: string; }, disableValidation: true]
// $ExpectType [props: { readonly a: string; }, disableValidation?: boolean | undefined]
export type MyClassParams = ConstructorParameters<typeof MyClass>

// ---------------------------------------------
Expand All @@ -367,7 +367,7 @@ export type MyClassWithTransformContext = Schema.Schema.Context<typeof MyClassWi
// $ExpectType Schema<{ readonly a: string; readonly b: number; }, { readonly a: string; }, "a" | "b" | "Tag1" | "Tag2">
MyClassWithTransform.struct

// $ExpectType [props: { readonly a: string; readonly b: number; }, disableValidation: true]
// $ExpectType [props: { readonly a: string; readonly b: number; }, disableValidation?: boolean | undefined]
export type MyClassWithTransformParams = ConstructorParameters<typeof MyClassWithTransform>

// ---------------------------------------------
Expand All @@ -388,7 +388,7 @@ export type MyClassWithTransformFromContext = Schema.Schema.Context<typeof MyCla
// $ExpectType Schema<{ readonly a: string; readonly b: number; }, { readonly a: string; }, "a" | "b" | "Tag1" | "Tag2">
MyClassWithTransformFrom.struct

// $ExpectType [props: { readonly a: string; readonly b: number; }, disableValidation: true]
// $ExpectType [props: { readonly a: string; readonly b: number; }, disableValidation?: boolean | undefined]
export type MyClassWithTransformFromParams = ConstructorParameters<typeof MyClassWithTransformFrom>

// ---------------------------------------------
Expand Down
16 changes: 8 additions & 8 deletions packages/schema/src/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,26 +206,26 @@ export const decode: <A, I, R>(
* @category validation
* @since 1.0.0
*/
export const validateSync = <A, I>(
schema: Schema.Schema<A, I, never>,
export const validateSync = <A, I, R>(
schema: Schema.Schema<A, I, R>,
options?: AST.ParseOptions
): (u: unknown, overrideOptions?: AST.ParseOptions) => A => getSync(AST.to(schema.ast), true, options)

/**
* @category validation
* @since 1.0.0
*/
export const validateOption = <A, I>(
schema: Schema.Schema<A, I, never>,
export const validateOption = <A, I, R>(
schema: Schema.Schema<A, I, R>,
options?: AST.ParseOptions
): (u: unknown, overrideOptions?: AST.ParseOptions) => Option.Option<A> => getOption(AST.to(schema.ast), true, options)

/**
* @category validation
* @since 1.0.0
*/
export const validateEither = <A, I>(
schema: Schema.Schema<A, I, never>,
export const validateEither = <A, I, R>(
schema: Schema.Schema<A, I, R>,
options?: AST.ParseOptions
): (u: unknown, overrideOptions?: AST.ParseOptions) => Either.Either<ParseResult.ParseIssue, A> =>
getEither(AST.to(schema.ast), true, options)
Expand Down Expand Up @@ -256,7 +256,7 @@ export const validate = <A, I, R>(
* @category validation
* @since 1.0.0
*/
export const is = <A, I>(schema: Schema.Schema<A, I, never>, options?: AST.ParseOptions) => {
export const is = <A, I, R>(schema: Schema.Schema<A, I, R>, options?: AST.ParseOptions) => {
const parser = goMemo(AST.to(schema.ast), true)
return (u: unknown, overrideOptions?: AST.ParseOptions): u is A =>
Either.isRight(parser(u, { ...mergeParseOptions(options, overrideOptions), isExact: true }) as any)
Expand All @@ -266,7 +266,7 @@ export const is = <A, I>(schema: Schema.Schema<A, I, never>, options?: AST.Parse
* @category validation
* @since 1.0.0
*/
export const asserts = <A, I>(schema: Schema.Schema<A, I, never>, options?: AST.ParseOptions) => {
export const asserts = <A, I, R>(schema: Schema.Schema<A, I, R>, options?: AST.ParseOptions) => {
const parser = goMemo(AST.to(schema.ast), true)
return (u: unknown, overrideOptions?: AST.ParseOptions): asserts u is A => {
const result: Either.Either<ParseResult.ParseIssue, any> = parser(u, {
Expand Down
43 changes: 22 additions & 21 deletions packages/schema/src/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,8 +340,8 @@ export const validate = <A, I, R>(
* @category validation
* @since 1.0.0
*/
export const validateEither = <A, I>(
schema: Schema<A, I, never>,
export const validateEither = <A, I, R>(
schema: Schema<A, I, R>,
options?: ParseOptions
) => {
const validateEither = Parser.validateEither(schema, options)
Expand Down Expand Up @@ -484,17 +484,16 @@ const getTemplateLiterals = (

const declareConstructor = <
const P extends ReadonlyArray<Schema<any, any, any>>,
R extends Schema.Context<P[number]>,
I,
A
>(
typeParameters: P,
decodeUnknown: (
...typeParameters: P
) => (input: unknown, options: ParseOptions, ast: AST.Declaration) => Effect.Effect<A, ParseResult.ParseIssue, R>,
...typeParameters: { readonly [K in keyof P]: Schema<Schema.To<P[K]>, Schema.From<P[K]>, never> }
) => (input: unknown, options: ParseOptions, ast: AST.Declaration) => Effect.Effect<A, ParseResult.ParseIssue, never>,
encodeUnknown: (
...typeParameters: P
) => (input: unknown, options: ParseOptions, ast: AST.Declaration) => Effect.Effect<I, ParseResult.ParseIssue, R>,
...typeParameters: { readonly [K in keyof P]: Schema<Schema.To<P[K]>, Schema.From<P[K]>, never> }
) => (input: unknown, options: ParseOptions, ast: AST.Declaration) => Effect.Effect<I, ParseResult.ParseIssue, never>,
annotations?: DeclareAnnotations<P, A>
): Schema<A, I, Schema.Context<P[number]>> =>
make(AST.createDeclaration(
Expand Down Expand Up @@ -539,14 +538,22 @@ export const declare: {
is: (input: unknown) => input is A,
annotations?: DeclareAnnotations<readonly [], A>
): Schema<A>
<const P extends ReadonlyArray<Schema<any, any, any>>, R extends Schema.Context<P[number]>, I, A>(
<const P extends ReadonlyArray<Schema<any, any, any>>, I, A>(
typeParameters: P,
decodeUnknown: (
...typeParameters: P
) => (input: unknown, options: ParseOptions, ast: AST.Declaration) => Effect.Effect<A, ParseResult.ParseIssue, R>,
...typeParameters: { readonly [K in keyof P]: Schema<Schema.To<P[K]>, Schema.From<P[K]>, never> }
) => (
input: unknown,
options: ParseOptions,
ast: AST.Declaration
) => Effect.Effect<A, ParseResult.ParseIssue, never>,
encodeUnknown: (
...typeParameters: P
) => (input: unknown, options: ParseOptions, ast: AST.Declaration) => Effect.Effect<I, ParseResult.ParseIssue, R>,
...typeParameters: { readonly [K in keyof P]: Schema<Schema.To<P[K]>, Schema.From<P[K]>, never> }
) => (
input: unknown,
options: ParseOptions,
ast: AST.Declaration
) => Effect.Effect<I, ParseResult.ParseIssue, never>,
annotations?: DeclareAnnotations<{ readonly [K in keyof P]: Schema.To<P[K]> }, A>
): Schema<A, I, Schema.Context<P[number]>>
} = function() {
Expand Down Expand Up @@ -4673,14 +4680,8 @@ type MissingSelfGeneric<Usage extends string, Params extends string = ""> =
*/
export interface Class<A, I, R, C, Self, Inherited = {}, Proto = {}> extends Schema<Self, I, R> {
new(
...args: [R] extends [never] ? [
props: Equals<C, {}> extends true ? void | {} : C,
disableValidation?: boolean | undefined
] :
[
props: Equals<C, {}> extends true ? void | {} : C,
disableValidation: true
]
props: Equals<C, {}> extends true ? void | {} : C,
disableValidation?: boolean | undefined
): A & Omit<Inherited, keyof A> & Proto
readonly struct: Schema<A, I, R>
Expand Down Expand Up @@ -4895,7 +4896,7 @@ const makeClass = <A, I, R>(
Base: any,
additionalProps?: any
): any => {
const validator = Parser.validateSync(selfSchema as any)
const validator = Parser.validateSync(selfSchema)
return class extends Base {
constructor(props?: any, disableValidation = false) {
Expand Down

0 comments on commit e787a57

Please sign in to comment.