Effect-TS / schema

Modeling the schema of data structures as first-class values

Home Page:https://effect.website

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Migrating from `io-ts` to `@effect/schema` question

floydspace opened this issue · comments

Hello guys, and thank you for your great work.

I got some time to explore this library. And currently stuck with understanding how to define the following scenario with fictional example:

const Circle = pipe(
  S.struct({ radius: S.number, _isVisible: S.boolean }),
  S.transform(?)
);

// decoding
expect(S.decode(Circle)({ radius: 10, _isVisible: true })).toEqual({
  radius: 10,
  _isVisible: true
});

// encoding
expect(S.encode(Circle)({ radius: 10, _isVisible: true })).toEqual({
  radius: 10
});

So the idea is to have an ability to define a certain flag in an object and validate it with the decoder, but omit it during encoding.

This can be easily achieved by io-ts experimental codecs liek this:

const CircleDecoder = D.struct({
  radius: D.number,
  _isVisible: D.boolean,
});

type CircleDecoder = D.TypeOf<typeof CircleDecoder>;
type CircleEncoder = Omit<CircleDecoder, "_isVisible">;

const CircleEncoder: E.Encoder<CircleEncoder, CircleDecoder> = {
  encode: (e) => ({
    radius: e.radius,
  }),
};

const Circle = C.make(CircleDecoder, CircleEncoder);

// decoding
expect(Circle.decode({ radius: 10, _isVisible: true })).toEqual({
  _tag: 'Right',
  right: { radius: 10, _isVisible: true }
});

// encoding
expect(Circle.encode({ radius: 10, _isVisible: true })).toEqual({
  radius: 10
});

or I'm using codecs wrong, and they must obey the encode(decode(obj)) === decode(encode(obj)) law?

they must obey the encode(decode(obj)) === decode(encode(obj)) law?

That's not a hard law, just something you usually want.

import * as S from '@effect/schema/Schema'
import * as E from '@effect/data/Either'

const From = S.struct({ radius: S.number, _isVisible: S.optional(S.boolean) })
const To = S.struct({ radius: S.number, _isVisible: S.boolean })

const Circle = S.transformEither(From, To, S.decodeEither(To), ({ _isVisible, ...rest }) => E.right(rest))

console.log(S.decode(Circle)({ radius: 10, _isVisible: true })) // { _isVisible: true, radius: 10 }
console.log(S.decode(Circle)({ radius: 10 })) // throws
console.log(S.encode(Circle)({ radius: 10, _isVisible: true })) // { radius: 10 }

Thank you @gcanti it works, if I may I have a subsequent question:

if I need to apply the discriminant to the Circle in your example it fails with "extend" can only handle type literals or unions of type literals

const From = S.struct({ radius: S.number, _isVisible: S.optional(S.boolean) });
const To = S.struct({ radius: S.number, _isVisible: S.boolean });

const Circle = pipe(
  S.transformEither(From, To, S.decodeEither(To), ({ _isVisible, ...rest }) =>
    E.right(rest)
  ),
  S.attachPropertySignature("_tag", "Circle")
);

looks like a bug, here

pipe(schema, extend(struct({ [key]: literal(value) }))),
should be

-pipe(schema, extend(struct({ [key]: literal(value) })))
+pipe(to(schema), extend(struct({ [key]: literal(value) })))

I'll fix asap

amazing, thank you.

Patch released