identity_credential/presentation/
presentation_builder.rs

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