Skip to content

feat: enhance exception handling with standardized error codes#155

Merged
abnegate merged 4 commits intomainfrom
migration-error-codes
Mar 10, 2026
Merged

feat: enhance exception handling with standardized error codes#155
abnegate merged 4 commits intomainfrom
migration-error-codes

Conversation

@ArnabChatterjee20k
Copy link
Contributor

@ArnabChatterjee20k ArnabChatterjee20k commented Mar 9, 2026

Added new constants for common HTTP error codes in the Exception class. Updated various parts of the codebase to utilize these constants for improved error handling, including specific validation and internal error messages in the Appwrite, JSON, and CSV source and destination classes.

Summary by CodeRabbit

  • Bug Fixes

    • Migration operations now emit consistent, numeric error codes and preserve original error context for validation, authorization, not-found, transfer, and internal failures—making errors clearer and more actionable.
  • Refactor

    • Centralized and standardized error classification across migration sources and destinations, including mapped status codes for database and transfer errors to improve reliability and diagnostics.

Added new constants for common HTTP error codes in the Exception class. Updated various parts of the codebase to utilize these constants for improved error handling, including specific validation and internal error messages in the Appwrite, JSON, and CSV source and destination classes.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 9, 2026

Walkthrough

Adds HTTP-like numeric error code constants to Migration\Exception and updates exception construction across migration sources, destinations, and Target to attach explicit integer codes and preserve original throwables as previous exceptions. Validation and internal-error cases are classified (e.g., CODE_VALIDATION, CODE_INTERNAL, CODE_NOT_FOUND, CODE_UNAUTHORIZED). Multiple sources (Appwrite, Firebase, CSV, JSON, NHost, Supabase, CSV/Local destinations) and Target now coerce, map, or compute status codes (NHost adds helper mapping methods) and rethrow errors with standardized codes while retaining original messages and chaining.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.91% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: adding standardized error codes to exception handling throughout the migration module.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch migration-error-codes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Migration/Sources/Appwrite.php`:
- Around line 209-213: The 403 branch currently throws a new Exception without
preserving the original code or previous exception; update the branch in
Appwrite.php so the thrown exception includes the 403 status code and the
original exception as the previous (mirror how the else branch forwards
$e->getCode() and $e as previous). Locate the conditional that checks if
($e->getCode() === 403) and change the throw to include the code and $e as the
previous exception so callers see the standardized code and original stack.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cd4a39a7-c212-4cf1-995d-91ad6eeed953

📥 Commits

Reviewing files that changed from the base of the PR and between c2d0169 and cd91e11.

📒 Files selected for processing (7)
  • src/Migration/Destinations/Appwrite.php
  • src/Migration/Destinations/JSON.php
  • src/Migration/Exception.php
  • src/Migration/Sources/Appwrite.php
  • src/Migration/Sources/CSV.php
  • src/Migration/Sources/JSON.php
  • src/Migration/Target.php

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/Migration/Sources/Supabase.php (2)

379-385: ⚠️ Potential issue | 🟠 Major

Use auth metadata in the exportUsers() error wrapper.

This catch still records user export failures as TYPE_BUCKET in GROUP_STORAGE, so the new status code is attached to the wrong resource and the wrong group.

💡 Minimal fix
         } catch (\Throwable $e) {
             $this->addError(new Exception(
-                Resource::TYPE_BUCKET,
-                Transfer::GROUP_STORAGE,
+                Resource::TYPE_USER,
+                Transfer::GROUP_AUTH,
                 message: $e->getMessage(),
                 code: parent::statusCodeFromThrowable($e),
                 previous: $e
             ));
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Sources/Supabase.php` around lines 379 - 385, The catch in
exportUsers() wraps user export failures with the wrong resource/group (uses
Resource::TYPE_BUCKET and Transfer::GROUP_STORAGE); change the addError call
inside the catch to use the auth/user metadata constants (e.g.
Resource::TYPE_USER and Transfer::GROUP_AUTH) while preserving message:
$e->getMessage(), code: parent::statusCodeFromThrowable($e), and previous: $e so
the status is attached to the correct resource/group.

467-473: ⚠️ Potential issue | 🟠 Major

Tag exportFiles() failures as file errors.

This wrapper still reports file-export failures as TYPE_BUCKET, which makes downstream error reporting collapse bucket and file failures together.

