feat: add email adapter DSN parsing#125
Conversation
Greptile SummaryThis PR introduces DSN-string parsing for email adapters (
Confidence Score: 3/5The PR has multiple real defects across both changed classes that would produce incorrect runtime behaviour before any fixes land. Three independent defects exist in the changed code: the Messenger failover loop silently swallows only Exception leaving TypeError and other Error subclasses uncaught; integer values returned directly by parse_url bypass range validation in parseIntOption; and a DSN test asserts the wrong exception message for smtp:// causing the test suite to fail. src/Utopia/Messaging/Messenger.php (catch clause and array indexing) and src/Utopia/Messaging/Adapter/Email.php (integer range validation) need the most attention; tests/Messaging/Adapter/Email/DsnTest.php has a failing assertion that should be corrected alongside the implementation fix. Important Files Changed
Reviews (4): Last reviewed commit: "feat: add email adapter DSN parsing" | Re-trigger Greptile |
| try { | ||
| return $adapter->send($message); | ||
| } catch (\Exception $e) { | ||
| $errors[] = $adapter->getName() | ||
| .' (adapter ' | ||
| .($index + 1) | ||
| .'): ' | ||
| .$e->getMessage(); | ||
| } |
There was a problem hiding this comment.
The catch block only catches
\Exception, so PHP \Error subclasses (\TypeError, \ValueError, \Error, etc.) thrown by an adapter will propagate uncaught and bypass the failover loop entirely. Catching \Throwable instead ensures all adapter-level failures are handled and the next adapter is tried.
| try { | |
| return $adapter->send($message); | |
| } catch (\Exception $e) { | |
| $errors[] = $adapter->getName() | |
| .' (adapter ' | |
| .($index + 1) | |
| .'): ' | |
| .$e->getMessage(); | |
| } | |
| try { | |
| return $adapter->send($message); | |
| } catch (\Throwable $e) { | |
| $errors[] = $adapter->getName() | |
| .' (adapter ' | |
| .($index + 1) | |
| .'): ' | |
| .$e->getMessage(); | |
| } |
| private static function parseIntOption(mixed $value, string $option): int | ||
| { | ||
| if (\is_int($value)) { | ||
| return $value; | ||
| } | ||
|
|
||
| if (! \is_string($value) || $value === '' || ! \ctype_digit($value)) { | ||
| throw new \InvalidArgumentException('Invalid "'.$option.'" option. Expected integer value.'); | ||
| } | ||
|
|
||
| return (int) $value; | ||
| } |
There was a problem hiding this comment.
parseIntOption skips range validation when the value is already a PHP int. parse_url returns $parts['port'] as a native integer, so a URL like smtp://host:0 or smtp://host:99999 bypasses the ctype_digit check entirely and passes an invalid port directly to the SMTP constructor. The same zero/overflow gap applies to query-string ports since ctype_digit("0") and ctype_digit("99999") both return true. Adding a positive-integer guard closes this for all call sites (port, timeout, timelimit).
| private static function parseIntOption(mixed $value, string $option): int | |
| { | |
| if (\is_int($value)) { | |
| return $value; | |
| } | |
| if (! \is_string($value) || $value === '' || ! \ctype_digit($value)) { | |
| throw new \InvalidArgumentException('Invalid "'.$option.'" option. Expected integer value.'); | |
| } | |
| return (int) $value; | |
| } | |
| private static function parseIntOption(mixed $value, string $option): int | |
| { | |
| if (\is_int($value)) { | |
| if ($value < 0) { | |
| throw new \InvalidArgumentException('Invalid "'.$option.'" option. Expected non-negative integer value.'); | |
| } | |
| return $value; | |
| } | |
| if (! \is_string($value) || $value === '' || ! \ctype_digit($value)) { | |
| throw new \InvalidArgumentException('Invalid "'.$option.'" option. Expected integer value.'); | |
| } | |
| return (int) $value; | |
| } |
| $this->validateAdapters($adapters); | ||
|
|
||
| $this->adapters = $adapters; |
There was a problem hiding this comment.
Messenger accepts any array of adapters, but later reads $adapters[0]. An associative or sparse array like ['primary' => $adapter] or [1 => $adapter] passes the validation loop and then validateAdapters() tries to call methods on a missing index. Normalizing the array before storing or validating it lets named or sparse adapter lists work consistently.
Add DSN (Data Source Name) parsing support for email adapters, enabling connection configuration via DSN strings.