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