identity_credential/credential/
enveloped_credential.rs1use std::fmt::Display;
5use std::ops::Deref;
6
7use identity_core::common::Context;
8use identity_core::common::DataUrl;
9use identity_core::common::InvalidDataUrl;
10use identity_core::common::Object;
11use identity_core::common::OneOrMany;
12use serde::Deserialize;
13use serde::Deserializer;
14use serde::Serialize;
15
16use crate::credential::credential_v2::deserialize_vc2_0_context;
17use crate::credential::CredentialV2;
18
19const ENVELOPED_VC_TYPE: &str = "EnvelopedVerifiableCredential";
20
21fn deserialize_enveloped_vc_type<'de, D>(deserializer: D) -> Result<Box<str>, D::Error>
22where
23 D: Deserializer<'de>,
24{
25 use serde::de::Error;
26 use serde::de::Unexpected;
27
28 let str = <&'de str>::deserialize(deserializer)?;
29 if str == ENVELOPED_VC_TYPE {
30 Ok(ENVELOPED_VC_TYPE.to_owned().into_boxed_str())
31 } else {
32 Err(Error::invalid_value(
33 Unexpected::Str(str),
34 &format!("\"{}\"", ENVELOPED_VC_TYPE).as_str(),
35 ))
36 }
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
42#[non_exhaustive]
43pub struct EnvelopedVc {
44 #[serde(rename = "@context", deserialize_with = "deserialize_vc2_0_context")]
46 context: OneOrMany<Context>,
47 pub id: VcDataUrl,
49 #[serde(rename = "type", deserialize_with = "deserialize_enveloped_vc_type")]
51 type_: Box<str>,
52 #[serde(flatten)]
54 pub properties: Object,
55}
56
57impl EnvelopedVc {
58 pub fn new(id: VcDataUrl) -> Self {
60 Self {
61 context: OneOrMany::One(CredentialV2::<()>::base_context().clone()),
62 id,
63 type_: ENVELOPED_VC_TYPE.to_owned().into_boxed_str(),
64 properties: Object::default(),
65 }
66 }
67
68 pub fn type_(&self) -> &str {
70 &self.type_
71 }
72
73 pub fn context(&self) -> &[Context] {
75 self.context.as_slice()
76 }
77
78 pub fn set_context(&mut self, contexts: impl IntoIterator<Item = Context>) {
96 use itertools::Itertools;
97
98 let contexts = std::iter::once(CredentialV2::<()>::base_context().clone())
99 .chain(contexts)
100 .unique()
101 .collect_vec();
102
103 self.context = contexts.into();
104 }
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
110#[serde(transparent)]
111pub struct VcDataUrl(DataUrl);
112
113impl VcDataUrl {
114 pub fn parse(input: &str) -> Result<Self, VcDataUrlParsingError> {
126 let data_url = DataUrl::parse(input)?;
127
128 if data_url.media_type().starts_with("application/vc") {
129 Ok(Self(data_url))
130 } else {
131 Err(VcDataUrlParsingError::InvalidMediaType(InvalidMediaType {
132 got: data_url.media_type().to_string(),
133 }))
134 }
135 }
136}
137
138impl Deref for VcDataUrl {
139 type Target = DataUrl;
140
141 fn deref(&self) -> &Self::Target {
142 &self.0
143 }
144}
145
146impl TryFrom<DataUrl> for VcDataUrl {
147 type Error = InvalidMediaType;
148
149 fn try_from(value: DataUrl) -> Result<Self, Self::Error> {
150 if value.media_type().starts_with("application/vc") {
151 Ok(Self(value))
152 } else {
153 Err(InvalidMediaType {
154 got: value.media_type().to_string(),
155 })
156 }
157 }
158}
159
160impl From<VcDataUrl> for DataUrl {
161 fn from(value: VcDataUrl) -> Self {
162 value.0
163 }
164}
165
166impl Display for VcDataUrl {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 write!(f, "{}", self.0)
169 }
170}
171
172#[derive(Debug, thiserror::Error)]
174#[non_exhaustive]
175pub enum VcDataUrlParsingError {
176 #[error(transparent)]
178 NotADataUrl(#[from] InvalidDataUrl),
179 #[error(transparent)]
181 InvalidMediaType(#[from] InvalidMediaType),
182}
183
184#[derive(Debug, thiserror::Error)]
186#[error("invalid media type `{got}`: expected `application/vc` or related media type")]
187#[non_exhaustive]
188pub struct InvalidMediaType {
189 pub got: String,
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn serde_roundtrip() {
199 let vc_data_url = VcDataUrl::parse("data:application/vc,QzVjV...RMjU").unwrap();
200 let enveloped_vc = EnvelopedVc::new(vc_data_url.clone());
201
202 let serialized = serde_json::to_string(&enveloped_vc).unwrap();
203 let deserialized: EnvelopedVc = serde_json::from_str(&serialized).unwrap();
204
205 assert_eq!(deserialized.type_(), ENVELOPED_VC_TYPE);
206 assert_eq!(deserialized.id, vc_data_url);
207 assert_eq!(deserialized.context(), &[CredentialV2::<()>::base_context().clone()]);
208 }
209
210 #[test]
211 fn deserialization_of_spec_example() {
212 let json = r#"
213{
214 "@context": "https://www.w3.org/ns/credentials/v2",
215 "id": "data:application/vc+sd-jwt,QzVjV...RMjU",
216 "type": "EnvelopedVerifiableCredential"
217}
218 "#;
219
220 let _enveloped_vc: EnvelopedVc = serde_json::from_str(json).unwrap();
221 }
222
223 #[test]
224 fn deserialization_of_invalid_type_fails() {
225 let err = deserialize_enveloped_vc_type(&mut serde_json::Deserializer::from_str("\"InvalidType\"")).unwrap_err();
226 assert_eq!(
227 err.to_string(),
228 "invalid value: string \"InvalidType\", expected \"EnvelopedVerifiableCredential\""
229 );
230 }
231}