Skip to main content

relay_base_schema/
spans.rs

1//! Defines data types relating to performance spans.
2
3use std::fmt;
4use std::str::FromStr;
5
6use relay_protocol::{Annotated, Empty, Error, FromValue, IntoValue, SkipSerialization, Value};
7use serde::Serialize;
8
9/// Trace status.
10///
11/// Values from <https://github.com/open-telemetry/opentelemetry-specification/blob/8fb6c14e4709e75a9aaa64b0dbbdf02a6067682a/specification/api-tracing.md#status>
12/// Mapping to HTTP from <https://github.com/open-telemetry/opentelemetry-specification/blob/8fb6c14e4709e75a9aaa64b0dbbdf02a6067682a/specification/data-http.md#status>
13//
14// Note: This type is represented as a u8 in Snuba/Clickhouse, with Unknown being the default
15// value. We use repr(u8) to statically validate that the trace status has 255 variants at most.
16#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
17#[serde(rename_all = "snake_case")]
18#[repr(u8)] // size limit in clickhouse
19pub enum SpanStatus {
20    /// The operation completed successfully.
21    ///
22    /// HTTP status 100..299 + successful redirects from the 3xx range.
23    Ok = 0,
24
25    /// The operation was cancelled (typically by the user).
26    Cancelled = 1,
27
28    /// Unknown. Any non-standard HTTP status code.
29    ///
30    /// "We do not know whether the transaction failed or succeeded"
31    Unknown = 2,
32
33    /// Client specified an invalid argument. 4xx.
34    ///
35    /// Note that this differs from FailedPrecondition. InvalidArgument indicates arguments that
36    /// are problematic regardless of the state of the system.
37    InvalidArgument = 3,
38
39    /// Deadline expired before operation could complete.
40    ///
41    /// For operations that change the state of the system, this error may be returned even if the
42    /// operation has been completed successfully.
43    ///
44    /// HTTP redirect loops and 504 Gateway Timeout
45    DeadlineExceeded = 4,
46
47    /// 404 Not Found. Some requested entity (file or directory) was not found.
48    NotFound = 5,
49
50    /// Already exists (409)
51    ///
52    /// Some entity that we attempted to create already exists.
53    AlreadyExists = 6,
54
55    /// 403 Forbidden
56    ///
57    /// The caller does not have permission to execute the specified operation.
58    PermissionDenied = 7,
59
60    /// 429 Too Many Requests
61    ///
62    /// Some resource has been exhausted, perhaps a per-user quota or perhaps the entire file
63    /// system is out of space.
64    ResourceExhausted = 8,
65
66    /// Operation was rejected because the system is not in a state required for the operation's
67    /// execution
68    FailedPrecondition = 9,
69
70    /// The operation was aborted, typically due to a concurrency issue.
71    Aborted = 10,
72
73    /// Operation was attempted past the valid range.
74    OutOfRange = 11,
75
76    /// 501 Not Implemented
77    ///
78    /// Operation is not implemented or not enabled.
79    Unimplemented = 12,
80
81    /// Other/generic 5xx.
82    InternalError = 13,
83
84    /// 503 Service Unavailable
85    Unavailable = 14,
86
87    /// Unrecoverable data loss or corruption
88    DataLoss = 15,
89
90    /// 401 Unauthorized (actually does mean unauthenticated according to RFC 7235)
91    ///
92    /// Prefer PermissionDenied if a user is logged in.
93    Unauthenticated = 16,
94
95    /// The operation failed.
96    ///
97    /// Generic error status from the [`sentry.status`] convention.
98    ///
99    /// [`sentry.status`]: https://getsentry.github.io/sentry-conventions/attributes/sentry/#sentry-status
100    Error = 17,
101}
102
103impl SpanStatus {
104    /// Returns the string representation of the status.
105    pub fn as_str(&self) -> &'static str {
106        match *self {
107            SpanStatus::Ok => "ok",
108            SpanStatus::DeadlineExceeded => "deadline_exceeded",
109            SpanStatus::Unauthenticated => "unauthenticated",
110            SpanStatus::PermissionDenied => "permission_denied",
111            SpanStatus::NotFound => "not_found",
112            SpanStatus::ResourceExhausted => "resource_exhausted",
113            SpanStatus::InvalidArgument => "invalid_argument",
114            SpanStatus::Unimplemented => "unimplemented",
115            SpanStatus::Unavailable => "unavailable",
116            SpanStatus::InternalError => "internal_error",
117            SpanStatus::Unknown => "unknown",
118            SpanStatus::Cancelled => "cancelled",
119            SpanStatus::AlreadyExists => "already_exists",
120            SpanStatus::FailedPrecondition => "failed_precondition",
121            SpanStatus::Aborted => "aborted",
122            SpanStatus::OutOfRange => "out_of_range",
123            SpanStatus::DataLoss => "data_loss",
124            SpanStatus::Error => "error",
125        }
126    }
127}
128
129impl AsRef<str> for SpanStatus {
130    fn as_ref(&self) -> &str {
131        self.as_str()
132    }
133}
134
135/// Error parsing a `SpanStatus`.
136#[derive(Clone, Copy, Debug)]
137pub struct ParseSpanStatusError;
138
139impl fmt::Display for ParseSpanStatusError {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        write!(f, "invalid span status")
142    }
143}
144
145impl std::error::Error for ParseSpanStatusError {}
146
147impl FromStr for SpanStatus {
148    type Err = ParseSpanStatusError;
149
150    fn from_str(string: &str) -> Result<SpanStatus, Self::Err> {
151        Ok(match string {
152            "ok" => SpanStatus::Ok,
153            "success" => SpanStatus::Ok, // Backwards compat with initial schema
154            "deadline_exceeded" => SpanStatus::DeadlineExceeded,
155            "unauthenticated" => SpanStatus::Unauthenticated,
156            "permission_denied" => SpanStatus::PermissionDenied,
157            "not_found" => SpanStatus::NotFound,
158            "resource_exhausted" => SpanStatus::ResourceExhausted,
159            "invalid_argument" => SpanStatus::InvalidArgument,
160            "unimplemented" => SpanStatus::Unimplemented,
161            "unavailable" => SpanStatus::Unavailable,
162            "internal_error" | "unknown_error" => SpanStatus::InternalError,
163            "failure" => SpanStatus::InternalError, // Backwards compat with initial schema
164            "unknown" => SpanStatus::Unknown,
165            "cancelled" => SpanStatus::Cancelled,
166            "already_exists" => SpanStatus::AlreadyExists,
167            "failed_precondition" => SpanStatus::FailedPrecondition,
168            "aborted" => SpanStatus::Aborted,
169            "out_of_range" => SpanStatus::OutOfRange,
170            "data_loss" => SpanStatus::DataLoss,
171            "error" => SpanStatus::Error,
172            _ => return Err(ParseSpanStatusError),
173        })
174    }
175}
176
177impl fmt::Display for SpanStatus {
178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179        f.write_str(self.as_str())
180    }
181}
182
183impl Empty for SpanStatus {
184    #[inline]
185    fn is_empty(&self) -> bool {
186        false
187    }
188}
189
190impl FromValue for SpanStatus {
191    fn from_value(value: Annotated<Value>) -> Annotated<Self> {
192        match value {
193            Annotated(Some(Value::String(value)), mut meta) => match value.parse() {
194                Ok(status) => Annotated(Some(status), meta),
195                Err(_) => {
196                    meta.add_error(Error::expected("a trace status"));
197                    meta.set_original_value(Some(value));
198                    Annotated(None, meta)
199                }
200            },
201            Annotated(Some(Value::I64(value)), mut meta) => Annotated(
202                Some(match value {
203                    0 => SpanStatus::Ok,
204                    1 => SpanStatus::Cancelled,
205                    2 => SpanStatus::Unknown,
206                    3 => SpanStatus::InvalidArgument,
207                    4 => SpanStatus::DeadlineExceeded,
208                    5 => SpanStatus::NotFound,
209                    6 => SpanStatus::AlreadyExists,
210                    7 => SpanStatus::PermissionDenied,
211                    8 => SpanStatus::ResourceExhausted,
212                    9 => SpanStatus::FailedPrecondition,
213                    10 => SpanStatus::Aborted,
214                    11 => SpanStatus::OutOfRange,
215                    12 => SpanStatus::Unimplemented,
216                    13 => SpanStatus::InternalError,
217                    14 => SpanStatus::Unavailable,
218                    15 => SpanStatus::DataLoss,
219                    16 => SpanStatus::Unauthenticated,
220                    17 => SpanStatus::Error,
221                    _ => {
222                        meta.add_error(Error::expected("a trace status"));
223                        meta.set_original_value(Some(value));
224                        return Annotated(None, meta);
225                    }
226                }),
227                meta,
228            ),
229            Annotated(None, meta) => Annotated(None, meta),
230            Annotated(Some(value), mut meta) => {
231                meta.add_error(Error::expected("a string"));
232                meta.set_original_value(Some(value));
233                Annotated(None, meta)
234            }
235        }
236    }
237}
238
239impl IntoValue for SpanStatus {
240    fn into_value(self) -> Value {
241        Value::String(self.to_string())
242    }
243
244    fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
245    where
246        Self: Sized,
247        S: serde::Serializer,
248    {
249        Serialize::serialize(self.as_str(), s)
250    }
251}