Skip to content

Commit

Permalink
Merge b396edd into 668db23
Browse files Browse the repository at this point in the history
  • Loading branch information
ForbesLindesay committed Mar 17, 2022
2 parents 668db23 + b396edd commit 7f90b90
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 6 deletions.
12 changes: 6 additions & 6 deletions .size-snapshot.json
@@ -1,13 +1,13 @@
{
"index.js": {
"bundled": 16672,
"minified": 11618,
"gzipped": 3110
"bundled": 17062,
"minified": 11813,
"gzipped": 3170
},
"index.mjs": {
"bundled": 14990,
"minified": 10732,
"gzipped": 3047,
"bundled": 15351,
"minified": 10929,
"gzipped": 3108,
"treeshaked": {
"rollup": {
"code": 17,
Expand Down
114 changes: 114 additions & 0 deletions README.md
Expand Up @@ -19,6 +19,7 @@ This package includes all these schemas:
- 🚀 FloatString
- 🚀 Integer
- 🚀 IntegerString
- 🚀 Migrate
- 🚀 ParsedBase64Array
- 🚀 ParsedBase64String
- 🚀 ParsedDateString
Expand Down Expand Up @@ -789,6 +790,119 @@ MySchema.serialize({
});
```

### Migrate

A simplified alternative to `ParsedValue`/`.withParser` for migrating legacy data. The `Migrate` cannot be serialized, so it's best used in a `Union` where one of the other types in the union handles serialization.

🚀 Migrating an object to a new schema:

```ts
import {deepEqual} from 'assert';

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Union(
t.Object({
version: t.Literal(2),
width: t.Number,
height: t.Number,
}),
s.Migrate(
t.Object({
version: t.Literal(1),
size: t.Number,
}),
({size}) => ({
version: 2,
width: size,
height: size,
}),
),
);

// ✅ Valid:
deepEqual(
MySchema.parse({
version: 2,
width: 10,
height: 15,
}),
{
version: 2,
width: 10,
height: 15,
},
);

// ✅ Valid:
deepEqual(
MySchema.parse({
version: 1,
size: 42,
}),
{
version: 2,
width: 42,
height: 42,
},
);

// ✅ Valid:
deepEqual(
MySchema.serialize({
version: 2,
width: 10,
height: 15,
}),
{
version: 2,
width: 10,
height: 15,
},
);
```

🚀 Setting a default for an optional property:

```ts
import {deepEqual} from 'assert';

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
values: t.Union(
t.Array(t.Number),
s.Migrate(t.Undefined, () => []),
),
});

// ✅ Valid:
deepEqual(MySchema.parse({values: [1, 2, 3]}), {values: [1, 2, 3]});

// ✅ Valid:
deepEqual(MySchema.parse({values: []}), {values: []});

// ✅ Valid:
deepEqual(MySchema.parse({values: undefined}), {values: []});

// ✅ Valid:
deepEqual(MySchema.parse({}), {values: []});

// ✅ Valid:
deepEqual(MySchema.serialize({values: [1, 2, 3]}), {values: [1, 2, 3]});

// ✅ Valid:
deepEqual(MySchema.serialize({values: []}), {values: []});

// 🚨 Invalid:
deepEqual(MySchema.serialize({values: undefined}), {values: []});

// 🚨 Invalid:
deepEqual(MySchema.serialize({}), {values: []});
```

### Url

Represent an absolute URL as a JavaScript `URL` object. You can pass options to constrain the valid URLs:
Expand Down
19 changes: 19 additions & 0 deletions src/__tests__/index.test.ts
Expand Up @@ -593,6 +593,25 @@ test(`IntegerString`, () => {
);
});

test(`Migrate`, () => {
const example = s.Migrate(t.Array(t.Number), (value): number[] =>
value.slice().reverse(),
);
expectOk(example.safeParse([1, 2, 3])).toEqual([3, 2, 1]);
expectFail(example.safeSerialize([1, 2, 3])).toMatchInlineSnapshot(
`"ParsedValue<number[]> does not support Runtype.serialize"`,
);
expect(example.test([1, 2, 3])).toBe(false);

const example2 = t.Union(
s.Migrate(t.Undefined, (): number[] => []),
t.Array(t.Number),
);
expectOk(example2.safeParse(undefined)).toEqual([]);
expectOk(example2.safeParse([])).toEqual([]);
expectOk(example2.safeParse([1, 2, 3])).toEqual([1, 2, 3]);
});

test(`ParsedBase64Array`, () => {
const utf8 = `hello world 🎂 🎁 💩`;
const b64 = Buffer.from(utf8).toString(`base64`);
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Expand Up @@ -18,6 +18,7 @@ export {default as Float} from './types/Float';
export {default as FloatString} from './types/FloatString';
export {default as Integer} from './types/Integer';
export {default as IntegerString} from './types/IntegerString';
export {default as Migrate} from './types/Migrate';
export {default as Url} from './types/Url';
export {default as UrlString} from './types/UrlString';

Expand Down
21 changes: 21 additions & 0 deletions src/types/Migrate.ts
@@ -0,0 +1,21 @@
import type {Codec} from 'funtypes';
import {ParsedValue} from 'funtypes';

export default function Migrate<S, T>(
source: Codec<S>,
migration: (legacyValue: S) => T,
): Codec<T> {
return ParsedValue(source, {
parse(value) {
try {
return {success: true, value: migration(value)};
} catch (ex: any) {
return {
success: false,
message:
typeof ex?.message === 'string' ? ex.message : `Migration failed`,
};
}
},
});
}

0 comments on commit 7f90b90

Please sign in to comment.