Files
rspade_system/app/RSpade/man/ajax_error_handling.txt
root 77b4d10af8 Refactor filename naming system and apply convention-based renames
Standardize settings file naming and relocate documentation files
Fix code quality violations from rsx:check
Reorganize user_management directory into logical subdirectories
Move Quill Bundle to core and align with Tom Select pattern
Simplify Site Settings page to focus on core site information
Complete Phase 5: Multi-tenant authentication with login flow and site selection
Add route query parameter rule and synchronize filename validation logic
Fix critical bug in UpdateNpmCommand causing missing JavaScript stubs
Implement filename convention rule and resolve VS Code auto-rename conflict
Implement js-sanitizer RPC server to eliminate 900+ Node.js process spawns
Implement RPC server architecture for JavaScript parsing
WIP: Add RPC server infrastructure for JS parsing (partial implementation)
Update jqhtml terminology from destroy to stop, fix datagrid DOM preservation
Add JQHTML-CLASS-01 rule and fix redundant class names
Improve code quality rules and resolve violations
Remove legacy fatal error format in favor of unified 'fatal' error type
Filter internal keys from window.rsxapp output
Update button styling and comprehensive form/modal documentation
Add conditional fly-in animation for modals
Fix non-deterministic bundle compilation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 19:10:02 +00:00

433 lines
13 KiB
Plaintext
Executable File