💡 Minimal fix
         } catch (\Throwable $e) {
             $this->addError(new Exception(
-                Resource::TYPE_BUCKET,
+                Resource::TYPE_FILE,
                 Transfer::GROUP_STORAGE,
                 message: $e->getMessage(),
                 code: parent::statusCodeFromThrowable($e),
                 previous: $e
             ));
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Sources/Supabase.php` around lines 467 - 473, The catch in
exportFiles() currently tags failures as Resource::TYPE_BUCKET; update that call
so the created Exception uses Resource::TYPE_FILE (or the codebase's
file-specific resource constant) instead of TYPE_BUCKET so exportFiles() errors
are classified as file errors; locate the addError(new Exception(...))
invocation in the exportFiles() catch block in Supabase.php and replace the
Resource::TYPE_BUCKET argument with the file resource constant while leaving the
other arguments (Transfer::GROUP_STORAGE, message, code, previous) unchanged.
src/Migration/Destinations/Appwrite.php (1)

667-717: ⚠️ Potential issue | 🟠 Major

Don't drop the standardized code during column rollback.

Line 701 now throws CODE_INTERNAL, but the rollback catch immediately replaces it with a fresh Utopia\Migration\Exception that has code 0 and no previous throwable. This path still loses the new classification.

💡 Minimal fix
-        } catch (\Throwable) {
+        } catch (\Throwable $e) {
             $this->database->deleteDocument('attributes', $column->getId());

             if (isset($twoWayAttribute)) {
                 $this->database->deleteDocument('attributes', $twoWayAttribute->getId());
             }

             throw new Exception(
                 resourceName: $resource->getName(),
                 resourceGroup: $resource->getGroup(),
                 resourceId: $resource->getId(),
                 message: 'Failed to create column',
+                code: (int) $e->getCode() ?: Exception::CODE_INTERNAL,
+                previous: $e,
             );
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Destinations/Appwrite.php` around lines 667 - 717, The rollback
catch currently swallows the original Throwable (from createAttribute or
createRelationship) and replaces it with a new Utopia\Migration\Exception with
code 0 and no previous throwable; change the catch to catch (\Throwable $e) and
when throwing the new Exception in the catch include the original $e as the
previous throwable (or at minimum preserve its code via $e->getCode()) so the
original error classification and stack are not lost — update the catch block
around createAttribute/createRelationship (referencing createAttribute,
createRelationship, deleteDocument and the thrown Exception) to attach $e as the
previous exception or copy its code into the new Exception.
src/Migration/Sources/Firebase.php (1)

39-43: ⚠️ Potential issue | 🟠 Major

Validate project_id before assigning $this->projectID.

Line 147 validation in report() is unreachable when project_id is missing. The constructor at line 42 directly dereferences $serviceAccount['project_id'] into the typed string property $projectID, which fails before report() executes. Move validation to the constructor to throw the standardized exception early.

Suggested fix
 public function __construct(array $serviceAccount)
 {
+    if (!isset($serviceAccount['project_id']) || !\is_string($serviceAccount['project_id']) || $serviceAccount['project_id'] === '') {
+        throw new \Exception('Invalid Firebase Service Account', Exception::CODE_UNAUTHORIZED);
+    }
+
     $this->serviceAccount = $serviceAccount;
     $this->projectID = $serviceAccount['project_id'];
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Migration/Sources/Firebase.php` around lines 39 - 43, The constructor in
class Firebase currently dereferences $serviceAccount['project_id'] into the
typed property $projectID which causes an unhandled error when project_id is
absent; move the validation from report() into the __construct(array
$serviceAccount) so you explicitly check isset($serviceAccount['project_id'])
and that it is a non-empty string, and if not throw the same standardized
exception used in report() (preserve the exception type/message) before
assigning $this->projectID, ensuring $this->serviceAccount is still set as
needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/Migration/Destinations/Appwrite.php`:
- Around line 667-717: The rollback catch currently swallows the original
Throwable (from createAttribute or createRelationship) and replaces it with a
new Utopia\Migration\Exception with code 0 and no previous throwable; change the
catch to catch (\Throwable $e) and when throwing the new Exception in the catch
include the original $e as the previous throwable (or at minimum preserve its
code via $e->getCode()) so the original error classification and stack are not
lost — update the catch block around createAttribute/createRelationship
(referencing createAttribute, createRelationship, deleteDocument and the thrown
Exception) to attach $e as the previous exception or copy its code into the new
Exception.

In `@src/Migration/Sources/Firebase.php`:
- Around line 39-43: The constructor in class Firebase currently dereferences
$serviceAccount['project_id'] into the typed property $projectID which causes an
unhandled error when project_id is absent; move the validation from report()
into the __construct(array $serviceAccount) so you explicitly check
isset($serviceAccount['project_id']) and that it is a non-empty string, and if
not throw the same standardized exception used in report() (preserve the
exception type/message) before assigning $this->projectID, ensuring
$this->serviceAccount is still set as needed.

In `@src/Migration/Sources/Supabase.php`:
- Around line 379-385: The catch in exportUsers() wraps user export failures
with the wrong resource/group (uses Resource::TYPE_BUCKET and
Transfer::GROUP_STORAGE); change the addError call inside the catch to use the
auth/user metadata constants (e.g. Resource::TYPE_USER and Transfer::GROUP_AUTH)
while preserving message: $e->getMessage(), code:
parent::statusCodeFromThrowable($e), and previous: $e so the status is attached
to the correct resource/group.
- Around line 467-473: The catch in exportFiles() currently tags failures as
Resource::TYPE_BUCKET; update that call so the created Exception uses
Resource::TYPE_FILE (or the codebase's file-specific resource constant) instead
of TYPE_BUCKET so exportFiles() errors are classified as file errors; locate the
addError(new Exception(...)) invocation in the exportFiles() catch block in
Supabase.php and replace the Resource::TYPE_BUCKET argument with the file
resource constant while leaving the other arguments (Transfer::GROUP_STORAGE,
message, code, previous) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4c73f54a-0e4c-47b3-8406-45b1a035fc08

📥 Commits

Reviewing files that changed from the base of the PR and between 864a351 and 9e70312.

📒 Files selected for processing (10)
  • src/Migration/Destinations/Appwrite.php
  • src/Migration/Destinations/CSV.php
  • src/Migration/Destinations/Local.php
  • src/Migration/Sources/Appwrite.php
  • src/Migration/Sources/Appwrite/Reader/Database.php
  • src/Migration/Sources/CSV.php
  • src/Migration/Sources/Firebase.php
  • src/Migration/Sources/JSON.php
  • src/Migration/Sources/NHost.php
  • src/Migration/Sources/Supabase.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/Migration/Sources/CSV.php
  • src/Migration/Sources/JSON.php

@abnegate abnegate merged commit 97583ae into main Mar 10, 2026
4 checks passed
@abnegate abnegate deleted the migration-error-codes branch March 10, 2026 06:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants