identity_credential/credential/
builder.rs

1// Copyright 2020-2022 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use 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/// A `CredentialBuilder` is used to create a customized `Credential`.
23#[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  /// Creates a new `CredentialBuilder`.
44  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  /// Adds a value to the `Credential` context set.
65  #[must_use]
66  pub fn context(mut self, value: impl Into<Context>) -> Self {
67    self.context.push(value.into());
68    self
69  }
70
71  /// Sets the value of the `Credential` `id`.
72  #[must_use]
73  pub fn id(mut self, value: Url) -> Self {
74    self.id = Some(value);
75    self
76  }
77
78  /// Adds a value to the `Credential` type set.
79  #[must_use]
80  pub fn type_(mut self, value: impl Into<String>) -> Self {
81    self.types.push(value.into());
82    self
83  }
84
85  /// Adds a value to the `credentialSubject` set.
86  #[must_use]
87  pub fn subject(mut self, value: Subject) -> Self {
88    self.subject.push(value);
89    self
90  }
91
92  /// Adds the values from the iterator to the `credentialSubject` set.
93  #[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  /// Sets the value of the `Credential` `issuer`.
102  #[must_use]
103  pub fn issuer(mut self, value: impl Into<Issuer>) -> Self {
104    self.issuer = Some(value.into());
105    self
106  }
107
108  /// Sets the value of the `Credential` `issuanceDate`.
109  #[must_use]
110  pub fn issuance_date(mut self, value: Timestamp) -> Self {
111    self.issuance_date = Some(value);
112    self
113  }
114
115  /// Sets the value of the `Credential` `expirationDate`.
116  #[must_use]
117  pub fn expiration_date(mut self, value: Timestamp) -> Self {
118    self.expiration_date = Some(value);
119    self
120  }
121
122  /// Adds a value to the `credentialStatus` set.
123  #[must_use]
124  pub fn status(mut self, value: impl Into<Status>) -> Self {
125    self.status = Some(value.into());
126    self
127  }
128
129  /// Adds a value to the `credentialSchema` set.
130  #[must_use]
131  pub fn schema(mut self, value: Schema) -> Self {
132    self.schema.push(value);
133    self
134  }
135
136  /// Adds a value to the `refreshService` set.
137  #[must_use]
138  pub fn refresh_service(mut self, value: RefreshService) -> Self {
139    self.refresh_service.push(value);
140    self
141  }
142
143  /// Adds a value to the `termsOfUse` set.
144  #[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  /// Adds a value to the `evidence` set.
151  #[must_use]
152  pub fn evidence(mut self, value: Evidence) -> Self {
153    self.evidence.push(value);
154    self
155  }
156
157  /// Sets the value of the `Credential` `nonTransferable` property.
158  #[must_use]
159  pub fn non_transferable(mut self, value: bool) -> Self {
160    self.non_transferable = Some(value);
161    self
162  }
163
164  /// Sets the value of the `proof` property.
165  #[must_use]
166  pub fn proof(mut self, value: Proof) -> Self {
167    self.proof = Some(value);
168    self
169  }
170
171  /// Returns a new `Credential` based on the `CredentialBuilder` configuration.
172  pub fn build(self) -> Result<Credential<T>> {
173    Credential::from_builder(self)
174  }
175}
176
177impl CredentialBuilder {
178  /// Adds a new custom property to the `Credential`.
179  #[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  /// Adds a series of custom properties to the `Credential`.
190  #[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}