mythic/protocol/
post_response.rs1use alloc::{
4 string::{String, ToString},
5 vec::Vec,
6};
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9
10use super::{
11 ACTION_POST_RESPONSE,
12 get_tasking::{AgentMessageExtras, AgentResponseExtras, TaskResponse},
13};
14
15#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
18pub struct ResponseReceipt {
19 pub task_id: Uuid,
20 pub status: String,
21 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub file_id: Option<Uuid>,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
24 pub error: Option<String>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
30pub struct ReqPostResponse {
31 pub action: String,
32 #[serde(flatten)]
33 pub extras: AgentMessageExtras,
34}
35
36impl ReqPostResponse {
37 pub fn new(responses: Vec<TaskResponse>) -> Self {
38 Self {
39 action: ACTION_POST_RESPONSE.to_string(),
40 extras: AgentMessageExtras {
41 responses,
42 shared: super::get_tasking::AgentExtras::default(),
43 },
44 }
45 }
46
47 pub fn from_extras(extras: AgentMessageExtras) -> Self {
50 Self {
51 action: ACTION_POST_RESPONSE.to_string(),
52 extras,
53 }
54 }
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
58pub struct RespPostResponse {
59 pub action: String,
60 #[serde(default)]
61 pub responses: Vec<ResponseReceipt>,
62 #[serde(flatten)]
63 pub extras: AgentResponseExtras,
64}
65
66impl RespPostResponse {
67 pub fn new(responses: Vec<ResponseReceipt>) -> Self {
68 Self {
69 action: ACTION_POST_RESPONSE.to_string(),
70 responses,
71 extras: AgentResponseExtras::default(),
72 }
73 }
74}
75
76#[cfg(test)]
79mod tests {
80 use super::*;
81 use alloc::vec;
82
83 #[test]
84 fn post_response_wraps_responses() {
85 let task_id = Uuid::nil();
86 let req = ReqPostResponse::new(vec![TaskResponse::completed(task_id, "ok")]);
87
88 assert_eq!(req.action, ACTION_POST_RESPONSE);
89 assert_eq!(req.extras.responses.len(), 1);
90 assert_eq!(req.extras.responses[0].task_id, task_id);
91 assert_eq!(req.extras.responses[0].status.as_deref(), Some("completed"));
92 }
93
94 #[test]
95 fn receipt_roundtrip() {
96 let uuid = Uuid::nil();
97 let receipt = ResponseReceipt {
98 task_id: uuid,
99 status: "sent".to_string(),
100 file_id: Some(Uuid::from_u128(1)),
101 error: Some("none".to_string()),
102 };
103 assert_eq!(
104 serde_json::from_str::<ResponseReceipt>(&serde_json::to_string(&receipt).unwrap())
105 .unwrap(),
106 receipt
107 );
108 }
109
110 #[test]
111 fn resp_post_response_roundtrip() {
112 let uuid = Uuid::nil();
113 let next_uuid = Uuid::from_u128(1);
114 let resp = RespPostResponse {
115 action: ACTION_POST_RESPONSE.to_string(),
116 responses: vec![ResponseReceipt {
117 task_id: uuid,
118 status: "sent".to_string(),
119 file_id: Some(next_uuid),
120 error: None,
121 }],
122 extras: AgentResponseExtras::default(),
123 };
124 assert_eq!(
125 serde_json::from_str::<RespPostResponse>(&serde_json::to_string(&resp).unwrap())
126 .unwrap(),
127 resp
128 );
129 }
130}