1use std::{collections::BTreeMap, fmt::Display, str::FromStr};
2
3use borderless_id_types::{AgentId, Uuid};
4use borderless_pkg::WasmPkg;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8pub use borderless_pkg as pkg;
9
10use crate::{
11 contracts::{Role, TxCtx},
12 events::Sink,
13 BorderlessId, ContractId,
14};
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
18pub struct Description {
19 pub display_name: String,
20 pub summary: String,
21 #[serde(default)]
22 pub legal: Option<String>,
23}
24
25#[derive(Debug, Default, Clone, Serialize, Deserialize)]
29pub struct Metadata {
30 #[serde(default)]
31 pub active_since: u64,
33
34 #[serde(default)]
35 pub tx_ctx_introduction: Option<TxCtx>,
39
40 #[serde(default)]
42 pub inactive_since: u64,
43
44 #[serde(default)]
45 pub tx_ctx_revocation: Option<TxCtx>,
49
50 #[serde(default)]
52 pub parent: Option<Uuid>,
53}
54
55#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
57#[serde(untagged)]
58pub enum Id {
59 Contract { contract_id: ContractId },
60 Agent { agent_id: AgentId },
61}
62
63impl Id {
64 pub fn as_cid(&self) -> Option<ContractId> {
65 match self {
66 Id::Contract { contract_id } => Some(*contract_id),
67 Id::Agent { .. } => None,
68 }
69 }
70
71 pub fn as_aid(&self) -> Option<AgentId> {
72 match self {
73 Id::Contract { .. } => None,
74 Id::Agent { agent_id } => Some(*agent_id),
75 }
76 }
77
78 pub fn contract(contract_id: ContractId) -> Self {
79 Id::Contract { contract_id }
80 }
81
82 pub fn agent(agent_id: AgentId) -> Self {
83 Id::Agent { agent_id }
84 }
85}
86
87impl Display for Id {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 match self {
90 Id::Contract { contract_id } => write!(f, "{contract_id}"),
91 Id::Agent { agent_id } => write!(f, "{agent_id}"),
92 }
93 }
94}
95
96impl AsRef<[u8; 16]> for Id {
97 fn as_ref(&self) -> &[u8; 16] {
98 match self {
99 Id::Contract { contract_id } => contract_id.as_ref(),
100 Id::Agent { agent_id } => agent_id.as_ref(),
101 }
102 }
103}
104
105impl PartialEq<ContractId> for Id {
106 fn eq(&self, other: &ContractId) -> bool {
107 match self {
108 Id::Contract { contract_id } => contract_id == other,
109 Id::Agent { .. } => false,
110 }
111 }
112}
113
114impl PartialEq<AgentId> for Id {
115 fn eq(&self, other: &AgentId) -> bool {
116 match self {
117 Id::Agent { agent_id } => agent_id == other,
118 Id::Contract { .. } => false,
119 }
120 }
121}
122
123impl From<ContractId> for Id {
124 fn from(contract_id: ContractId) -> Self {
125 Id::Contract { contract_id }
126 }
127}
128
129impl From<AgentId> for Id {
130 fn from(agent_id: AgentId) -> Self {
131 Id::Agent { agent_id }
132 }
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct Introduction {
164 #[serde(flatten)]
166 pub id: Id,
167
168 #[serde(default)]
170 pub participants: Vec<BorderlessId>,
171
172 pub initial_state: Value,
176
177 #[serde(default)]
181 pub roles: Vec<Role>,
182
183 #[serde(default)]
185 pub sinks: Vec<Sink>,
186
187 pub desc: Description,
189
190 #[serde(default)]
191 pub meta: Metadata,
193
194 pub package: WasmPkg,
196}
197
198impl Introduction {
199 pub fn to_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
201 serde_json::to_vec(&self)
202 }
203
204 pub fn from_bytes(bytes: &[u8]) -> Result<Self, serde_json::Error> {
206 serde_json::from_slice(bytes)
207 }
208
209 pub fn pretty_print(&self) -> Result<String, serde_json::Error> {
211 serde_json::to_string_pretty(&self)
212 }
213}
214
215impl FromStr for Introduction {
216 type Err = serde_json::Error;
217
218 fn from_str(s: &str) -> Result<Self, Self::Err> {
219 serde_json::from_str(s)
220 }
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct IntroductionDto {
229 #[serde(flatten)]
233 #[serde(default)]
234 #[serde(skip_serializing_if = "Option::is_none")]
235 pub id: Option<Id>,
236
237 #[serde(default)]
239 #[serde(skip_serializing_if = "Vec::is_empty")]
240 pub participants: Vec<BorderlessId>,
241
242 pub initial_state: Value,
246
247 #[serde(default)]
251 #[serde(skip_serializing_if = "Vec::is_empty")]
252 pub roles: Vec<Role>,
253
254 #[serde(default)]
256 #[serde(skip_serializing_if = "Vec::is_empty")]
257 pub sinks: Vec<Sink>,
258
259 pub desc: Description,
261
262 pub package: WasmPkg,
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct Revocation {
271 #[serde(flatten)]
273 pub id: Id,
274
275 pub reason: String,
277}
278
279impl Revocation {
280 pub fn to_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
282 serde_json::to_vec(&self)
283 }
284
285 pub fn from_bytes(bytes: &[u8]) -> Result<Self, serde_json::Error> {
287 serde_json::from_slice(bytes)
288 }
289
290 pub fn pretty_print(&self) -> Result<String, serde_json::Error> {
292 serde_json::to_string_pretty(&self)
293 }
294}
295
296impl FromStr for Revocation {
297 type Err = serde_json::Error;
298
299 fn from_str(s: &str) -> Result<Self, Self::Err> {
300 serde_json::from_str(s)
301 }
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize)]
306pub struct Symbols {
307 pub state: BTreeMap<String, u64>,
309 pub actions: BTreeMap<String, u32>,
311}
312
313impl Symbols {
314 pub fn from_symbols(state_syms: &[(&str, u64)], action_syms: &[(&str, u32)]) -> Self {
319 let mut state = BTreeMap::new();
321 for (name, addr) in state_syms {
322 state.insert(name.to_string(), *addr);
323 }
324 let mut actions = BTreeMap::new();
325 for (name, addr) in action_syms {
326 actions.insert(name.to_string(), *addr);
327 }
328 Self { state, actions }
329 }
330
331 pub fn to_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
333 serde_json::to_vec(self)
334 }
335
336 pub fn from_bytes(bytes: &[u8]) -> Result<Self, serde_json::Error> {
338 serde_json::from_slice(bytes)
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 #[test]
347 fn general_id() {
348 let cid = r#"{ "contract_id": "cbcd81bb-b90c-8806-8341-fe95b8ede45a" }"#;
349 let aid = r#"{ "agent_id": "abcd81bb-b90c-8806-8341-fe95b8ede45a" }"#;
350 let parsed: Result<Id, _> = serde_json::from_str(&cid);
351 assert!(parsed.is_ok(), "{}", parsed.unwrap_err());
352 match parsed.unwrap() {
353 Id::Contract { contract_id } => assert_eq!(
354 contract_id.to_string(),
355 "cbcd81bb-b90c-8806-8341-fe95b8ede45a"
356 ),
357 Id::Agent { .. } => panic!("result was not an agent-id"),
358 }
359
360 let parsed: Result<Id, _> = serde_json::from_str(&aid);
361 assert!(parsed.is_ok(), "{}", parsed.unwrap_err());
362 match parsed.unwrap() {
363 Id::Agent { agent_id } => {
364 assert_eq!(agent_id.to_string(), "abcd81bb-b90c-8806-8341-fe95b8ede45a")
365 }
366 Id::Contract { .. } => panic!("result was not a contract-id"),
367 }
368 }
369
370 #[test]
371 fn parse_introduction() {
372 let json = r#"
373{
374 "contract_id": "cc8ca79c-3bbb-89d2-bb28-29636c170387",
375 "participants": [],
376 "initial_state": {
377 "switch": true,
378 "counter": 0,
379 "history": []
380 },
381 "roles": [],
382 "sinks": [],
383 "desc": {
384 "display_name": "flipper",
385 "summary": "a flipper contract for testing the abi",
386 "legal": null
387 },
388 "meta": {},
389 "package": {
390 "name": "flipper-contract",
391 "pkg_type": "contract",
392 "source": {
393 "version": "0.1.0",
394 "digest": "",
395 "wasm": ""
396 }
397 }
398}
399"#;
400 let result: Result<Introduction, _> = serde_json::from_str(&json);
401 assert!(result.is_ok(), "{}", result.unwrap_err());
402 let introduction = result.unwrap();
403 assert_eq!(
404 introduction.id,
405 Id::Contract {
406 contract_id: "cc8ca79c-3bbb-89d2-bb28-29636c170387".parse().unwrap()
407 }
408 );
409 let json = json.replace(r#""contract_id": "c"#, r#""agent_id": "a"#);
410 let result: Result<Introduction, _> = serde_json::from_str(&json);
411 assert!(result.is_ok(), "{}", result.unwrap_err());
412 let introduction = result.unwrap();
413 assert_eq!(
414 introduction.id,
415 Id::Agent {
416 agent_id: "ac8ca79c-3bbb-89d2-bb28-29636c170387".parse().unwrap()
417 }
418 );
419 }
420}