Skip to main content

aauth_person_server/
error.rs

1use bytes::Bytes;
2use http::{HeaderValue, Response, StatusCode};
3use serde_json::json;
4
5/// An AAuth error, answered as `application/problem+json`.
6#[derive(Clone, Debug, thiserror::Error)]
7#[error("{error}: {detail}")]
8pub struct AAuthError {
9    /// The HTTP status.
10    pub status: StatusCode,
11    /// The error code, such as `mission_not_found`.
12    pub error: String,
13    /// A human-readable explanation.
14    pub detail: String,
15    /// Headers the response carries, such as `Signature-Error`.
16    pub headers: Vec<(&'static str, String)>,
17}
18
19impl AAuthError {
20    /// An error answered with `status`, `error` and `detail`.
21    pub fn new(status: StatusCode, error: impl Into<String>, detail: impl Into<String>) -> Self {
22        Self {
23            status,
24            error: error.into(),
25            detail: detail.into(),
26            headers: Vec::new(),
27        }
28    }
29
30    pub(crate) fn invalid_request(detail: impl Into<String>) -> Self {
31        Self::new(StatusCode::BAD_REQUEST, "invalid_request", detail)
32    }
33
34    /// A `403`: the agent may not do what it asked.
35    pub(crate) fn forbidden(detail: impl Into<String>) -> Self {
36        Self::new(StatusCode::FORBIDDEN, "invalid_request", detail)
37    }
38
39    /// A `403 mission_terminated`: nothing is issued under an ended mission.
40    pub(crate) fn mission_terminated(detail: impl Into<String>) -> Self {
41        Self::new(StatusCode::FORBIDDEN, "mission_terminated", detail)
42    }
43
44    /// The same `404` for a mission that does not exist, is not this agent's,
45    /// or is not this key's person's.
46    pub(crate) fn mission_not_found() -> Self {
47        Self::new(
48            StatusCode::NOT_FOUND,
49            "mission_not_found",
50            "no such mission",
51        )
52    }
53
54    /// A `401` naming the signature problem in `Signature-Error`.
55    pub(crate) fn signature(code: &str, detail: impl Into<String>) -> Self {
56        let mut error = Self::new(StatusCode::UNAUTHORIZED, code, detail);
57        error
58            .headers
59            .push(("signature-error", format!("error={code}")));
60        error
61    }
62
63    /// The problem response this error is answered with.
64    pub fn into_response(self) -> Response<Bytes> {
65        let mut response = problem(self.status, &self.error, Some(&self.detail));
66        for (name, value) in self.headers {
67            if let Ok(value) = HeaderValue::from_str(&value) {
68                response.headers_mut().insert(name, value);
69            }
70        }
71        response
72    }
73}
74
75impl From<crate::store::StoreError> for AAuthError {
76    fn from(error: crate::store::StoreError) -> Self {
77        Self::new(
78            StatusCode::SERVICE_UNAVAILABLE,
79            "temporarily_unavailable",
80            error.to_string(),
81        )
82    }
83}
84
85/// A problem response: `{error, detail?}`.
86pub fn problem(status: StatusCode, error: &str, detail: Option<&str>) -> Response<Bytes> {
87    let mut body = json!({ "error": error });
88    if let Some(detail) = detail.filter(|d| !d.is_empty()) {
89        body["detail"] = json!(detail);
90    }
91    let mut response = Response::new(Bytes::from(body.to_string()));
92    *response.status_mut() = status;
93    response.headers_mut().insert(
94        "content-type",
95        HeaderValue::from_static("application/problem+json"),
96    );
97    response
98}