Skip to content

Commit

Permalink
refactor api errors as c-like structs
Browse files Browse the repository at this point in the history
  • Loading branch information
dimxy committed Oct 17, 2024
1 parent 28a645d commit ef93d1a
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 130 deletions.
25 changes: 17 additions & 8 deletions mm2src/mm2_main/src/ext_api/one_inch/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ pub enum ApiIntegrationRpcError {
MyAddressError(String),
InvalidParam(String),
#[display(fmt = "Parameter {param} out of bounds, value: {value}, min: {min} max: {max}")]
OutOfBounds { param: String, value: String, min: String, max: String },
OutOfBounds {
param: String,
value: String,
min: String,
max: String,
},
#[display(fmt = "allowance not enough for 1inch contract, available: {allowance}, needed: {amount}")]
OneInchAllowanceNotEnough {
allowance: BigDecimal,
Expand Down Expand Up @@ -54,13 +59,17 @@ impl ApiIntegrationRpcError {
pub(crate) fn from_api_error(error: ApiClientError, decimals: u8) -> Self {
match error {
ApiClientError::InvalidParam(error) => ApiIntegrationRpcError::InvalidParam(error),
ApiClientError::OutOfBounds { param, value, min, max } => ApiIntegrationRpcError::OutOfBounds { param, value, min, max },
ApiClientError::HttpClientError(_)
| ApiClientError::ParseBodyError(_)
| ApiClientError::GeneralApiError(_) => ApiIntegrationRpcError::OneInchError(error),
ApiClientError::AllowanceNotEnough(nested_err) => ApiIntegrationRpcError::OneInchAllowanceNotEnough {
allowance: u256_to_big_decimal(nested_err.allowance, decimals).unwrap_or_default(),
amount: u256_to_big_decimal(nested_err.amount, decimals).unwrap_or_default(),
ApiClientError::OutOfBounds { param, value, min, max } => {
ApiIntegrationRpcError::OutOfBounds { param, value, min, max }
},
ApiClientError::TransportError(_)
| ApiClientError::ParseBodyError { .. }
| ApiClientError::GeneralApiError { .. } => ApiIntegrationRpcError::OneInchError(error),
ApiClientError::AllowanceNotEnough { allowance, amount, .. } => {
ApiIntegrationRpcError::OneInchAllowanceNotEnough {
allowance: u256_to_big_decimal(allowance, decimals).unwrap_or_default(),
amount: u256_to_big_decimal(amount, decimals).unwrap_or_default(),
}
},
}
}
Expand Down
23 changes: 12 additions & 11 deletions mm2src/mm2_main/src/ext_api/one_inch/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ pub struct ClassicSwapQuoteRequest {
pub rel: String,
/// Swap amount in coins (with fraction)
pub amount: MmNumber,
/// Partner fee, percentage of src token amount will be sent to referrer address, min: 0; max: 3.
/// Partner fee, percentage of src token amount will be sent to referrer address, min: 0; max: 3.
/// Should be the same for quote and swap rpc. Default is 0
pub fee: Option<f32>,
/// Specify liquidity sources
/// Specify liquidity sources
/// e.g.: &protocols=WETH,CURVE,BALANCER,...,ZRX
/// (by default - all used)
pub protocols: Option<String>,
/// Network price per gas, in Gwei for this rpc.
/// Network price per gas, in Gwei for this rpc.
/// 1inch takes in account gas expenses to determine exchange route. Should be the same for a quote and swap.
/// If not set the 'fast' network gas price will be used
pub gas_price: Option<String>,
Expand All @@ -43,7 +43,7 @@ pub struct ClassicSwapQuoteRequest {
pub parts: Option<u32>,
/// Limit maximum number of main route parts. Should be the same for a quote and swap. Default: 20; max: 50;
pub main_route_parts: Option<u32>,
/// Maximum amount of gas for a swap.
/// Maximum amount of gas for a swap.
/// Should be the same for a quote and swap. Default: 11500000; max: 11500000
pub gas_limit: Option<u128>,
/// Return fromToken and toToken info in response (default is true)
Expand Down Expand Up @@ -72,14 +72,14 @@ pub struct ClassicSwapCreateRequest {
pub amount: MmNumber,
/// Allowed slippage, min: 0; max: 50
pub slippage: f32,
/// Partner fee, percentage of src token amount will be sent to referrer address, min: 0; max: 3.
/// Partner fee, percentage of src token amount will be sent to referrer address, min: 0; max: 3.
/// Should be the same for quote and swap rpc. Default is 0
pub fee: Option<f32>,
/// Specify liquidity sources
/// Specify liquidity sources
/// e.g.: &protocols=WETH,CURVE,BALANCER,...,ZRX
/// (by default - all used)
pub protocols: Option<String>,
/// Network price per gas, in Gwei for this rpc.
/// Network price per gas, in Gwei for this rpc.
/// 1inch takes in account gas expenses to determine exchange route. Should be the same for a quote and swap.
/// If not set the 'fast' network gas price will be used
pub gas_price: Option<String>,
Expand All @@ -90,7 +90,7 @@ pub struct ClassicSwapCreateRequest {
pub parts: Option<u32>,
/// Limit maximum number of main route parts. Should be the same for a quote and swap. Default: 20; max: 50;
pub main_route_parts: Option<u32>,
/// Maximum amount of gas for a swap.
/// Maximum amount of gas for a swap.
/// Should be the same for a quote and swap. Default: 11500000; max: 11500000
pub gas_limit: Option<u128>,
/// Return fromToken and toToken info in response (default is true)
Expand All @@ -112,14 +112,15 @@ pub struct ClassicSwapCreateRequest {
pub compatibility: Option<bool>,
/// This address will receive funds after the swap. By default same address as 'my address'
pub receiver: Option<String>,
/// Address to receive partner fee
/// Address to receive the partner fee. Must be set explicitly if fee is also set
pub referrer: Option<String>,
/// if true, disable most of the checks, default: false
pub disable_estimate: Option<bool>,
/// if true, the algorithm can cancel part of the route, if the rate has become less attractive.
/// if true, the algorithm can cancel part of the route, if the rate has become less attractive.
/// Unswapped tokens will return to 'my address'. Default: true
pub allow_partial_fill: Option<bool>,
/// Enable this flag in case you did an approval to permit2 smart contract (default false)
/// Enable this flag for auto approval by Permit2 contract if you did an approval to Uniswap Permit2 smart contract for this token.
/// Default is false
pub use_permit2: Option<bool>,
}

Expand Down
13 changes: 10 additions & 3 deletions mm2src/trading_api/src/one_inch_api/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,20 @@ impl ApiClient {
pub(crate) async fn call_api<T: DeserializeOwned>(api_url: &Url) -> MmResult<T, ApiClientError> {
let (status_code, _, body) = slurp_url_with_headers(api_url.as_str(), Self::get_headers())
.await
.mm_err(ApiClientError::HttpClientError)?;
let body = serde_json::from_slice(&body).map_to_mm(|err| ApiClientError::ParseBodyError(err.to_string()))?;
.mm_err(ApiClientError::TransportError)?;
let body = serde_json::from_slice(&body).map_to_mm(|err| ApiClientError::ParseBodyError {
error_msg: err.to_string(),
})?;
if status_code != StatusCode::OK {
let error = NativeError::new(status_code, body);
return Err(MmError::new(ApiClientError::from_native_error(error)));
}
serde_json::from_value(body).map_err(|err| ApiClientError::ParseBodyError(err.to_string()).into())
serde_json::from_value(body).map_err(|err| {
ApiClientError::ParseBodyError {
error_msg: err.to_string(),
}
.into()
})
}

pub async fn call_swap_api(
Expand Down
123 changes: 51 additions & 72 deletions mm2src/trading_api/src/one_inch_api/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,37 @@ use mm2_net::transport::SlurpError;
use serde::{Deserialize, Serialize};
use serde_json::Value;

#[derive(Debug, Serialize)]
pub struct GeneralApiError {
pub error: String,
pub description: Option<String>,
pub status_code: u16,
}

impl std::fmt::Display for GeneralApiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"error description: {}",
self.description.as_ref().unwrap_or(&"".to_owned())
)
}
}

#[derive(Debug, Serialize)]
pub struct AllowanceNotEnoughError {
pub error: String,
pub description: Option<String>,
pub status_code: u16,
/// Amount to approve for the API contract
pub amount: U256,
/// Existing allowance for the API contract
pub allowance: U256,
}

impl std::fmt::Display for AllowanceNotEnoughError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"error description: {}",
self.description.as_ref().unwrap_or(&"".to_owned())
)
}
}

#[derive(Debug, Display, Serialize, EnumFromStringify)]
pub enum ApiClientError {
#[from_stringify("url::ParseError")]
InvalidParam(String),
#[display(fmt = "Parameter {param} out of bounds, value: {value}, min: {min} max: {max}")]
OutOfBounds { param: String, value: String, min: String, max: String },
HttpClientError(SlurpError),
ParseBodyError(String),
GeneralApiError(GeneralApiError),
AllowanceNotEnough(AllowanceNotEnoughError),
OutOfBounds {
param: String,
value: String,
min: String,
max: String,
},
TransportError(SlurpError),
ParseBodyError {
error_msg: String,
},
#[display(fmt = "General API error: {error_msg} description: {description}")]
GeneralApiError {
error_msg: String,
description: String,
status_code: u16,
},
#[display(fmt = "Allowance not enough, needed: {amount} allowance: {allowance}")]
AllowanceNotEnough {
error_msg: String,
description: String,
status_code: u16,
/// Amount to approve for the API contract
amount: U256,
/// Existing allowance for the API contract
allowance: U256,
},
}

// API error meta 'type' field known values
Expand All @@ -80,31 +63,27 @@ pub(crate) struct Meta {
pub meta_value: String,
}

#[derive(Debug)]
pub(crate) struct OtherError {
pub error: String,
pub status_code: u16,
}

#[derive(Debug)]
pub(crate) enum NativeError {
Error400(Error400),
OtherError(OtherError),
ParseError(String),
HttpError { error_msg: String, status_code: u16 },
HttpError400(Error400),
ParseError { error_msg: String },
}

impl NativeError {
pub(crate) fn new(status_code: StatusCode, body: Value) -> Self {
if status_code == StatusCode::BAD_REQUEST {
match serde_json::from_value(body) {
Ok(err) => Self::Error400(err),
Err(err) => Self::ParseError(err.to_string()),
Ok(err) => Self::HttpError400(err),
Err(err) => Self::ParseError {
error_msg: format!("could not parse error response: {}", err.to_string()),
},
}
} else {
Self::OtherError(OtherError {
error: body["error"].as_str().unwrap_or_default().to_owned(),
Self::HttpError {
error_msg: body["error"].as_str().unwrap_or_default().to_owned(),
status_code: status_code.into(),
})
}
}
}
}
Expand All @@ -114,7 +93,7 @@ impl ApiClientError {
/// Look for known API errors. If none found return as general API error
pub(crate) fn from_native_error(api_error: NativeError) -> ApiClientError {
match api_error {
NativeError::Error400(error_400) => {
NativeError::HttpError400(error_400) => {
if let Some(meta) = error_400.meta {
// Try if it's "Not enough allowance" error 'meta' data:
if let Some(meta_allowance) = meta.iter().find(|m| m.meta_type == META_TYPE_ALLOWANCE) {
Expand All @@ -125,27 +104,27 @@ impl ApiClientError {
Default::default()
};
let allowance = U256::from_dec_str(&meta_allowance.meta_value).unwrap_or_default();
return ApiClientError::AllowanceNotEnough(AllowanceNotEnoughError {
error: error_400.error,
return ApiClientError::AllowanceNotEnough {
error_msg: error_400.error,
status_code: error_400.status_code,
description: error_400.description,
description: error_400.description.unwrap_or_default(),
amount,
allowance,
});
};
}
}
ApiClientError::GeneralApiError(GeneralApiError {
error: error_400.error,
ApiClientError::GeneralApiError {
error_msg: error_400.error,
status_code: error_400.status_code,
description: error_400.description,
})
description: error_400.description.unwrap_or_default(),
}
},
NativeError::HttpError { error_msg, status_code } => ApiClientError::GeneralApiError {
error_msg,
status_code,
description: Default::default(),
},
NativeError::OtherError(other_error) => ApiClientError::GeneralApiError(GeneralApiError {
error: other_error.error,
status_code: other_error.status_code,
description: None,
}),
NativeError::ParseError(err_str) => ApiClientError::ParseBodyError(err_str),
NativeError::ParseError { error_msg } => ApiClientError::ParseBodyError { error_msg },
}
}
}
Loading

0 comments on commit ef93d1a

Please sign in to comment.