standout_dispatch/
contract.rs1use serde::ser::SerializeStruct;
12use serde::{Serialize, Serializer};
13
14pub trait ContractSurface {
15 const SCHEMA_VERSION: u32;
16
17 fn envelope(self) -> Envelope<Self>
18 where
19 Self: Sized,
20 {
21 Envelope { data: self }
22 }
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub struct Envelope<T> {
27 data: T,
28}
29
30impl<T: ContractSurface> Envelope<T> {
31 pub fn new(data: T) -> Self {
32 Self { data }
33 }
34
35 pub const fn schema_version(&self) -> u32 {
36 T::SCHEMA_VERSION
37 }
38
39 pub fn data(&self) -> &T {
40 &self.data
41 }
42
43 pub fn into_data(self) -> T {
44 self.data
45 }
46}
47
48impl<T: ContractSurface + Serialize> Serialize for Envelope<T> {
49 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
50 let mut envelope = serializer.serialize_struct("Envelope", 2)?;
51 envelope.serialize_field("schema_version", &T::SCHEMA_VERSION)?;
52 envelope.serialize_field("data", &self.data)?;
53 envelope.end()
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[derive(Serialize)]
62 struct Listing {
63 items: Vec<&'static str>,
64 }
65
66 impl ContractSurface for Listing {
67 const SCHEMA_VERSION: u32 = 3;
68 }
69
70 #[test]
71 fn the_envelope_stamps_the_version_before_the_data() {
72 let json = serde_json::to_string(&Listing { items: vec!["a"] }.envelope()).unwrap();
73 assert_eq!(json, r#"{"schema_version":3,"data":{"items":["a"]}}"#);
74 }
75
76 #[test]
77 fn the_envelope_hands_the_data_back() {
78 let envelope = Envelope::new(Listing {
79 items: vec!["a", "b"],
80 });
81 assert_eq!(envelope.schema_version(), 3);
82 assert_eq!(envelope.data().items.len(), 2);
83 assert_eq!(envelope.into_data().items, ["a", "b"]);
84 }
85}