Skip to main content

ruma_common/canonical_json/redaction/
serializer.rs

1#![allow(clippy::exhaustive_structs)]
2
3use serde::{Serialize, Serializer, ser::SerializeMap};
4
5use super::{RetainKey, RetainedKeys, retained_event_keys};
6use crate::{CanonicalJsonObject, CanonicalJsonValue, room_version_rules::RedactionRules};
7
8/// [`CanonicalJsonObject`] serializer that redacts fields on the fly.
9///
10/// The main use case for this is to compute the hashes or signatures of an event, where the event
11/// needs to be redacted and have a few other fields removed.
12///
13/// This avoids having to `.clone()` a `CanonicalJsonObject`, and then to
14/// [`redact()`](super::redact) it and potentially remove other fields, and serialize it with
15/// [`serde_json::to_vec()`].
16#[derive(Debug, Clone, Copy, Default)]
17pub struct RedactingSerializer<'a> {
18    /// The redaction rules to apply, if any.
19    rules: Option<&'a RedactionRules>,
20
21    /// Custom fields to redact at the root of the object.
22    custom_redacted_root_fields: &'a [&'a str],
23}
24
25impl<'a> RedactingSerializer<'a> {
26    /// Construct a new `RedactingSerializer` that doesn't redact anything.
27    pub fn new() -> Self {
28        Self::default()
29    }
30
31    /// Add the redaction rules to apply.
32    ///
33    /// If this is set, the object to serialize must be an event with a `type` field.
34    pub fn rules(mut self, rules: &'a RedactionRules) -> Self {
35        self.rules = Some(rules);
36        self
37    }
38
39    /// Add custom fields to redact at the root of the object.
40    pub fn custom_redacted_root_fields(mut self, fields: &'a [&'a str]) -> Self {
41        self.custom_redacted_root_fields = fields;
42        self
43    }
44
45    /// Serialize the given object while redacting it.
46    pub fn serialize(&self, object: &CanonicalJsonObject) -> Result<String, serde_json::Error> {
47        let retained_keys = self
48            .rules
49            .map(|rules| {
50                retained_event_keys(object)
51                    .map(|retained_keys| RetainedKeysWithRules { retained_keys, rules })
52            })
53            .transpose()
54            .map_err(serde::ser::Error::custom)?;
55
56        serde_json::to_string(&RedactingObjectSerializer {
57            object,
58            retained_keys,
59            custom_redacted_fields: self.custom_redacted_root_fields,
60        })
61    }
62}
63
64/// Wrapper around [`CanonicalJsonObject`] that redacts fields during serialization.
65struct RedactingObjectSerializer<'a> {
66    object: &'a CanonicalJsonObject,
67    retained_keys: Option<RetainedKeysWithRules<'a>>,
68    custom_redacted_fields: &'a [&'a str],
69}
70
71impl<'a> Serialize for RedactingObjectSerializer<'a> {
72    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
73    where
74        S: Serializer,
75    {
76        let mut serialize_map = serializer.serialize_map(None)?;
77
78        for (key, value) in self.object {
79            if self.custom_redacted_fields.contains(&key.as_str()) {
80                continue;
81            }
82
83            if let Some(RetainedKeysWithRules { retained_keys, rules }) = &self.retained_keys {
84                if let RetainKey::Yes { child_retained_keys } =
85                    retained_keys.should_retain_key(rules, key)
86                {
87                    // We only support recursive redacting for objects.
88                    if let Some(retained_keys) = child_retained_keys
89                        && let CanonicalJsonValue::Object(child_object) = value
90                    {
91                        serialize_map.serialize_entry(
92                            key,
93                            &Self {
94                                object: child_object,
95                                retained_keys: Some(RetainedKeysWithRules { retained_keys, rules }),
96                                custom_redacted_fields: &[],
97                            },
98                        )?;
99                    } else {
100                        serialize_map.serialize_entry(key, value)?;
101                    }
102                }
103            } else {
104                serialize_map.serialize_entry(key, value)?;
105            }
106        }
107
108        serialize_map.end()
109    }
110}
111
112/// The retained keys for an object and the redaction rules to apply.
113struct RetainedKeysWithRules<'a> {
114    /// The keys to retain for the object.
115    retained_keys: RetainedKeys,
116
117    /// The redaction rules to apply.
118    rules: &'a RedactionRules,
119}