relay_event_normalization/eap/
size.rs

1use relay_event_schema::protocol::{Attribute, Attributes};
2use relay_protocol::{Annotated, Value};
3
4/// Calculates the canonical size of [`Attributes`].
5///
6/// Simple data types have a fixed size assigned to them:
7///  - Boolean: 1 byte
8///  - Integer: 8 byte
9///  - Double: 8 Byte
10///  - Strings are counted by their byte (UTF-8 encoded) representation.
11///
12/// Complex types like objects and arrays are counted as the sum of all contained simple data types.
13///
14/// The size of all attributes is the sum of all attribute values and their keys.
15pub fn attributes_size(attributes: &Attributes) -> usize {
16    attributes
17        .0
18        .iter()
19        .map(|(k, v)| k.len() + attribute_size(v))
20        .sum()
21}
22
23/// Calculates the size of a single attribute.
24///
25/// As described in [`attributes_size`], only the value of the attribute is considered for the size
26/// of an attribute.
27pub fn attribute_size(v: &Annotated<Attribute>) -> usize {
28    v.value().map(|v| &v.value.value).map_or(0, value_size)
29}
30
31/// Recursively calculates the size of a [`Value`], using the rules described in [`attributes_size`].
32pub fn value_size(v: &Annotated<Value>) -> usize {
33    let Some(v) = v.value() else {
34        return 0;
35    };
36
37    match v {
38        Value::Bool(_) => 1,
39        Value::I64(_) => 8,
40        Value::U64(_) => 8,
41        Value::F64(_) => 8,
42        Value::String(v) => v.len(),
43        Value::Array(v) => v.iter().map(value_size).sum(),
44        Value::Object(v) => v.iter().map(|(k, v)| k.len() + value_size(v)).sum(),
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use relay_protocol::{Error, Object};
51
52    use super::*;
53
54    #[test]
55    fn test_value_size_basic() {
56        assert_eq!(value_size(&Value::Bool(true).into()), 1);
57        assert_eq!(value_size(&Value::Bool(false).into()), 1);
58        assert_eq!(value_size(&Value::I64(0).into()), 8);
59        assert_eq!(value_size(&Value::I64(i64::MIN).into()), 8);
60        assert_eq!(value_size(&Value::I64(i64::MAX).into()), 8);
61        assert_eq!(value_size(&Value::U64(0).into()), 8);
62        assert_eq!(value_size(&Value::U64(u64::MIN).into()), 8);
63        assert_eq!(value_size(&Value::U64(u64::MAX).into()), 8);
64        assert_eq!(value_size(&Value::F64(123.42).into()), 8);
65        assert_eq!(value_size(&Value::F64(f64::MAX).into()), 8);
66        assert_eq!(value_size(&Value::F64(f64::MIN).into()), 8);
67        assert_eq!(value_size(&Value::F64(f64::NAN).into()), 8);
68        assert_eq!(value_size(&Value::F64(f64::NEG_INFINITY).into()), 8);
69        assert_eq!(value_size(&Value::String("foobar".to_owned()).into()), 6);
70        assert_eq!(value_size(&Value::String("ඞ".to_owned()).into()), 3);
71        assert_eq!(value_size(&Value::String("".to_owned()).into()), 0);
72    }
73
74    #[test]
75    fn test_value_size_array() {
76        let array = Value::Array(vec![
77            Annotated::empty(),
78            Annotated::new(Value::Bool(true)),
79            Annotated::new(Value::Bool(false)),
80            Annotated::from_error(Error::invalid("oops"), Some(Value::U64(0))),
81            Annotated::new(Value::String("42".to_owned())),
82            Annotated::new(Value::Array(vec![])),
83            Annotated::new(Value::Array(vec![
84                Annotated::new(Value::Array(vec![Annotated::new(Value::Bool(false))])),
85                Annotated::new(Value::Object(Object::from([
86                    ("ඞ".to_owned(), Annotated::new(Value::I64(3))),
87                    ("empty_key".to_owned(), Annotated::empty()),
88                ]))),
89                Annotated::empty(),
90            ])),
91        ]);
92
93        assert_eq!(value_size(&array.into()), 25);
94    }
95
96    #[test]
97    fn test_value_size_object() {
98        let obj = Value::Object(Object::from([
99            ("".to_owned(), Annotated::new(Value::Bool(false))),
100            ("1".to_owned(), Annotated::new(Value::Bool(false))),
101            ("ඞ".to_owned(), Annotated::empty()),
102            (
103                "key".to_owned(),
104                Annotated::new(Value::Object(Object::from([
105                    (
106                        "foo".to_owned(),
107                        Annotated::new(Value::Array(vec![
108                            Annotated::new(Value::I64(21)),
109                            Annotated::new(Value::F64(42.0)),
110                        ])),
111                    ),
112                    ("bar".to_owned(), Annotated::empty()),
113                ]))),
114            ),
115        ]));
116
117        assert_eq!(value_size(&obj.into()), 31);
118    }
119
120    macro_rules! assert_calculated_size_of {
121        ($expected:expr, $json:expr) => {{
122            let json = $json;
123
124            let attrs = Annotated::<Attributes>::from_json(json)
125                .unwrap()
126                .into_value()
127                .unwrap();
128
129            let size = attributes_size(&attrs);
130            assert_eq!(size, $expected, "attrs: {json}");
131        }};
132    }
133
134    #[test]
135    fn test_attributes_size_full() {
136        assert_calculated_size_of!(
137            82,
138            r#"{
139            "k1": {
140                "value": "string value",
141                "type": "string"
142            },
143            "k2": {
144                "value": 18446744073709551615,
145                "type": "integer"
146            },
147            "k3": {
148                "value": 42.01234567891234567899,
149                "type": "double"
150            },
151            "k4": {
152                "value": false,
153                "type": "boolean"
154            },
155            "k5": {
156                "value": {
157                    "nested": {
158                        "array": [1.0, 2, -12, "7 bytes", false]
159                    }
160                },
161                "type": "not yet supported"
162            }
163        }"#
164        );
165    }
166}