feat(swap): use LI.FI error codes for error categorization#217
feat(swap): use LI.FI error codes for error categorization#217
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request refactors the LI.FI error handling to use structured error codes from the LI.FI API instead of parsing error messages. This is a more robust and maintainable approach that aligns with the LI.FI API documentation.
Changes:
- Replaced message-based error categorization with code-based error categorization
- Added two new error kinds:
RATE_LIMIT_EXCEEDEDandTIMEOUT - Updated the
LifiErrormodel to include acodefield instead of anerrorsfield
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| app/api/swap/providers/lifi/utils.py | Refactored categorize_error() to map LI.FI error codes (1000-1013) to SwapErrorKind values |
| app/api/swap/providers/lifi/test_client.py | Updated test to verify error categorization and added comprehensive parametrized test for all error codes |
| app/api/swap/providers/lifi/models.py | Changed LifiError model to include code field instead of errors field |
| app/api/swap/providers/lifi/mocks.py | Updated mock error response to include error code instead of nested errors array |
| app/api/swap/providers/lifi/client.py | Simplified error handling to pass entire LifiError object to categorize_error() |
| app/api/swap/models.py | Added RATE_LIMIT_EXCEEDED and TIMEOUT error kinds to SwapErrorKind enum |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
app/api/swap/providers/lifi/utils.py
Outdated
| def categorize_error(error: LifiError) -> SwapErrorKind: | ||
| """Map LI.FI error codes to SwapErrorKind. | ||
|
|
||
| Ref: https://docs.li.fi/api-reference/error-codes | ||
| """ | ||
| # DefaultError | ||
| if error.code == 1000: | ||
| return SwapErrorKind.UNKNOWN | ||
|
|
||
| error_lower = error_message.lower() | ||
| # FailedToBuildTransactionError | ||
| if error.code == 1001: | ||
| return SwapErrorKind.INVALID_REQUEST | ||
|
|
||
| if any( | ||
| phrase in error_lower | ||
| for phrase in [ | ||
| "no possible route", | ||
| "no routes found", | ||
| "insufficient liquidity", | ||
| "not enough liquidity", | ||
| ] | ||
| ): | ||
| # NoQuoteError | ||
| if error.code == 1002: | ||
| return SwapErrorKind.INSUFFICIENT_LIQUIDITY | ||
|
|
||
| if any( | ||
| phrase in error_lower | ||
| for phrase in [ | ||
| "amount too low", | ||
| "amount too small", | ||
| "fees higher than amount", | ||
| ] | ||
| ): | ||
| return SwapErrorKind.AMOUNT_TOO_LOW | ||
|
|
||
| if any( | ||
| phrase in error_lower | ||
| for phrase in [ | ||
| "slippage", | ||
| "validation", | ||
| "malformed", | ||
| ] | ||
| ): | ||
| # NotFoundError | ||
| if error.code == 1003: | ||
| return SwapErrorKind.INVALID_REQUEST | ||
|
|
||
| # NotProcessableError | ||
| if error.code == 1004: | ||
| return SwapErrorKind.INVALID_REQUEST | ||
|
|
||
| # RateLimitError | ||
| if error.code == 1005: | ||
| return SwapErrorKind.RATE_LIMIT_EXCEEDED | ||
|
|
||
| # ServerError | ||
| if error.code == 1006: | ||
| return SwapErrorKind.UNKNOWN | ||
|
|
||
| # SlippageError | ||
| if error.code == 1007: | ||
| return SwapErrorKind.INVALID_REQUEST | ||
|
|
||
| # ThirdPartyError | ||
| if error.code == 1008: | ||
| return SwapErrorKind.UNKNOWN | ||
|
|
||
| # TimeoutError | ||
| if error.code == 1009: | ||
| return SwapErrorKind.TIMEOUT | ||
|
|
||
| # UnauthorizedError | ||
| if error.code == 1010: | ||
| return SwapErrorKind.INVALID_REQUEST | ||
|
|
||
| # ValidationError | ||
| if error.code == 1011: | ||
| return SwapErrorKind.INVALID_REQUEST | ||
|
|
||
| # RpcFailure | ||
| if error.code == 1012: | ||
| return SwapErrorKind.UNKNOWN | ||
|
|
||
| # MalformedSchema | ||
| if error.code == 1013: | ||
| return SwapErrorKind.INVALID_REQUEST | ||
|
|
||
| return SwapErrorKind.UNKNOWN |
There was a problem hiding this comment.
The error code to SwapErrorKind mapping would be more maintainable and performant using a dictionary instead of a long chain of if statements. Consider refactoring to:
ERROR_CODE_MAPPING: dict[int, SwapErrorKind] = {
1000: SwapErrorKind.UNKNOWN, # DefaultError
1001: SwapErrorKind.INVALID_REQUEST, # FailedToBuildTransactionError
1002: SwapErrorKind.INSUFFICIENT_LIQUIDITY, # NoQuoteError
1003: SwapErrorKind.INVALID_REQUEST, # NotFoundError
1004: SwapErrorKind.INVALID_REQUEST, # NotProcessableError
1005: SwapErrorKind.RATE_LIMIT_EXCEEDED, # RateLimitError
1006: SwapErrorKind.UNKNOWN, # ServerError
1007: SwapErrorKind.INVALID_REQUEST, # SlippageError
1008: SwapErrorKind.UNKNOWN, # ThirdPartyError
1009: SwapErrorKind.TIMEOUT, # TimeoutError
1010: SwapErrorKind.INVALID_REQUEST, # UnauthorizedError
1011: SwapErrorKind.INVALID_REQUEST, # ValidationError
1012: SwapErrorKind.UNKNOWN, # RpcFailure
1013: SwapErrorKind.INVALID_REQUEST, # MalformedSchema
}
def categorize_error(error: LifiError) -> SwapErrorKind:
"""Map LI.FI error codes to SwapErrorKind.
Ref: https://docs.li.fi/api-reference/error-codes
"""
if error.code is None:
return SwapErrorKind.UNKNOWN
return ERROR_CODE_MAPPING.get(error.code, SwapErrorKind.UNKNOWN)This approach is more maintainable, easier to extend, and has O(1) lookup time instead of O(n).
4383937 to
9c4a4c4
Compare
Figuring out the
SwapErrorcategories from the error code rather than parsing the error messages.https://docs.li.fi/api-reference/error-codes