relay_event_schema/protocol/
nel.rs1use std::fmt;
6use std::str::FromStr;
7
8use relay_protocol::{Annotated, Empty, FromValue, IntoValue, Object, Value};
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11
12use crate::processor::ProcessValue;
13use crate::protocol::IpAddr;
14
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ProcessValue)]
17#[serde(rename_all = "lowercase")]
18pub enum NetworkReportPhases {
19 DNS,
21 Connections,
23 Application,
25 Other(String),
27}
28
29impl NetworkReportPhases {
30 pub fn as_str(&self) -> &str {
32 match *self {
33 NetworkReportPhases::DNS => "dns",
34 NetworkReportPhases::Connections => "connection",
35 NetworkReportPhases::Application => "application",
36 NetworkReportPhases::Other(ref unknown) => unknown,
37 }
38 }
39}
40
41impl fmt::Display for NetworkReportPhases {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 f.write_str(self.as_str())
44 }
45}
46
47impl AsRef<str> for NetworkReportPhases {
48 fn as_ref(&self) -> &str {
49 self.as_str()
50 }
51}
52
53impl Empty for NetworkReportPhases {
54 #[inline]
55 fn is_empty(&self) -> bool {
56 false
57 }
58}
59
60#[derive(Clone, Copy, Debug)]
62pub struct ParseNetworkReportPhaseError;
63
64impl fmt::Display for ParseNetworkReportPhaseError {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 write!(f, "invalid network report phase")
67 }
68}
69
70impl FromStr for NetworkReportPhases {
71 type Err = ParseNetworkReportPhaseError;
72
73 fn from_str(s: &str) -> Result<Self, Self::Err> {
74 let s = s.to_lowercase();
75 Ok(match s.as_str() {
76 "dns" => NetworkReportPhases::DNS,
77 "connection" => NetworkReportPhases::Connections,
78 "application" => NetworkReportPhases::Application,
79 _ => NetworkReportPhases::Other(s),
80 })
81 }
82}
83
84impl FromValue for NetworkReportPhases {
85 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
86 match value {
87 Annotated(Some(Value::String(value)), mut meta) => match value.parse() {
88 Ok(phase) => Annotated(Some(phase), meta),
89 Err(_) => {
90 meta.add_error(relay_protocol::Error::expected("a string"));
91 meta.set_original_value(Some(value));
92 Annotated(None, meta)
93 }
94 },
95 Annotated(None, meta) => Annotated(None, meta),
96 Annotated(Some(value), mut meta) => {
97 meta.add_error(relay_protocol::Error::expected("a string"));
98 meta.set_original_value(Some(value));
99 Annotated(None, meta)
100 }
101 }
102 }
103}
104
105impl IntoValue for NetworkReportPhases {
106 fn into_value(self) -> Value {
107 Value::String(match self {
108 Self::Other(s) => s,
109 _ => self.as_str().to_owned(),
110 })
111 }
112
113 fn serialize_payload<S>(
114 &self,
115 s: S,
116 _behavior: relay_protocol::SkipSerialization,
117 ) -> Result<S::Ok, S::Error>
118 where
119 Self: Sized,
120 S: serde::Serializer,
121 {
122 Serialize::serialize(self.as_str(), s)
123 }
124}
125
126#[derive(Debug, Error)]
128pub enum NetworkReportError {
129 #[error("incoming json is unparsable")]
131 InvalidJson(#[from] serde_json::Error),
132}
133
134#[derive(Debug, Default, Clone, PartialEq, FromValue, IntoValue, Empty)]
136pub struct BodyRaw {
137 pub elapsed_time: Annotated<u64>,
139 pub method: Annotated<String>,
141 pub phase: Annotated<NetworkReportPhases>,
143 pub protocol: Annotated<String>,
145 pub referrer: Annotated<String>,
147 pub sampling_fraction: Annotated<f64>,
149 pub server_ip: Annotated<IpAddr>,
151 pub status_code: Annotated<i64>,
153 #[metastructure(field = "type")]
155 pub ty: Annotated<String>,
156 #[metastructure(additional_properties, pii = "maybe")]
158 pub other: Object<Value>,
159}
160
161#[derive(Debug, Default, Clone, PartialEq, FromValue, IntoValue, Empty)]
165pub struct NetworkReportRaw {
166 pub age: Annotated<i64>,
168 #[metastructure(field = "type")]
170 pub ty: Annotated<String>,
171 #[metastructure(pii = "true")]
173 pub url: Annotated<String>,
174 pub user_agent: Annotated<String>,
176 pub body: Annotated<BodyRaw>,
178 #[metastructure(additional_properties, pii = "maybe")]
180 pub other: Object<Value>,
181}
182
183#[cfg(test)]
184mod tests {
185 use relay_protocol::{assert_annotated_snapshot, Annotated};
186
187 use crate::protocol::NetworkReportRaw;
188
189 #[test]
190 fn test_nel_raw_basic() {
191 let json = r#"{
192 "age": 31042,
193 "body": {
194 "elapsed_time": 0,
195 "method": "GET",
196 "phase": "connection",
197 "protocol": "http/1.1",
198 "referrer": "",
199 "sampling_fraction": 1.0,
200 "server_ip": "127.0.0.1",
201 "status_code": 0,
202 "type": "tcp.refused"
203 },
204 "type": "network-error",
205 "url": "http://example.com/",
206 "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
207 }"#;
208
209 let report: Annotated<NetworkReportRaw> =
210 Annotated::from_json_bytes(json.as_bytes()).unwrap();
211
212 assert_annotated_snapshot!(report, @r###"
213 {
214 "age": 31042,
215 "type": "network-error",
216 "url": "http://example.com/",
217 "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
218 "body": {
219 "elapsed_time": 0,
220 "method": "GET",
221 "phase": "connection",
222 "protocol": "http/1.1",
223 "referrer": "",
224 "sampling_fraction": 1.0,
225 "server_ip": "127.0.0.1",
226 "status_code": 0,
227 "type": "tcp.refused"
228 }
229 }
230 "###);
231 }
232}