//! `PROTO` - data types used by the runtime to implement the humblespec service protocol.
//! The serde representation of the types in this module is part of the service protocol
//! specified in `humblespec/service_protocol.md`.

use hyper::Body;
use hyper::Response;

use serde::{Deserialize, Serialize};

// The "Error Response"
#[derive(Debug, Serialize, Deserialize)]
pub struct ErrorResponse {
    pub code: u16,
    pub kind: ErrorResponseKind,
}

pub trait ToErrorResponse {
    fn to_error_response(self) -> ErrorResponse;
}

#[derive(Debug, Serialize, Deserialize)]
pub enum ErrorResponseKind {
    Service(ServiceError),
    Runtime(RuntimeError),
}

/// A service-level error.
///
/// This type is returned by implementors of a humblegen service trait function
/// as part of a `HandlerResponse`.
#[derive(Debug, Serialize, Deserialize)]
pub enum ServiceError {
    /// The request cannot be fulfilled due to an authentication problem.
    /// Maps to HTTP status code 401.
    Authentication,
    /// The request cannot be fulfilled due to an authorization problem.
    /// Maps to HTTP status code 403.
    Authorization,
    /// The request cannot be fulfilled due to an error internal to the service
    /// that is _not_ domain-specific. (Domain-specific errors should be represented
    /// as humblespec result types in the service definition.)
    /// Maps to HTTP status code 500.
    /// Examples: no database connection
    Internal(String),
}

/// Responses generated by humblegen-rt for conditions that are outside
/// of handler trait impl's realm of control.
#[derive(Debug, Serialize, Deserialize)]
pub enum RuntimeError {
    NoServiceMounted,
    ServiceMountsAmbiguous,
    NoRouteMountedInService {
        service: String,
    },
    RouteMountsAmbiguous {
        service: String,
    },
    RouteParamInvalid {
        param_name: String,
        parse_error: String,
    },
    QueryInvalid(String),
    PostBodyReadError(String),
    PostBodyInvalid(String),
    SerializeHandlerResponse(String),
    SerializeErrorResponse(String),
}

impl ErrorResponse {
    pub fn to_hyper_response(&self) -> Response<Body> {
        hyper::Response::builder()
            .status(self.code)
            .body(
                serde_json::to_string_pretty(self)
                    .expect("runtime responses must be JSON-serializable")
                    .into(),
            )
            .expect("runtime responses must always be buildable")
    }
}

impl ToErrorResponse for ServiceError {
    fn to_error_response(self) -> ErrorResponse {
        ErrorResponse {
            code: self.status_code(),
            kind: ErrorResponseKind::Service(self),
        }
    }
}

impl ToErrorResponse for RuntimeError {
    fn to_error_response(self) -> ErrorResponse {
        ErrorResponse {
            code: self.status_code(),
            kind: ErrorResponseKind::Runtime(self),
        }
    }
}

impl RuntimeError {
    fn status_code(&self) -> u16 {
        match self {
            RuntimeError::NoServiceMounted => 404,
            RuntimeError::NoRouteMountedInService { .. } => 404,
            RuntimeError::RouteMountsAmbiguous { .. } => 500,
            RuntimeError::ServiceMountsAmbiguous => 500,
            RuntimeError::RouteParamInvalid { .. } => 400,
            RuntimeError::QueryInvalid(_) => 400,
            RuntimeError::PostBodyReadError(_) => 400,
            RuntimeError::PostBodyInvalid(_) => 400,
            RuntimeError::SerializeHandlerResponse(_) => 500,
            RuntimeError::SerializeErrorResponse(_) => 500,
        }
    }
}

impl ServiceError {
    pub fn status_code(&self) -> u16 {
        match self {
            ServiceError::Authentication => 401,
            ServiceError::Authorization => 403,
            ServiceError::Internal(_) => 500,
        }
    }
}

impl From<super::handler::ServiceError> for ServiceError {
    fn from(e: super::handler::ServiceError) -> Self {
        match e {
            super::handler::ServiceError::Authentication => ServiceError::Authentication,
            super::handler::ServiceError::Authorization => ServiceError::Authorization,
            super::handler::ServiceError::Internal(e) => ServiceError::Internal(format!("{}", e)),
        }
    }
}
