| 1 | import * as Data from "effect/Data"; |
| 2 | |
| 3 | /** A problem the CA attached to an order or one of its identifiers. */ |
| 4 | export interface IdentifierProblem { |
| 5 | /** The identifier the problem concerns, when the CA said. */ |
| 6 | readonly identifier?: string | undefined; |
| 7 | /** The `urn:ietf:params:acme:error:*` type. */ |
| 8 | readonly type?: string | undefined; |
| 9 | /** Human-readable detail from the CA. */ |
| 10 | readonly detail?: string | undefined; |
| 11 | } |
| 12 | |
| 13 | /** |
| 14 | * The CA moved the order to `invalid`. Carries the problem documents the |
| 15 | * order and its authorizations reported, one per failed identifier where |
| 16 | * the CA said which. |
| 17 | */ |
| 18 | export class OrderInvalid extends Data.TaggedError("ACME.OrderInvalid")<{ |
| 19 | readonly orderUrl: string; |
| 20 | readonly problems: ReadonlyArray<IdentifierProblem>; |
| 21 | }> { |
| 22 | override get message() { |
| 23 | const details = this.problems |
| 24 | .map((p) => [p.identifier, p.type, p.detail].filter(Boolean).join(": ")) |
| 25 | .join("; "); |
| 26 | return `ACME order ${this.orderUrl} is invalid${details ? `: ${details}` : ""}`; |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | /** The CA rejected the DNS-01 challenge for one identifier. */ |
| 31 | export class ChallengeFailed extends Data.TaggedError("ACME.ChallengeFailed")<{ |
| 32 | readonly identifier: string; |
| 33 | readonly type?: string | undefined; |
| 34 | readonly detail?: string | undefined; |
| 35 | }> { |
| 36 | override get message() { |
| 37 | return `DNS-01 challenge for ${this.identifier} failed${this.detail ? `: ${this.detail}` : ""}`; |
| 38 | } |
| 39 | } |
| 40 | |
| 41 | /** The CA offered no `dns-01` challenge for an identifier. */ |
| 42 | export class ChallengeUnsupported extends Data.TaggedError( |
| 43 | "ACME.ChallengeUnsupported", |
| 44 | )<{ |
| 45 | readonly identifier: string; |
| 46 | readonly offered: ReadonlyArray<string>; |
| 47 | }> { |
| 48 | override get message() { |
| 49 | return `The CA offered no dns-01 challenge for ${this.identifier} (offered: ${this.offered.join(", ") || "none"})`; |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | /** The order never reached a terminal state within the polling budget. */ |
| 54 | export class OrderTimeout extends Data.TaggedError("ACME.OrderTimeout")<{ |
| 55 | readonly orderUrl: string; |
| 56 | readonly status: string; |
| 57 | }> { |
| 58 | override get message() { |
| 59 | return `ACME order ${this.orderUrl} is still "${this.status}" after the polling budget`; |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | /** The `_acme-challenge` TXT record never became visible to public resolvers. */ |
| 64 | export class DnsPropagationTimeout extends Data.TaggedError( |
| 65 | "ACME.DnsPropagationTimeout", |
| 66 | )<{ |
| 67 | readonly fqdn: string; |
| 68 | readonly value: string; |
| 69 | }> { |
| 70 | override get message() { |
| 71 | return `TXT ${this.fqdn} did not propagate within the wait budget`; |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | /** A DNS solver could not publish or remove a challenge record. */ |
| 76 | export class DnsSolverError extends Data.TaggedError("ACME.DnsSolverError")<{ |
| 77 | readonly message: string; |
| 78 | readonly cause?: unknown; |
| 79 | }> {} |
| 80 | |
| 81 | /** A solver descriptor names a `type` no registered DNS provider handles. */ |
| 82 | export class DnsSolverNotRegistered extends Data.TaggedError( |
| 83 | "ACME.DnsSolverNotRegistered", |
| 84 | )<{ |
| 85 | readonly type: string; |
| 86 | }> { |
| 87 | override get message() { |
| 88 | return `No DNS-01 solver is registered for "${this.type}". Include the DNS provider's providers() layer (e.g. Cloudflare.providers()) in the stack.`; |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | /** Key generation, CSR encoding or certificate parsing failed. */ |
| 93 | export class PkiError extends Data.TaggedError("ACME.PkiError")<{ |
| 94 | readonly message: string; |
| 95 | readonly cause?: unknown; |
| 96 | }> {} |