1use exo_core::{Did, Hash256, Timestamp};
19use serde::{Deserialize, Serialize};
20use uuid::Uuid;
21
22use crate::error::{ApiError, Result};
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub enum ApiRequest {
27 CreateTransaction {
28 actor: Did,
29 scope: String,
30 },
31 TransitionState {
32 tx_id: Uuid,
33 target_state: String,
34 actor: Did,
35 },
36 QueryTransaction {
37 tx_id: Uuid,
38 },
39 ResolveIdentity {
40 did: Did,
41 },
42 RegisterIdentity {
43 did: Did,
44 public_key_hash: Hash256,
45 },
46 Deliberate {
47 proposal_hash: Hash256,
48 actor: Did,
49 },
50 Vote {
51 proposal_id: Uuid,
52 approve: bool,
53 actor: Did,
54 },
55 Challenge {
56 target_id: Uuid,
57 grounds: String,
58 actor: Did,
59 },
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub enum ApiResponse {
65 Success {
66 correlation_id: Uuid,
67 timestamp: Timestamp,
68 },
69 Error {
70 code: u32,
71 message: String,
72 },
73 TransactionState {
74 tx_id: Uuid,
75 state: String,
76 },
77 Identity {
78 did: Did,
79 verified: bool,
80 },
81 Receipt {
82 hash: Hash256,
83 timestamp: Timestamp,
84 },
85}
86
87pub fn canonical_request_hash(request: &ApiRequest) -> Result<Hash256> {
89 let mut buf = Vec::new();
90 write_canonical_request(request, &mut buf)?;
91 Ok(Hash256::digest(&buf))
92}
93
94fn write_canonical_request<W: std::io::Write>(request: &ApiRequest, writer: W) -> Result<()> {
95 ciborium::into_writer(request, writer)
96 .map_err(|err| ApiError::SerializationError(err.to_string()))
97}
98
99#[cfg(test)]
100#[allow(clippy::unwrap_used)]
101mod tests {
102 use super::*;
103 fn did(n: &str) -> Did {
104 Did::new(&format!("did:exo:{n}")).unwrap()
105 }
106
107 #[test]
108 fn request_variants_serde() {
109 let reqs: Vec<ApiRequest> = vec![
110 ApiRequest::CreateTransaction {
111 actor: did("a"),
112 scope: "s".into(),
113 },
114 ApiRequest::TransitionState {
115 tx_id: Uuid::nil(),
116 target_state: "t".into(),
117 actor: did("a"),
118 },
119 ApiRequest::QueryTransaction { tx_id: Uuid::nil() },
120 ApiRequest::ResolveIdentity { did: did("a") },
121 ApiRequest::RegisterIdentity {
122 did: did("a"),
123 public_key_hash: Hash256::ZERO,
124 },
125 ApiRequest::Deliberate {
126 proposal_hash: Hash256::ZERO,
127 actor: did("a"),
128 },
129 ApiRequest::Vote {
130 proposal_id: Uuid::nil(),
131 approve: true,
132 actor: did("a"),
133 },
134 ApiRequest::Challenge {
135 target_id: Uuid::nil(),
136 grounds: "g".into(),
137 actor: did("a"),
138 },
139 ];
140 for r in &reqs {
141 let j = serde_json::to_string(r).unwrap();
142 assert!(!j.is_empty());
143 }
144 }
145 #[test]
146 fn response_variants_serde() {
147 let resps: Vec<ApiResponse> = vec![
148 ApiResponse::Success {
149 correlation_id: Uuid::nil(),
150 timestamp: Timestamp::ZERO,
151 },
152 ApiResponse::Error {
153 code: 400,
154 message: "bad".into(),
155 },
156 ApiResponse::TransactionState {
157 tx_id: Uuid::nil(),
158 state: "s".into(),
159 },
160 ApiResponse::Identity {
161 did: did("a"),
162 verified: true,
163 },
164 ApiResponse::Receipt {
165 hash: Hash256::ZERO,
166 timestamp: Timestamp::ZERO,
167 },
168 ];
169 for r in &resps {
170 let j = serde_json::to_string(r).unwrap();
171 assert!(!j.is_empty());
172 }
173 }
174 #[test]
175 fn canonical_hash_deterministic() {
176 let r = ApiRequest::CreateTransaction {
177 actor: did("a"),
178 scope: "s".into(),
179 };
180 assert_eq!(
181 canonical_request_hash(&r).unwrap(),
182 canonical_request_hash(&r).unwrap()
183 );
184 }
185 #[test]
186 fn canonical_hash_differs() {
187 let r1 = ApiRequest::CreateTransaction {
188 actor: did("a"),
189 scope: "s1".into(),
190 };
191 let r2 = ApiRequest::CreateTransaction {
192 actor: did("a"),
193 scope: "s2".into(),
194 };
195 assert_ne!(
196 canonical_request_hash(&r1).unwrap(),
197 canonical_request_hash(&r2).unwrap()
198 );
199 }
200
201 #[test]
202 fn canonical_hash_writer_error_returns_error() {
203 let r = ApiRequest::CreateTransaction {
204 actor: did("a"),
205 scope: "s".into(),
206 };
207
208 let err = write_canonical_request(&r, FailingWriter).unwrap_err();
209 assert!(err.to_string().contains("serialization error"));
210 }
211
212 struct FailingWriter;
213
214 impl std::io::Write for FailingWriter {
215 fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
216 Err(std::io::Error::other("forced writer failure"))
217 }
218
219 fn flush(&mut self) -> std::io::Result<()> {
220 Ok(())
221 }
222 }
223}