status = $status; $this->errorCode = self::normaliseCode( $errorCode ); $this->retryable = $retryable; $this->httpStatus = $httpStatus; $this->responseKind = $responseKind; $this->entryRef = substr( $entryRef, 0, 100 ); $this->evidence = self::normaliseCode( $evidence ); } /** * The action demonstrably completed. * * @param string $evidence What the success is based on, e.g. * `wp_mail_true`, `entry_id`, `no_exception`. * @param string $entryRef ID of a record the action created, if any. */ public static function succeeded( string $evidence, string $entryRef = '' ): self { return new self( FollowUpStatus::SUCCEEDED, '', false, 0, self::RESPONSE_NONE, $entryRef, $evidence ); } /** * The action was deliberately not executed. */ public static function skipped( string $reason ): self { return new self( FollowUpStatus::SKIPPED, $reason, false, 0, self::RESPONSE_NONE, '', '' ); } /** * The action did not run and running it again is safe. */ public static function failedRetryable( string $errorCode ): self { return new self( FollowUpStatus::FAILED_RETRYABLE, $errorCode, true, 0, self::RESPONSE_NONE, '', '' ); } /** * The action failed; retrying without fixing the cause is pointless. */ public static function failedPermanent( string $errorCode, string $entryRef = '' ): self { return new self( FollowUpStatus::FAILED_PERMANENT, $errorCode, false, 0, self::RESPONSE_NONE, $entryRef, '' ); } /** * The action may or may not have run. */ public static function unknown( string $errorCode ): self { return new self( FollowUpStatus::UNKNOWN, $errorCode, false, 0, self::RESPONSE_NONE, '', '' ); } /** * Copy with transport diagnostics attached. */ public function withTransport( int $httpStatus, string $responseKind ): self { $copy = clone $this; $copy->httpStatus = $httpStatus; $copy->responseKind = $responseKind; return $copy; } /** * Copy with a different status, keeping diagnostics — used when a * retryable failure has exhausted its retries. */ public function withStatus( string $status ): self { $copy = clone $this; $copy->status = $status; $copy->retryable = $status === FollowUpStatus::FAILED_RETRYABLE; return $copy; } public function getStatus(): string { return $this->status; } public function getErrorCode(): string { return $this->errorCode; } public function isRetryable(): bool { return $this->retryable; } public function getHttpStatus(): int { return $this->httpStatus; } public function getResponseKind(): string { return $this->responseKind; } public function getEntryRef(): string { return $this->entryRef; } public function getEvidence(): string { return $this->evidence; } /** * @return array */ public function toArray(): array { return array( 'status' => $this->status, 'error_code' => $this->errorCode, 'retryable' => $this->retryable, 'http_status' => $this->httpStatus, 'response_kind' => $this->responseKind, 'entry_ref' => $this->entryRef, 'evidence' => $this->evidence, ); } /** * Error codes are machine identifiers: [a-z0-9_:.-], at most 64 * characters. Anything else is replaced as a whole rather than * "cleaned" — replacing single characters would turn * `max@example.com` into a still readable `max_example.com`. A code * is never the place for a message. */ private static function normaliseCode( string $code ): string { if ( $code === '' ) { return ''; } $code = strtolower( $code ); if ( ! preg_match( '/^[a-z0-9_:.\-]{1,64}$/', $code ) ) { return 'unclassified_error'; } return $code; } }