gbiz-info-api 0.2.0

gBizINFO REST API (v2) を Rust から利用するためのクライアントライブラリ
Documentation
//! gBizINFO API のエラー型定義。

use std::fmt::{Display, Formatter};

/// gBizINFO API の呼び出しで発生するエラー。
///
/// ```no_run
/// # use gbiz_info_api::{GbizInfoClient, GbizError};
/// # async fn example(client: GbizInfoClient) {
/// match client.hojin("0000000000000").await {
///     Ok(res) => { /* ... */ }
///     Err(err) if err.is_not_found() => eprintln!("法人が見つかりません"),
///     Err(GbizError::Status { status, body }) => eprintln!("API エラー {status}: {body}"),
///     Err(err) => eprintln!("通信エラー等: {err}"),
/// }
/// # }
/// ```
#[derive(Debug)]
pub enum GbizError {
    /// HTTP 通信自体に失敗した(接続不可・タイムアウト等)。
    Request(reqwest::Error),
    /// API がエラーステータスを返した。
    Status {
        /// HTTP ステータスコード
        status: u16,
        /// レスポンスボディ
        body: String,
    },
    /// レスポンスボディの JSON デコードに失敗した。
    Decode {
        /// デコードエラーの詳細
        error: serde_json::Error,
        /// レスポンスボディ
        body: String,
    },
}

impl GbizError {
    /// API がエラーステータスを返した場合の HTTP ステータスコード。
    /// 通信エラー・デコードエラーの場合は `None`。
    pub fn status_code(&self) -> Option<u16> {
        match self {
            GbizError::Status { status, .. } => Some(*status),
            _ => None,
        }
    }

    /// 指定した法人番号が存在しない場合など、404 エラーかどうか。
    pub fn is_not_found(&self) -> bool {
        self.status_code() == Some(404)
    }
}

impl Display for GbizError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            GbizError::Request(e) => write!(f, "リクエストに失敗しました: {e}"),
            GbizError::Status { status, body } => {
                write!(f, "API がエラーステータス {status} を返しました: {body}")
            }
            GbizError::Decode { error, body } => {
                write!(f, "レスポンスのデコードに失敗しました: {error}: {body}")
            }
        }
    }
}

impl std::error::Error for GbizError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            GbizError::Request(e) => Some(e),
            GbizError::Decode { error, .. } => Some(error),
            GbizError::Status { .. } => None,
        }
    }
}

impl From<reqwest::Error> for GbizError {
    fn from(e: reqwest::Error) -> Self {
        GbizError::Request(e)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn status_code_and_not_found() {
        let err = GbizError::Status {
            status: 404,
            body: "{}".to_string(),
        };
        assert_eq!(err.status_code(), Some(404));
        assert!(err.is_not_found());

        let err = GbizError::Status {
            status: 401,
            body: String::new(),
        };
        assert_eq!(err.status_code(), Some(401));
        assert!(!err.is_not_found());

        let err = GbizError::Decode {
            error: serde_json::from_str::<serde_json::Value>("x").unwrap_err(),
            body: "x".to_string(),
        };
        assert_eq!(err.status_code(), None);
        assert!(!err.is_not_found());
    }

    #[test]
    fn display_contains_context() {
        let err = GbizError::Status {
            status: 401,
            body: "unauthorized".to_string(),
        };
        let msg = err.to_string();
        assert!(msg.contains("401"));
        assert!(msg.contains("unauthorized"));
    }
}