Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

preserve defect information & TaggedError message #2359

Merged
merged 8 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fluffy-bananas-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/schema": patch
---

preserve defect information in Cause.Die
5 changes: 5 additions & 0 deletions .changeset/real-peaches-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/schema": patch
---

use provided message in Schema.TaggedError .toString
54 changes: 41 additions & 13 deletions packages/schema/src/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6044,16 +6044,14 @@ export const TaggedClass = <Self = never>(identifier?: string) =>
{},
{}
> =>
{
return makeClass({
makeClass({
kind: "TaggedClass",
identifier: identifier ?? tag,
fields: extendFields({ _tag: literal(tag) }, fields),
Base: Data.Class,
tag: { _tag: tag },
annotations
})
}

/**
* @category classes
Expand All @@ -6076,13 +6074,25 @@ export const TaggedError = <Self = never>(identifier?: string) =>
Cause.YieldableError
> =>
{
class Base extends Data.Error {}
;(Base.prototype as any).name = tag
return makeClass({
kind: "TaggedError",
identifier: identifier ?? tag,
fields: extendFields({ _tag: literal(tag) }, fields),
Base: Data.Error,
Base,
tag: { _tag: tag },
annotations
annotations,
toStringOverride(self) {
if (!(Predicate.isString(self.message) && self.message.length > 0)) {
return Pretty.make(self.constructor as any)(self)
}
let message = `${self._tag}: ${self.message}`
if (Predicate.isString(self.stack)) {
message = `${message}\n${self.stack.split("\n").slice(1).join("\n")}`
}
return message
}
})
}

Expand Down Expand Up @@ -6173,14 +6183,15 @@ const extendFields = (a: Struct.Fields, b: Struct.Fields): Struct.Fields => {
return out
}

const makeClass = ({ Base, annotations, fields, fromSchema, identifier, kind, tag }: {
const makeClass = ({ Base, annotations, fields, fromSchema, identifier, kind, tag, toStringOverride }: {
kind: string
identifier: string
fields: Struct.Fields
Base: new(...args: ReadonlyArray<any>) => any
fromSchema?: Schema.Any | undefined
tag?: { _tag: AST.LiteralValue } | undefined
annotations?: Annotations.Schema<any> | undefined
toStringOverride?: ((self: any) => string) | undefined
}): any => {
const classSymbol = Symbol.for(`@effect/schema/${kind}/${identifier}`)
const schema = fromSchema ?? struct(fields)
Expand All @@ -6207,7 +6218,7 @@ const makeClass = ({ Base, annotations, fields, fromSchema, identifier, kind, ta
}

toString() {
return Pretty.make(this.constructor as any)(this)
return toStringOverride !== undefined ? toStringOverride(this) : Pretty.make(this.constructor as any)(this)
}

static pipe() {
Expand Down Expand Up @@ -6636,13 +6647,30 @@ function causeEncode<E>(cause: Cause.Cause<E>): CauseEncoded<E> {
}
}

const causeDefectPretty: Schema<unknown> = transform(
/**
* @category Cause transformations
* @since 1.0.0
*/
export const causeDefectUnknown: $unknown = transform(
unknown,
unknown,
identity,
(u) => {
if (Predicate.isObject(u) && "message" in u && typeof u.message === "string") {
const err = new Error(u.message, { cause: u })
if ("name" in u && typeof u.name === "string") {
err.name = u.name
}
err.stack = "stack" in u && typeof u.stack === "string" ? u.stack : ""
return err
}
return String(u)
},
(defect) => {
if (Predicate.isObject(defect)) {
return Cause.pretty(Cause.die(defect))
if (defect instanceof Error) {
return {
name: defect.name,
message: defect.message
}
}
return String(defect)
}
Expand All @@ -6665,7 +6693,7 @@ export interface cause<E extends Schema.All, DR> extends
* @category Cause transformations
* @since 1.0.0
*/
export const cause = <E extends Schema.All, DR = never>({ defect = causeDefectPretty, error }: {
export const cause = <E extends Schema.All, DR = never>({ defect = causeDefectUnknown, error }: {
readonly error: E
readonly defect?: Schema<unknown, unknown, DR> | undefined
}): cause<E, DR> => {
Expand Down Expand Up @@ -6817,7 +6845,7 @@ export interface exit<A extends Schema.All, E extends Schema.All, DR> extends
* @since 1.0.0
*/
export const exit = <A extends Schema.All, E extends Schema.All, DR = never>(
{ defect = causeDefectPretty, failure, success }: {
{ defect = causeDefectUnknown, failure, success }: {
readonly failure: E
readonly success: A
readonly defect?: Schema<unknown, unknown, DR> | undefined
Expand Down
11 changes: 6 additions & 5 deletions packages/schema/test/Cause/cause.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe("Cause > cause", () => {
_tag: "Die",
defect: { stack: "fail", message: "error" }
},
Cause.die({ stack: "fail", message: "error" })
Cause.die(new Error("error"))
)
await Util.expectDecodeUnknownSuccess(
schema,
Expand Down Expand Up @@ -140,12 +140,13 @@ describe("Cause > cause", () => {

let failWithStack = S.encodeSync(schema)(Cause.die(new Error("fail")))
assert(failWithStack._tag === "Die")
assert.include(failWithStack.defect, "Error: fail")
assert.include(failWithStack.defect, "cause.test.ts")
assert.deepStrictEqual(failWithStack.defect, {
name: "Error",
message: "fail"
})

failWithStack = S.encodeSync(schemaUnknown)(Cause.die(new Error("fail")))
assert(failWithStack._tag === "Die")
assert.strictEqual((failWithStack.defect as any).message, "fail")
assert.include((failWithStack.defect as any).stack, "cause.test.ts")
assert.strictEqual((failWithStack.defect as Error).message, "fail")
})
})
18 changes: 18 additions & 0 deletions packages/schema/test/Schema/Class.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,24 @@ describe("Schema > Class APIs", () => {
expect(err.id).toEqual(1)
})

it("TaggedError/message", () => {
class MyError extends S.TaggedError<MyError>()("MyError", {
id: S.number
}) {
get message() {
return `bad id: ${this.id}`
}
}

const err = new MyError({ id: 1 })

expect(String(err)).include(`MyError: bad id: 1`)
expect(String(err)).toContain("Class.test.ts:")
expect(err.stack).toContain("Class.test.ts:")
expect(err._tag).toEqual("MyError")
expect(err.id).toEqual(1)
})

describe("TaggedRequest", () => {
it("should expose the fields", () => {
class TRA extends S.TaggedRequest<TRA>()("TRA", S.string, S.number, {
Expand Down