relay_event_schema/protocol/contexts/
os.rs

1use relay_protocol::{Annotated, Empty, FromValue, IntoValue, Object, Value};
2
3use crate::processor::ProcessValue;
4use crate::protocol::LenientString;
5
6/// Operating system information.
7///
8/// OS context describes the operating system on which the event was created. In web contexts, this
9/// is the operating system of the browser (generally pulled from the User-Agent string).
10#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
11pub struct OsContext {
12    /// Computed field from `name` and `version`. Needed by the metrics extraction.
13    pub os: Annotated<String>,
14
15    /// Name of the operating system.
16    pub name: Annotated<String>,
17
18    /// Version of the operating system.
19    pub version: Annotated<String>,
20
21    /// Internal build number of the operating system.
22    #[metastructure(pii = "maybe")]
23    pub build: Annotated<LenientString>,
24
25    /// Current kernel version.
26    ///
27    /// This is typically the entire output of the `uname` syscall.
28    #[metastructure(pii = "maybe")]
29    pub kernel_version: Annotated<String>,
30
31    /// Indicator if the OS is rooted (mobile mostly).
32    pub rooted: Annotated<bool>,
33
34    /// Meta-data for the Linux Distribution.
35    #[metastructure(pii = "maybe")]
36    pub distribution_name: Annotated<String>,
37    #[metastructure(pii = "maybe")]
38    pub distribution_version: Annotated<String>,
39    #[metastructure(pii = "maybe")]
40    pub distribution_pretty_name: Annotated<String>,
41
42    /// Unprocessed operating system info.
43    ///
44    /// An unprocessed description string obtained by the operating system. For some well-known
45    /// runtimes, Sentry will attempt to parse `name` and `version` from this string, if they are
46    /// not explicitly given.
47    #[metastructure(pii = "maybe")]
48    pub raw_description: Annotated<String>,
49
50    /// Additional arbitrary fields for forwards compatibility.
51    #[metastructure(additional_properties, retain = true, pii = "maybe")]
52    pub other: Object<Value>,
53}
54
55impl super::DefaultContext for OsContext {
56    fn default_key() -> &'static str {
57        "os"
58    }
59
60    fn from_context(context: super::Context) -> Option<Self> {
61        match context {
62            super::Context::Os(c) => Some(*c),
63            _ => None,
64        }
65    }
66
67    fn cast(context: &super::Context) -> Option<&Self> {
68        match context {
69            super::Context::Os(c) => Some(c),
70            _ => None,
71        }
72    }
73
74    fn cast_mut(context: &mut super::Context) -> Option<&mut Self> {
75        match context {
76            super::Context::Os(c) => Some(c),
77            _ => None,
78        }
79    }
80
81    fn into_context(self) -> super::Context {
82        super::Context::Os(Box::new(self))
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use crate::protocol::Context;
90
91    #[test]
92    fn test_os_context_roundtrip() {
93        let json = r#"{
94  "os": "iOS 11.4.2",
95  "name": "iOS",
96  "version": "11.4.2",
97  "build": "FEEDFACE",
98  "kernel_version": "17.4.0",
99  "rooted": true,
100  "raw_description": "iOS 11.4.2 FEEDFACE (17.4.0)",
101  "other": "value",
102  "type": "os"
103}"#;
104        let context = Annotated::new(Context::Os(Box::new(OsContext {
105            os: Annotated::new("iOS 11.4.2".to_string()),
106            name: Annotated::new("iOS".to_string()),
107            version: Annotated::new("11.4.2".to_string()),
108            build: Annotated::new(LenientString("FEEDFACE".to_string())),
109            kernel_version: Annotated::new("17.4.0".to_string()),
110            rooted: Annotated::new(true),
111            raw_description: Annotated::new("iOS 11.4.2 FEEDFACE (17.4.0)".to_string()),
112            distribution_name: Annotated::empty(),
113            distribution_version: Annotated::empty(),
114            distribution_pretty_name: Annotated::empty(),
115            other: {
116                let mut map = Object::new();
117                map.insert(
118                    "other".to_string(),
119                    Annotated::new(Value::String("value".to_string())),
120                );
121                map
122            },
123        })));
124
125        assert_eq!(context, Annotated::from_json(json).unwrap());
126        assert_eq!(json, context.to_json_pretty().unwrap());
127    }
128
129    #[test]
130    fn test_os_context_linux_roundtrip() {
131        let json: &str = r#"{
132  "os": "Linux 5.15.133",
133  "name": "Linux",
134  "version": "5.15.133",
135  "build": "1-microsoft-standard-WSL2",
136  "distribution_name": "ubuntu",
137  "distribution_version": "22.04",
138  "distribution_pretty_name": "Ubuntu 22.04.4 LTS",
139  "type": "os"
140}"#;
141        let context = Annotated::new(Context::Os(Box::new(OsContext {
142            os: Annotated::new("Linux 5.15.133".to_string()),
143            name: Annotated::new("Linux".to_string()),
144            version: Annotated::new("5.15.133".to_string()),
145            build: Annotated::new(LenientString("1-microsoft-standard-WSL2".to_string())),
146            kernel_version: Annotated::empty(),
147            rooted: Annotated::empty(),
148            raw_description: Annotated::empty(),
149            distribution_name: Annotated::new("ubuntu".to_string()),
150            distribution_version: Annotated::new("22.04".to_string()),
151            distribution_pretty_name: Annotated::new("Ubuntu 22.04.4 LTS".to_string()),
152            other: Object::default(),
153        })));
154
155        assert_eq!(context, Annotated::from_json(json).unwrap());
156        assert_eq!(json, context.to_json_pretty().unwrap());
157    }
158}