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