ssi_dids_core/resolution/
dereference.rs1use iref::{Iri, IriBuf};
2use ssi_core::one_or_many::OneOrMany;
3
4use crate::{
5 document::{self, representation, service, DIDVerificationMethod, Represented, Resource},
6 DIDURLBuf, Fragment, PrimaryDIDURL, DIDURL,
7};
8
9use super::{DIDResolver, Error, Metadata, Output, Parameters, MEDIA_TYPE_URL};
10
11#[derive(Debug, thiserror::Error)]
12pub enum DerefError {
13 #[error("DID resolution failed: {0}")]
14 Resolution(#[from] Error),
15
16 #[error("missing service endpoint `{0}`")]
17 MissingServiceEndpoint(String),
18
19 #[error("unsupported service endpoint map")]
20 UnsupportedServiceEndpointMap,
21
22 #[error("unsupported multiple service endpoints")]
23 UnsupportedMultipleServiceEndpoints,
24
25 #[error("service endpoint construction failed: {0}")]
26 ServiceEndpointConstructionFailed(#[from] ServiceEndpointConstructionConflict),
27
28 #[error("both the DID URL and input service endpoint URL have a fragment component")]
29 FragmentConflict,
30
31 #[error("tried to dereference null primary content")]
32 NullDereference,
33
34 #[error("DID document not found")]
35 NotFound,
36
37 #[error("could not find resource `{0}` in DID document")]
38 ResourceNotFound(DIDURLBuf),
39}
40
41pub struct DerefOutput<T = Content> {
42 pub content: T,
43 pub content_metadata: document::Metadata,
44 pub metadata: Metadata,
45}
46
47impl<T> DerefOutput<T> {
48 pub fn new(content: T, content_metadata: document::Metadata, metadata: Metadata) -> Self {
49 Self {
50 content,
51 content_metadata,
52 metadata,
53 }
54 }
55
56 pub fn url(url: IriBuf) -> Self
57 where
58 T: From<IriBuf>,
59 {
60 Self::new(
61 url.into(),
62 document::Metadata::default(),
63 Metadata::from_content_type(Some(MEDIA_TYPE_URL.to_string())),
64 )
65 }
66
67 pub fn map<U>(self, f: impl FnOnce(T) -> U) -> DerefOutput<U> {
68 DerefOutput {
69 content: f(self.content),
70 content_metadata: self.content_metadata,
71 metadata: self.metadata,
72 }
73 }
74
75 pub fn cast<U: From<T>>(self) -> DerefOutput<U> {
76 self.map(T::into)
77 }
78
79 pub fn into_content(self) -> T {
80 self.content
81 }
82}
83
84impl DerefOutput<PrimaryContent> {
85 pub fn null() -> Self {
86 Self::new(
87 PrimaryContent::Null,
88 document::Metadata::default(),
89 Metadata::default(),
90 )
91 }
92}
93
94pub enum PrimaryContent {
95 Null,
96 Url(IriBuf), Document(document::Represented),
98}
99
100impl From<IriBuf> for PrimaryContent {
101 fn from(value: IriBuf) -> Self {
102 Self::Url(value)
103 }
104}
105
106#[derive(Debug)]
107pub enum Content {
108 Null,
109 Url(IriBuf), Resource(Resource),
111}
112
113impl Content {
114 pub fn as_verification_method(&self) -> Option<&DIDVerificationMethod> {
115 match self {
116 Self::Resource(r) => r.as_verification_method(),
117 _ => None,
118 }
119 }
120
121 pub fn into_verification_method(self) -> Result<DIDVerificationMethod, Self> {
122 match self {
123 Self::Resource(r) => r.into_verification_method().map_err(Self::Resource),
124 other => Err(other),
125 }
126 }
127}
128
129impl From<PrimaryContent> for Content {
130 fn from(value: PrimaryContent) -> Self {
131 match value {
132 PrimaryContent::Null => Self::Null,
133 PrimaryContent::Url(url) => Self::Url(url),
134 PrimaryContent::Document(doc) => {
135 Self::Resource(Resource::Document(doc.into_document()))
136 }
137 }
138 }
139}
140
141pub(crate) async fn dereference_primary_resource<'a, R: ?Sized + DIDResolver>(
143 resolver: &'a R,
144 primary_did_url: &'a PrimaryDIDURL,
145 parameters: Parameters,
146 resolution_output: Output,
147) -> Result<DerefOutput<PrimaryContent>, DerefError> {
148 match ¶meters.service {
150 Some(id) => {
151 match resolution_output.document.service(id) {
153 Some(service) => {
154 let input_service_endpoint_url = match &service.service_endpoint {
157 None => return Err(DerefError::MissingServiceEndpoint(id.clone())),
158 Some(OneOrMany::One(service::Endpoint::Uri(uri))) => uri.as_iri(),
159 Some(OneOrMany::One(service::Endpoint::Map(_))) => {
160 return Err(DerefError::UnsupportedServiceEndpointMap)
161 }
162 Some(OneOrMany::Many(_)) => {
163 return Err(DerefError::UnsupportedMultipleServiceEndpoints)
164 }
165 };
166
167 let r = construct_service_endpoint(
169 primary_did_url,
170 ¶meters,
171 input_service_endpoint_url,
172 );
173
174 match r {
175 Ok(output_service_endpoint_url) => {
176 Ok(DerefOutput::url(output_service_endpoint_url))
178 }
179 Err(e) => Err(e.into()),
180 }
181 }
182 None => Err(DerefError::MissingServiceEndpoint(id.clone())),
183 }
184 }
185 None => {
186 if primary_did_url.path().is_empty() && primary_did_url.query().is_none() {
188 return Ok(DerefOutput::new(
190 PrimaryContent::Document(resolution_output.document),
191 document::Metadata::default(),
192 resolution_output.metadata,
193 ));
194 }
195
196 if !primary_did_url.path().is_empty() || primary_did_url.query().is_some() {
198 return resolver
199 .dereference_primary_with_path_or_query(primary_did_url)
200 .await;
201 }
202
203 Err(DerefError::NotFound)
205 }
206 }
207}
208
209#[derive(Debug, thiserror::Error)]
210pub enum ServiceEndpointConstructionConflict {
211 #[error("both the DID URL and `relativeRef` parameter have a path component")]
212 Path,
213
214 #[error("both the DID URL and input service endpoint URL have a query component")]
215 Query,
216
217 #[error("both the DID URL and input service endpoint URL have a fragment component")]
218 Fragment,
219}
220
221fn construct_service_endpoint(
223 did_url: &DIDURL,
224 did_parameters: &Parameters,
225 service_endpoint_url: &Iri,
226) -> Result<IriBuf, ServiceEndpointConstructionConflict> {
227 let mut output = IriBuf::from_scheme(service_endpoint_url.scheme().to_owned());
229 output.set_authority(service_endpoint_url.authority());
230 output.set_path(service_endpoint_url.path());
231
232 let relative_ref_path = did_parameters
233 .relative_ref
234 .as_ref()
235 .map(|r| r.path())
236 .unwrap_or("".try_into().unwrap());
237 match (did_url.path().is_empty(), relative_ref_path.is_empty()) {
238 (false, true) => output.set_path(did_url.path().as_str().try_into().unwrap()),
239 (true, false) => output.set_path(relative_ref_path),
240 (false, false) => return Err(ServiceEndpointConstructionConflict::Path),
241 (true, true) => (),
242 }
243
244 match (did_url.query(), service_endpoint_url.query()) {
245 (Some(query), None) => output.set_query(Some(query.as_str().try_into().unwrap())),
246 (None, Some(query)) => output.set_query(Some(query)),
247 (Some(_), Some(_)) => return Err(ServiceEndpointConstructionConflict::Query),
248 (None, None) => (),
249 }
250
251 match (did_url.fragment(), service_endpoint_url.fragment()) {
252 (Some(fragment), None) => output.set_fragment(Some(fragment.as_str().try_into().unwrap())),
253 (None, Some(fragment)) => output.set_fragment(Some(fragment)),
254 (Some(_), Some(_)) => return Err(ServiceEndpointConstructionConflict::Fragment),
255 (None, None) => (),
256 }
257
258 Ok(output)
259}
260
261impl Represented {
262 pub fn dereference_secondary_resource(
263 self,
264 primary_did_url: &PrimaryDIDURL,
265 fragment: &Fragment,
266 content_metadata: document::Metadata,
267 metadata: Metadata,
268 ) -> Result<DerefOutput, DerefError> {
269 match self {
270 Self::Json(d) => d.dereference_secondary_resource(
271 primary_did_url,
272 fragment,
273 content_metadata,
274 metadata,
275 ),
276 Self::JsonLd(d) => d.dereference_secondary_resource(
277 primary_did_url,
278 fragment,
279 content_metadata,
280 metadata,
281 ),
282 }
283 }
284}
285
286impl representation::Json {
287 pub fn dereference_secondary_resource(
288 self,
289 primary_did_url: &PrimaryDIDURL,
290 fragment: &Fragment,
291 content_metadata: document::Metadata,
292 metadata: Metadata,
293 ) -> Result<DerefOutput, DerefError> {
294 let id = primary_did_url.to_owned().with_fragment(fragment);
295 match self.into_document().into_resource(&id) {
296 Some(resource) => Ok(DerefOutput::new(
297 Content::Resource(resource),
298 content_metadata,
299 metadata,
300 )),
301 None => Err(DerefError::ResourceNotFound(id)),
302 }
303 }
304}
305
306impl representation::JsonLd {
307 pub fn dereference_secondary_resource(
308 self,
309 primary_did_url: &PrimaryDIDURL,
310 fragment: &Fragment,
311 content_metadata: document::Metadata,
312 metadata: Metadata,
313 ) -> Result<DerefOutput, DerefError> {
314 let id = primary_did_url.to_owned().with_fragment(fragment);
319 match self.into_document().into_resource(&id) {
320 Some(resource) => Ok(DerefOutput::new(
321 Content::Resource(resource),
322 content_metadata,
323 metadata,
324 )),
325 None => Err(DerefError::ResourceNotFound(id)),
326 }
327 }
328}
329
330pub(crate) fn dereference_secondary_resource(
332 primary_did_url: &PrimaryDIDURL,
333 fragment: &Fragment,
334 primary_deref_output: DerefOutput<PrimaryContent>,
335) -> Result<DerefOutput, DerefError> {
336 match primary_deref_output.content {
338 PrimaryContent::Document(doc) => doc.dereference_secondary_resource(
339 primary_did_url,
340 fragment,
341 primary_deref_output.content_metadata,
342 primary_deref_output.metadata,
343 ),
344 PrimaryContent::Url(mut url) => {
345 if url.fragment().is_some() {
348 Err(DerefError::FragmentConflict)
349 } else {
350 url.set_fragment(Some(fragment.as_str().try_into().unwrap()));
351 Ok(DerefOutput::new(
352 Content::Url(url),
353 primary_deref_output.content_metadata,
354 primary_deref_output.metadata,
355 ))
356 }
357 }
358 PrimaryContent::Null => Err(DerefError::NullDereference),
359 }
360}