identity_credential/revocation/validity_timeframe_2024/
revocation_timeframe_status.rs1use crate::credential::Status;
5use crate::error::Error;
6use crate::error::Result;
7use identity_core::common::Duration;
8use identity_core::common::Object;
9use identity_core::common::Timestamp;
10use identity_core::common::Url;
11use identity_core::common::Value;
12use serde::de::Visitor;
13use serde::Deserialize;
14use serde::Serialize;
15
16fn deserialize_status_entry_type<'de, D>(deserializer: D) -> Result<String, D::Error>
17where
18 D: serde::Deserializer<'de>,
19{
20 struct ExactStrVisitor(&'static str);
21 impl Visitor<'_> for ExactStrVisitor {
22 type Value = &'static str;
23 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 write!(formatter, "the exact string \"{}\"", self.0)
25 }
26 fn visit_str<E: serde::de::Error>(self, str: &str) -> Result<Self::Value, E> {
27 if str == self.0 {
28 Ok(self.0)
29 } else {
30 Err(E::custom(format!("not \"{}\"", self.0)))
31 }
32 }
33 }
34
35 deserializer
36 .deserialize_str(ExactStrVisitor(RevocationTimeframeStatus::TYPE))
37 .map(ToOwned::to_owned)
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)]
42#[serde(rename_all = "camelCase")]
43pub struct RevocationTimeframeStatus {
44 id: Url,
45 #[serde(rename = "type", deserialize_with = "deserialize_status_entry_type")]
46 type_: String,
47 start_validity_timeframe: Timestamp,
48 end_validity_timeframe: Timestamp,
49 #[serde(
50 deserialize_with = "serde_aux::prelude::deserialize_option_number_from_string",
51 skip_serializing_if = "Option::is_none"
52 )]
53 revocation_bitmap_index: Option<u32>,
54}
55
56impl RevocationTimeframeStatus {
57 pub const START_TIMEFRAME_PROPERTY: &'static str = "startValidityTimeframe";
59 pub const END_TIMEFRAME_PROPERTY: &'static str = "endValidityTimeframe";
61 pub const TYPE: &'static str = "RevocationTimeframe2024";
63 const INDEX_PROPERTY: &'static str = "revocationBitmapIndex";
65
66 pub fn new(start_validity: Option<Timestamp>, duration: Duration, id: Url, index: u32) -> Result<Self> {
68 let start_validity_timeframe = start_validity.unwrap_or(Timestamp::now_utc());
69 let end_validity_timeframe = start_validity_timeframe
70 .checked_add(duration)
71 .ok_or(Error::InvalidStatus(
72 "With that granularity, endValidityTimeFrame will turn out not to be in the valid range for RFC 3339"
73 .to_owned(),
74 ))?;
75
76 Ok(Self {
77 id,
78 type_: Self::TYPE.to_owned(),
79 start_validity_timeframe,
80 end_validity_timeframe,
81 revocation_bitmap_index: Some(index),
82 })
83 }
84
85 pub fn start_validity_timeframe(&self) -> Timestamp {
87 self.start_validity_timeframe
88 }
89
90 pub fn end_validity_timeframe(&self) -> Timestamp {
92 self.end_validity_timeframe
93 }
94
95 pub fn id(&self) -> &Url {
98 &self.id
99 }
100
101 pub fn index(&self) -> Option<u32> {
103 self.revocation_bitmap_index
104 }
105}
106
107impl TryFrom<&Status> for RevocationTimeframeStatus {
108 type Error = Error;
109 fn try_from(status: &Status) -> Result<Self, Self::Error> {
110 let json_status: String = serde_json::to_string(&status)
113 .map_err(|err| Self::Error::InvalidStatus(format!("failed to read `Status`; {}", &err.to_string())))?;
114 serde_json::from_str(&json_status).map_err(|err| {
115 Self::Error::InvalidStatus(format!(
116 "failed to convert `Status` to `RevocationTimeframeStatus`; {}",
117 &err.to_string(),
118 ))
119 })
120 }
121}
122
123impl From<RevocationTimeframeStatus> for Status {
124 fn from(revocation_timeframe_status: RevocationTimeframeStatus) -> Self {
125 let mut properties = Object::new();
126 properties.insert(
127 RevocationTimeframeStatus::START_TIMEFRAME_PROPERTY.to_owned(),
128 Value::String(revocation_timeframe_status.start_validity_timeframe().to_rfc3339()),
129 );
130 properties.insert(
131 RevocationTimeframeStatus::END_TIMEFRAME_PROPERTY.to_owned(),
132 Value::String(revocation_timeframe_status.end_validity_timeframe().to_rfc3339()),
133 );
134 if let Some(value) = revocation_timeframe_status.index() {
135 properties.insert(
136 RevocationTimeframeStatus::INDEX_PROPERTY.to_owned(),
137 Value::String(value.to_string()),
138 );
139 }
140
141 Status::new_with_properties(
142 revocation_timeframe_status.id,
143 RevocationTimeframeStatus::TYPE.to_owned(),
144 properties,
145 )
146 }
147}
148
149#[derive(Clone, Debug, PartialEq, Eq)]
151pub struct VerifierRevocationTimeframeStatus(pub(crate) RevocationTimeframeStatus);
152
153impl TryFrom<Status> for VerifierRevocationTimeframeStatus {
154 type Error = Error;
155
156 fn try_from(status: Status) -> Result<Self> {
157 Ok(Self((&status).try_into().map_err(|err: Error| {
158 Self::Error::InvalidStatus(format!(
159 "failed to convert `Status` to `VerifierRevocationTimeframeStatus`; {}",
160 &err.to_string()
161 ))
162 })?))
163 }
164}
165
166impl From<VerifierRevocationTimeframeStatus> for Status {
167 fn from(status: VerifierRevocationTimeframeStatus) -> Self {
168 status.0.into()
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 const EXAMPLE_SERIALIZED: &str = r#"{
177 "id": "did:iota:snd:0xae6ccfdb155a69e0ef153fb5fcfd50c08a8fee36babe1f7d71dede8f4e202432#my-revocation-service",
178 "startValidityTimeframe": "2024-03-19T13:57:50Z",
179 "endValidityTimeframe": "2024-03-19T13:58:50Z",
180 "revocationBitmapIndex": "5",
181 "type": "RevocationTimeframe2024"
182 }"#;
183
184 fn get_example_status() -> anyhow::Result<RevocationTimeframeStatus> {
185 let duration = Duration::minutes(1);
186 let service_url = Url::parse(
187 "did:iota:snd:0xae6ccfdb155a69e0ef153fb5fcfd50c08a8fee36babe1f7d71dede8f4e202432#my-revocation-service",
188 )?;
189 let credential_index: u32 = 5;
190 let start_validity_timeframe = Timestamp::parse("2024-03-19T13:57:50Z")?;
191
192 Ok(RevocationTimeframeStatus::new(
193 Some(start_validity_timeframe),
194 duration,
195 service_url,
196 credential_index,
197 )?)
198 }
199
200 #[test]
201 fn revocation_timeframe_status_serialization_works() -> anyhow::Result<()> {
202 let status = get_example_status()?;
203
204 let serialized = serde_json::to_string(&status).expect("Failed to deserialize");
205 dbg!(&serialized);
206
207 Ok(())
208 }
209
210 #[test]
211 fn revocation_timeframe_status_deserialization_works() -> anyhow::Result<()> {
212 let status = get_example_status()?;
213 let deserialized =
214 serde_json::from_str::<RevocationTimeframeStatus>(EXAMPLE_SERIALIZED).expect("Failed to deserialize");
215
216 assert_eq!(status, deserialized);
217
218 Ok(())
219 }
220}