identity_credential/revocation/status_list_2021/
entry.rs1use identity_core::common::Url;
5use serde::de::Error;
6use serde::de::Visitor;
7use serde::Deserialize;
8use serde::Serialize;
9
10use crate::credential::Status;
11
12use super::credential::StatusPurpose;
13
14const CREDENTIAL_STATUS_TYPE: &str = "StatusList2021Entry";
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: 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(CREDENTIAL_STATUS_TYPE))
37 .map(ToOwned::to_owned)
38}
39
40fn serialize_number_as_string<S>(value: &usize, serializer: S) -> Result<S::Ok, S::Error>
42where
43 S: serde::Serializer,
44{
45 serializer.serialize_str(&value.to_string())
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)]
50#[serde(rename_all = "camelCase")]
51pub struct StatusList2021Entry {
52 id: Url,
53 #[serde(rename = "type", deserialize_with = "deserialize_status_entry_type")]
54 type_: String,
55 status_purpose: StatusPurpose,
56 #[serde(
57 deserialize_with = "serde_aux::prelude::deserialize_number_from_string",
58 serialize_with = "serialize_number_as_string"
59 )]
60 status_list_index: usize,
61 status_list_credential: Url,
62}
63
64impl TryFrom<&Status> for StatusList2021Entry {
65 type Error = serde_json::Error;
66 fn try_from(status: &Status) -> Result<Self, Self::Error> {
67 let json_status = serde_json::to_value(status)?;
68 serde_json::from_value(json_status)
69 }
70}
71
72impl From<StatusList2021Entry> for Status {
73 fn from(entry: StatusList2021Entry) -> Self {
74 let json_status = serde_json::to_value(entry).unwrap(); serde_json::from_value(json_status).unwrap() }
77}
78
79impl StatusList2021Entry {
80 pub fn new(status_list: Url, purpose: StatusPurpose, index: usize, id: Option<Url>) -> Self {
82 let id = id.unwrap_or_else(|| {
83 let mut id = status_list.clone();
84 id.set_fragment(None);
85 id
86 });
87
88 Self {
89 id,
90 type_: CREDENTIAL_STATUS_TYPE.to_owned(),
91 status_purpose: purpose,
92 status_list_credential: status_list,
93 status_list_index: index,
94 }
95 }
96
97 pub const fn id(&self) -> &Url {
99 &self.id
100 }
101
102 pub const fn purpose(&self) -> StatusPurpose {
104 self.status_purpose
105 }
106
107 pub const fn index(&self) -> usize {
109 self.status_list_index
110 }
111
112 pub const fn status_list_credential(&self) -> &Url {
114 &self.status_list_credential
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 const STATUS_LIST_ENTRY_SAMPLE: &str = r#"
123{
124 "id": "https://example.com/credentials/status/3#94567",
125 "type": "StatusList2021Entry",
126 "statusPurpose": "revocation",
127 "statusListIndex": "94567",
128 "statusListCredential": "https://example.com/credentials/status/3"
129}"#;
130
131 #[test]
132 fn entry_deserialization_works() {
133 let deserialized =
134 serde_json::from_str::<StatusList2021Entry>(STATUS_LIST_ENTRY_SAMPLE).expect("Failed to deserialize");
135 let status = StatusList2021Entry::new(
136 Url::parse("https://example.com/credentials/status/3").unwrap(),
137 StatusPurpose::Revocation,
138 94567,
139 Url::parse("https://example.com/credentials/status/3#94567").ok(),
140 );
141 assert_eq!(status, deserialized);
142 }
143
144 #[test]
145 #[should_panic]
146 fn deserializing_wrong_status_type_fails() {
147 let status = serde_json::json!({
148 "id": "https://example.com/credentials/status/3#94567",
149 "type": "Whatever2024",
150 "statusPurpose": "revocation",
151 "statusListIndex": "94567",
152 "statusListCredential": "https://example.com/credentials/status/3"
153 });
154 serde_json::from_value::<StatusList2021Entry>(status).expect("wrong type");
155 }
156
157 #[test]
158 fn test_status_list_index_serialization() {
159 let base_url = Url::parse("https://example.com/credentials/status/3").unwrap();
160
161 let entry1 = StatusList2021Entry::new(base_url.clone(), StatusPurpose::Revocation, 94567, None);
162 let json1 = serde_json::to_value(&entry1).unwrap();
163 assert_eq!(json1["statusListIndex"], "94567");
164 }
165}