identity_credential/sd_jwt_vc/metadata/
claim.rs1use std::fmt::Display;
5use std::ops::Deref;
6
7use anyhow::anyhow;
8use anyhow::Context;
9use itertools::Itertools;
10use serde::Deserialize;
11use serde::Serialize;
12use serde::Serializer;
13use serde_json::Value;
14
15use crate::sd_jwt_vc::Error;
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct ClaimMetadata {
20 pub path: ClaimPath,
22 #[serde(skip_serializing_if = "Vec::is_empty", default)]
24 pub display: Vec<ClaimDisplay>,
25 #[serde(skip_serializing_if = "Option::is_none")]
28 pub mandatory: Option<bool>,
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub sd: Option<ClaimDisclosability>,
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub svg_id: Option<String>,
35}
36
37impl ClaimMetadata {
38 pub fn check_value_disclosability(&self, value: &Value) -> Result<(), Error> {
40 if self.sd.unwrap_or_default() == ClaimDisclosability::Allowed {
41 return Ok(());
42 }
43
44 let interested_claims = self.path.reverse_index(value);
45 if self.sd.unwrap_or_default() == ClaimDisclosability::Always && interested_claims.is_ok() {
46 return Err(Error::Validation(anyhow!(
47 "claim or claims with path {} must always be disclosable",
48 &self.path
49 )));
50 }
51
52 if self.sd.unwrap_or_default() == ClaimDisclosability::Never && interested_claims.is_err() {
53 return Err(Error::Validation(anyhow!(
54 "claim or claims with path {} must never be disclosable",
55 &self.path
56 )));
57 }
58
59 Ok(())
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67#[serde(try_from = "Vec<ClaimPathSegment>")]
68pub struct ClaimPath(Vec<ClaimPathSegment>);
69
70impl ClaimPath {
71 fn reverse_index<'v>(&self, value: &'v Value) -> anyhow::Result<OneOrManyValue<'v>> {
72 let mut segments = self.iter();
73 let first_segment = segments.next().context("empty claim path")?;
74 segments.try_fold(index_value(value, first_segment)?, |values, segment| {
75 values.get(segment)
76 })
77 }
78}
79
80impl TryFrom<Vec<ClaimPathSegment>> for ClaimPath {
81 type Error = anyhow::Error;
82 fn try_from(value: Vec<ClaimPathSegment>) -> Result<Self, Self::Error> {
83 if value.is_empty() {
84 Err(anyhow::anyhow!("`ClaimPath` cannot be empty"))
85 } else {
86 Ok(Self(value))
87 }
88 }
89}
90
91impl Display for ClaimPath {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 let segments = self.iter().join(", ");
94 write!(f, "[{segments}]")
95 }
96}
97
98impl Deref for ClaimPath {
99 type Target = [ClaimPathSegment];
100 fn deref(&self) -> &Self::Target {
101 &self.0
102 }
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
107#[serde(untagged, try_from = "Value")]
108pub enum ClaimPathSegment {
109 Name(String),
111 Position(usize),
113 #[serde(serialize_with = "serialize_all_variant")]
115 All,
116}
117
118impl Display for ClaimPathSegment {
119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 match self {
121 Self::All => write!(f, "null"),
122 Self::Name(name) => write!(f, "\"{name}\""),
123 Self::Position(i) => write!(f, "{i}"),
124 }
125 }
126}
127
128impl TryFrom<Value> for ClaimPathSegment {
129 type Error = anyhow::Error;
130 fn try_from(value: Value) -> Result<Self, Self::Error> {
131 match value {
132 Value::Null => Ok(ClaimPathSegment::All),
133 Value::String(s) => Ok(ClaimPathSegment::Name(s)),
134 Value::Number(n) => n
135 .as_u64()
136 .ok_or_else(|| anyhow::anyhow!("expected number greater or equal to 0"))
137 .map(|n| ClaimPathSegment::Position(n as usize)),
138 _ => Err(anyhow::anyhow!("expected either a string, number, or null")),
139 }
140 }
141}
142
143fn serialize_all_variant<S>(serializer: S) -> Result<S::Ok, S::Error>
144where
145 S: Serializer,
146{
147 serializer.serialize_none()
148}
149
150#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
152#[serde(rename_all = "lowercase")]
153pub enum ClaimDisclosability {
154 Always,
156 #[default]
158 Allowed,
159 Never,
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
165pub struct ClaimDisplay {
166 pub locale: String,
168 pub label: String,
170 pub description: Option<String>,
172}
173
174enum OneOrManyValue<'v> {
175 One(&'v Value),
176 Many(Box<dyn Iterator<Item = &'v Value> + 'v>),
177}
178
179impl<'v> OneOrManyValue<'v> {
180 fn get(self, segment: &ClaimPathSegment) -> anyhow::Result<OneOrManyValue<'v>> {
181 match self {
182 Self::One(value) => index_value(value, segment),
183 Self::Many(values) => {
184 let new_values = values
185 .map(|value| index_value(value, segment))
186 .collect::<anyhow::Result<Vec<_>>>()?
187 .into_iter()
188 .flatten();
189
190 Ok(OneOrManyValue::Many(Box::new(new_values)))
191 }
192 }
193 }
194}
195
196struct OneOrManyValueIter<'v>(Option<OneOrManyValue<'v>>);
197
198impl<'v> OneOrManyValueIter<'v> {
199 fn new(value: OneOrManyValue<'v>) -> Self {
200 Self(Some(value))
201 }
202}
203
204impl<'v> IntoIterator for OneOrManyValue<'v> {
205 type IntoIter = OneOrManyValueIter<'v>;
206 type Item = &'v Value;
207 fn into_iter(self) -> Self::IntoIter {
208 OneOrManyValueIter::new(self)
209 }
210}
211
212impl<'v> Iterator for OneOrManyValueIter<'v> {
213 type Item = &'v Value;
214 fn next(&mut self) -> Option<Self::Item> {
215 match self.0.take()? {
216 OneOrManyValue::One(v) => Some(v),
217 OneOrManyValue::Many(mut values) => {
218 let value = values.next();
219 self.0 = Some(OneOrManyValue::Many(values));
220
221 value
222 }
223 }
224 }
225}
226
227fn index_value<'v>(value: &'v Value, segment: &ClaimPathSegment) -> anyhow::Result<OneOrManyValue<'v>> {
228 match segment {
229 ClaimPathSegment::Name(name) => value.get(name).map(OneOrManyValue::One),
230 ClaimPathSegment::Position(i) => value.get(i).map(OneOrManyValue::One),
231 ClaimPathSegment::All => value
232 .as_array()
233 .map(|values| OneOrManyValue::Many(Box::new(values.iter()))),
234 }
235 .ok_or_else(|| anyhow::anyhow!("value {value:#} has no element {segment}"))
236}
237
238#[cfg(test)]
239mod tests {
240 use serde_json::json;
241
242 use super::*;
243
244 fn sample_obj() -> Value {
245 json!({
246 "vct": "https://betelgeuse.example.com/education_credential",
247 "name": "Arthur Dent",
248 "address": {
249 "street_address": "42 Market Street",
250 "city": "Milliways",
251 "postal_code": "12345"
252 },
253 "degrees": [
254 {
255 "type": "Bachelor of Science",
256 "university": "University of Betelgeuse"
257 },
258 {
259 "type": "Master of Science",
260 "university": "University of Betelgeuse"
261 }
262 ],
263 "nationalities": ["British", "Betelgeusian"]
264 })
265 }
266
267 #[test]
268 fn claim_path_works() {
269 let name_path = serde_json::from_value::<ClaimPath>(json!(["name"])).unwrap();
270 let city_path = serde_json::from_value::<ClaimPath>(json!(["address", "city"])).unwrap();
271 let first_degree_path = serde_json::from_value::<ClaimPath>(json!(["degrees", 0])).unwrap();
272 let degrees_types_path = serde_json::from_value::<ClaimPath>(json!(["degrees", null, "type"])).unwrap();
273
274 assert!(matches!(
275 name_path.reverse_index(&sample_obj()).unwrap(),
276 OneOrManyValue::One(&Value::String(_))
277 ));
278 assert!(matches!(
279 city_path.reverse_index(&sample_obj()).unwrap(),
280 OneOrManyValue::One(&Value::String(_))
281 ));
282 assert!(matches!(
283 first_degree_path.reverse_index(&sample_obj()).unwrap(),
284 OneOrManyValue::One(&Value::Object(_))
285 ));
286 let obj = &sample_obj();
287 let mut degree_types = degrees_types_path.reverse_index(obj).unwrap().into_iter();
288 assert_eq!(degree_types.next().unwrap().as_str(), Some("Bachelor of Science"));
289 assert_eq!(degree_types.next().unwrap().as_str(), Some("Master of Science"));
290 assert_eq!(degree_types.next(), None);
291 }
292}