relay_event_schema/protocol/
transaction.rs1use std::fmt;
2use std::str::FromStr;
3
4use relay_protocol::{Annotated, Empty, ErrorKind, FromValue, IntoValue, SkipSerialization, Value};
5use serde::{Deserialize, Serialize};
6
7use crate::processor::ProcessValue;
8use crate::protocol::Timestamp;
9
10#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
12#[serde(rename_all = "kebab-case")]
13#[derive(Default)]
14pub enum TransactionSource {
15 Custom,
17 Url,
19 Route,
21 View,
23 Component,
25 Sanitized,
27 Task,
29 #[default]
31 Unknown,
32 Other(String),
34}
35
36impl TransactionSource {
37 pub fn as_str(&self) -> &str {
38 match self {
39 Self::Custom => "custom",
40 Self::Url => "url",
41 Self::Route => "route",
42 Self::View => "view",
43 Self::Component => "component",
44 Self::Sanitized => "sanitized",
45 Self::Task => "task",
46 Self::Unknown => "unknown",
47 Self::Other(s) => s,
48 }
49 }
50}
51
52impl FromStr for TransactionSource {
53 type Err = std::convert::Infallible;
54
55 fn from_str(s: &str) -> Result<Self, Self::Err> {
56 match s {
57 "custom" => Ok(Self::Custom),
58 "url" => Ok(Self::Url),
59 "route" => Ok(Self::Route),
60 "view" => Ok(Self::View),
61 "component" => Ok(Self::Component),
62 "sanitized" => Ok(Self::Sanitized),
63 "task" => Ok(Self::Task),
64 "unknown" => Ok(Self::Unknown),
65 s => Ok(Self::Other(s.to_owned())),
66 }
67 }
68}
69
70impl fmt::Display for TransactionSource {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 write!(f, "{}", self.as_str())
73 }
74}
75
76impl Empty for TransactionSource {
77 #[inline]
78 fn is_empty(&self) -> bool {
79 matches!(self, Self::Unknown)
80 }
81}
82
83impl FromValue for TransactionSource {
84 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
85 match String::from_value(value) {
86 Annotated(Some(value), mut meta) => match value.parse() {
87 Ok(source) => Annotated(Some(source), meta),
88 Err(_) => {
89 meta.add_error(ErrorKind::InvalidData);
90 meta.set_original_value(Some(value));
91 Annotated(None, meta)
92 }
93 },
94 Annotated(None, meta) => Annotated(None, meta),
95 }
96 }
97}
98
99impl IntoValue for TransactionSource {
100 fn into_value(self) -> Value
101 where
102 Self: Sized,
103 {
104 Value::String(match self {
105 Self::Other(s) => s,
106 _ => self.as_str().to_owned(),
107 })
108 }
109
110 fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
111 where
112 Self: Sized,
113 S: serde::Serializer,
114 {
115 serde::Serialize::serialize(self.as_str(), s)
116 }
117}
118
119impl ProcessValue for TransactionSource {}
120
121#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
122pub struct TransactionNameChange {
123 pub source: Annotated<TransactionSource>,
125
126 pub propagations: Annotated<u64>,
128
129 pub timestamp: Annotated<Timestamp>,
133}
134
135#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
137pub struct TransactionInfo {
138 pub source: Annotated<TransactionSource>,
143
144 #[metastructure(max_chars = 200, trim_whitespace = true)]
148 pub original: Annotated<String>,
149
150 pub changes: Annotated<Vec<Annotated<TransactionNameChange>>>,
155
156 pub propagations: Annotated<u64>,
158}
159
160#[cfg(test)]
161mod tests {
162 use chrono::{TimeZone, Utc};
163 use similar_asserts::assert_eq;
164
165 use super::*;
166
167 #[test]
168 fn test_other_source_roundtrip() {
169 let json = r#""something-new""#;
170 let source = Annotated::new(TransactionSource::Other("something-new".to_owned()));
171
172 assert_eq!(source, Annotated::from_json(json).unwrap());
173 assert_eq!(json, source.payload_to_json_pretty().unwrap());
174 }
175
176 #[test]
177 fn test_transaction_info_roundtrip() {
178 let json = r#"{
179 "source": "route",
180 "original": "/auth/login/john123/",
181 "changes": [
182 {
183 "source": "url",
184 "propagations": 1,
185 "timestamp": 946684800.0
186 }
187 ],
188 "propagations": 2
189}"#;
190
191 let info = Annotated::new(TransactionInfo {
192 source: Annotated::new(TransactionSource::Route),
193 original: Annotated::new("/auth/login/john123/".to_owned()),
194 changes: Annotated::new(vec![Annotated::new(TransactionNameChange {
195 source: Annotated::new(TransactionSource::Url),
196 propagations: Annotated::new(1),
197 timestamp: Annotated::new(
198 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
199 ),
200 })]),
201 propagations: Annotated::new(2),
202 });
203
204 assert_eq!(info, Annotated::from_json(json).unwrap());
205 assert_eq!(json, info.to_json_pretty().unwrap());
206 }
207}