Report the Azure error code as the Flysystem failure reason - #71
Conversation
|
Note on the red PHPStan (8.5) check: it reports the same 4 pre-existing |
The adapter passed only `previous:` when converting SDK failures into Flysystem exceptions, leaving reason() empty. BlobStorageException already carries the Azure error code, so callers had to unwrap getPrevious() and type-check against the SDK to tell a missing blob apart from an authorization or throttling failure. Route those throw sites through a helper that prefixes the error code to the service message. checksum() already used getMessage(), so this also makes the file internally consistent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9fa1d4c to
843a610
Compare
|
Hi Alex — thanks for the thoughtful PR and the detailed explanation. During review, I noticed that deleteDirectory() could receive a BlobStorageException wrapped inside UnableToListContents, which meant the Azure error code was still lost in that path. |
d707c64
into
php-oss-for-azure:main
Problem
AzureBlobStorageAdapterconverts SDK failures into Flysystem exceptions using only the namedprevious:argument:UnableToReadFile::fromLocation()takes(string $location, string $reason = '', ?Throwable $previous = null), soreason()is left empty and the exception message is just:That is unfortunate here specifically, because the SDK already has the information callers need.
BlobStorageExceptioncarrieserrorCode,errorCodeValue,statusCodeandrequestId, so the difference betweenBlobNotFound,AuthorizationPermissionMismatchandServerBusyis known at the throw site and then discarded from everything exceptgetPrevious().The practical consequence is that a caller wanting to distinguish "blob is missing" (fall back / regenerate) from "auth or throttling failure" (surface, retry, alert) has to unwrap
getPrevious()and type-check against the SDK — which defeats the point of coding against the Flysystem interface. It also matters inside Flysystem itself:MountManagerre-throws with$exception->reason(), so an empty reason propagates as an empty reason.checksum()in this same file already passes$e->getMessage(), so the adapter was internally inconsistent too.Change
Route the throw sites through a small private helper that prefixes the Azure error code to the service message when the cause is a
BlobStorageException, and falls back togetMessage()otherwise:Applied to
write,read,readStream,delete,deleteDirectory,mimeType,lastModified,fileSizeandchecksum.$previousis still set everywhere, so nothing that inspects it today breaks. No signature or behavior changes beyond the reason string being populated.This also aligns the adapter with the other Flysystem adapters —
Local,GoogleCloudStorageand the built-inAzureBlobStorageall populate the reason. I've opened thephpleague/flysystem#1913 to fix the same gap inAwsS3V3Adapter.Tests
tests/Storage/BlobFlysystem/Integration):it_reports_the_azure_error_code_as_the_failure_reason()reads a missing blob and assertsBlobNotFoundappears inreason().tests/Storage/BlobFlysystem/Unit): extended the existing invalid-transfer-size test to assert the reason is populated on the non-BlobStorageExceptionpath.Verified locally against Azurite (
mcr.microsoft.com/azure-storage/azurite) on PHP 8.3:vendor/bin/phpunit tests/Storage/BlobFlysystem— 71 tests, 120 assertions, all passing.Failed asserting that '' contains "BlobNotFound"), so it is not vacuous.vendor/bin/pint --test— passed.vendor/bin/phpstan --no-progress --memory-limit=2G— the 4 reported errors are pre-existing on a clean checkout (property.unusedintests/Storage/Blob/{Functional,Integration}/BlobClientTest.php); none are in the files touched here.CHANGELOG.mdfor the package is updated underUnreleased. Nothing here is breaking, soUPGRADE.mdis untouched.Unrelated observation
While in the file:
fileSize()throwsUnableToRetrieveMetadata::lastModified($path, ...)rather than::fileSize(...), so a failingfileSize()call reports itself as a last-modified failure. I left it alone to keep this PR to one concern — happy to send a separate PR if you'd like.