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