Skip updates that do not fit their model instead of crashing

#17 made unsupported update types silent: createUpdate() throws a
LogicException, and createUpdateList() and WebhookHandler log it and
skip the update. An update of a known type whose payload no longer
fits the model (the API made a field optional, added an enum value)
fails earlier, with a TypeError or ValueError from the model
constructor, and nothing catches those:

- createUpdateList() lets it out of getUpdates(), and
  LongPollingHandler::handle() catches only \Exception, so the loop
  stops and restarts into the same batch
- WebhookHandler answers 500 and MAX keeps retrying

createUpdate() now turns TypeError and ValueError into a
LogicException (the original one kept as previous) and logs a warning
with the payload, so the existing silent mode skips such an update.
Errors thrown by user handlers are not affected.
This commit is contained in:
Timofey
2026-09-28 00:34:08 +05:00
parent 9bfe60a3a2
commit a50e5be823
3 changed files with 117 additions and 21 deletions
+37 -21
View File
@@ -75,6 +75,8 @@ use LogicException;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use ReflectionException;
use TypeError;
use ValueError;
/**
* Creates DTOs from raw associative arrays returned by the API client.
@@ -375,27 +377,41 @@ readonly class ModelFactory
*/
public function createUpdate(array $data): AbstractUpdate
{
return match (UpdateType::tryFrom($data['update_type'] ?? '')) {
UpdateType::MessageCreated => MessageCreatedUpdate::fromArray($data),
UpdateType::MessageCallback => MessageCallbackUpdate::fromArray($data),
UpdateType::MessageEdited => MessageEditedUpdate::fromArray($data),
UpdateType::MessageRemoved => MessageRemovedUpdate::fromArray($data),
UpdateType::BotAdded => BotAddedToChatUpdate::fromArray($data),
UpdateType::BotRemoved => BotRemovedFromChatUpdate::fromArray($data),
UpdateType::DialogMuted => DialogMutedUpdate::fromArray($data),
UpdateType::DialogUnmuted => DialogUnmutedUpdate::fromArray($data),
UpdateType::DialogCleared => DialogClearedUpdate::fromArray($data),
UpdateType::DialogRemoved => DialogRemovedUpdate::fromArray($data),
UpdateType::UserAdded => UserAddedToChatUpdate::fromArray($data),
UpdateType::UserRemoved => UserRemovedFromChatUpdate::fromArray($data),
UpdateType::BotStarted => BotStartedUpdate::fromArray($data),
UpdateType::BotStopped => BotStoppedUpdate::fromArray($data),
UpdateType::ChatTitleChanged => ChatTitleChangedUpdate::fromArray($data),
UpdateType::MessageChatCreated => MessageChatCreatedUpdate::fromArray($data),
default => throw new LogicException(
'Unknown or unsupported update type received: ' . ($data['update_type'] ?? 'none')
),
};
try {
return match (UpdateType::tryFrom($data['update_type'] ?? '')) {
UpdateType::MessageCreated => MessageCreatedUpdate::fromArray($data),
UpdateType::MessageCallback => MessageCallbackUpdate::fromArray($data),
UpdateType::MessageEdited => MessageEditedUpdate::fromArray($data),
UpdateType::MessageRemoved => MessageRemovedUpdate::fromArray($data),
UpdateType::BotAdded => BotAddedToChatUpdate::fromArray($data),
UpdateType::BotRemoved => BotRemovedFromChatUpdate::fromArray($data),
UpdateType::DialogMuted => DialogMutedUpdate::fromArray($data),
UpdateType::DialogUnmuted => DialogUnmutedUpdate::fromArray($data),
UpdateType::DialogCleared => DialogClearedUpdate::fromArray($data),
UpdateType::DialogRemoved => DialogRemovedUpdate::fromArray($data),
UpdateType::UserAdded => UserAddedToChatUpdate::fromArray($data),
UpdateType::UserRemoved => UserRemovedFromChatUpdate::fromArray($data),
UpdateType::BotStarted => BotStartedUpdate::fromArray($data),
UpdateType::BotStopped => BotStoppedUpdate::fromArray($data),
UpdateType::ChatTitleChanged => ChatTitleChangedUpdate::fromArray($data),
UpdateType::MessageChatCreated => MessageChatCreatedUpdate::fromArray($data),
default => throw new LogicException(
'Unknown or unsupported update type received: ' . ($data['update_type'] ?? 'none')
),
};
} catch (TypeError|ValueError $e) {
// The update type is known, but its payload does not fit the model: the API has
// changed a field. Skip it like an unsupported update, so a webhook or a long-polling
// loop does not stop on it, but log it louder.
$message = sprintf(
'Failed to parse update of type %s: %s',
is_string($data['update_type'] ?? null) ? $data['update_type'] : 'none',
$e->getMessage(),
);
$this->logger->warning($message, ['payload' => $data, 'exception' => $e]);
throw new LogicException($message, 0, $e);
}
}
/**