twine_lib/twine/
strand.rs1use super::{Tagged, Tixel, TwineBlock};
2use crate::errors::VerificationError;
3use crate::Ipld;
4use crate::{
5 as_cid::AsCid,
6 crypto::{get_hasher, PublicKey},
7 schemas::StrandSchemaVersion,
8 specification::Subspec,
9 verify::Verified,
10};
11use ipld_core::{cid::Cid, codec::Codec, serde::from_ipld};
12use multihash_codetable::Code;
13use semver::Version;
14use serde::de::DeserializeOwned;
15use serde_ipld_dagcbor::codec::DagCborCodec;
16use serde_ipld_dagjson::codec::DagJsonCodec;
17use std::{fmt::Display, sync::Arc};
18
19#[derive(Debug, PartialEq, Eq, Hash, Clone)]
36pub struct Strand(pub(crate) Arc<Verified<StrandSchemaVersion>>);
37
38impl Strand {
39 pub fn try_new<C>(container: C) -> Result<Self, VerificationError>
43 where
44 C: TryInto<StrandSchemaVersion>,
45 VerificationError: From<<C as TryInto<StrandSchemaVersion>>::Error>,
46 {
47 let container = container.try_into()?;
48 Ok(Self(Arc::new(Verified::try_new(container)?)))
49 }
50
51 pub fn cid(&self) -> Cid {
53 *self.0.cid()
54 }
55
56 pub fn key(&self) -> PublicKey {
58 self.0.key()
59 }
60
61 pub fn radix(&self) -> u8 {
63 self.0.radix()
64 }
65
66 pub fn spec_str(&self) -> &str {
68 self.0.spec_str()
69 }
70
71 pub fn version(&self) -> Version {
73 self.0.version()
74 }
75
76 pub fn subspec(&self) -> Option<Subspec> {
78 self.0.subspec()
79 }
80
81 pub fn details(&self) -> &Ipld {
86 self.0.details()
87 }
88
89 pub fn extract_details<T: DeserializeOwned>(&self) -> Result<T, VerificationError> {
91 let details = self.details();
92 Ok(from_ipld(details.clone()).map_err(|e| VerificationError::Payload(e.to_string()))?)
93 }
94
95 pub fn expiry(&self) -> Option<chrono::DateTime<chrono::Utc>> {
97 self.0.expiry()
98 }
99
100 pub fn verify_tixel(&self, tixel: &Tixel) -> Result<(), VerificationError> {
102 self.0.verify_tixel(tixel)
103 }
104
105 pub fn hasher(&self) -> Code {
107 self.0.hasher()
108 }
109}
110
111impl From<Strand> for Cid {
112 fn from(t: Strand) -> Self {
113 t.cid()
114 }
115}
116
117impl AsCid for Strand {
118 fn as_cid(&self) -> &Cid {
119 self.0.cid()
120 }
121}
122
123impl TwineBlock for Strand {
124 fn cid(&self) -> &Cid {
125 self.as_cid()
126 }
127
128 fn from_tagged_dag_json<S: Display>(json: S) -> Result<Self, VerificationError> {
129 let t: Tagged<Strand> = DagJsonCodec::decode_from_slice(json.to_string().as_bytes())?;
130 Ok(t.unpack())
131 }
132
133 fn from_bytes_unchecked(hasher: Code, bytes: Vec<u8>) -> Result<Self, VerificationError> {
134 let mut twine: StrandSchemaVersion = DagCborCodec::decode_from_slice(bytes.as_slice())?;
135 if let StrandSchemaVersion::V1(_) = twine {
137 twine.compute_cid(hasher);
138 }
139 Ok(Self(Arc::new(Verified::try_new(twine)?)))
140 }
141
142 fn from_block<T: AsRef<[u8]>>(cid: Cid, bytes: T) -> Result<Self, VerificationError> {
143 let hasher = get_hasher(&cid)?;
144 let twine = Self::from_bytes_unchecked(hasher, bytes.as_ref().to_vec())?;
145 twine.verify_cid(&cid)?;
146 Ok(twine)
147 }
148
149 fn tagged_dag_json(&self) -> String {
150 format!(
151 "{{\"cid\":{},\"data\":{}}}",
152 String::from_utf8(DagJsonCodec::encode_to_vec(&self.cid()).unwrap()).unwrap(),
153 String::from_utf8(DagJsonCodec::encode_to_vec(&self.0).unwrap()).unwrap()
154 )
155 }
156
157 fn bytes(&self) -> Arc<[u8]> {
158 DagCborCodec::encode_to_vec(&self.0)
159 .unwrap()
160 .as_slice()
161 .into()
162 }
163
164 fn content_bytes(&self) -> Arc<[u8]> {
165 self.0.content_bytes()
166 }
167}
168
169impl Display for Strand {
170 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171 write!(f, "{}", self.tagged_dag_json_pretty())
172 }
173}