Skip to content

error handling for generated services #47

@ctaggart

Description

@ctaggart

Part of this sdk migrated from failure to thiserror a few months ago. Based in large part on an article titled Migrating from quick-error to SNAFU: a story on revamped error handling in Rust and its pull request Enet4/dicom-rs#62, I'm looking at using snafu. I created a branch that switches the AutoRust code to use it. It is possible to just wrap the source error types at first, then come back later to add more specialized errors and context info when needed.

Here is an example of my plan a service operation. As an example, let's look at "operationId": "ResourceGroups_CreateOrUpdate",. It returns these responses:

        "responses": {
          "200": {
            "description": "OK - Returns information about the new resource group.",
            "schema": {
              "$ref": "#/definitions/ResourceGroup"
            }
          },
          "201": {
            "description": "Created - Returns information about the new resource group.",
            "schema": {
              "$ref": "#/definitions/ResourceGroup"
            }
          },
          "default": {
            "description": "Error response describing why the operation failed.",
            "schema": {
              "$ref": "#/definitions/CloudError"
            }
          }
        }

I am assuming for now that if successful responses are defined, that default is an error response. If that doesn't prove to be true, a DefaultResponse will need to be added. If there is more than one successful response, they can be wrapped in a Response enum. A new Error type is created to capture any defined error responses or additional errors. Both types defined in a module named the same as the operation function.

pub mod resource_groups {
    use crate::{models::*};
    use reqwest::StatusCode;
    use snafu::{ResultExt, Snafu};

    pub async fn create_or_update(
        configuration: &crate::Configuration,
        resource_group_name: &str,
        parameters: &ResourceGroup,
        subscription_id: &str,
    ) -> std::result::Result<create_or_update::Response, create_or_update::Error> {
        let client = &configuration.client;
        let uri_str = &format!(
            "{}/subscriptions/{}/resourcegroups/{}",
            &configuration.base_path, subscription_id, resource_group_name
        );
        let mut req_builder = client.put(uri_str);
        if let Some(token) = &configuration.bearer_access_token {
            req_builder = req_builder.bearer_auth(token);
        }
        req_builder = req_builder.query(&[("api-version", &configuration.api_version)]);
        req_builder = req_builder.json(parameters);
        let req = req_builder.build().context(create_or_update::BuildRequestError)?;
        let rsp = client.execute(req).await.context(create_or_update::ExecuteRequestError)?;
        match rsp.status() {
            StatusCode::OK => {
                let body = rsp.bytes().await.context(create_or_update::ResponseBytesError)?;
                let rsp_value: ResourceGroup = serde_json::from_slice(&body).context(create_or_update::DeserializeError { body })?;
                Ok(create_or_update::Response::Ok200(rsp_value))
            }
            StatusCode::CREATED => {
                let body = rsp.bytes().await.context(create_or_update::ResponseBytesError)?;
                let rsp_value: ResourceGroup = serde_json::from_slice(&body).context(create_or_update::DeserializeError { body })?;
                Ok(create_or_update::Response::Created201(rsp_value))
            }
            status_code => {
                let body = rsp.bytes().await.context(create_or_update::ResponseBytesError)?;
                let rsp_value: CloudError = serde_json::from_slice(&body).context(create_or_update::DeserializeError { body })?;
                create_or_update::DefaultErrorResponse { status_code, value: rsp_value }.fail()
            }
        }
    }

    pub mod create_or_update {
        use crate::models::*;
        use reqwest::StatusCode;
        use snafu::Snafu;

        pub enum Response {
            Ok200(ResourceGroup),
            Created201(ResourceGroup),
        }

        #[derive(Debug, Snafu)]
        #[snafu(visibility(pub(crate)))]
        pub enum Error {
            DefaultErrorResponse { status_code: StatusCode, value: CloudError },
            BuildRequestError { source: reqwest::Error },
            ExecuteRequestError { source: reqwest::Error },
            ResponseBytesError { source: reqwest::Error },
            DeserializeError { source: serde_json::Error, body: bytes::Bytes },
        }
    }

snafu creates structs in the module for each one of the enum values. You reference the structs, from the operation in a .context() or creating it directly and calling fail() when it isn't wrapping a source.

Feedback welcome. I imagine there may be benefits to swapping out thiserror for snafu in our sdk, but they both work with std::error::Error.

Metadata

Metadata

Assignees

No one assigned

    Labels

    CodeGenIssues that relate to code generation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions