Integrate OpenAPI/Swagger documentation using drf-spectacular, enhancing API documentation with comprehensive guides and schema generation. Add multiple documentation files covering authentication, error codes, rate limiting, and migration strategies. Update settings and URLs to support new documentation endpoints and schema configurations.
This commit is contained in:
365
docs/MIGRATION-GUIDE.md
Normal file
365
docs/MIGRATION-GUIDE.md
Normal file
@@ -0,0 +1,365 @@
|
||||
# API Migration Guide
|
||||
|
||||
**Version**: 1.0.0
|
||||
**Last Updated**: 2025-11-16
|
||||
|
||||
Guide for migrating existing API consumers to IGNY8 API Standard v1.0.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The IGNY8 API v1.0 introduces a unified response format that standardizes all API responses. This guide helps you migrate existing code to work with the new format.
|
||||
|
||||
---
|
||||
|
||||
## What Changed
|
||||
|
||||
### Before (Legacy Format)
|
||||
|
||||
**Success Response**:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Keyword",
|
||||
"status": "active"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response**:
|
||||
```json
|
||||
{
|
||||
"detail": "Not found."
|
||||
}
|
||||
```
|
||||
|
||||
### After (Unified Format v1.0)
|
||||
|
||||
**Success Response**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "Keyword",
|
||||
"status": "active"
|
||||
},
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Resource not found",
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Steps
|
||||
|
||||
### Step 1: Update Response Parsing
|
||||
|
||||
#### Before
|
||||
|
||||
```python
|
||||
response = requests.get(url, headers=headers)
|
||||
data = response.json()
|
||||
|
||||
# Direct access
|
||||
keyword_id = data['id']
|
||||
keyword_name = data['name']
|
||||
```
|
||||
|
||||
#### After
|
||||
|
||||
```python
|
||||
response = requests.get(url, headers=headers)
|
||||
data = response.json()
|
||||
|
||||
# Check success first
|
||||
if data['success']:
|
||||
# Extract data from unified format
|
||||
keyword_data = data['data'] # or data['results'] for lists
|
||||
keyword_id = keyword_data['id']
|
||||
keyword_name = keyword_data['name']
|
||||
else:
|
||||
# Handle error
|
||||
error_message = data['error']
|
||||
raise Exception(error_message)
|
||||
```
|
||||
|
||||
### Step 2: Update Error Handling
|
||||
|
||||
#### Before
|
||||
|
||||
```python
|
||||
try:
|
||||
response = requests.get(url, headers=headers)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
except requests.HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
print("Not found")
|
||||
elif e.response.status_code == 400:
|
||||
print("Bad request")
|
||||
```
|
||||
|
||||
#### After
|
||||
|
||||
```python
|
||||
response = requests.get(url, headers=headers)
|
||||
data = response.json()
|
||||
|
||||
if not data['success']:
|
||||
# Unified error format
|
||||
error_message = data['error']
|
||||
field_errors = data.get('errors', {})
|
||||
|
||||
if response.status_code == 404:
|
||||
print(f"Not found: {error_message}")
|
||||
elif response.status_code == 400:
|
||||
print(f"Validation error: {error_message}")
|
||||
for field, errors in field_errors.items():
|
||||
print(f" {field}: {', '.join(errors)}")
|
||||
```
|
||||
|
||||
### Step 3: Update Pagination Handling
|
||||
|
||||
#### Before
|
||||
|
||||
```python
|
||||
response = requests.get(url, headers=headers)
|
||||
data = response.json()
|
||||
|
||||
results = data['results']
|
||||
next_page = data['next']
|
||||
count = data['count']
|
||||
```
|
||||
|
||||
#### After
|
||||
|
||||
```python
|
||||
response = requests.get(url, headers=headers)
|
||||
data = response.json()
|
||||
|
||||
if data['success']:
|
||||
# Paginated response format
|
||||
results = data['results'] # Same field name
|
||||
next_page = data['next'] # Same field name
|
||||
count = data['count'] # Same field name
|
||||
else:
|
||||
# Handle error
|
||||
raise Exception(data['error'])
|
||||
```
|
||||
|
||||
### Step 4: Update Frontend Code
|
||||
|
||||
#### Before (JavaScript)
|
||||
|
||||
```javascript
|
||||
const response = await fetch(url, { headers });
|
||||
const data = await response.json();
|
||||
|
||||
// Direct access
|
||||
const keywordId = data.id;
|
||||
const keywordName = data.name;
|
||||
```
|
||||
|
||||
#### After (JavaScript)
|
||||
|
||||
```javascript
|
||||
const response = await fetch(url, { headers });
|
||||
const data = await response.json();
|
||||
|
||||
// Check success first
|
||||
if (data.success) {
|
||||
// Extract data from unified format
|
||||
const keywordData = data.data || data.results;
|
||||
const keywordId = keywordData.id;
|
||||
const keywordName = keywordData.name;
|
||||
} else {
|
||||
// Handle error
|
||||
console.error('Error:', data.error);
|
||||
if (data.errors) {
|
||||
// Handle field-specific errors
|
||||
Object.entries(data.errors).forEach(([field, errors]) => {
|
||||
console.error(`${field}: ${errors.join(', ')}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Helper Functions
|
||||
|
||||
### Python Helper
|
||||
|
||||
```python
|
||||
def parse_api_response(response):
|
||||
"""Parse unified API response format"""
|
||||
data = response.json()
|
||||
|
||||
if data.get('success'):
|
||||
# Return data or results
|
||||
return data.get('data') or data.get('results')
|
||||
else:
|
||||
# Raise exception with error details
|
||||
error_msg = data.get('error', 'Unknown error')
|
||||
errors = data.get('errors', {})
|
||||
|
||||
if errors:
|
||||
error_msg += f": {errors}"
|
||||
|
||||
raise Exception(error_msg)
|
||||
|
||||
# Usage
|
||||
response = requests.get(url, headers=headers)
|
||||
keyword_data = parse_api_response(response)
|
||||
```
|
||||
|
||||
### JavaScript Helper
|
||||
|
||||
```javascript
|
||||
function parseApiResponse(data) {
|
||||
if (data.success) {
|
||||
return data.data || data.results;
|
||||
} else {
|
||||
const error = new Error(data.error);
|
||||
error.errors = data.errors || {};
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const response = await fetch(url, { headers });
|
||||
const data = await response.json();
|
||||
try {
|
||||
const keywordData = parseApiResponse(data);
|
||||
} catch (error) {
|
||||
console.error('API Error:', error.message);
|
||||
if (error.errors) {
|
||||
// Handle field-specific errors
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### 1. Response Structure
|
||||
|
||||
**Breaking**: All responses now include `success` field and wrap data in `data` or `results`.
|
||||
|
||||
**Migration**: Update all response parsing code to check `success` and extract `data`/`results`.
|
||||
|
||||
### 2. Error Format
|
||||
|
||||
**Breaking**: Error responses now use unified format with `error` and `errors` fields.
|
||||
|
||||
**Migration**: Update error handling to use new format.
|
||||
|
||||
### 3. Request ID
|
||||
|
||||
**New**: All responses include `request_id` for debugging.
|
||||
|
||||
**Migration**: Optional - can be used for support requests.
|
||||
|
||||
---
|
||||
|
||||
## Non-Breaking Changes
|
||||
|
||||
### 1. Pagination
|
||||
|
||||
**Status**: Compatible - same field names (`count`, `next`, `previous`, `results`)
|
||||
|
||||
**Migration**: No changes needed, but wrap in success check.
|
||||
|
||||
### 2. Authentication
|
||||
|
||||
**Status**: Compatible - same JWT Bearer token format
|
||||
|
||||
**Migration**: No changes needed.
|
||||
|
||||
### 3. Endpoint URLs
|
||||
|
||||
**Status**: Compatible - same endpoint paths
|
||||
|
||||
**Migration**: No changes needed.
|
||||
|
||||
---
|
||||
|
||||
## Testing Migration
|
||||
|
||||
### 1. Update Test Code
|
||||
|
||||
```python
|
||||
# Before
|
||||
def test_get_keyword():
|
||||
response = client.get('/api/v1/planner/keywords/1/')
|
||||
assert response.status_code == 200
|
||||
assert response.json()['id'] == 1
|
||||
|
||||
# After
|
||||
def test_get_keyword():
|
||||
response = client.get('/api/v1/planner/keywords/1/')
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data['success'] == True
|
||||
assert data['data']['id'] == 1
|
||||
```
|
||||
|
||||
### 2. Test Error Handling
|
||||
|
||||
```python
|
||||
def test_not_found():
|
||||
response = client.get('/api/v1/planner/keywords/99999/')
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
assert data['success'] == False
|
||||
assert data['error'] == "Resource not found"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
- [ ] Update response parsing to check `success` field
|
||||
- [ ] Extract data from `data` or `results` field
|
||||
- [ ] Update error handling to use unified format
|
||||
- [ ] Update pagination handling (wrap in success check)
|
||||
- [ ] Update frontend code (if applicable)
|
||||
- [ ] Update test code
|
||||
- [ ] Test all endpoints
|
||||
- [ ] Update documentation
|
||||
- [ ] Deploy and monitor
|
||||
|
||||
---
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If issues arise during migration:
|
||||
|
||||
1. **Temporary Compatibility Layer**: Add wrapper to convert unified format back to legacy format
|
||||
2. **Feature Flag**: Use feature flag to toggle between formats
|
||||
3. **Gradual Migration**: Migrate endpoints one module at a time
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
For migration support:
|
||||
- Review [API Documentation](API-DOCUMENTATION.md)
|
||||
- Check [Error Codes Reference](ERROR-CODES.md)
|
||||
- Contact support with `request_id` from failed requests
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-11-16
|
||||
**API Version**: 1.0.0
|
||||
|
||||
Reference in New Issue
Block a user