Skip to content

Commit

Permalink
allow passing structs when encoding schema classes (Effect-TS#2128)
Browse files Browse the repository at this point in the history
Co-authored-by: gcanti <[email protected]>
  • Loading branch information
tim-smart and gcanti authored Feb 14, 2024
1 parent b415577 commit e572b07
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 27 deletions.
26 changes: 26 additions & 0 deletions .changeset/shaggy-trees-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
"@effect/schema": patch
---

allow passing structs when encoding schema classes

The following will no longer throw an error:

```ts
import * as S from "@effect/schema/Schema";

class C extends S.Class<C>()({
n: S.NumberFromString,
}) {
get b() {
return 1;
}
}
class D extends S.Class<D>()({
n: S.NumberFromString,
b: S.number,
}) {}

console.log(S.encodeSync(D)(new C({ n: 1 })));
// Output: { b: 1, n: '1' }
```
19 changes: 16 additions & 3 deletions packages/schema/src/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4896,15 +4896,15 @@ const makeClass = <A, I, R>(
Base: any,
additionalProps?: any
): any => {
const validator = Parser.validateSync(selfSchema)
const validate = Parser.validateSync(selfSchema)
return class extends Base {
constructor(props?: any, disableValidation = false) {
if (additionalProps !== undefined) {
props = { ...additionalProps, ...props }
}
if (disableValidation !== true) {
props = validator(props)
props = validate(props)
}
super(props, true)
}
Expand All @@ -4921,10 +4921,23 @@ const makeClass = <A, I, R>(
static get ast() {
const toSchema = to(selfSchema)
const encode = Parser.encodeUnknown(toSchema)
const pretty = Pretty.make(toSchema)
const arb = arbitrary.make(toSchema)
const declaration: Schema<any, any, never> = declare(
(input): input is any => input instanceof this,
[],
() => (input, _, ast) =>
input instanceof this ? ParseResult.succeed(input) : ParseResult.fail(ParseResult.type(ast, input)),
() => (input, _, ast) =>
input instanceof this
? ParseResult.succeed(input)
: ParseResult.mapError(
ParseResult.map(
encode(input),
(props) => new this(props, true)
),
() => ParseResult.type(ast, input)
),
{
identifier: this.name,
title: this.name,
Expand Down
85 changes: 61 additions & 24 deletions packages/schema/test/Schema/Class.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,32 +480,69 @@ describe("Schema > Class", () => {
)
})

it.skip("encode works with struct", async () => {
assert.doesNotThrow(() => S.encodeSync(Person)({ id: 1, name: "John" } as Person))
class A extends S.Class<A>()({
n: S.NumberFromString
}) {}
class B extends S.Class<B>()({
a: A
}) {}
await Util.expectEncodeSuccess(S.union(B, S.NumberFromString), 1, "1")
await Util.expectEncodeSuccess(B, { a: { n: 1 } }, { a: { n: "1" } })

class C extends S.Class<C>()({
n: S.NumberFromString
}) {
get b() {
return 1
describe("encode", () => {
it("struct + a class without methods nor getters", async () => {
class A extends S.Class<A>()({
n: S.NumberFromString
}) {}
await Util.expectEncodeSuccess(A, { n: 1 }, { n: "1" })
})

it("struct + a class with a getter", async () => {
class A extends S.Class<A>()({
n: S.NumberFromString
}) {
get s() {
return "s"
}
}
}
class D extends S.Class<D>()({
n: S.NumberFromString,
b: S.number
}) {}
await Util.expectEncodeSuccess(A, { n: 1 } as any, { n: "1" })
})

await Util.expectEncodeSuccess(D, new C({ n: 1 }), { n: "1", b: 1 })
it("struct + nested classes", async () => {
class A extends S.Class<A>()({
n: S.NumberFromString
}) {}
class B extends S.Class<B>()({
a: A
}) {}
await Util.expectEncodeSuccess(S.union(B, S.NumberFromString), 1, "1")
await Util.expectEncodeSuccess(B, { a: { n: 1 } }, { a: { n: "1" } })
})

class E extends S.Class<E>()({ a: S.string }) {}
await Util.expectEncodeFailure(S.to(E), null as any, "Expected E (an instance of E), actual null")
it("class + a class with a getter", async () => {
class A extends S.Class<A>()({
n: S.NumberFromString
}) {
get s() {
return "s"
}
}
class B extends S.Class<B>()({
n: S.NumberFromString,
s: S.string
}) {}

await Util.expectEncodeSuccess(B, new A({ n: 1 }), { n: "1", s: "s" })
})

describe("encode(S.to(Class))", () => {
it("should always return an instance", async () => {
class A extends S.Class<A>()({
n: S.NumberFromString
}) {}
const schema = S.to(A)
await Util.expectEncodeSuccess(schema, new A({ n: 1 }), new A({ n: 1 }))
await Util.expectEncodeSuccess(schema, { n: 1 }, new A({ n: 1 }))
})

it("should fail on bad values", async () => {
class A extends S.Class<A>()({
n: S.NumberFromString
}) {}
const schema = S.to(A)
await Util.expectEncodeFailure(schema, null as any, "Expected A (an instance of A), actual null")
})
})
})
})

0 comments on commit e572b07

Please sign in to comment.