#![allow(clippy::result_large_err)]
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use serde_json::Value;
use trust_tasks_https::status_for_code;
use trust_tasks_rs::{ErrorPayload, ErrorResponse, RejectReason, TrustTask, TypeUri};
use uuid::Uuid;
use crate::error::AppError;
pub(super) const TRANSPORT_TRUST_TASK: &str = "trust-task";
pub(crate) struct TrustTaskOutcome {
pub(crate) status: StatusCode,
pub(crate) body: Vec<u8>,
}
impl IntoResponse for TrustTaskOutcome {
fn into_response(self) -> Response {
(
self.status,
[(axum::http::header::CONTENT_TYPE, "application/json")],
self.body,
)
.into_response()
}
}
pub(super) fn parse_payload<T: serde::de::DeserializeOwned>(
doc: &TrustTask<Value>,
) -> Result<T, TrustTaskOutcome> {
serde_json::from_value::<T>(doc.payload.clone()).map_err(|e| {
reject_with(
doc,
RejectReason::MalformedRequest {
reason: format!("payload parse: {e}"),
},
)
})
}
pub(super) fn app_error_to_reject(doc: &TrustTask<Value>, err: AppError) -> TrustTaskOutcome {
let message = err.to_string();
let reason = match err {
AppError::Authentication(_) | AppError::Unauthorized(_) | AppError::Forbidden(_) => {
RejectReason::PermissionDenied { reason: message }
}
AppError::Validation(_) | AppError::TrustTaskMalformed(_) => {
RejectReason::MalformedRequest { reason: message }
}
AppError::NotFound(_) | AppError::Conflict(_) => RejectReason::TaskFailed {
reason: message,
details: None,
},
_ => RejectReason::InternalError { reason: message },
};
reject_with(doc, reason)
}
pub(super) fn reject_with(doc: &TrustTask<Value>, reason: RejectReason) -> TrustTaskOutcome {
let routed = doc.reject_with(format!("urn:uuid:{}", Uuid::new_v4()), reason);
error_response(routed)
}
pub(super) fn success_response<R: serde::Serialize>(
doc: &TrustTask<Value>,
payload: R,
) -> TrustTaskOutcome {
let response_doc = doc.respond_with(format!("urn:uuid:{}", Uuid::new_v4()), payload);
let body = match serde_json::to_vec(&response_doc) {
Ok(b) => b,
Err(e) => {
tracing::error!(error = %e, "failed to serialise success response doc");
return reject_with(
doc,
RejectReason::InternalError {
reason: format!("response serialisation: {e}"),
},
);
}
};
TrustTaskOutcome {
status: StatusCode::OK,
body,
}
}
#[allow(dead_code)]
pub(super) fn not_implemented_yet(doc: TrustTask<Value>, reason: &str) -> TrustTaskOutcome {
let reject = RejectReason::TaskFailed {
reason: reason.to_string(),
details: None,
};
let routed = doc.reject_with(format!("urn:uuid:{}", Uuid::new_v4()), reject);
error_response(routed)
}
pub(super) fn method_not_found(doc: TrustTask<Value>, type_uri: &str) -> TrustTaskOutcome {
let reject = RejectReason::UnsupportedType {
type_uri: type_uri.to_string(),
};
let routed = doc.reject_with(format!("urn:uuid:{}", Uuid::new_v4()), reject);
error_response(routed)
}
pub(super) fn error_response(err_doc: ErrorResponse) -> TrustTaskOutcome {
let status = StatusCode::from_u16(status_for_code(&err_doc.payload.code))
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
let body = serde_json::to_vec(&err_doc).unwrap_or_else(|_| Vec::new());
TrustTaskOutcome { status, body }
}
pub(super) fn body_parse_error_response(reason: &str) -> TrustTaskOutcome {
let reject = RejectReason::MalformedRequest {
reason: format!("body did not parse as a Trust Task document: {reason}"),
};
let payload: ErrorPayload = reject.into();
let type_uri: TypeUri = "https://trusttasks.org/spec/trust-task-error/0.1"
.parse()
.expect("framework error Type URI parses");
let err = ErrorResponse {
id: format!("urn:uuid:{}", Uuid::new_v4()),
thread_id: None,
type_uri,
issuer: None,
recipient: None,
issued_at: Some(chrono::Utc::now()),
expires_at: None,
payload,
context: None,
proof: None,
extra: Default::default(),
};
error_response(err)
}