AJAX ERROR HANDLING
===================
OVERVIEW
RSpade implements a standardized Ajax error handling architecture that:
- Returns HTTP 200 for ALL Ajax responses (success and errors)
- Encodes success/failure in response body via _success field
- Differentiates between business logic errors and fatal PHP errors
- Provides consistent error display across dev and production modes
- Prepares for future batch request architecture
RESPONSE FORMATS
All Ajax endpoints return HTTP 200 with one of these formats:
Success Response
----------------
{
"_success": true,
"_ajax_return_value": { /* developer's return value */ },
"console_debug": [ /* optional debug messages */ ]
}
Business Logic Errors (Validation, Auth)
-----------------------------------------
{
"_success": false,
"error_type": "response_form_error|response_auth_required|response_unauthorized",
"reason": "User-friendly message",
"details": { /* optional, e.g., field-specific validation errors */ },
"console_debug": [ /* optional debug messages */ ]
}
Fatal PHP Errors (Uncaught Exceptions)
---------------------------------------
{
"_success": false,
"error_type": "fatal",
"error": {
"file": "path/to/file.php",
"line": 123,
"error": "Full exception message with SQL/context",
"backtrace": [
{
"file": "path/to/file.php",
"line": 456,
"function": "methodName",
"class": "ClassName",
"type": "->"
},
/* ... up to 10 frames */
]
},
"console_debug": [ /* optional debug messages */ ]
}
Network/Infrastructure Errors
------------------------------
No response body - PHP never executed
xhr.status === 0 or timeout
Only occurs when nginx/PHP-FPM is down
HTTP STATUS CODE STRATEGY
ALL Ajax Responses: HTTP 200
----------------------------
- Success: HTTP 200 with _success: true
- Validation errors: HTTP 200 with _success: false
- Fatal PHP errors: HTTP 200 with _success: false
- Auth errors: HTTP 200 with _success: false
Non-200 Responses
-----------------
Only when server infrastructure is completely down (nginx crashed,
PHP-FPM dead, network failure). These bypass PHP entirely.
Rationale
---------
- Batch requests need uniform status codes (some succeed, some fail)
- Always get parseable response body
- Simpler client handling - check _success field, not HTTP status
- Non-200 only means "couldn't reach PHP at all"
ERROR TYPES
fatal
-----
Uncaught PHP exceptions, database errors, bugs in code.
Dev mode: Shows file/line/error/backtrace
Prod mode: Generic "An error occurred" message
response_form_error (validation)
--------------------------------
Validation failures with field-specific details.
Used via: return response_form_error($reason, $details);
Example:
return response_form_error('Validation failed', [
'email' => 'Email address is required',
'phone' => 'Invalid phone number format'
]);
response_auth_required
----------------------
User not logged in, needs authentication.
Used via: return response_auth_required($reason, $redirect);
response_unauthorized
---------------------
User logged in but lacks permission for this action.
Used via: return response_unauthorized($reason, $redirect);
network
-------
Client-side only. Server unreachable (xhr.status === 0).
SERVER-SIDE IMPLEMENTATION
Ajax Endpoints Return Plain Data
---------------------------------
Endpoint methods return plain arrays/objects. Framework wraps:
// Your endpoint
#[Ajax_Endpoint]
public static function get_user(Request $request, array $params = []): array
{
$user = User::find($params['id']);
if (!$user) {
return response_form_error('User not found', ['id' => 'Invalid']);
}
return [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email
];
}
// Framework wraps response:
{
"_success": true,
"_ajax_return_value": {
"id": 123,
"name": "John Doe",
"email": "john@example.com"
}
}
Never Return _success Field Manually
-------------------------------------
The _success field is RESERVED for framework wrapping. If your endpoint
returns an array with a boolean _success key, the framework throws an error:
// ❌ WRONG - Framework throws exception
return ['_success' => true, 'data' => $user];
// ✅ CORRECT - Let framework wrap
return ['data' => $user];
Exception Handling - Let It Bubble
-----------------------------------
Don't wrap database/framework operations in try/catch. Let exceptions
bubble to the global handler which formats them properly.
// ❌ WRONG
try {
$user->save();
} catch (Exception $e) {
return ['_success' => false, 'message' => $e->getMessage()];
}
// ✅ CORRECT
$user->save(); // Let it throw - framework catches and formats
When Try/Catch IS Appropriate
------------------------------
Use try/catch for EXPECTED failures:
- File uploads (may fail validation)
- External API calls (network issues expected)
- User input parsing (malformed data expected)
Don't use for:
- Database operations (schema errors are bugs)
- Framework operations (let global handler catch)
- "Just in case" wrapping
CLIENT-SIDE IMPLEMENTATION
Ajax.js Automatically Unwraps Responses
----------------------------------------
When you call an Ajax endpoint, Ajax.js:
1. Makes HTTP request
2. Receives 200 response with JSON body
3. Checks _success field:
- _success: true → Resolve promise with _ajax_return_value
- _success: false → Reject promise with Error object
4. Network errors also reject with Error object
Exception-Driven Pattern
------------------------
Ajax calls work like any async operation:
- Success: Promise resolves with unwrapped value
- Failure: Promise rejects with Error object
try {
const user = await User_Controller.get_user(123);
// user is the _ajax_return_value, already unwrapped
console.log(user.name);
} catch (error) {
// error is standard Error object with extra properties
console.error(error.message); // User-displayable message
}
Error Object Structure
----------------------
Rejected promises receive standard Error objects with additional properties:
error.type - 'fatal'|'form_error'|'auth_required'|
'unauthorized'|'network'
error.message - User-displayable message
error.details - Full error data from server
(for form_error: field-specific errors)
(for fatal: file/line/backtrace)
Automatic Error Handling
------------------------
Uncaught Ajax errors automatically display via Modal.error():
// No try/catch - error shows in modal automatically
const user = await User_Controller.get_user(123);
Global unhandled rejection handler detects uncaught Ajax promises and
shows red danger modal with error details. This prevents silent failures.
Rsx_Form Error Handling
-----------------------
Rsx_Form components have built-in error handling:
const result = await Modal.form({
title: 'Add User',
component: 'Add_User_Form',
on_submit: async (form) => {
try {
const values = form.vals();
const result = await Controller.add_user(values);
return result; // Success - close modal
} catch (error) {
await form.render_error(error);
return false; // Keep modal open
}
}
});
Rsx_Form.render_error() handles all error types:
- form_error: Shows inline field errors + unmatched errors alert
- fatal/network/auth: Shows error in form's error container
Form_Utils Error Display
------------------------
For non-Rsx_Form forms, use Form_Utils.apply_form_errors():
try {
const result = await Controller.save_user(form_data);
await Modal.alert('User saved!');
} catch (error) {
if (error.type === 'form_error') {
await Form_Utils.apply_form_errors($form, error.details);
} else {
Rsx.render_error(error, '#error_container');
}
}
Form_Utils.apply_form_errors() automatically:
- Matches errors to fields by name attribute
- Adds .is-invalid class and inline error text
- Shows alert ONLY for errors that couldn't match to fields
- No duplicate alerts for matched fields
Generic Error Display
---------------------
Rsx.render_error() displays errors in any container:
// Display in specific container
Rsx.render_error(error, '#error_container');
// Display in form's error container (for Rsx_Form use form.render_error())
Rsx.render_error(error, this.$id('error'));
Handles all error types:
- fatal: Shows file:line and full message
- form_error: Shows bulleted list of unmatched validation errors
- auth/network: Shows user-friendly message
- generic: Shows error message in danger alert
Modal Error Display
-------------------
For critical errors that need immediate attention:
await Modal.error(error, 'Critical Error');
- Shows red danger modal
- Can stack over other open modals
- Used automatically for uncaught Ajax errors
- Displays error message in monospace pre format
DEVELOPER VS PRODUCTION MODE
Developer Mode (IS_DEVELOPER=true)
-----------------------------------
Fatal errors show:
- Full exception message
- File path and line number
- SQL queries with parameter values
- Stack trace (up to 10 frames)
- Console debug messages
Example display:
Fatal Error
SQLSTATE[23000]: Integrity constraint violation: 1048
Column 'login_user_id' cannot be null (Connection: mysql,
SQL: insert into `users` ...)
vendor/laravel/framework/src/Illuminate/Database/Connection.php:829
Production Mode (IS_DEVELOPER=false)
-------------------------------------
Fatal errors show:
- Generic message: "An unexpected error occurred. Please try
again or contact support."
- No file paths, SQL, or technical details
- Errors logged server-side for admin review
Validation/auth errors show same user-friendly messages in both modes.
BATCH REQUESTS (FUTURE)
When batch requests are implemented, response format:
{
"batch_results": [
{"_success": true, "_ajax_return_value": {...}},
{"_success": false, "error_type": "validation", "reason": "...", "details": {...}},
{"_success": true, "_ajax_return_value": {...}},
{"_success": false, "error_type": "fatal", "error": {...}}
]
}
HTTP status: Always 200
Each result: Has own _success field
Client: Process each result individually
TESTING
Test Success Response
---------------------
php artisan rsx:ajax Controller action --args='{"id":1}'
Test Validation Error
---------------------
php artisan rsx:ajax Controller action --args='{"id":""}'
Test Fatal Error
----------------
Trigger exception in endpoint (e.g., invalid SQL, missing file)
Test Network Error
------------------
Stop PHP-FPM/nginx, try Ajax call from browser
CONSOLE DEBUG MESSAGES
Add debug output to Ajax responses:
console_debug('channel', $var1, $var2);
In response:
{
"_success": true,
"_ajax_return_value": {...},
"console_debug": [
["channel", [arg1, arg2]],
["log", ["message"]]
]
}
JavaScript automatically logs these to browser console.
COMMON PATTERNS
Simple Data Retrieval
----------------------
#[Ajax_Endpoint]
public static function get_data(Request $request, array $params = []): array
{
return ['items' => Item::all()->toArray()];
}
Validation with Response Helper
--------------------------------
#[Ajax_Endpoint]
public static function save(Request $request, array $params = []): array
{
if (empty($params['email'])) {
return response_form_error('Validation failed', [
'email' => 'Email is required'
]);
}
$user = User::create($params);
return ['id' => $user->id];
}
Let Database Errors Bubble
---------------------------
#[Ajax_Endpoint]
public static function save(Request $request, array $params = []): array
{
$user = new User();
$user->name = $params['name'];
$user->save(); // If this fails, exception bubbles naturally
return ['id' => $user->id];
}
SEE ALSO
php artisan rsx:man ajax - Ajax system overview
php artisan rsx:man jqhtml - Component system
php artisan rsx:man modals - Modal dialogs
php artisan rsx:man error_handling - General error handling