identity_document/service/
builder.rs

1// Copyright 2020-2023 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use identity_core::common::Object;
5
6use crate::error::Result;
7use crate::service::Service;
8use crate::service::ServiceEndpoint;
9use identity_did::DIDUrl;
10
11/// A `ServiceBuilder` is used to generate a customized `Service`.
12#[derive(Clone, Debug, Default)]
13pub struct ServiceBuilder {
14  pub(crate) id: Option<DIDUrl>,
15  pub(crate) type_: Vec<String>,
16  pub(crate) service_endpoint: Option<ServiceEndpoint>,
17  pub(crate) properties: Object,
18}
19
20impl ServiceBuilder {
21  /// Creates a new `ServiceBuilder`.
22  pub fn new(properties: Object) -> Self {
23    Self {
24      id: None,
25      type_: Vec::new(),
26      service_endpoint: None,
27      properties,
28    }
29  }
30
31  /// Sets the `id` value of the generated `Service`.
32  #[must_use]
33  pub fn id(mut self, value: DIDUrl) -> Self {
34    self.id = Some(value);
35    self
36  }
37
38  /// Appends a `type` value to the generated `Service`.
39  #[must_use]
40  pub fn type_(mut self, value: impl Into<String>) -> Self {
41    self.type_.push(value.into());
42    self
43  }
44
45  /// Sets the `type` value of the generated `Service`, overwriting any previous `type` entries.
46  ///
47  /// NOTE: the entries must be unique.
48  #[must_use]
49  pub fn types(mut self, values: impl Into<Vec<String>>) -> Self {
50    self.type_ = values.into();
51    self
52  }
53
54  /// Sets the `serviceEndpoint` value of the generated `Service`.
55  #[must_use]
56  pub fn service_endpoint(mut self, value: impl Into<ServiceEndpoint>) -> Self {
57    self.service_endpoint = Some(value.into());
58    self
59  }
60
61  /// Returns a new `Service` based on the `ServiceBuilder` configuration.
62  pub fn build(self) -> Result<Service> {
63    Service::from_builder(self)
64  }
65}
66
67#[cfg(test)]
68mod tests {
69  use crate::Error;
70  use identity_core::common::Url;
71
72  use super::*;
73
74  #[test]
75  fn test_success() {
76    let _: Service = ServiceBuilder::default()
77      .id("did:example:123#service".parse().unwrap())
78      .type_("ServiceType")
79      .service_endpoint(Url::parse("https://example.com").unwrap())
80      .build()
81      .unwrap();
82  }
83
84  #[test]
85  fn test_missing_id() {
86    let result: Result<Service> = ServiceBuilder::default()
87      .type_("ServiceType")
88      .service_endpoint(Url::parse("https://example.com").unwrap())
89      .build();
90    assert!(matches!(result.unwrap_err(), Error::InvalidService(_)));
91  }
92
93  #[test]
94  fn test_missing_id_fragment() {
95    let result: Result<Service> = ServiceBuilder::default()
96      .id("did:example:123".parse().unwrap())
97      .type_("ServiceType")
98      .service_endpoint(Url::parse("https://example.com").unwrap())
99      .build();
100    assert!(matches!(result.unwrap_err(), Error::InvalidService(_)));
101  }
102
103  #[test]
104  fn test_missing_type_() {
105    let result: Result<Service> = ServiceBuilder::default()
106      .id("did:example:123#service".parse().unwrap())
107      .service_endpoint(Url::parse("https://example.com").unwrap())
108      .build();
109    assert!(matches!(result.unwrap_err(), Error::InvalidService(_)));
110  }
111
112  #[test]
113  fn test_missing_service_endpoint() {
114    let result: Result<Service> = ServiceBuilder::default()
115      .id("did:example:123#service".parse().unwrap())
116      .type_("ServiceType")
117      .build();
118    assert!(matches!(result.unwrap_err(), Error::InvalidService(_)));
119  }
120}