diff --git a/.changeset/clever-waves-notice.md b/.changeset/clever-waves-notice.md
new file mode 100644
index 000000000..09972e78c
--- /dev/null
+++ b/.changeset/clever-waves-notice.md
@@ -0,0 +1,5 @@
+---
+"@effect/schema": minor
+---
+
+Make transformations strict by default (and allow relaxing constraints with `strict: false` option)
diff --git a/README.md b/README.md
index 4b21a9dc9..0c94a2064 100644
--- a/README.md
+++ b/README.md
@@ -1374,7 +1374,12 @@ To perform these kinds of transformations, the `@effect/schema` library provides
### transform
```ts
-(from: Schema, to: Schema, decode: (b: B) => unknown, encode: (c: C) => unknown): Schema
+declare const transform: (
+ from: Schema,
+ to: Schema,
+ decode: (b: B) => C,
+ encode: (c: C) => B
+) => Schema;
```
```mermaid
@@ -1404,6 +1409,22 @@ export const transformedSchema: S.Schema =
In the example above, we defined a schema for the `string` type and a schema for the tuple type `[string]`. We also defined the functions `decode` and `encode` that convert a `string` into a tuple and a tuple into a `string`, respectively. Then, we used the `transform` combinator to convert the string schema into a schema for the tuple type `[string]`. The resulting schema can be used to parse values of type `string` into values of type `[string]`.
+#### Non-strict option
+
+If you need to be less restrictive in your `decode` and `encode` functions, you can make use of the `{ strict: false }` option:
+
+```ts
+declare const transform: (
+ from: Schema,
+ to: Schema,
+ decode: (b: B) => unknown, // Less strict constraint
+ encode: (c: C) => unknown, // Less strict constraint
+ options: { strict: false }
+) => Schema;
+```
+
+This is useful when you want to relax the type constraints imposed by the `decode` and `encode` functions, making them more permissive.
+
### transformOrFail
The `transformOrFail` combinator works in a similar way, but allows the transformation function to return a `ParseResult` object, which can either be a success or a failure.
diff --git a/docs/modules/Schema.ts.md b/docs/modules/Schema.ts.md
index 0fb92290b..f46fe5cd0 100644
--- a/docs/modules/Schema.ts.md
+++ b/docs/modules/Schema.ts.md
@@ -1356,16 +1356,29 @@ using the provided mapping functions.
```ts
export declare const transform: {
+ (
+ to: Schema,
+ decode: (b: B, options: ParseOptions, ast: AST.AST) => C,
+ encode: (c: C, options: ParseOptions, ast: AST.AST) => B
+ ): (self: Schema) => Schema
(
to: Schema,
decode: (b: B, options: ParseOptions, ast: AST.AST) => unknown,
- encode: (c: C, options: ParseOptions, ast: AST.AST) => unknown
+ encode: (c: C, options: ParseOptions, ast: AST.AST) => unknown,
+ options: { strict: false }
): (self: Schema) => Schema
+ (
+ from: Schema,
+ to: Schema,
+ decode: (b: B, options: ParseOptions, ast: AST.AST) => C,
+ encode: (c: C, options: ParseOptions, ast: AST.AST) => B
+ ): Schema
(
from: Schema,
to: Schema,
decode: (b: B, options: ParseOptions, ast: AST.AST) => unknown,
- encode: (c: C, options: ParseOptions, ast: AST.AST) => unknown
+ encode: (c: C, options: ParseOptions, ast: AST.AST) => unknown,
+ options: { strict: false }
): Schema
}
```
@@ -1381,18 +1394,29 @@ using the provided decoding functions.
```ts
export declare const transformOrFail: {
+ (
+ to: Schema,
+ decode: (b: B, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult,
+ encode: (c: C, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult
+ ): (self: Schema) => Schema
(
to: Schema,
decode: (b: B, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult,
encode: (c: C, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult,
- annotations?: AST.Annotated['annotations']
+ options: { strict: false }
): (self: Schema) => Schema
+ (
+ from: Schema,
+ to: Schema,
+ decode: (b: B, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult,
+ encode: (c: C, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult
+ ): Schema
(
from: Schema,
to: Schema,
decode: (b: B, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult,
encode: (c: C, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult,
- annotations?: AST.Annotated['annotations']
+ options: { strict: false }
): Schema
}
```
diff --git a/dtslint/Schema.ts b/dtslint/Schema.ts
index f606bcbd2..cc55034e0 100644
--- a/dtslint/Schema.ts
+++ b/dtslint/Schema.ts
@@ -1,6 +1,7 @@
import * as Brand from "effect/Brand"
import { pipe, identity } from "effect/Function";
import * as S from "@effect/schema/Schema";
+import * as ParseResult from "@effect/schema/ParseResult";
// ---------------------------------------------
// From
@@ -597,3 +598,35 @@ S.mutable(S.lazy(() => S.array(S.string)))
// $ExpectType Schema
S.mutable(S.transform(S.array(S.string), S.array(S.string), identity, identity))
+
+// ---------------------------------------------
+// transform
+// ---------------------------------------------
+
+// $ExpectType Schema
+S.string.pipe(S.transform(S.number, s => s.length, n => String(n)))
+
+// $ExpectType Schema
+S.string.pipe(S.transform(S.number, s => s, n => n, { strict: false }))
+
+// @ts-expect-error
+S.string.pipe(S.transform(S.number, s => s, n => String(n)))
+
+// @ts-expect-error
+S.string.pipe(S.transform(S.number, s => s.length, n => n))
+
+// ---------------------------------------------
+// transformOrFail
+// ---------------------------------------------
+
+// $ExpectType Schema
+S.string.pipe(S.transformOrFail(S.number, s => ParseResult.success(s.length), n => ParseResult.success(String(n))))
+
+// $ExpectType Schema
+S.string.pipe(S.transformOrFail(S.number, s => ParseResult.success(s), n => ParseResult.success(String(n)), { strict: false }))
+
+// @ts-expect-error
+S.string.pipe(S.transformOrFail(S.number, s => ParseResult.success(s), n => ParseResult.success(String(n))))
+
+// @ts-expect-error
+S.string.pipe(S.transformOrFail(S.number, s => ParseResult.success(s.length), n => ParseResult.success(n)))
diff --git a/src/Schema.ts b/src/Schema.ts
index 5b13fc7da..67aa0baa2 100644
--- a/src/Schema.ts
+++ b/src/Schema.ts
@@ -1203,32 +1203,41 @@ export function filter(
* @since 1.0.0
*/
export const transformOrFail: {
+ (
+ to: Schema,
+ decode: (b: B, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult,
+ encode: (c: C, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult
+ ): (self: Schema) => Schema
(
to: Schema,
decode: (b: B, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult,
encode: (c: C, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult,
- annotations?: AST.Annotated["annotations"]
+ options: { strict: false }
): (self: Schema) => Schema
+ (
+ from: Schema,
+ to: Schema,
+ decode: (b: B, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult,
+ encode: (c: C, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult
+ ): Schema
(
from: Schema,
to: Schema,
decode: (b: B, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult,
encode: (c: C, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult,
- annotations?: AST.Annotated["annotations"]
+ options: { strict: false }
): Schema
-} = dual(4, (
+} = dual((args) => isSchema(args[0]) && isSchema(args[1]), (
from: Schema,
to: Schema,
decode: (b: B, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult,
- encode: (c: C, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult,
- annotations?: AST.Annotated["annotations"]
+ encode: (c: C, options: ParseOptions, ast: AST.AST) => ParseResult.ParseResult
): Schema =>
make(
AST.createTransform(
from.ast,
to.ast,
- AST.createFinalTransformation(decode, encode),
- annotations
+ AST.createFinalTransformation(decode, encode)
)
))
@@ -1240,24 +1249,37 @@ export const transformOrFail: {
* @since 1.0.0
*/
export const transform: {
+ (
+ to: Schema,
+ decode: (b: B, options: ParseOptions, ast: AST.AST) => C,
+ encode: (c: C, options: ParseOptions, ast: AST.AST) => B
+ ): (self: Schema) => Schema
(
to: Schema,
decode: (b: B, options: ParseOptions, ast: AST.AST) => unknown,
- encode: (c: C, options: ParseOptions, ast: AST.AST) => unknown
+ encode: (c: C, options: ParseOptions, ast: AST.AST) => unknown,
+ options: { strict: false }
): (self: Schema) => Schema
+ (
+ from: Schema,
+ to: Schema,
+ decode: (b: B, options: ParseOptions, ast: AST.AST) => C,
+ encode: (c: C, options: ParseOptions, ast: AST.AST) => B
+ ): Schema
(
from: Schema,
to: Schema,
decode: (b: B, options: ParseOptions, ast: AST.AST) => unknown,
- encode: (c: C, options: ParseOptions, ast: AST.AST) => unknown
+ encode: (c: C, options: ParseOptions, ast: AST.AST) => unknown,
+ options: { strict: false }
): Schema
} = dual(
- 4,
+ (args) => isSchema(args[0]) && isSchema(args[1]),
(
from: Schema,
to: Schema,
- decode: (b: B, options: ParseOptions, ast: AST.AST) => unknown,
- encode: (c: C, options: ParseOptions, ast: AST.AST) => unknown
+ decode: (b: B, options: ParseOptions, ast: AST.AST) => C,
+ encode: (c: C, options: ParseOptions, ast: AST.AST) => B
): Schema =>
transformOrFail(
from,
@@ -1734,7 +1756,8 @@ export const lowercase = (self: Schema): Schema
self,
to(self).pipe(lowercased()),
(s) => s.toLowerCase(),
- identity
+ identity,
+ { strict: false }
)
/**
@@ -1756,7 +1779,8 @@ export const trim = (self: Schema): Schema =>
self,
to(self).pipe(trimmed()),
(s) => s.trim(),
- identity
+ identity,
+ { strict: false }
)
/**
@@ -1803,7 +1827,7 @@ export const parseJson = (self: Schema, options?: {
} catch (e: any) {
return ParseResult.failure(ParseResult.type(ast, u, e.message))
}
- })
+ }, { strict: false })
}
// ---------------------------------------------
@@ -2137,7 +2161,8 @@ export const clamp =
self,
self.pipe(to, between(min, max)),
(self) => N.clamp(self, min, max),
- identity
+ identity,
+ { strict: false }
)
/**
@@ -2172,7 +2197,8 @@ export const numberFromString = (self: Schema): Schem
const n = Number(s)
return isNaN(n) ? ParseResult.failure(ParseResult.type(ast, s)) : ParseResult.success(n)
},
- (n) => ParseResult.success(String(n))
+ (n) => ParseResult.success(String(n)),
+ { strict: false }
)
}
@@ -2311,7 +2337,8 @@ export const symbolFromString = (self: Schema): Schem
self,
symbolFromSelf,
(s) => Symbol.for(s),
- (sym) => sym.description
+ (sym) => sym.description,
+ { strict: false }
)
}
@@ -2506,7 +2533,8 @@ export const clampBigint =
self,
self.pipe(to, betweenBigint(min, max)),
(self) => BigInt_.clamp(self, min, max),
- identity
+ identity,
+ { strict: false }
)
/**
@@ -2534,7 +2562,8 @@ export const bigintFromString = (self: Schema): Schem
return ParseResult.failure(ParseResult.type(ast, s))
}
},
- (n) => ParseResult.success(String(n))
+ (n) => ParseResult.success(String(n)),
+ { strict: false }
)
}
@@ -2565,7 +2594,8 @@ export const bigintFromNumber = (self: Schema): Schem
}
return ParseResult.success(Number(b))
- }
+ },
+ { strict: false }
)
}
@@ -2689,7 +2719,8 @@ export const uint8ArrayFromNumbers = >(
self,
Uint8ArrayFromSelf,
(a) => Uint8Array.from(a),
- (n) => Array.from(n)
+ (n) => Array.from(n),
+ { strict: false }
)
const _Uint8Array: Schema, Uint8Array> = uint8ArrayFromNumbers(
@@ -2734,12 +2765,12 @@ const makeEncodingTransform = (
ParseResult.parseError([ParseResult.type(ast, s, decodeException.message)])
),
(u) => ParseResult.success(encode(u)),
- {
- [AST.IdentifierAnnotationId]: id,
- [Internal.PrettyHookId]: (): Pretty => (u) => `${id}(${encode(u)})`,
- [Internal.ArbitraryHookId]: () => arbitrary
- }
- )
+ { strict: false }
+ ).pipe(annotations({
+ [AST.IdentifierAnnotationId]: id,
+ [Internal.PrettyHookId]: (): Pretty => (u) => `${id}(${encode(u)})`,
+ [Internal.ArbitraryHookId]: () => arbitrary
+ }))
/**
* Transforms a base64 `string` into a `Uint8Array`.
@@ -2954,7 +2985,8 @@ export const dateFromString = (self: Schema): Schema<
self,
ValidDateFromSelf,
(s) => new Date(s),
- (n) => n.toISOString()
+ (n) => n.toISOString(),
+ { strict: false }
)
const _Date: Schema = dateFromString(string)
@@ -3365,7 +3397,8 @@ export const data = <
item,
to(dataFromSelf(item)),
toData,
- (a) => Array.isArray(a) ? Array.from(a) : Object.assign({}, a)
+ (a) => Array.isArray(a) ? Array.from(a) : Object.assign({}, a),
+ { strict: false }
)
// ---------------------------------------------