Skip to main content

ojak_vocab/
json.rs

1//! Reading and writing the generated vocabulary types.
2//!
3//! The types in [`crate::generated`] are read from a normalised document, the
4//! form [`crate::read`] produces, and written in the same spelling. Their
5//! field-by-field code is generated; what it calls is here, written by hand.
6//!
7//! Reading is tolerant of values and strict about types. A property whose
8//! value is not the shape its range allows is read as absent, because peers
9//! send values nobody specified and one odd property should not cost the
10//! whole object. An object whose `type` is not the one asked for is an error,
11//! because reading a `Like` as a `Follow` is not tolerance.
12
13use 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
26/// A value that can be read from a normalised document.
27pub trait FromJson: Sized {
28    /// Read `value`.
29    ///
30    /// # Errors
31    ///
32    /// When `value` is not a shape this type can be read from.
33    fn from_json(value: &Value) -> Result<Self, JsonError>;
34}
35
36/// A type documents name in their `type`: what an inbox listener is
37/// registered for.
38pub trait Typed {
39    /// The type's `type` in a normalised document, such as `Create`.
40    const TYPE: &'static str;
41}
42
43/// A value that can be written into a document in Ojak's spelling.
44pub trait ToJson {
45    /// Write `self`.
46    fn to_json(&self) -> Value;
47}
48
49/// Why a value could not be read.
50#[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/// Natural-language text: a value in no particular language, and values per
72/// language.
73///
74/// ActivityStreams writes these as two properties, `content` and
75/// `contentMap`, and JSON-LD reads them as one; this is the one.
76#[derive(Clone, Debug, Default, Eq, PartialEq)]
77pub struct Text {
78    /// The value with no language tag.
79    pub value: Option<String>,
80    /// Values by BCP 47 language tag.
81    pub languages: BTreeMap<String, String>,
82}
83
84impl Text {
85    /// Text with no language.
86    #[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    /// Whether there is no text at all.
95    #[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/// A Data Integrity proof's purpose.
131#[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/// A unit of length, as `Place.units` gives one.
175#[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
217/// The string a value holds, directly or as a JSON-LD value object.
218fn 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
229/// The scalar a value holds, directly or as a JSON-LD value object.
230fn 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            // A reference written as a node, where the key has no `@id` type.
310            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
331/// A portable object's `ap` URI (FEP-ef61) with its DID's colons
332/// percent-encoded. The canonical `ap://did:key:z6Mk…/path` has colons in its
333/// authority, which RFC 3986 does not allow and no IRI parser accepts; the
334/// encoded form is the same identifier, and parses.
335fn 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
348/// The canonical spelling of an `ap` URI read through [`portable_encoded`],
349/// so that what was read is written as it came.
350fn 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
397/// `value` as an object.
398///
399/// # Errors
400///
401/// When it is not one.
402pub 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/// Whether `object`'s `type` is `expected`, alone or among several.
409#[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
418/// Check that `object`'s `type` is `expected`.
419///
420/// # Errors
421///
422/// When it is absent or another type.
423pub 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/// `object`'s `id`.
437#[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
442/// The value under the first of `keys` present.
443fn 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/// A property holding at most one value: the first of `keys` present, and
448/// the first of its values that reads.
449#[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/// [`one`], reading each value with `read`.
455#[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/// [`one`], with each value adjusted by `map` before it is read.
468#[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/// A property that may hold several values: the first of `keys` present, and
483/// every one of its values that reads.
484#[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/// [`many`], reading each value with `read`.
490#[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
503/// A reference, or an object read with `read`: how a property whose values
504/// are sent without a `type` reads them.
505///
506/// # Errors
507///
508/// When the value is neither an IRI nor an object `read` accepts.
509pub 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
522/// Write a reference, or an object with `write`.
523pub 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/// `value` without its `type`, for a property whose values are sent without
531/// one.
532#[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/// [`many`], with each value adjusted by `map` before it is read.
541#[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/// A text property, under `key` in either of its spellings: merged, as a
558/// normalised document has it, or split into `key` and `{key}Map`.
559#[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/// Start writing an object.
578#[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
590/// Write a property holding at most one value under each of `keys`.
591pub 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
595/// [`put_one`], writing the value with `write`.
596pub 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
610/// Write a property that may hold several values under each of `keys`: one
611/// value without an array, several with one, none not at all.
612pub 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
616/// [`put_many`], writing each value with `write`.
617pub 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
633/// Write a text property as `key` and `{key}Map`, which is how peers that do
634/// not process JSON-LD expect to find it.
635pub 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/// A `Link` where an `Image` is expected, read as an `Image` of its `href`.
650///
651/// Misskey and others send `icon` and `image` that way. Anything else is
652/// passed through unchanged.
653#[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
675/// Implement `Serialize` and `Deserialize` through [`ToJson`] and
676/// [`FromJson`].
677macro_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;