use std::fmt;
use std::str::FromStr;
use relay_protocol::{Annotated, Empty, ErrorKind, FromValue, IntoValue, SkipSerialization, Value};
use serde::{Deserialize, Serialize};
use crate::processor::ProcessValue;
use crate::protocol::Timestamp;
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum TransactionSource {
Custom,
Url,
Route,
View,
Component,
Sanitized,
Task,
Unknown,
Other(String),
}
impl TransactionSource {
pub fn as_str(&self) -> &str {
match self {
Self::Custom => "custom",
Self::Url => "url",
Self::Route => "route",
Self::View => "view",
Self::Component => "component",
Self::Sanitized => "sanitized",
Self::Task => "task",
Self::Unknown => "unknown",
Self::Other(ref s) => s,
}
}
}
impl FromStr for TransactionSource {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"custom" => Ok(Self::Custom),
"url" => Ok(Self::Url),
"route" => Ok(Self::Route),
"view" => Ok(Self::View),
"component" => Ok(Self::Component),
"sanitized" => Ok(Self::Sanitized),
"task" => Ok(Self::Task),
"unknown" => Ok(Self::Unknown),
s => Ok(Self::Other(s.to_owned())),
}
}
}
impl fmt::Display for TransactionSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl Default for TransactionSource {
fn default() -> Self {
Self::Unknown
}
}
impl Empty for TransactionSource {
#[inline]
fn is_empty(&self) -> bool {
matches!(self, Self::Unknown)
}
}
impl FromValue for TransactionSource {
fn from_value(value: Annotated<Value>) -> Annotated<Self> {
match String::from_value(value) {
Annotated(Some(value), mut meta) => match value.parse() {
Ok(source) => Annotated(Some(source), meta),
Err(_) => {
meta.add_error(ErrorKind::InvalidData);
meta.set_original_value(Some(value));
Annotated(None, meta)
}
},
Annotated(None, meta) => Annotated(None, meta),
}
}
}
impl IntoValue for TransactionSource {
fn into_value(self) -> Value
where
Self: Sized,
{
Value::String(match self {
Self::Other(s) => s,
_ => self.as_str().to_owned(),
})
}
fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
where
Self: Sized,
S: serde::Serializer,
{
serde::Serialize::serialize(self.as_str(), s)
}
}
impl ProcessValue for TransactionSource {}
#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
pub struct TransactionNameChange {
pub source: Annotated<TransactionSource>,
pub propagations: Annotated<u64>,
pub timestamp: Annotated<Timestamp>,
}
#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
pub struct TransactionInfo {
pub source: Annotated<TransactionSource>,
#[metastructure(max_chars = 200, trim_whitespace = "true")]
pub original: Annotated<String>,
pub changes: Annotated<Vec<Annotated<TransactionNameChange>>>,
pub propagations: Annotated<u64>,
}
#[cfg(test)]
mod tests {
use chrono::{TimeZone, Utc};
use similar_asserts::assert_eq;
use super::*;
#[test]
fn test_other_source_roundtrip() {
let json = r#""something-new""#;
let source = Annotated::new(TransactionSource::Other("something-new".to_owned()));
assert_eq!(source, Annotated::from_json(json).unwrap());
assert_eq!(json, source.payload_to_json_pretty().unwrap());
}
#[test]
fn test_transaction_info_roundtrip() {
let json = r#"{
"source": "route",
"original": "/auth/login/john123/",
"changes": [
{
"source": "url",
"propagations": 1,
"timestamp": 946684800.0
}
],
"propagations": 2
}"#;
let info = Annotated::new(TransactionInfo {
source: Annotated::new(TransactionSource::Route),
original: Annotated::new("/auth/login/john123/".to_owned()),
changes: Annotated::new(vec![Annotated::new(TransactionNameChange {
source: Annotated::new(TransactionSource::Url),
propagations: Annotated::new(1),
timestamp: Annotated::new(
Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
),
})]),
propagations: Annotated::new(2),
});
assert_eq!(info, Annotated::from_json(json).unwrap());
assert_eq!(json, info.to_json_pretty().unwrap());
}
}