use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OperationKind {
FindMany,
FindUnique,
Create,
Update,
Delete,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OperationRequest {
pub model: String,
pub kind: OperationKind,
#[serde(default)]
pub payload: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "snake_case")]
pub enum OperationResponse {
Ok { data: serde_json::Value },
Err(OperationError),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OperationError {
pub code: String,
pub message: String,
}
impl OperationResponse {
pub fn ok<T: Serialize>(value: &T) -> Result<Self, serde_json::Error> {
Ok(Self::Ok {
data: serde_json::to_value(value)?,
})
}
pub fn err(code: impl Into<String>, message: impl Into<String>) -> Self {
Self::Err(OperationError {
code: code.into(),
message: message.into(),
})
}
}
impl From<crate::RusqliteError> for OperationResponse {
fn from(error: crate::RusqliteError) -> Self {
let code = match &error {
crate::RusqliteError::NotFound => "not_found",
crate::RusqliteError::Locked => "locked",
crate::RusqliteError::Sqlite(_) => "sqlite",
crate::RusqliteError::BatchTooLarge { .. } => "batch_too_large",
crate::RusqliteError::DuplicateBatchKey { .. } => "duplicate_batch_key",
crate::RusqliteError::Validation(_) => "validation",
};
Self::err(code, error.to_string())
}
}
pub fn json_request_from(bytes: &[u8]) -> Result<OperationRequest, serde_json::Error> {
serde_json::from_slice(bytes)
}
pub fn json_response_into(response: &OperationResponse) -> Vec<u8> {
serde_json::to_vec(response).unwrap_or_else(|_| {
br#"{"status":"err","code":"serialize","message":"response serialization failed"}"#.to_vec()
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_round_trips_through_json() {
let req = OperationRequest {
model: "Account".into(),
kind: OperationKind::FindUnique,
payload: serde_json::json!({"id": 42}),
};
let bytes = serde_json::to_vec(&req).unwrap();
let decoded = json_request_from(&bytes).unwrap();
assert_eq!(decoded.model, "Account");
assert_eq!(decoded.kind, OperationKind::FindUnique);
assert_eq!(decoded.payload, req.payload);
}
#[test]
fn response_ok_serializes_with_status_tag() {
let resp = OperationResponse::ok(&serde_json::json!({"id": 1})).unwrap();
let bytes = json_response_into(&resp);
let text = std::str::from_utf8(&bytes).unwrap();
assert!(text.contains("\"status\":\"ok\""));
assert!(text.contains("\"id\":1"));
}
#[test]
fn response_err_carries_code_and_message() {
let resp = OperationResponse::err("not_found", "row missing");
let bytes = json_response_into(&resp);
let text = std::str::from_utf8(&bytes).unwrap();
assert!(text.contains("\"status\":\"err\""));
assert!(text.contains("\"code\":\"not_found\""));
}
#[test]
fn rusqlite_error_maps_to_response_with_stable_code() {
let resp: OperationResponse = crate::RusqliteError::NotFound.into();
let bytes = json_response_into(&resp);
assert!(std::str::from_utf8(&bytes).unwrap().contains("not_found"));
}
}