identity_credential/credential/
builder.rs1use identity_core::common::Context;
5use identity_core::common::Object;
6use identity_core::common::Timestamp;
7use identity_core::common::Url;
8use identity_core::common::Value;
9
10use crate::credential::Credential;
11use crate::credential::Evidence;
12use crate::credential::Issuer;
13use crate::credential::Policy;
14use crate::credential::RefreshService;
15use crate::credential::Schema;
16use crate::credential::Status;
17use crate::credential::Subject;
18use crate::error::Result;
19
20use super::Proof;
21
22#[derive(Clone, Debug)]
24pub struct CredentialBuilder<T = Object> {
25 pub(crate) context: Vec<Context>,
26 pub(crate) id: Option<Url>,
27 pub(crate) types: Vec<String>,
28 pub(crate) subject: Vec<Subject>,
29 pub(crate) issuer: Option<Issuer>,
30 pub(crate) issuance_date: Option<Timestamp>,
31 pub(crate) expiration_date: Option<Timestamp>,
32 pub(crate) status: Option<Status>,
33 pub(crate) schema: Vec<Schema>,
34 pub(crate) refresh_service: Vec<RefreshService>,
35 pub(crate) terms_of_use: Vec<Policy>,
36 pub(crate) evidence: Vec<Evidence>,
37 pub(crate) non_transferable: Option<bool>,
38 pub(crate) proof: Option<Proof>,
39 pub(crate) properties: T,
40}
41
42impl<T> CredentialBuilder<T> {
43 pub fn new(properties: T) -> Self {
45 Self {
46 context: vec![Credential::<T>::base_context().clone()],
47 id: None,
48 types: vec![Credential::<T>::base_type().into()],
49 subject: Vec::new(),
50 issuer: None,
51 issuance_date: None,
52 expiration_date: None,
53 status: None,
54 schema: Vec::new(),
55 refresh_service: Vec::new(),
56 terms_of_use: Vec::new(),
57 evidence: Vec::new(),
58 non_transferable: None,
59 proof: None,
60 properties,
61 }
62 }
63
64 #[must_use]
66 pub fn context(mut self, value: impl Into<Context>) -> Self {
67 self.context.push(value.into());
68 self
69 }
70
71 #[must_use]
73 pub fn id(mut self, value: Url) -> Self {
74 self.id = Some(value);
75 self
76 }
77
78 #[must_use]
80 pub fn type_(mut self, value: impl Into<String>) -> Self {
81 self.types.push(value.into());
82 self
83 }
84
85 #[must_use]
87 pub fn subject(mut self, value: Subject) -> Self {
88 self.subject.push(value);
89 self
90 }
91
92 #[must_use]
94 pub fn subjects<I: IntoIterator<Item = Subject>>(mut self, values: I) -> Self {
95 for value in values {
96 self.subject.push(value);
97 }
98 self
99 }
100
101 #[must_use]
103 pub fn issuer(mut self, value: impl Into<Issuer>) -> Self {
104 self.issuer = Some(value.into());
105 self
106 }
107
108 #[must_use]
110 pub fn issuance_date(mut self, value: Timestamp) -> Self {
111 self.issuance_date = Some(value);
112 self
113 }
114
115 #[must_use]
117 pub fn expiration_date(mut self, value: Timestamp) -> Self {
118 self.expiration_date = Some(value);
119 self
120 }
121
122 #[must_use]
124 pub fn status(mut self, value: impl Into<Status>) -> Self {
125 self.status = Some(value.into());
126 self
127 }
128
129 #[must_use]
131 pub fn schema(mut self, value: Schema) -> Self {
132 self.schema.push(value);
133 self
134 }
135
136 #[must_use]
138 pub fn refresh_service(mut self, value: RefreshService) -> Self {
139 self.refresh_service.push(value);
140 self
141 }
142
143 #[must_use]
145 pub fn terms_of_use(mut self, value: Policy) -> Self {
146 self.terms_of_use.push(value);
147 self
148 }
149
150 #[must_use]
152 pub fn evidence(mut self, value: Evidence) -> Self {
153 self.evidence.push(value);
154 self
155 }
156
157 #[must_use]
159 pub fn non_transferable(mut self, value: bool) -> Self {
160 self.non_transferable = Some(value);
161 self
162 }
163
164 #[must_use]
166 pub fn proof(mut self, value: Proof) -> Self {
167 self.proof = Some(value);
168 self
169 }
170
171 pub fn build(self) -> Result<Credential<T>> {
173 Credential::from_builder(self)
174 }
175}
176
177impl CredentialBuilder {
178 #[must_use]
180 pub fn property<K, V>(mut self, key: K, value: V) -> Self
181 where
182 K: Into<String>,
183 V: Into<Value>,
184 {
185 self.properties.insert(key.into(), value.into());
186 self
187 }
188
189 #[must_use]
191 pub fn properties<K, V, I>(mut self, iter: I) -> Self
192 where
193 I: IntoIterator<Item = (K, V)>,
194 K: Into<String>,
195 V: Into<Value>,
196 {
197 self
198 .properties
199 .extend(iter.into_iter().map(|(k, v)| (k.into(), v.into())));
200 self
201 }
202}
203
204impl<T> Default for CredentialBuilder<T>
205where
206 T: Default,
207{
208 fn default() -> Self {
209 Self::new(T::default())
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use identity_core::common::Object;
216 use identity_core::common::Timestamp;
217 use identity_core::common::Url;
218 use identity_core::convert::FromJson;
219 use serde_json::json;
220 use serde_json::Value;
221
222 use crate::credential::Credential;
223 use crate::credential::CredentialBuilder;
224 use crate::credential::Proof;
225 use crate::credential::Subject;
226
227 fn subject() -> Subject {
228 let json: Value = json!({
229 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
230 "degree": {
231 "type": "BachelorDegree",
232 "name": "Bachelor of Science and Arts"
233 }
234 });
235
236 Subject::from_json_value(json).unwrap()
237 }
238
239 fn issuer() -> Url {
240 Url::parse("did:example:issuer").unwrap()
241 }
242
243 #[test]
244 fn test_credential_builder_valid() {
245 let proof = Proof::new("test-type".to_owned(), Object::new());
246 let credential: Credential = CredentialBuilder::default()
247 .context(Url::parse("https://www.w3.org/2018/credentials/examples/v1").unwrap())
248 .id(Url::parse("http://example.edu/credentials/3732").unwrap())
249 .type_("UniversityDegreeCredential")
250 .subject(subject())
251 .issuer(issuer())
252 .issuance_date(Timestamp::parse("2010-01-01T00:00:00Z").unwrap())
253 .proof(proof)
254 .build()
255 .unwrap();
256
257 assert_eq!(credential.context.len(), 2);
258 assert_eq!(credential.context.get(0).unwrap(), Credential::<Object>::base_context());
259 assert_eq!(
260 credential.context.get(1).unwrap(),
261 "https://www.w3.org/2018/credentials/examples/v1"
262 );
263 assert_eq!(credential.id.unwrap(), "http://example.edu/credentials/3732");
264 assert_eq!(credential.types.len(), 2);
265 assert_eq!(credential.types.get(0).unwrap(), Credential::<Object>::base_type());
266 assert_eq!(credential.types.get(1).unwrap(), "UniversityDegreeCredential");
267 assert_eq!(credential.credential_subject.len(), 1);
268 assert_eq!(credential.issuer.url(), "did:example:issuer");
269 assert_eq!(credential.issuance_date.to_string(), "2010-01-01T00:00:00Z");
270 assert_eq!(
271 credential.credential_subject.get(0).unwrap().id.as_ref().unwrap(),
272 "did:example:ebfeb1f712ebc6f1c276e12ec21"
273 );
274 assert_eq!(
275 credential.credential_subject.get(0).unwrap().properties["degree"]["type"],
276 "BachelorDegree"
277 );
278 assert_eq!(
279 credential.credential_subject.get(0).unwrap().properties["degree"]["name"],
280 "Bachelor of Science and Arts"
281 );
282 assert_eq!(credential.proof.unwrap().type_, "test-type");
283 }
284
285 #[test]
286 #[should_panic = "MissingSubject"]
287 fn test_builder_missing_subjects() {
288 let _: Credential = CredentialBuilder::default().issuer(issuer()).build().unwrap();
289 }
290
291 #[test]
292 #[should_panic = "MissingIssuer"]
293 fn test_builder_missing_issuer() {
294 let _: Credential = CredentialBuilder::default().subject(subject()).build().unwrap();
295 }
296}