1use alloc::{
14 borrow::ToOwned,
15 boxed::Box,
16 collections::BTreeMap,
17 format,
18 string::{String, ToString},
19 vec::Vec,
20};
21use core::fmt;
22use serde_json::{Map, Number, Value};
23
24use crate::{Iri, Reference};
25
26pub trait FromJson: Sized {
28 fn from_json(value: &Value) -> Result<Self, JsonError>;
34}
35
36pub trait Typed {
39 const TYPE: &'static str;
41}
42
43pub trait ToJson {
45 fn to_json(&self) -> Value;
47}
48
49#[derive(Clone, Debug, Eq, PartialEq)]
51pub struct JsonError {
52 message: String,
53}
54
55impl JsonError {
56 fn new(message: impl Into<String>) -> Self {
57 Self {
58 message: message.into(),
59 }
60 }
61}
62
63impl fmt::Display for JsonError {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 f.write_str(&self.message)
66 }
67}
68
69impl core::error::Error for JsonError {}
70
71#[derive(Clone, Debug, Default, Eq, PartialEq)]
77pub struct Text {
78 pub value: Option<String>,
80 pub languages: BTreeMap<String, String>,
82}
83
84impl Text {
85 #[must_use]
87 pub fn plain(value: impl Into<String>) -> Self {
88 Self {
89 value: Some(value.into()),
90 languages: BTreeMap::new(),
91 }
92 }
93
94 #[must_use]
96 pub fn is_empty(&self) -> bool {
97 self.value.is_none() && self.languages.is_empty()
98 }
99
100 fn collect(&mut self, value: &Value) {
101 match value {
102 Value::String(text) => {
103 self.value.get_or_insert_with(|| text.clone());
104 }
105 Value::Object(object) => {
106 let Some(Value::String(text)) = object.get("@value") else {
107 return;
108 };
109 match object.get("@language").and_then(Value::as_str) {
110 Some(language) => {
111 self.languages
112 .entry(language.to_owned())
113 .or_insert_with(|| text.clone());
114 }
115 None => {
116 self.value.get_or_insert_with(|| text.clone());
117 }
118 }
119 }
120 Value::Array(values) => {
121 for value in values {
122 self.collect(value);
123 }
124 }
125 _ => {}
126 }
127 }
128}
129
130#[derive(Clone, Copy, Debug, Eq, PartialEq)]
132pub enum ProofPurpose {
133 AssertionMethod,
134 Authentication,
135 CapabilityInvocation,
136 CapabilityDelegation,
137 KeyAgreement,
138}
139
140impl ProofPurpose {
141 const ALL: [(Self, &'static str); 5] = [
142 (Self::AssertionMethod, "assertionMethod"),
143 (Self::Authentication, "authentication"),
144 (Self::CapabilityInvocation, "capabilityInvocation"),
145 (Self::CapabilityDelegation, "capabilityDelegation"),
146 (Self::KeyAgreement, "keyAgreement"),
147 ];
148}
149
150impl FromJson for ProofPurpose {
151 fn from_json(value: &Value) -> Result<Self, JsonError> {
152 let name = string(value)?;
153 let name = name
154 .strip_prefix("https://w3id.org/security#")
155 .unwrap_or(name);
156 Self::ALL
157 .iter()
158 .find(|(_, known)| *known == name)
159 .map(|(purpose, _)| *purpose)
160 .ok_or_else(|| JsonError::new(format!("unknown proof purpose {name}")))
161 }
162}
163
164impl ToJson for ProofPurpose {
165 fn to_json(&self) -> Value {
166 let name = Self::ALL
167 .iter()
168 .find(|(purpose, _)| purpose == self)
169 .map_or("", |(_, name)| name);
170 Value::String(name.to_owned())
171 }
172}
173
174#[derive(Clone, Copy, Debug, Eq, PartialEq)]
176pub enum Units {
177 Centimetres,
178 Feet,
179 Inches,
180 Kilometres,
181 Metres,
182 Miles,
183}
184
185impl Units {
186 const ALL: [(Self, &'static str); 6] = [
187 (Self::Centimetres, "cm"),
188 (Self::Feet, "feet"),
189 (Self::Inches, "inches"),
190 (Self::Kilometres, "km"),
191 (Self::Metres, "m"),
192 (Self::Miles, "miles"),
193 ];
194}
195
196impl FromJson for Units {
197 fn from_json(value: &Value) -> Result<Self, JsonError> {
198 let name = string(value)?;
199 Self::ALL
200 .iter()
201 .find(|(_, known)| *known == name)
202 .map(|(units, _)| *units)
203 .ok_or_else(|| JsonError::new(format!("unknown units {name}")))
204 }
205}
206
207impl ToJson for Units {
208 fn to_json(&self) -> Value {
209 let name = Self::ALL
210 .iter()
211 .find(|(units, _)| units == self)
212 .map_or("", |(_, name)| name);
213 Value::String(name.to_owned())
214 }
215}
216
217fn string(value: &Value) -> Result<&str, JsonError> {
219 match value {
220 Value::String(text) => Ok(text),
221 Value::Object(object) => match object.get("@value") {
222 Some(Value::String(text)) => Ok(text),
223 _ => Err(JsonError::new("expected a string")),
224 },
225 _ => Err(JsonError::new("expected a string")),
226 }
227}
228
229fn scalar(value: &Value) -> &Value {
231 match value {
232 Value::Object(object) => object.get("@value").unwrap_or(value),
233 other => other,
234 }
235}
236
237impl FromJson for String {
238 fn from_json(value: &Value) -> Result<Self, JsonError> {
239 string(value).map(ToOwned::to_owned)
240 }
241}
242
243impl ToJson for String {
244 fn to_json(&self) -> Value {
245 Value::String(self.clone())
246 }
247}
248
249impl FromJson for bool {
250 fn from_json(value: &Value) -> Result<Self, JsonError> {
251 match scalar(value) {
252 Value::Bool(flag) => Ok(*flag),
253 Value::String(text) if text == "true" => Ok(true),
254 Value::String(text) if text == "false" => Ok(false),
255 _ => Err(JsonError::new("expected a boolean")),
256 }
257 }
258}
259
260impl ToJson for bool {
261 fn to_json(&self) -> Value {
262 Value::Bool(*self)
263 }
264}
265
266impl FromJson for u64 {
267 fn from_json(value: &Value) -> Result<Self, JsonError> {
268 match scalar(value) {
269 Value::Number(number) => number
270 .as_u64()
271 .ok_or_else(|| JsonError::new("expected a non-negative integer")),
272 Value::String(text) => text
273 .parse()
274 .map_err(|_| JsonError::new("expected a non-negative integer")),
275 _ => Err(JsonError::new("expected a non-negative integer")),
276 }
277 }
278}
279
280impl ToJson for u64 {
281 fn to_json(&self) -> Value {
282 Value::Number(Number::from(*self))
283 }
284}
285
286impl FromJson for f64 {
287 fn from_json(value: &Value) -> Result<Self, JsonError> {
288 match scalar(value) {
289 Value::Number(number) => number
290 .as_f64()
291 .ok_or_else(|| JsonError::new("expected a number")),
292 Value::String(text) => text
293 .parse()
294 .map_err(|_| JsonError::new("expected a number")),
295 _ => Err(JsonError::new("expected a number")),
296 }
297 }
298}
299
300impl ToJson for f64 {
301 fn to_json(&self) -> Value {
302 Number::from_f64(*self).map_or(Value::Null, Value::Number)
303 }
304}
305
306impl FromJson for Iri {
307 fn from_json(value: &Value) -> Result<Self, JsonError> {
308 let text = match value {
309 Value::Object(object) if object.len() == 1 && object.contains_key("id") => {
311 object["id"].as_str()
312 }
313 other => string(other).ok(),
314 };
315 let text = text.ok_or_else(|| JsonError::new("expected an IRI"))?;
316 match text.parse() {
317 Ok(iri) => Ok(iri),
318 Err(_) => portable_encoded(text)
319 .and_then(|encoded| encoded.parse().ok())
320 .ok_or_else(|| JsonError::new("not an IRI")),
321 }
322 }
323}
324
325impl ToJson for Iri {
326 fn to_json(&self) -> Value {
327 Value::String(portable_decoded(self.as_str()).unwrap_or_else(|| self.as_str().to_owned()))
328 }
329}
330
331fn portable_encoded(text: &str) -> Option<String> {
336 let (scheme, rest) = text.split_once("://")?;
337 if !matches!(scheme.to_ascii_lowercase().as_str(), "ap" | "ap+ef61") {
338 return None;
339 }
340 let end = rest.find(['/', '?', '#']).unwrap_or(rest.len());
341 let (authority, rest) = rest.split_at(end);
342 Some(format!(
343 "{scheme}://{}{rest}",
344 authority.replace(':', "%3A")
345 ))
346}
347
348fn portable_decoded(iri: &str) -> Option<String> {
351 let (scheme, rest) = iri.split_once("://")?;
352 if !matches!(scheme.to_ascii_lowercase().as_str(), "ap" | "ap+ef61") {
353 return None;
354 }
355 let end = rest.find(['/', '?', '#']).unwrap_or(rest.len());
356 let (authority, rest) = rest.split_at(end);
357 if !authority.to_ascii_lowercase().contains("%3a") {
358 return None;
359 }
360 let authority = authority.replace("%3A", ":").replace("%3a", ":");
361 Some(format!("{scheme}://{authority}{rest}"))
362}
363
364impl<T: FromJson> FromJson for Box<T> {
365 fn from_json(value: &Value) -> Result<Self, JsonError> {
366 T::from_json(value).map(Box::new)
367 }
368}
369
370impl<T: ToJson> ToJson for Box<T> {
371 fn to_json(&self) -> Value {
372 (**self).to_json()
373 }
374}
375
376impl<T: FromJson> FromJson for Reference<T> {
377 fn from_json(value: &Value) -> Result<Self, JsonError> {
378 match value {
379 Value::String(_) => Iri::from_json(value).map(Reference::Id),
380 Value::Object(object) if object.len() == 1 && object.contains_key("id") => {
381 Iri::from_json(value).map(Reference::Id)
382 }
383 _ => T::from_json(value).map(Reference::object),
384 }
385 }
386}
387
388impl<T: ToJson> ToJson for Reference<T> {
389 fn to_json(&self) -> Value {
390 match self {
391 Reference::Id(id) => id.to_json(),
392 Reference::Object(object) => object.to_json(),
393 }
394 }
395}
396
397pub fn object(value: &Value) -> Result<&Map<String, Value>, JsonError> {
403 value
404 .as_object()
405 .ok_or_else(|| JsonError::new("expected an object"))
406}
407
408#[must_use]
410pub fn has_type(object: &Map<String, Value>, expected: &str) -> bool {
411 match object.get("type") {
412 Some(Value::String(name)) => name == expected,
413 Some(Value::Array(names)) => names.iter().any(|name| name.as_str() == Some(expected)),
414 _ => false,
415 }
416}
417
418pub fn expect_type(object: &Map<String, Value>, expected: &str) -> Result<(), JsonError> {
424 if has_type(object, expected) {
425 Ok(())
426 } else {
427 Err(JsonError::new(format!(
428 "expected type {expected}, found {}",
429 object
430 .get("type")
431 .map_or_else(|| "none".to_string(), Value::to_string)
432 )))
433 }
434}
435
436#[must_use]
438pub fn id(object: &Map<String, Value>) -> Option<Iri> {
439 object.get("id").and_then(|id| Iri::from_json(id).ok())
440}
441
442fn first_present<'a>(object: &'a Map<String, Value>, keys: &[&str]) -> Option<&'a Value> {
444 keys.iter().find_map(|key| object.get(*key))
445}
446
447#[must_use]
450pub fn one<T: FromJson>(object: &Map<String, Value>, keys: &[&str]) -> Option<T> {
451 one_with(object, keys, T::from_json)
452}
453
454#[must_use]
456pub fn one_with<T>(
457 object: &Map<String, Value>,
458 keys: &[&str],
459 read: fn(&Value) -> Result<T, JsonError>,
460) -> Option<T> {
461 match first_present(object, keys)? {
462 Value::Array(values) => values.iter().find_map(|value| read(value).ok()),
463 value => read(value).ok(),
464 }
465}
466
467#[must_use]
469pub fn one_mapped<T: FromJson>(
470 object: &Map<String, Value>,
471 keys: &[&str],
472 map: fn(&Value) -> Value,
473) -> Option<T> {
474 match first_present(object, keys)? {
475 Value::Array(values) => values
476 .iter()
477 .find_map(|value| T::from_json(&map(value)).ok()),
478 value => T::from_json(&map(value)).ok(),
479 }
480}
481
482#[must_use]
485pub fn many<T: FromJson>(object: &Map<String, Value>, keys: &[&str]) -> Vec<T> {
486 many_with(object, keys, T::from_json)
487}
488
489#[must_use]
491pub fn many_with<T>(
492 object: &Map<String, Value>,
493 keys: &[&str],
494 read: fn(&Value) -> Result<T, JsonError>,
495) -> Vec<T> {
496 match first_present(object, keys) {
497 Some(Value::Array(values)) => values.iter().filter_map(|value| read(value).ok()).collect(),
498 Some(value) => read(value).ok().into_iter().collect(),
499 None => Vec::new(),
500 }
501}
502
503pub fn reference_with<T>(
510 value: &Value,
511 read: fn(&Value) -> Result<T, JsonError>,
512) -> Result<Reference<T>, JsonError> {
513 match value {
514 Value::String(_) => Iri::from_json(value).map(Reference::Id),
515 Value::Object(object) if object.len() == 1 && object.contains_key("id") => {
516 Iri::from_json(value).map(Reference::Id)
517 }
518 _ => read(value).map(Reference::object),
519 }
520}
521
522pub fn write_reference<T>(reference: &Reference<T>, write: fn(&T) -> Value) -> Value {
524 match reference {
525 Reference::Id(id) => id.to_json(),
526 Reference::Object(object) => write(object),
527 }
528}
529
530#[must_use]
533pub fn without_type(mut value: Value) -> Value {
534 if let Value::Object(object) = &mut value {
535 object.remove("type");
536 }
537 value
538}
539
540#[must_use]
542pub fn many_mapped<T: FromJson>(
543 object: &Map<String, Value>,
544 keys: &[&str],
545 map: fn(&Value) -> Value,
546) -> Vec<T> {
547 match first_present(object, keys) {
548 Some(Value::Array(values)) => values
549 .iter()
550 .filter_map(|value| T::from_json(&map(value)).ok())
551 .collect(),
552 Some(value) => T::from_json(&map(value)).ok().into_iter().collect(),
553 None => Vec::new(),
554 }
555}
556
557#[must_use]
560pub fn text(object: &Map<String, Value>, key: &str) -> Text {
561 let mut text = Text::default();
562 if let Some(value) = object.get(key) {
563 text.collect(value);
564 }
565 if let Some(Value::Object(map)) = object.get(&format!("{key}Map")) {
566 for (language, value) in map {
567 if let Value::String(value) = value {
568 text.languages
569 .entry(language.clone())
570 .or_insert_with(|| value.clone());
571 }
572 }
573 }
574 text
575}
576
577#[must_use]
579pub fn new_object(type_term: Option<&str>, id: Option<&Iri>) -> Map<String, Value> {
580 let mut object = Map::new();
581 if let Some(id) = id {
582 object.insert("id".to_owned(), id.to_json());
583 }
584 if let Some(type_term) = type_term {
585 object.insert("type".to_owned(), Value::String(type_term.to_owned()));
586 }
587 object
588}
589
590pub fn put_one<T: ToJson>(object: &mut Map<String, Value>, keys: &[&str], value: Option<&T>) {
592 put_one_with(object, keys, value, T::to_json);
593}
594
595pub fn put_one_with<T>(
597 object: &mut Map<String, Value>,
598 keys: &[&str],
599 value: Option<&T>,
600 write: fn(&T) -> Value,
601) {
602 if let Some(value) = value {
603 let value = write(value);
604 for key in keys {
605 object.insert((*key).to_owned(), value.clone());
606 }
607 }
608}
609
610pub fn put_many<T: ToJson>(object: &mut Map<String, Value>, keys: &[&str], values: &[T]) {
613 put_many_with(object, keys, values, T::to_json);
614}
615
616pub fn put_many_with<T>(
618 object: &mut Map<String, Value>,
619 keys: &[&str],
620 values: &[T],
621 write: fn(&T) -> Value,
622) {
623 let value = match values {
624 [] => return,
625 [value] => write(value),
626 values => Value::Array(values.iter().map(write).collect()),
627 };
628 for key in keys {
629 object.insert((*key).to_owned(), value.clone());
630 }
631}
632
633pub fn put_text(object: &mut Map<String, Value>, key: &str, text: &Text) {
636 if let Some(value) = &text.value {
637 object.insert(key.to_owned(), Value::String(value.clone()));
638 }
639 if !text.languages.is_empty() {
640 let map = text
641 .languages
642 .iter()
643 .map(|(language, value)| (language.clone(), Value::String(value.clone())))
644 .collect();
645 object.insert(format!("{key}Map"), Value::Object(map));
646 }
647}
648
649#[must_use]
654pub fn link_to_image(value: &Value) -> Value {
655 let Value::Object(object) = value else {
656 return value.clone();
657 };
658 if !has_type(object, "Link") {
659 return value.clone();
660 }
661 let Some(href) = object.get("href") else {
662 return value.clone();
663 };
664 let mut image = Map::new();
665 image.insert("type".to_owned(), Value::String("Image".to_owned()));
666 image.insert("url".to_owned(), href.clone());
667 for key in ["mediaType", "name", "width", "height"] {
668 if let Some(field) = object.get(key) {
669 image.insert(key.to_owned(), field.clone());
670 }
671 }
672 Value::Object(image)
673}
674
675macro_rules! serde_via_json {
678 ($($name:ident),* $(,)?) => {$(
679 impl serde::Serialize for $name {
680 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
681 serde::Serialize::serialize(&$crate::json::ToJson::to_json(self), serializer)
682 }
683 }
684
685 impl<'de> serde::Deserialize<'de> for $name {
686 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
687 let value = <serde_json::Value as serde::Deserialize>::deserialize(deserializer)?;
688 $crate::json::FromJson::from_json(&value).map_err(serde::de::Error::custom)
689 }
690 }
691 )*};
692}
693
694pub(crate) use serde_via_json;