dpp_digital_link/linktype/
negotiate.rs1use dpp_domain::{Audience, Disclosure};
4use serde::{Deserialize, Serialize};
5
6use super::media_type::DppMediaType;
7use super::request::ResolutionRequest;
8use super::vocabulary::Gs1LinkType;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct LinkDescriptor {
17 pub href: String,
19 pub link_type: Gs1LinkType,
21 pub media_type: DppMediaType,
23 pub disclosure: Disclosure,
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub title: Option<String>,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub language: Option<String>,
31}
32
33pub fn negotiate<'a>(
41 available: &'a [LinkDescriptor],
42 request: &ResolutionRequest,
43) -> Option<&'a LinkDescriptor> {
44 let audience = request.audience.unwrap_or(Audience::Public);
45
46 let accessible: Vec<&LinkDescriptor> = available
48 .iter()
49 .filter(|d| audience.may_see(d.disclosure))
50 .collect();
51
52 if accessible.is_empty() {
53 return None;
54 }
55
56 let by_link_type: Vec<&LinkDescriptor> = if let Some(ref lt) = request.link_type {
58 let matched: Vec<_> = accessible
59 .iter()
60 .filter(|d| d.link_type == *lt)
61 .copied()
62 .collect();
63 if matched.is_empty() {
64 return None;
65 }
66 matched
67 } else {
68 accessible
69 };
70
71 let candidates = &by_link_type;
72
73 if let Some(ref mt) = request.media_type
75 && let Some(exact) = candidates.iter().find(|d| d.media_type == *mt)
76 {
77 return Some(exact);
78 }
79
80 candidates.first().copied()
82}