Skip to content

Commit

Permalink
improve error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-smart committed Mar 18, 2024
1 parent cc6d6a5 commit 3bd5bef
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 71 deletions.
23 changes: 15 additions & 8 deletions src/Cache.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { TypeIdError } from "@effect/platform/Error"
import type { CacheDriver, ParentCacheDriver } from "dfx/Cache/driver"
import * as Effect from "effect/Effect"
import * as Option from "effect/Option"
import * as Schedule from "effect/Schedule"
import type * as Scope from "effect/Scope"
import * as Effect from "effect/Effect"
import * as Stream from "effect/Stream"
import type { CacheDriver, ParentCacheDriver } from "dfx/Cache/driver"

export * from "dfx/Cache/driver"
export {
Expand Down Expand Up @@ -253,10 +254,16 @@ export const make = <EOps, EDriver, EMiss, A>({
}),
)

export class CacheMissError {
readonly _tag = "CacheMissError"
constructor(
readonly cacheName: string,
readonly id: string,
) {}
export const CacheErrorTypeId = Symbol.for("dfx/Cache/CacheError")

export class CacheMissError extends TypeIdError(
CacheErrorTypeId,
"CacheMissError",
)<{
cacheName: string
id: string
}> {
get message() {
return `Cache miss for "${this.cacheName}" with id: ${this.id}`
}
}
8 changes: 6 additions & 2 deletions src/Cache/prelude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,10 @@ export const roles = <RM, EM, E>(
return yield* _(
makeWithParent({
driver,
id: _ => Effect.fail(new CacheMissError("RolesCache/id", _.id)),
id: _ =>
Effect.fail(
new CacheMissError({ cacheName: "RolesCache/id", id: _.id }),
),
ops: opsWithParent({
id: (a: Discord.Role) => a.id,
fromParent: Stream.map(gateway.fromDispatch("GUILD_CREATE"), g => [
Expand All @@ -262,7 +265,8 @@ export const roles = <RM, EM, E>(
g => g.id,
),
}),
onMiss: (_, id) => Effect.fail(new CacheMissError("RolesCache", id)),
onMiss: (_, id) =>
Effect.fail(new CacheMissError({ cacheName: "RolesCache", id })),
onParentMiss: guildId =>
rest
.getGuildRoles(guildId)
Expand Down
30 changes: 21 additions & 9 deletions src/DiscordREST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,25 @@ import * as Discord from "dfx/types"
import { LIB_VERSION } from "dfx/version"
import type { Scope } from "effect/Scope"
import * as Effectable from "effect/Effectable"
import { TypeIdError } from "@effect/platform/Error"

export class DiscordRESTError {
readonly _tag = "DiscordRESTError"
constructor(
readonly error: Http.error.HttpClientError,
readonly body?: unknown,
) {}
export const DiscordRESTErrorTypeId = Symbol.for(
"dfx/DiscordREST/DiscordRESTError",
)

export class DiscordRESTError extends TypeIdError(
DiscordRESTErrorTypeId,
"DiscordRESTError",
)<{
error: Http.error.HttpClientError
body?: unknown
}> {
get message() {
const httpMessage = this.error.message
return this.body !== undefined
? `${httpMessage}: ${JSON.stringify(this.body)}`
: httpMessage
}
}

const make = Effect.gen(function* (_) {
Expand Down Expand Up @@ -146,12 +158,12 @@ const make = Effect.gen(function* (_) {
Http.client.catchAll(error =>
error.reason === "StatusCode"
? error.response.json.pipe(
Effect.mapError(_ => new DiscordRESTError(_)),
Effect.mapError(_ => new DiscordRESTError({ error })),
Effect.flatMap(body =>
Effect.fail(new DiscordRESTError(error, body)),
Effect.fail(new DiscordRESTError({ error, body })),
),
)
: Effect.fail(new DiscordRESTError(error)),
: Effect.fail(new DiscordRESTError({ error })),
),
)

Expand Down
69 changes: 24 additions & 45 deletions src/Interactions/context.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { TypeIdError } from "@effect/platform/Error"
import * as IxHelpers from "dfx/Helpers/interactions"
import { InteractionsErrorTypeId } from "dfx/Interactions/error"
import type * as Discord from "dfx/types"
import type { NoSuchElementException } from "effect/Cause"
import { GenericTag } from "effect/Context"
import * as Effect from "effect/Effect"
import type * as HashMap from "effect/HashMap"
import * as Option from "effect/Option"
import * as Arr from "effect/ReadonlyArray"
import * as Effect from "effect/Effect"
import * as IxHelpers from "dfx/Helpers/interactions"
import type * as Discord from "dfx/types"

export interface DiscordInteraction {
readonly _: unique symbol
Expand Down Expand Up @@ -59,44 +62,31 @@ export const SubCommandContext = GenericTag<
SubCommandContext
>("dfx/Interactions/SubCommandContext")

export class ResolvedDataNotFound {
readonly _tag = "ResolvedDataNotFound"
constructor(
readonly data: Discord.Interaction,
readonly name?: string,
) {}
}

export const resolvedValues = <A>(
f: (id: Discord.Snowflake, data: Discord.ResolvedDatum) => A | undefined,
): Effect.Effect<ReadonlyArray<A>, ResolvedDataNotFound, DiscordInteraction> =>
Effect.flatMap(Interaction, ix =>
Effect.mapError(
IxHelpers.resolveValues(f)(ix),
() => new ResolvedDataNotFound(ix),
),
)
): Effect.Effect<
ReadonlyArray<A>,
NoSuchElementException,
DiscordInteraction
> => Effect.flatMap(Interaction, ix => IxHelpers.resolveValues(f)(ix))

export const resolved = <A>(
name: string,
f: (id: Discord.Snowflake, data: Discord.ResolvedDatum) => A | undefined,
): Effect.Effect<A, ResolvedDataNotFound, DiscordInteraction> =>
Effect.flatMap(Interaction, ix =>
Effect.mapError(
IxHelpers.resolveOptionValue(name, f)(ix),
() => new ResolvedDataNotFound(ix, name),
),
)
): Effect.Effect<A, NoSuchElementException, DiscordInteraction> =>
Effect.flatMap(Interaction, ix => IxHelpers.resolveOptionValue(name, f)(ix))

export const focusedOptionValue = Effect.map(
FocusedOptionContext,
_ => _.focusedOption.value ?? "",
)

export class SubCommandNotFound {
readonly _tag = "SubCommandNotFound"
constructor(readonly data: Discord.ApplicationCommandDatum) {}
}
export class SubCommandNotFound extends TypeIdError(
InteractionsErrorTypeId,
"SubCommandNotFound",
)<{
data: Discord.ApplicationCommandDatum
}> {}

export const handleSubCommands = <
NER extends Record<
Expand Down Expand Up @@ -128,7 +118,7 @@ export const handleSubCommands = <
Effect.flatMap(data =>
Effect.mapError(
Arr.findFirst(IxHelpers.allSubCommands(data), _ => !!commands[_.name]),
() => new SubCommandNotFound(data),
() => new SubCommandNotFound({ data }),
),
),
Effect.flatMap(command =>
Expand Down Expand Up @@ -188,18 +178,7 @@ export const modalValues = Effect.map(ModalSubmitData, IxHelpers.componentsMap)
export const modalValueOption = (name: string) =>
Effect.map(ModalSubmitData, IxHelpers.componentValue(name))

export class ModalValueNotFound {
readonly _tag = "ModalValueNotFound"
constructor(
readonly data: Discord.ModalSubmitDatum,
readonly name: string,
) {}
}

export const modalValue = (name: string) =>
Effect.flatMap(ModalSubmitData, data =>
Effect.mapError(
IxHelpers.componentValue(name)(data),
() => new ModalValueNotFound(data, name),
),
)
export const modalValue = (
name: string,
): Effect.Effect<string, NoSuchElementException, DiscordModalSubmit> =>
Effect.flatMap(ModalSubmitData, data => IxHelpers.componentValue(name)(data))
4 changes: 2 additions & 2 deletions src/Interactions/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import type {
DiscordInteraction,
DiscordMessageComponent,
DiscordModalSubmit,
ResolvedDataNotFound,
SubCommandContext,
} from "dfx/Interactions/context"
import type * as Discord from "dfx/types"
import type { NoSuchElementException } from "effect/Cause"

export type InteractionDefinition<R, E> =
| GlobalApplicationCommand<R, E>
Expand Down Expand Up @@ -143,7 +143,7 @@ export interface CommandHelper<A> {
resolve: <T>(
name: AllResolvables<A>["name"],
f: (id: Discord.Snowflake, data: Discord.ResolvedDatum) => T | undefined,
) => Effect.Effect<T, ResolvedDataNotFound, DiscordInteraction>
) => Effect.Effect<T, NoSuchElementException, DiscordInteraction>

option: (
name: AllCommandOptions<A>["name"],
Expand Down
3 changes: 3 additions & 0 deletions src/Interactions/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const InteractionsErrorTypeId = Symbol.for(
"dfx/Interactions/InteractionsError",
)
12 changes: 7 additions & 5 deletions src/Interactions/webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import type {
import { Interaction } from "dfx/Interactions/index"
import type * as Discord from "dfx/types"
import * as Verify from "discord-verify"
import { RefailError } from "@effect/platform/Error"
import { InteractionsErrorTypeId } from "dfx/Interactions/error"

export class BadWebhookSignature {
readonly _tag = "BadWebhookSignature"
Expand Down Expand Up @@ -88,10 +90,10 @@ export const layerConfig: (
config: Config.Config<MakeConfigOpts>,
) => Layer.effect(WebhookConfig, Effect.map(config, makeConfig))

export class WebhookParseError {
readonly _tag = "WebhookParseError"
constructor(readonly reason: unknown) {}
}
export class WebhookParseError extends RefailError(
InteractionsErrorTypeId,
"WebhookParseError",
)<{}> {}

const fromHeadersAndBody = (headers: Headers, body: string) =>
Effect.tap(WebhookConfig, ({ algorithm, crypto, publicKey }) =>
Expand All @@ -100,7 +102,7 @@ const fromHeadersAndBody = (headers: Headers, body: string) =>
Effect.flatMap(() =>
Effect.try({
try: () => JSON.parse(body) as Discord.Interaction,
catch: reason => new WebhookParseError(reason),
catch: error => new WebhookParseError({ error }),
}),
),
)
Expand Down

0 comments on commit 3bd5bef

Please sign in to comment.