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