# News API Exception Handling

## Overview

The news API module now uses a structured exception handling approach instead of returning error dictionaries. This provides better error handling, cleaner code, and consistent API responses.

## Exception Hierarchy

All news API exceptions inherit from `NewsAPIException`, which provides:
- Human-readable error messages
- HTTP status codes
- Machine-readable error codes
- Optional additional details

```python
from local_deep_research.news.exceptions import NewsAPIException

class NewsAPIException(Exception):
    def __init__(self, message: str, status_code: int = 500,
                 error_code: Optional[str] = None,
                 details: Optional[Dict[str, Any]] = None)
```

## Available Exceptions

### InvalidLimitException
- **Status Code**: 400
- **Error Code**: `INVALID_LIMIT`
- **Usage**: When an invalid limit parameter is provided
- **Example**:
```python
if limit < 1:
    raise InvalidLimitException(limit)
```

### SubscriptionNotFoundException
- **Status Code**: 404
- **Error Code**: `SUBSCRIPTION_NOT_FOUND`
- **Usage**: When a requested subscription doesn't exist
- **Example**:
```python
if not subscription:
    raise SubscriptionNotFoundException(subscription_id)
```

### SubscriptionCreationException
- **Status Code**: 500
- **Error Code**: `SUBSCRIPTION_CREATE_FAILED`
- **Usage**: When subscription creation fails
- **Example**:
```python
except Exception as e:
    raise SubscriptionCreationException(str(e), {"query": query})
```

### SubscriptionUpdateException
- **Status Code**: 500
- **Error Code**: `SUBSCRIPTION_UPDATE_FAILED`
- **Usage**: When subscription update fails

### SubscriptionDeletionException
- **Status Code**: 500
- **Error Code**: `SUBSCRIPTION_DELETE_FAILED`
- **Usage**: When subscription deletion fails

### DatabaseAccessException
- **Status Code**: 500
- **Error Code**: `DATABASE_ERROR`
- **Usage**: When database operations fail
- **Example**:
```python
except Exception as e:
    raise DatabaseAccessException("operation_name", str(e))
```

### NewsFeedGenerationException
- **Status Code**: 500
- **Error Code**: `FEED_GENERATION_FAILED`
- **Usage**: When news feed generation fails

### ResearchProcessingException
- **Status Code**: 500
- **Error Code**: `RESEARCH_PROCESSING_FAILED`
- **Usage**: When processing research items fails

### NotImplementedException
- **Status Code**: 501
- **Error Code**: `NOT_IMPLEMENTED`
- **Usage**: For features not yet implemented
- **Example**:
```python
raise NotImplementedException("feature_name")
```

### InvalidParameterException
- **Status Code**: 400
- **Error Code**: `INVALID_PARAMETER`
- **Usage**: When invalid parameters are provided

### SchedulerNotificationException
- **Status Code**: 500
- **Error Code**: `SCHEDULER_NOTIFICATION_FAILED`
- **Usage**: When scheduler notification fails (non-critical)

## Flask Integration

### Error Handlers

The Flask application automatically handles `NewsAPIException` and its subclasses:

```python
@app.errorhandler(NewsAPIException)
def handle_news_api_exception(error):
    return jsonify(error.to_dict()), error.status_code
```

### Response Format

All exceptions are converted to consistent JSON responses:

```json
{
    "error": "Human-readable error message",
    "error_code": "MACHINE_READABLE_CODE",
    "status_code": 400,
    "details": {
        "additional": "context",
        "if": "available"
    }
}
```

## Migration Guide

### Before (Error Dictionaries)

```python
def get_news_feed(limit):
    if limit < 1:
        return {
            "error": "Limit must be at least 1",
            "news_items": []
        }

    try:
        # ... code ...
    except Exception as e:
        logger.exception("Error getting news feed")
        return {"error": str(e), "news_items": []}
```

### After (Exceptions)

```python
def get_news_feed(limit):
    if limit < 1:
        raise InvalidLimitException(limit)

    try:
        # ... code ...
    except NewsAPIException:
        raise  # Re-raise our custom exceptions
    except Exception as e:
        logger.exception("Error getting news feed")
        raise NewsFeedGenerationException(str(e))
```

## Benefits

1. **Cleaner Code**: Functions focus on their primary logic without error response formatting
2. **Consistent Error Handling**: All errors follow the same format
3. **Better Testing**: Easier to test exception cases with pytest.raises
4. **Type Safety**: IDEs can better understand return types
5. **Centralized Logging**: Error logging can be handled in one place
6. **HTTP Status Codes**: Proper HTTP status codes are automatically set

## Testing

Test exception handling using pytest:

```python
import pytest
from local_deep_research.news.exceptions import InvalidLimitException

def test_invalid_limit():
    with pytest.raises(InvalidLimitException) as exc_info:
        news_api.get_news_feed(limit=-1)

    assert exc_info.value.status_code == 400
    assert exc_info.value.details["provided_limit"] == -1
```

## Best Practices

1. **Always catch and re-raise NewsAPIException subclasses**:
```python
except NewsAPIException:
    raise  # Let Flask handle it
except Exception as e:
    # Convert to appropriate NewsAPIException
    raise DatabaseAccessException("operation", str(e))
```

2. **Include relevant details**:
```python
raise SubscriptionCreationException(
    "Database constraint violated",
    details={"query": query, "type": subscription_type}
)
```

3. **Use appropriate exception types**: Choose the most specific exception that matches the error condition

4. **Log before raising**: For unexpected errors, log the full exception before converting to NewsAPIException

## Future Improvements

- Add retry logic for transient errors
- Implement exception metrics/monitoring
- Add internationalization support for error messages
- Create exception middleware for advanced error handling
