How do I return a struct together with a reqwest::Error

I have a struct:

pub struct IsContractResponse {
    pub address: String,
    pub is_contract: bool,
    pub count: u8,
}

And I need to return it whenever a reqwest ::Error occurs in the following function:

pub async fn is_contract(
    container: IsContractResponse,
    json_rpc_api: String,
) -> Result<IsContractResponse, reqwest::Error> {
    let new_eth_request = EthRequest {
        jsonrpc: "2.0".to_string(),
        method: "eth_getCode".to_string(),
        params: [
            Params::String(container.address.to_string()),
            Params::String("latest".to_string()),
        ],
        id: 1,
    };

    let new_eth_response: EthBlockCtResponse = reqwest::Client::new()
        .post(&json_rpc_api)
        .json(&new_eth_request)
        .send()
        .await?
        .json()
        .await?;

    let mut container = container;
    if new_eth_response.result.chars().count() > 2 {
        container.is_contract = true;
    }

    Ok(container)
}

How do I implement the Error, and how do I return it?

Why did you delete it?

All i want to do is return a count and an address on error so I can retry it (from a futuresunorded list). Is that so unsafe?

I deleted it because I felt it was not the correct response nor solution, really sorry for that.

If you do want to go the route of implementing the From trait, then I would recommend having a separate struct for the error response. That way, you'd both know it's an error, maybe have error details in the struct, and also have the data needed to retry.

Now i have this:

struct ContractErrorData {
    address: String,
    count: u8,
}

struct ContractError {
    kind: String,
    message: String,
    data: ContractErrorData,

}

impl From<reqwest::Error> for ContractError {
    fn from(error: reqwest::Error) -> Self {
        ContractError {
            kind: String::from("reqwest"),
            message: error.to_string(),
            data: //??? How do I send data to self from is_contract()

        }
    }  
}

Ah, based on my understanding of the question, I assumed the data you wanted along with the error was some known default.

If you want to use the data from the container that's passed to is_contract, you would be better off using map_err (or something similar) to return a ContractError on reqwest::Error.

pub async fn is_contract(
    container: IsContractResponse,
    json_rpc_api: String,
) -> Result<IsContractResponse, ContractError> {
    let new_eth_request = EthRequest {
        jsonrpc: "2.0".to_string(),
        method: "eth_getCode".to_string(),
        params: [
            Params::String(container.address.to_string()),
            Params::String("latest".to_string()),
        ],
        id: 1,
    };

    let new_eth_response: EthBlockCtResponse = reqwest::Client::new()
        .post(&json_rpc_api)
        .json(&new_eth_request)
        .send()
        .await
        .map_err(|err| ContractError::new(err, &container))?
        .json()
        .await
        .map_err(|err| ContractError::new(err, &container))?;

    let mut container = container;
    if new_eth_response.result.chars().count() > 2 {
        container.is_contract = true;
    }

    Ok(container)
}

And, of course, implement ContractError::new (or however you want to transform reqwest::Error to ContractError):

impl ContractError {
    // However you want it, this is just an example
    pub fn new(error: reqwest::Error, container: &IsContractResponse) -> Self {
        ContractError {
            kind: String::from("reqwest"),
            message: error.to_string(),
            data: ContractErrorData {
                address: container.address.clone(),
                count: container.count,
            },
        }
    }
}

Ah thanks for your help, I didn't see your response. I'll study it and try to understand it. For now I ended up solving it with another function that wrap's around the request and returns a struct containing a container with the reqwest::error and the IsContractResponse if that makes sense:

pub struct IsContractResponse {
    pub address: String,
    pub is_contract: bool,
    pub count: u8,
}

pub struct IsContractErr {
    container: IsContractResponse,
    error: reqwest::Error,
}

pub async fn is_contract(
    container: IsContractResponse,
    json_rpc_api: String,
) -> Result<IsContractResponse, IsContractErr> {
    let mut container = container;
    container.count += 1;

    let res = is_contract_reqwest(&container.address, json_rpc_api).await;

    if res.is_err() {
        let error = IsContractErr {
            container: container,
            error: res.err().unwrap(),
        };

        return Err(error);
    }

    if res.unwrap().result.chars().count() > 2 {
        container.is_contract = true;
    }

    Ok(container)
}

pub async fn is_contract_reqwest(
    address: &str,
    json_rpc_api: String,
) -> Result<EthBlockCtResponse, reqwest::Error> {
    let new_eth_request = EthRequest {
        jsonrpc: "2.0".to_string(),
        method: "eth_getCode".to_string(),
        params: [
            Params::String(address.to_string()),
            Params::String("latest".to_string()),
        ],
        id: 1,
    };

    let new_eth_response: EthBlockCtResponse = reqwest::Client::new()
        .post(&json_rpc_api)
        .json(&new_eth_request)
        .send()
        .await?
        .json()
        .await?;

    Ok(new_eth_response)
}

Is this a valid/idiomatic way to solve the problem or will I run into unforeseen errors later?

I would say that extracting is_contract_reqwest to its own function is pretty good, especially if in the future you need to use it elsewhere.

Also, if you use a match you can avoid using those unwraps.

pub async fn is_contract(
    container: IsContractResponse,
    json_rpc_api: String,
) -> Result<IsContractResponse, IsContractErr> {
    let mut container = container;
    container.count += 1;

    let res = match is_contract_reqwest(&container.address, json_rpc_api).await {
        Ok(res) => res,
        Err(err) => return Err(IsContractErr {
            container: container,
            error: err,
        }),
    };

    if res.result.chars().count() > 2 {
        container.is_contract = true;
    }

    Ok(container)
}
1 Like