Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Commit

Permalink
merge transformEither and transformEffect into transformResult (#178)
Browse files Browse the repository at this point in the history
  • Loading branch information
gcanti authored Mar 24, 2023
1 parent 4ef6463 commit f60341f
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 122 deletions.
5 changes: 5 additions & 0 deletions .changeset/funny-walls-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/schema": minor
---

merge transformEither and transformEffect into transformResult
103 changes: 58 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ Modeling the schema of data structures as first-class values

Welcome to the documentation for `@effect/schema`, **a library for defining and using schemas** to validate and transform data in TypeScript.

`@effect/schema` allows you to define a `Schema` that describes the structure and data types of a piece of data, and then use that `Schema` to perform various operations such as **parsing** from `unknown`, **decoding**, **encoding**, **verifying** that a value conforms to a given `Schema`.
`@effect/schema` allows you to define a `Schema<I, A>` that describes the structure and data types of a piece of data, and then use that `Schema` to perform various operations such as:

`@effect/schema` also provides a number of other features, including the ability to derive various artifacts such as `Arbitrary`s, `JSONSchema`s, and `Pretty`s from a `Schema`, as well as the ability to customize the library through the use of custom artifact compilers and custom `Schema` combinators.
- parsing from `unknown`
- decoding from `I` to `A`
- encoding from `A` to `I`
- verifying that a value conforms to a given `Schema`
- generating fast-check arbitraries
- pretty printing

If you're eager to learn how to define your first schema, jump straight to the [**Basic usage**](https://github.com/effect-ts/schema#basic-usage) section!

Expand Down Expand Up @@ -122,13 +127,26 @@ const parsePerson = S.parseEither(Person);
const input: unknown = { name: "Alice", age: 30 }

const result1 = parsePerson(input);
if (S.isSuccess(result1)) {
if (E.isRight(result1)) {
console.log(result1.right); // { name: "Alice", age: 30 }
}

const result2 = parsePerson(null);
if (E.isLeft(result2)) {
console.log(result2.left); // [PR.type(..., null)]
console.log(result2.left);
/*
{
_tag: 'ParseError',
errors: [
{
_tag: 'Type',
expected: [Object],
actual: null,
message: [Object]
}
]
}
*/
}
```

Expand All @@ -147,8 +165,8 @@ try {
}
/*
Parsing failed:
error(s) found
└─ key "name"
Error: error(s) found
└─ ["name"]
└─ is missing
*/
```
Expand Down Expand Up @@ -181,10 +199,7 @@ console.log(
)
);
/*
{
_tag: 'Right',
right: { name: 'Bob', age: 40 }
}
{ _tag: 'Right', right: { name: 'Bob', age: 40 } }
*/
```

Expand Down Expand Up @@ -216,25 +231,14 @@ console.log(
/*
{
_tag: 'Left',
left: [
{
_tag: 'Key',
key: 'age',
errors: [
{ _tag: 'Type', expected: ..., actual: 'abc' },
[length]: 1
]
},
{
_tag: 'Key',
key: 'email',
errors: [
{ _tag: 'Unexpected', actual: '[email protected]' },
[length]: 1
]
},
[length]: 2
]
left: {
_tag: 'ParseError',
errors: [
{ _tag: 'Key', key: 'email', errors: [ [Object], [length]: 1 ] },
{ _tag: 'Key', key: 'age', errors: [ [Object], [length]: 1 ] },
[length]: 2
]
}
}
*/
```
Expand All @@ -245,7 +249,7 @@ To use the `Schema` defined above to encode a value to `unknown`, you can use th

```ts
import * as S from "@effect/schema/Schema";
import { pipe } from "@effect/data/Function";
import * as E from "@effect/data/Either";

// Age is a schema that can parse a string to a number and encode a number to a string
const Age = S.numberFromString(S.string);
Expand All @@ -256,7 +260,7 @@ const Person = S.struct({
});

const encoded = S.encodeEither(Person)({ name: "Alice", age: 30 });
if (S.isSuccess(encoded)) {
if (E.isRight(encoded)) {
console.log(encoded.right); // { name: "Alice", age: "30" }
}
```
Expand All @@ -280,12 +284,12 @@ const Person = S.struct({
const result = S.parseEither(Person)({});
if (E.isLeft(result)) {
console.error("Parsing failed:");
console.error(formatErrors(result.left));
console.error(formatErrors(result.left.errors));
}
/*
Parsing failed:
error(s) found
└─ key "name"
└─ ["name"]
└─ is missing
*/
```
Expand Down Expand Up @@ -321,7 +325,7 @@ const Person = S.struct({
});

// const assertsPerson: (input: unknown) => asserts input is Person
const assertsPerson: S.ToAsserts<typeof Person> = P.asserts(Person);
const assertsPerson: S.ToAsserts<typeof Person> = S.asserts(Person);

try {
assertsPerson({ name: "Alice", age: "30" });
Expand All @@ -332,7 +336,7 @@ try {
/*
The input does not match the schema:
Error: error(s) found
└─ key "age"
└─ ["age"]
└─ Expected number, actual "30"
*/

Expand All @@ -345,24 +349,34 @@ assertsPerson({ name: "Alice", age: 30 });
The `arbitrary` function provided by the `@effect/schema/Arbitrary` module represents a way of generating random values that conform to a given `Schema`. This can be useful for testing purposes, as it allows you to generate random test data that is guaranteed to be valid according to the `Schema`.

```ts
import { pipe } from "@effect/data/Function";
import * as S from "@effect/schema/Schema";
import * as A from "@effect/schema/Arbitrary";
import * as fc from "fast-check";

const Person = S.struct({
name: S.string,
age: S.number,
age: pipe(S.string, S.numberFromString, S.int())
});

const PersonArbitrary = A.arbitrary(Person)(fc);
// Arbitrary for the To type
const PersonArbitraryTo = A.to(Person)(fc);

console.log(fc.sample(PersonArbitrary, 2));
console.log(fc.sample(PersonArbitraryTo, 2));
/*
[
{ name: '!U?z/X', age: -2.5223372357846707e-44 },
{ name: 'valukeypro', age: -1.401298464324817e-45 }
{ name: 'WJh;`Jz', age: 3.4028216409684243e+38 },
{ name: 'x&~', age: 139480325657985020 }
]
*/

// Arbitrary for the From type
const PersonArbitraryFrom = A.from(Person)(fc);

console.log(fc.sample(PersonArbitraryFrom, 2));
/*
[ { name: 'Q}"H@aT', age: ']P$8w' }, { name: '|', age: '"' } ]
*/
```

## Pretty print
Expand Down Expand Up @@ -981,7 +995,7 @@ In some cases, we may need to transform the output of a schema to a different ty

To perform these kinds of transformations, the `@effect/schema` library provides the `transform` combinator.

**transform**
### transform

```ts
<I1, A1, I2, A2>(from: Schema<I1, A1>, to: Schema<I2, A2>, decode: (a1: A1) => I2, encode: (i2: I2) => A1): Schema<I1, A2>
Expand Down Expand Up @@ -1012,9 +1026,9 @@ const transformedSchema: S.Schema<string, readonly [string]> = S.transform(S.str

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]`.

The `transformEither` 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.
### transformResult

Here's an example of the `transformEither` combinator which converts a `string` into a `boolean`:
The `transformResult` 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.

```ts
import * as PR from "@effect/schema/ParseResult";
Expand All @@ -1031,8 +1045,7 @@ const decode = (s: string) =>
// define a function that converts a boolean into a string
const encode = (b: boolean) => PR.success(String(b));

// use the transformEither combinator to convert the string schema into the boolean schema
const transformedSchema: S.Schema<string, boolean> = S.transformEither(S.string, S.boolean, decode, encode);
const transformedSchema: S.Schema<string, boolean> = S.transformEffect(S.string, S.boolean, decode, encode);
```

### String transformations
Expand Down
32 changes: 3 additions & 29 deletions docs/modules/Schema.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ Added in v1.0.0
- [rest](#rest)
- [struct](#struct)
- [transform](#transform)
- [transformEffect](#transformeffect)
- [transformEither](#transformeither)
- [transformResult](#transformresult)
- [tuple](#tuple)
- [union](#union)
- [constructors](#constructors)
Expand Down Expand Up @@ -619,15 +618,15 @@ export declare const transform: {

Added in v1.0.0

## transformEffect
## transformResult

Create a new `Schema` by transforming the input and output of an existing `Schema`
using the provided decoding functions.

**Signature**

```ts
export declare const transformEffect: {
export declare const transformResult: {
<I2, A2, A1>(
to: Schema<I2, A2>,
decode: (a1: A1, options?: ParseOptions | undefined) => PR.IO<PR.ParseError, I2>,
Expand All @@ -644,31 +643,6 @@ export declare const transformEffect: {

Added in v1.0.0

## transformEither

Create a new `Schema` by transforming the input and output of an existing `Schema`
using the provided decoding functions.

**Signature**

```ts
export declare const transformEither: {
<I2, A2, A1>(
to: Schema<I2, A2>,
decode: (a1: A1, options?: ParseOptions | undefined) => Either<PR.ParseError, I2>,
encode: (i2: I2, options?: ParseOptions | undefined) => Either<PR.ParseError, A1>
): <I1>(self: Schema<I1, A1>) => Schema<I1, A2>
<I1, A1, I2, A2>(
from: Schema<I1, A1>,
to: Schema<I2, A2>,
decode: (a1: A1, options?: ParseOptions | undefined) => Either<PR.ParseError, I2>,
encode: (i2: I2, options?: ParseOptions | undefined) => Either<PR.ParseError, A1>
): Schema<I1, A2>
}
```

Added in v1.0.0

## tuple

**Signature**
Expand Down
49 changes: 4 additions & 45 deletions src/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,7 @@ export function filter<A>(
@category combinators
@since 1.0.0
*/
export const transformEffect: {
export const transformResult: {
<I2, A2, A1>(
to: Schema<I2, A2>,
decode: (a1: A1, options?: ParseOptions) => ParseResult<I2>,
Expand All @@ -781,47 +781,6 @@ export const transformEffect: {
encode: (i2: I2, options?: ParseOptions) => ParseResult<A1>
): Schema<I1, A2> => make(AST.createTransform(from.ast, to.ast, decode, encode)))

/**
Create a new `Schema` by transforming the input and output of an existing `Schema`
using the provided decoding functions.
@category combinators
@since 1.0.0
*/
export const transformEither: {
<I2, A2, A1>(
to: Schema<I2, A2>,
decode: (
a1: A1,
options?: ParseOptions
) => E.Either<PR.ParseError, I2>,
encode: (
i2: I2,
options?: ParseOptions
) => E.Either<PR.ParseError, A1>
): <I1>(self: Schema<I1, A1>) => Schema<I1, A2>
<I1, A1, I2, A2>(
from: Schema<I1, A1>,
to: Schema<I2, A2>,
decode: (
a1: A1,
options?: ParseOptions
) => E.Either<PR.ParseError, I2>,
encode: (
i2: I2,
options?: ParseOptions
) => E.Either<PR.ParseError, A1>
): Schema<I1, A2>
} = dual(4, <I1, A1, I2, A2>(
from: Schema<I1, A1>,
to: Schema<I2, A2>,
decode: (
a1: A1,
options?: ParseOptions
) => E.Either<PR.ParseError, I2>,
encode: (i2: I2, options?: ParseOptions) => E.Either<PR.ParseError, A1>
): Schema<I1, A2> => make(AST.createTransform(from.ast, to.ast, decode, encode)))

/**
Create a new `Schema` by transforming the input and output of an existing `Schema`
using the provided mapping functions.
Expand Down Expand Up @@ -849,7 +808,7 @@ export const transform: {
decode: (a1: A1) => I2,
encode: (i2: I2) => A1
): Schema<I1, A2> =>
transformEither(from, to, (a) => E.right(decode(a)), (b) => E.right(encode(b)))
transformResult(from, to, (a) => E.right(decode(a)), (b) => E.right(encode(b)))
)

/**
Expand Down Expand Up @@ -1417,7 +1376,7 @@ export const date: Schema<Date> = declare(
@category parsers
@since 1.0.0
*/
export const dateFromString: Schema<string, Date> = transformEffect(
export const dateFromString: Schema<string, Date> = transformResult(
string,
date,
(s) => {
Expand Down Expand Up @@ -1856,7 +1815,7 @@ export const clamp = <A extends number>(min: number, max: number) =>
@since 1.0.0
*/
export const numberFromString = <I>(self: Schema<I, string>): Schema<I, number> => {
const schema: Schema<I, number> = transformEffect(
const schema: Schema<I, number> = transformResult(
self,
number,
(s) => {
Expand Down
4 changes: 2 additions & 2 deletions test/Forbidden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ describe.concurrent("Forbidden", () => {

it("transform", () => {
const schema = pipe(
S.transformEither(
S.transformResult(
S.string,
S.transformEffect(
S.transformResult(
S.string,
S.string,
(s) => PR.flatMap(Util.sleep, () => PR.success(s)),
Expand Down
2 changes: 1 addition & 1 deletion test/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ describe.concurrent("Schema", () => {
const To = S.struct({ radius: S.number, _isVisible: S.boolean })

const Circle = pipe(
S.transformEither(From, To, S.parseEither(To), ({ _isVisible, ...rest }) => E.right(rest)),
S.transformResult(From, To, S.parseEither(To), ({ _isVisible, ...rest }) => E.right(rest)),
S.attachPropertySignature("_tag", "Circle")
)
expect(S.decode(Circle)({ radius: 10, _isVisible: true })).toEqual({
Expand Down

0 comments on commit f60341f

Please sign in to comment.