Files
rspade_system/docs/skills/ajax-error-handling/SKILL.md
root 1b46c5270c Add skills documentation and misc updates
Add form value persistence across cache revalidation re-renders

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-29 04:38:06 +00:00

246 lines
5.9 KiB
Markdown
Executable File

---
name: ajax-error-handling
description: Handling Ajax errors in RSX including response formats, error codes, client-side error display, and Form_Utils. Use when implementing error handling for Ajax calls, working with response_error(), form validation errors, or debugging Ajax failures.
---
# RSX Ajax Error Handling
## Response Architecture
RSX returns **HTTP 200 for ALL Ajax responses** (success and errors). Success/failure is encoded in the response body via `_success` field.
```javascript
// Success response
{
"_success": true,
"_ajax_return_value": { /* your data */ }
}
// Error response
{
"_success": false,
"error_code": "validation|not_found|unauthorized|auth_required|fatal",
"reason": "User-friendly message",
"metadata": { /* field errors for validation */ }
}
```
**Rationale**: Batch requests need uniform status codes. Always get parseable response body. Non-200 only means "couldn't reach PHP at all".
---
## Error Codes
| Constant | Purpose |
|----------|---------|
| `Ajax::ERROR_VALIDATION` | Field validation failures |
| `Ajax::ERROR_NOT_FOUND` | Resource not found |
| `Ajax::ERROR_UNAUTHORIZED` | User lacks permission |
| `Ajax::ERROR_AUTH_REQUIRED` | User not logged in |
| `Ajax::ERROR_FATAL` | Uncaught PHP exceptions |
Constants available in both PHP (`Ajax::ERROR_*`) and JavaScript (`Ajax.ERROR_*`).
---
## Server-Side: Returning Errors
Use `response_error()` helper - never return `_success` manually:
```php
#[Ajax_Endpoint]
public static function save(Request $request, array $params = []) {
// Validation error with field-specific messages
if (empty($params['email'])) {
return response_error(Ajax::ERROR_VALIDATION, [
'email' => 'Email is required'
]);
}
// Not found error
$user = User_Model::find($params['id']);
if (!$user) {
return response_error(Ajax::ERROR_NOT_FOUND, 'User not found');
}
// Success - just return data (framework wraps it)
$user->name = $params['name'];
$user->save();
return ['id' => $user->id];
}
```
### Let Exceptions Bubble
Don't wrap database/framework operations in try/catch. Let exceptions bubble to the global handler:
```php
// WRONG - Don't catch framework exceptions
try {
$user->save();
} catch (Exception $e) {
return ['error' => $e->getMessage()];
}
// CORRECT - Let it throw
$user->save(); // Framework catches and formats
```
**When try/catch IS appropriate**: File uploads, external API calls, user input parsing (expected failures).
---
## Client-Side: Handling Errors
Ajax.js automatically unwraps responses:
- `_success: true` → Promise resolves with `_ajax_return_value`
- `_success: false` → Promise rejects with Error object
```javascript
try {
const user = await User_Controller.get_user(123);
console.log(user.name); // Already unwrapped
} catch (error) {
console.log(error.code); // 'validation', 'not_found', etc.
console.log(error.message); // User-displayable message
console.log(error.metadata); // Field errors for validation
}
```
### Automatic Error Display
Uncaught Ajax errors automatically display via `Modal.error()`:
```javascript
// No try/catch - error shows in modal automatically
const user = await User_Controller.get_user(123);
```
---
## Form Error Handling
### With Rsx_Form (Recommended)
```javascript
const result = await Modal.form({
title: 'Add User',
component: 'User_Form',
on_submit: async (form) => {
try {
const result = await Controller.save(form.vals());
return result; // Success - close modal
} catch (error) {
await form.render_error(error);
return false; // Keep modal open
}
}
});
```
`form.render_error()` handles all error types:
- **validation**: Shows inline field errors + alert for unmatched errors
- **fatal/network/auth**: Shows error in form's error container
### With Form_Utils (Non-Rsx_Form)
```javascript
try {
const result = await Controller.save(form_data);
} catch (error) {
if (error.code === Ajax.ERROR_VALIDATION) {
Form_Utils.apply_form_errors($form, error.metadata);
} else {
Rsx.render_error(error, '#error_container');
}
}
```
`Form_Utils.apply_form_errors()`:
- 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
---
## Error Display Methods
### Modal.error() - Critical Errors
```javascript
await Modal.error(error, 'Operation Failed');
```
Red danger modal, can stack over other modals.
### Rsx.render_error() - Container Display
```javascript
Rsx.render_error(error, '#error_container');
Rsx.render_error(error, this.$sid('error'));
```
Displays error in any container element.
---
## Developer vs Production Mode
**Developer Mode** (`IS_DEVELOPER=true`):
- Full exception message
- File path and line number
- SQL queries with parameters
- Stack trace (up to 10 frames)
**Production Mode**:
- Generic message: "An unexpected error occurred"
- No technical details exposed
- Errors logged server-side
---
## Common Patterns
### Simple Validation
```php
#[Ajax_Endpoint]
public static function save(Request $request, array $params = []) {
$errors = [];
if (empty($params['email'])) {
$errors['email'] = 'Email is required';
}
if (empty($params['name'])) {
$errors['name'] = 'Name is required';
}
if ($errors) {
return response_error(Ajax::ERROR_VALIDATION, $errors);
}
// ... save logic
}
```
### Check Specific Error Type
```javascript
try {
const data = await Controller.get_data(id);
} catch (error) {
if (error.code === Ajax.ERROR_NOT_FOUND) {
show_not_found_message();
} else if (error.code === Ajax.ERROR_UNAUTHORIZED) {
redirect_to_login();
} else {
// Let default handler show modal
throw error;
}
}
```
## More Information
Details: `php artisan rsx:man ajax_error_handling`