use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
pub struct OperationResult<T = ()> {
pub success: bool,
pub status: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<T>,
}
impl<T> OperationResult<T> {
pub fn ok(result: T, message: impl Into<String>) -> Self {
Self::ok_with_status(0, result, message)
}
pub fn ok_with_status(status: i32, result: T, message: impl Into<String>) -> Self {
Self {
success: true,
status,
message: message.into(),
result: Some(result),
}
}
pub fn failed(status: i32, message: impl Into<String>) -> Self {
Self {
success: false,
status,
message: message.into(),
result: None,
}
}
pub fn with_status(mut self, status: i32) -> Self {
self.status = status;
self
}
pub fn with_message(mut self, message: impl Into<String>) -> Self {
self.message = message.into();
self
}
}
impl OperationResult<()> {
pub fn success(message: impl Into<String>) -> Self {
Self::success_with_status(0, message)
}
pub fn success_with_status(status: i32, message: impl Into<String>) -> Self {
Self {
success: true,
status,
message: message.into(),
result: None,
}
}
pub fn with_result<T>(self, result: T) -> OperationResult<T> {
OperationResult {
success: self.success,
status: self.status,
message: self.message,
result: Some(result),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Serialize)]
struct UserDto {
id: u32,
}
#[test]
fn unit_result_omits_the_result_member() {
let value = serde_json::to_value(OperationResult::success("deleted")).unwrap();
assert_eq!(
value,
serde_json::json!({ "success": true, "status": 0, "message": "deleted" })
);
}
#[test]
fn generic_result_carries_the_payload_and_its_business_status() {
let value = serde_json::to_value(OperationResult::ok_with_status(
7,
UserDto { id: 7 },
"created",
))
.unwrap();
assert_eq!(
value,
serde_json::json!({
"success": true,
"status": 7,
"message": "created",
"result": { "id": 7 }
})
);
}
#[test]
fn failed_result_keeps_the_business_status_and_drops_the_payload() {
let value =
serde_json::to_value(OperationResult::<UserDto>::failed(1001, "state conflict"))
.unwrap();
assert_eq!(
value,
serde_json::json!({
"success": false,
"status": 1001,
"message": "state conflict"
})
);
}
#[test]
fn setters_adjust_an_outcome_decided_while_the_operation_runs() {
let mut outcome = OperationResult::success("published");
if true {
outcome = outcome.with_status(1001).with_message("already published");
}
outcome.status = 1001;
let value = serde_json::to_value(outcome).unwrap();
assert_eq!(
value,
serde_json::json!({
"success": true,
"status": 1001,
"message": "already published"
})
);
}
#[test]
fn with_result_attaches_the_payload_to_a_unit_envelope() {
let value = serde_json::to_value(
OperationResult::success("created").with_result(UserDto { id: 9 }),
)
.unwrap();
assert_eq!(
value,
serde_json::json!({
"success": true,
"status": 0,
"message": "created",
"result": { "id": 9 }
})
);
}
}