identity_credential/credential/
linked_verifiable_presentation_service.rs1use identity_core::common::Object;
5use identity_core::common::OrderedSet;
6use identity_core::common::Url;
7use identity_did::DIDUrl;
8use identity_document::service::Service;
9use identity_document::service::ServiceBuilder;
10use identity_document::service::ServiceEndpoint;
11use serde::Deserialize;
12use serde::Serialize;
13
14use crate::error::Result;
15use crate::Error;
16use crate::Error::LinkedVerifiablePresentationError;
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(try_from = "Service", into = "Service")]
21pub struct LinkedVerifiablePresentationService(Service);
22
23impl TryFrom<Service> for LinkedVerifiablePresentationService {
24 type Error = Error;
25
26 fn try_from(service: Service) -> std::result::Result<Self, Self::Error> {
27 LinkedVerifiablePresentationService::check_structure(&service)?;
28 Ok(LinkedVerifiablePresentationService(service))
29 }
30}
31
32impl From<LinkedVerifiablePresentationService> for Service {
33 fn from(service: LinkedVerifiablePresentationService) -> Self {
34 service.0
35 }
36}
37
38impl LinkedVerifiablePresentationService {
39 pub(crate) fn linked_verifiable_presentation_service_type() -> &'static str {
40 "LinkedVerifiablePresentation"
41 }
42
43 pub fn new(
46 did_url: DIDUrl,
47 verifiable_presentation_urls: impl Into<OrderedSet<Url>>,
48 properties: Object,
49 ) -> Result<Self> {
50 let verifiable_presentation_urls: OrderedSet<Url> = verifiable_presentation_urls.into();
51 let builder: ServiceBuilder = Service::builder(properties)
52 .id(did_url)
53 .type_(Self::linked_verifiable_presentation_service_type());
54 if verifiable_presentation_urls.len() == 1 {
55 let vp_url = verifiable_presentation_urls
56 .into_iter()
57 .next()
58 .expect("element 0 exists");
59 let service = builder
60 .service_endpoint(vp_url)
61 .build()
62 .map_err(|err| LinkedVerifiablePresentationError(Box::new(err)))?;
63 Ok(Self(service))
64 } else {
65 let service = builder
66 .service_endpoint(ServiceEndpoint::Set(verifiable_presentation_urls))
67 .build()
68 .map_err(|err| LinkedVerifiablePresentationError(Box::new(err)))?;
69 Ok(Self(service))
70 }
71 }
72
73 pub fn check_structure(service: &Service) -> Result<()> {
78 if service.type_().len() != 1 {
79 return Err(LinkedVerifiablePresentationError("invalid service type".into()));
80 }
81
82 let service_type = service
83 .type_()
84 .get(0)
85 .ok_or_else(|| LinkedVerifiablePresentationError("missing service type".into()))?;
86
87 if service_type != Self::linked_verifiable_presentation_service_type() {
88 return Err(LinkedVerifiablePresentationError(
89 format!(
90 "expected `{}` service type",
91 Self::linked_verifiable_presentation_service_type()
92 )
93 .into(),
94 ));
95 }
96
97 match service.service_endpoint() {
98 ServiceEndpoint::One(_) => Ok(()),
99 ServiceEndpoint::Set(_) => Ok(()),
100 ServiceEndpoint::Map(_) => Err(LinkedVerifiablePresentationError(
101 "service endpoints must be either a string or a set".into(),
102 )),
103 }
104 }
105
106 pub fn verifiable_presentation_urls(&self) -> &[Url] {
108 match self.0.service_endpoint() {
109 ServiceEndpoint::One(endpoint) => std::slice::from_ref(endpoint),
110 ServiceEndpoint::Set(endpoints) => endpoints.as_slice(),
111 ServiceEndpoint::Map(_) => {
112 unreachable!("the service endpoint is never a map per the `LinkedVerifiablePresentationService` type invariant")
113 }
114 }
115 }
116
117 pub fn id(&self) -> &DIDUrl {
119 self.0.id()
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::credential::linked_verifiable_presentation_service::LinkedVerifiablePresentationService;
126 use identity_core::common::Object;
127 use identity_core::common::OrderedSet;
128 use identity_core::common::Url;
129 use identity_core::convert::FromJson;
130 use identity_did::DIDUrl;
131 use identity_document::service::Service;
132 use serde_json::json;
133
134 #[test]
135 fn test_create_service_single_vp() {
136 let mut linked_vps: OrderedSet<Url> = OrderedSet::new();
137 linked_vps.append(Url::parse("https://foo.example-1.com").unwrap());
138
139 let service: LinkedVerifiablePresentationService = LinkedVerifiablePresentationService::new(
140 DIDUrl::parse("did:example:123#foo").unwrap(),
141 linked_vps,
142 Object::new(),
143 )
144 .unwrap();
145
146 let service_from_json: Service = Service::from_json_value(json!({
147 "id": "did:example:123#foo",
148 "type": "LinkedVerifiablePresentation",
149 "serviceEndpoint": "https://foo.example-1.com"
150 }))
151 .unwrap();
152 assert_eq!(Service::from(service), service_from_json);
153 }
154
155 #[test]
156 fn test_create_service_multiple_vps() {
157 let url_1 = "https://foo.example-1.com";
158 let url_2 = "https://bar.example-2.com";
159 let mut linked_vps = OrderedSet::new();
160 linked_vps.append(Url::parse(url_1).unwrap());
161 linked_vps.append(Url::parse(url_2).unwrap());
162
163 let service: LinkedVerifiablePresentationService = LinkedVerifiablePresentationService::new(
164 DIDUrl::parse("did:example:123#foo").unwrap(),
165 linked_vps,
166 Object::new(),
167 )
168 .unwrap();
169
170 let service_from_json: Service = Service::from_json_value(json!({
171 "id":"did:example:123#foo",
172 "type": "LinkedVerifiablePresentation",
173 "serviceEndpoint": [url_1, url_2]
174 }))
175 .unwrap();
176 assert_eq!(Service::from(service), service_from_json);
177 }
178
179 #[test]
180 fn test_valid_single_vp() {
181 let service: Service = Service::from_json_value(json!({
182 "id": "did:example:123#foo",
183 "type": "LinkedVerifiablePresentation",
184 "serviceEndpoint": "https://foo.example-1.com"
185 }))
186 .unwrap();
187 let service: LinkedVerifiablePresentationService = LinkedVerifiablePresentationService::try_from(service).unwrap();
188 let linked_vps: Vec<Url> = vec![Url::parse("https://foo.example-1.com").unwrap()];
189 assert_eq!(service.verifiable_presentation_urls(), linked_vps);
190 }
191
192 #[test]
193 fn test_valid_multiple_vps() {
194 let service: Service = Service::from_json_value(json!({
195 "id": "did:example:123#foo",
196 "type": "LinkedVerifiablePresentation",
197 "serviceEndpoint": ["https://foo.example-1.com", "https://foo.example-2.com"]
198 }))
199 .unwrap();
200 let service: LinkedVerifiablePresentationService = LinkedVerifiablePresentationService::try_from(service).unwrap();
201 let linked_vps: Vec<Url> = vec![
202 Url::parse("https://foo.example-1.com").unwrap(),
203 Url::parse("https://foo.example-2.com").unwrap(),
204 ];
205 assert_eq!(service.verifiable_presentation_urls(), linked_vps);
206 }
207}