relay_event_schema/protocol/
clientsdk.rs1use crate::processor::ProcessValue;
2use crate::protocol::IpAddr;
3use relay_protocol::{
4 Annotated, Array, Empty, ErrorKind, FromValue, IntoValue, Object, SkipSerialization, Value,
5};
6use serde::{Serialize, Serializer};
7use std::str::FromStr;
8use thiserror::Error;
9
10#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
12pub struct ClientSdkPackage {
13 pub name: Annotated<String>,
15 pub version: Annotated<String>,
17}
18
19#[derive(Debug, Clone, Error)]
21pub enum ParseSettingError {
22 #[error("Invalid value for 'infer_ip'.")]
23 InferIp,
24}
25
26#[derive(Debug, Clone, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
32pub struct ClientSdkSettings {
33 infer_ip: Annotated<AutoInferSetting>,
34}
35
36impl ClientSdkSettings {
37 pub fn infer_ip(&self) -> AutoInferSetting {
49 if self.infer_ip.meta().has_errors() {
50 AutoInferSetting::Never
51 } else {
52 self.infer_ip.value().copied().unwrap_or_default()
53 }
54 }
55}
56
57#[derive(Debug, Copy, Clone, PartialEq, Default, ProcessValue)]
60pub enum AutoInferSetting {
61 Auto,
63
64 Never,
66
67 #[default]
83 Legacy,
84}
85
86impl Empty for AutoInferSetting {
87 fn is_empty(&self) -> bool {
88 matches!(self, AutoInferSetting::Legacy)
89 }
90}
91
92impl AutoInferSetting {
93 pub fn as_str(&self) -> &'static str {
95 match self {
96 AutoInferSetting::Auto => "auto",
97 AutoInferSetting::Never => "never",
98 AutoInferSetting::Legacy => "legacy",
99 }
100 }
101}
102
103impl FromStr for AutoInferSetting {
104 type Err = ParseSettingError;
105
106 fn from_str(s: &str) -> Result<Self, Self::Err> {
107 match s {
108 "auto" => Ok(AutoInferSetting::Auto),
109 "never" => Ok(AutoInferSetting::Never),
110 "legacy" => Ok(AutoInferSetting::Legacy),
111 _ => Err(ParseSettingError::InferIp),
112 }
113 }
114}
115
116impl FromValue for AutoInferSetting {
117 fn from_value(value: Annotated<Value>) -> Annotated<Self>
118 where
119 Self: Sized,
120 {
121 match String::from_value(value) {
122 Annotated(Some(value), mut meta) => match value.parse() {
123 Ok(infer_ip) => Annotated(Some(infer_ip), meta),
124 Err(_) => {
125 meta.add_error(ErrorKind::InvalidData);
126 meta.set_original_value(Some(value));
127 Annotated(None, meta)
128 }
129 },
130 Annotated(None, meta) => Annotated(None, meta),
131 }
132 }
133}
134
135impl IntoValue for AutoInferSetting {
136 fn into_value(self) -> Value {
137 Value::String(self.as_str().to_string())
138 }
139
140 fn serialize_payload<S>(&self, s: S, _: SkipSerialization) -> Result<S::Ok, S::Error>
141 where
142 Self: Sized,
143 S: Serializer,
144 {
145 Serialize::serialize(self.as_str(), s)
146 }
147}
148
149#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
151#[metastructure(process_func = "process_client_sdk_info", value_type = "ClientSdkInfo")]
152pub struct ClientSdkInfo {
153 #[metastructure(required = true, max_chars = 256, max_chars_allowance = 20)]
163 pub name: Annotated<String>,
164
165 #[metastructure(required = true, max_chars = 256, max_chars_allowance = 20)]
172 pub version: Annotated<String>,
173
174 #[metastructure(skip_serialization = "empty_deep")]
180 pub integrations: Annotated<Array<String>>,
181
182 #[metastructure(skip_serialization = "empty_deep")]
189 pub features: Annotated<Array<String>>,
190
191 #[metastructure(skip_serialization = "empty_deep")]
198 pub packages: Annotated<Array<ClientSdkPackage>>,
199
200 #[metastructure(pii = "true", skip_serialization = "empty", omit_from_schema)]
203 pub client_ip: Annotated<IpAddr>,
204
205 #[metastructure(skip_serialization = "empty")]
207 pub settings: Annotated<ClientSdkSettings>,
208
209 #[metastructure(additional_properties)]
211 pub other: Object<Value>,
212}
213
214impl ClientSdkInfo {
215 pub fn has_integration<T: AsRef<str>>(&self, integration: T) -> bool {
216 if let Some(integrations) = self.integrations.value() {
217 for x in integrations {
218 if x.as_str().unwrap_or_default() == integration.as_ref() {
219 return true;
220 }
221 }
222 };
223 false
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use relay_protocol::Map;
230 use similar_asserts::assert_eq;
231
232 use super::*;
233
234 #[test]
235 fn test_client_sdk_roundtrip() {
236 let json = r#"{
237 "name": "sentry.rust",
238 "version": "1.0.0",
239 "integrations": [
240 "actix"
241 ],
242 "features": [
243 "feature1"
244 ],
245 "packages": [
246 {
247 "name": "cargo:sentry",
248 "version": "0.10.0"
249 },
250 {
251 "name": "cargo:sentry-actix",
252 "version": "0.10.0"
253 }
254 ],
255 "client_ip": "127.0.0.1",
256 "other": "value"
257}"#;
258 let sdk = Annotated::new(ClientSdkInfo {
259 name: Annotated::new("sentry.rust".to_string()),
260 version: Annotated::new("1.0.0".to_string()),
261 integrations: Annotated::new(vec![Annotated::new("actix".to_string())]),
262 features: Annotated::new(vec![Annotated::new("feature1".to_string())]),
263 packages: Annotated::new(vec![
264 Annotated::new(ClientSdkPackage {
265 name: Annotated::new("cargo:sentry".to_string()),
266 version: Annotated::new("0.10.0".to_string()),
267 }),
268 Annotated::new(ClientSdkPackage {
269 name: Annotated::new("cargo:sentry-actix".to_string()),
270 version: Annotated::new("0.10.0".to_string()),
271 }),
272 ]),
273 client_ip: Annotated::new(IpAddr("127.0.0.1".to_owned())),
274 settings: Annotated::empty(),
275 other: {
276 let mut map = Map::new();
277 map.insert(
278 "other".to_string(),
279 Annotated::new(Value::String("value".to_string())),
280 );
281 map
282 },
283 });
284
285 assert_eq!(sdk, Annotated::from_json(json).unwrap());
286 assert_eq!(json, sdk.to_json_pretty().unwrap());
287 }
288
289 #[test]
290 fn test_client_sdk_default_values() {
291 let json = r#"{
292 "name": "sentry.rust",
293 "version": "1.0.0",
294 "client_ip": "127.0.0.1"
295}"#;
296 let sdk = Annotated::new(ClientSdkInfo {
297 name: Annotated::new("sentry.rust".to_string()),
298 version: Annotated::new("1.0.0".to_string()),
299 integrations: Annotated::empty(),
300 features: Annotated::empty(),
301 packages: Annotated::empty(),
302 client_ip: Annotated::new(IpAddr("127.0.0.1".to_owned())),
303 settings: Annotated::empty(),
304 other: Default::default(),
305 });
306
307 assert_eq!(sdk, Annotated::from_json(json).unwrap());
308 assert_eq!(json, sdk.to_json_pretty().unwrap());
309 }
310
311 #[test]
312 fn test_sdk_settings_auto() {
313 let json = r#"{
314 "settings": {
315 "infer_ip": "auto"
316 }
317}"#;
318 let sdk = Annotated::new(ClientSdkInfo {
319 settings: Annotated::new(ClientSdkSettings {
320 infer_ip: Annotated::new(AutoInferSetting::Auto),
321 }),
322 ..Default::default()
323 });
324
325 assert_eq!(sdk, Annotated::from_json(json).unwrap());
326 assert_eq!(json, sdk.to_json_pretty().unwrap());
327 }
328
329 #[test]
330 fn test_sdk_settings_never() {
331 let json = r#"{
332 "settings": {
333 "infer_ip": "never"
334 }
335}"#;
336 let sdk = Annotated::new(ClientSdkInfo {
337 settings: Annotated::new(ClientSdkSettings {
338 infer_ip: Annotated::new(AutoInferSetting::Never),
339 }),
340 ..Default::default()
341 });
342
343 assert_eq!(sdk, Annotated::from_json(json).unwrap());
344 assert_eq!(json, sdk.to_json_pretty().unwrap());
345 }
346
347 #[test]
348 fn test_sdk_settings_default() {
349 let sdk = Annotated::new(ClientSdkInfo {
350 settings: Annotated::new(ClientSdkSettings {
351 infer_ip: Annotated::empty(),
352 }),
353 ..Default::default()
354 });
355
356 assert_eq!(
357 sdk.value().unwrap().settings.value().unwrap().infer_ip(),
358 AutoInferSetting::Legacy
359 )
360 }
361
362 #[test]
363 fn test_infer_ip_invalid() {
364 let json = r#"{
365 "settings": {
366 "infer_ip": "invalid"
367 }
368 }"#;
369 let sdk: Annotated<ClientSdkInfo> = Annotated::from_json(json).unwrap();
370 assert_eq!(
371 sdk.value().unwrap().settings.value().unwrap().infer_ip(),
372 AutoInferSetting::Never
373 );
374 }
375}