identity_credential/presentation/
presentation_builder.rs

1// Copyright 2020-2023 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4#![allow(deprecated)]
5
6use identity_core::common::Context;
7use identity_core::common::Object;
8use identity_core::common::Url;
9use identity_core::common::Value;
10
11use crate::credential::Credential;
12use crate::credential::CredentialV2;
13use crate::credential::Policy;
14use crate::credential::RefreshService;
15use crate::error::Result;
16
17use super::Presentation;
18
19/// A `PresentationBuilder` is used to create a customized [Presentation].
20#[derive(Clone, Debug)]
21pub struct PresentationBuilder<CRED, T = Object> {
22  pub(crate) context: Vec<Context>,
23  pub(crate) id: Option<Url>,
24  pub(crate) types: Vec<String>,
25  pub(crate) credentials: Vec<CRED>,
26  pub(crate) holder: Url,
27  pub(crate) refresh_service: Vec<RefreshService>,
28  pub(crate) terms_of_use: Vec<Policy>,
29  pub(crate) properties: T,
30}
31
32impl<CRED, T> PresentationBuilder<CRED, T> {
33  /// Creates a new `PresentationBuilder`.
34  pub fn new(holder: Url, properties: T) -> Self {
35    Self {
36      context: Vec::new(),
37      id: None,
38      types: vec![Presentation::<T>::base_type().into()],
39      credentials: Vec::new(),
40      holder,
41      refresh_service: Vec::new(),
42      terms_of_use: Vec::new(),
43      properties,
44    }
45  }
46
47  /// Adds a value to the `context` set.
48  #[must_use]
49  pub fn context(mut self, value: impl Into<Context>) -> Self {
50    self.context.push(value.into());
51    self
52  }
53
54  /// Sets the unique identifier of the presentation.
55  #[must_use]
56  pub fn id(mut self, value: Url) -> Self {
57    self.id = Some(value);
58    self
59  }
60
61  /// Adds a value to the `type` set.
62  #[must_use]
63  pub fn type_(mut self, value: impl Into<String>) -> Self {
64    self.types.push(value.into());
65    self
66  }
67
68  /// Adds a value to the `verifiableCredential` set.
69  #[must_use]
70  pub fn credential(mut self, value: CRED) -> Self {
71    self.credentials.push(value);
72    self
73  }
74
75  /// Adds a value to the `refreshService` set.
76  #[must_use]
77  pub fn refresh_service(mut self, value: RefreshService) -> Self {
78    self.refresh_service.push(value);
79    self
80  }
81
82  /// Adds a value to the `termsOfUse` set.
83  #[must_use]
84  pub fn terms_of_use(mut self, value: Policy) -> Self {
85    self.terms_of_use.push(value);
86    self
87  }
88
89  /// Returns a new `Presentation` based on the `PresentationBuilder` configuration.
90  /// # Notes
91  /// If no context has been set, the base context of VC Data Model 1.1 will be used.
92  pub fn build(mut self) -> Result<Presentation<CRED, T>> {
93    if self.context.first() != Some(Credential::<()>::base_context())
94      || self.context.first() != Some(CredentialV2::<()>::base_context())
95    {
96      self.context.insert(0, Credential::<()>::base_context().clone());
97    }
98
99    Presentation::from_builder(self)
100  }
101
102  /// Returns a new `Presentation` based on the `PresentationBuilder` configuration.
103  /// # Notes
104  /// If no context has been set, the base context of VC Data Model 2.0 will be used.
105  pub fn build_v2(mut self) -> Result<Presentation<CRED, T>> {
106    if self.context.first() != Some(Credential::<()>::base_context())
107      || self.context.first() != Some(CredentialV2::<()>::base_context())
108    {
109      self.context.insert(0, CredentialV2::<()>::base_context().clone());
110    }
111
112    Presentation::from_builder(self)
113  }
114}
115
116impl PresentationBuilder<Object> {
117  /// Adds a new custom property.
118  #[must_use]
119  pub fn property<K, V>(mut self, key: K, value: V) -> Self
120  where
121    K: Into<String>,
122    V: Into<Value>,
123  {
124    self.properties.insert(key.into(), value.into());
125    self
126  }
127
128  /// Adds a series of custom properties.
129  #[must_use]
130  pub fn properties<K, V, I>(mut self, iter: I) -> Self
131  where
132    I: IntoIterator<Item = (K, V)>,
133    K: Into<String>,
134    V: Into<Value>,
135  {
136    self
137      .properties
138      .extend(iter.into_iter().map(|(k, v)| (k.into(), v.into())));
139    self
140  }
141}
142
143#[cfg(test)]
144mod tests {
145  use serde_json::json;
146  use serde_json::Value;
147
148  use identity_core::common::Object;
149  use identity_core::common::Url;
150  use identity_core::convert::FromJson;
151
152  use crate::credential::Credential;
153  use crate::credential::CredentialBuilder;
154  use crate::credential::Jwt;
155  use crate::credential::Subject;
156  use crate::presentation::Presentation;
157  use crate::presentation::PresentationBuilder;
158
159  fn subject() -> Subject {
160    let json: Value = json!({
161      "id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
162      "degree": {
163        "type": "BachelorDegree",
164        "name": "Bachelor of Science and Arts"
165      }
166    });
167
168    Subject::from_json_value(json).unwrap()
169  }
170
171  fn issuer() -> Url {
172    Url::parse("did:example:issuer").unwrap()
173  }
174
175  #[test]
176  fn test_presentation_builder_valid() {
177    let credential: Credential = CredentialBuilder::default()
178      .type_("ExampleCredential")
179      .subject(subject())
180      .issuer(issuer())
181      .build()
182      .unwrap();
183
184    let credential_jwt = Jwt::new(credential.serialize_jwt(None).unwrap());
185
186    let presentation: Presentation<Jwt> = PresentationBuilder::new(Url::parse("did:test:abc1").unwrap(), Object::new())
187      .type_("ExamplePresentation")
188      .credential(credential_jwt)
189      .build()
190      .unwrap();
191
192    assert_eq!(presentation.context.len(), 1);
193    assert_eq!(
194      presentation.context.get(0).unwrap(),
195      Presentation::<Object>::base_context()
196    );
197    assert_eq!(presentation.types.len(), 2);
198    assert_eq!(presentation.types.get(0).unwrap(), Presentation::<Object>::base_type());
199    assert_eq!(presentation.types.get(1).unwrap(), "ExamplePresentation");
200    assert_eq!(presentation.verifiable_credential.len(), 1);
201  }
202
203  #[test]
204  fn test_presentation_builder_valid_without_credentials() {
205    let presentation: Presentation<Jwt> = PresentationBuilder::new(Url::parse("did:test:abc1").unwrap(), Object::new())
206      .type_("ExamplePresentation")
207      .build()
208      .unwrap();
209
210    assert_eq!(presentation.context.len(), 1);
211    assert_eq!(
212      presentation.context.get(0).unwrap(),
213      Presentation::<Object>::base_context()
214    );
215    assert_eq!(presentation.types.len(), 2);
216    assert_eq!(presentation.types.get(0).unwrap(), Presentation::<Object>::base_type());
217    assert_eq!(presentation.types.get(1).unwrap(), "ExamplePresentation");
218    assert_eq!(presentation.verifiable_credential.len(), 0);
219  }
220}