Skip to content

Commit 537513a

Browse files
authored
feat: Remove magic strings and add enum variants instead (#903)
Closes: #897 - Added a `BroadcastErrorType` enum - Added a `MessageType` enum with a conversion from `ClientMessage`/`ServerMessage` to `MessageType` - Added a `MetricName` enum - To work easier with Metrics, I added an extension trait in `autopush-common/metrics.rs`, with methods `inc`, `inc_raw`, `inc_with_tags`. The `inc` and `inc_with_tags` methods accept a `MetricName` - This replaces the `cadence::CountedExt` trait with our own `StatsdClientExt` trait with almost the same methods. Since it replaces a lot of text, and it's my first PR, I might have missed something. But the local tests run through.
2 parents 7018f25 + a35b68a commit 537513a

File tree

27 files changed

+558
-167
lines changed

27 files changed

+558
-167
lines changed

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
[workspace]
22
members = [
3-
"autopush-common",
4-
"autoendpoint",
5-
"autoconnect",
6-
"autoconnect/autoconnect-common",
7-
"autoconnect/autoconnect-settings",
8-
"autoconnect/autoconnect-web",
9-
"autoconnect/autoconnect-ws",
10-
"autoconnect/autoconnect-ws/autoconnect-ws-sm",
3+
"autopush-common",
4+
"autoendpoint",
5+
"autoconnect",
6+
"autoconnect/autoconnect-common",
7+
"autoconnect/autoconnect-settings",
8+
"autoconnect/autoconnect-web",
9+
"autoconnect/autoconnect-ws",
10+
"autoconnect/autoconnect-ws/autoconnect-ws-sm",
1111
]
1212
resolver = "2"
1313

1414
[workspace.package]
1515
version = "1.74.3"
1616
authors = [
17-
"Ben Bangert <[email protected]>",
18-
"JR Conlin <[email protected]>",
19-
"Phil Jenvey <[email protected]>",
20-
"Alex Crichton <[email protected]>",
21-
"Mark Drobnak <[email protected]>",
17+
"Ben Bangert <[email protected]>",
18+
"JR Conlin <[email protected]>",
19+
"Phil Jenvey <[email protected]>",
20+
"Alex Crichton <[email protected]>",
21+
"Mark Drobnak <[email protected]>",
2222
]
2323
edition = "2021"
2424
rust-version = "1.86.0"
@@ -49,19 +49,19 @@ env_logger = "0.11"
4949
fernet = "0.2.0"
5050
futures = { version = "0.3", features = ["compat"] }
5151
futures-util = { version = "0.3", features = [
52-
"async-await",
53-
"compat",
54-
"sink",
55-
"io",
52+
"async-await",
53+
"compat",
54+
"sink",
55+
"io",
5656
] }
5757
futures-locks = "0.7"
5858
hex = "0.4.2"
5959
httparse = "1.3"
6060
hyper = "1.6"
6161
lazy_static = "1.4"
6262
log = { version = "0.4", features = [
63-
"max_level_debug",
64-
"release_max_level_info",
63+
"max_level_debug",
64+
"release_max_level_info",
6565
] }
6666
mockall = "0.13"
6767
mozsvc-common = "0.2"
@@ -70,7 +70,7 @@ rand = "0.9"
7070
regex = "1.4"
7171
reqwest = { version = "0.12", features = ["json", "blocking"] }
7272
sentry = { version = "0.36", features = [
73-
"debug-logs",
73+
"debug-logs",
7474
] } # Using debug-logs avoids https://github.com/getsentry/sentry-rust/issues/237
7575
sentry-core = { version = "0.36" }
7676
sentry-actix = "0.36"
@@ -79,9 +79,9 @@ serde = "1.0"
7979
serde_derive = "1.0"
8080
serde_json = "1.0"
8181
slog = { version = "2.7", features = [
82-
"dynamic-keys",
83-
"max_level_trace",
84-
"release_max_level_info",
82+
"dynamic-keys",
83+
"max_level_trace",
84+
"release_max_level_info",
8585
] }
8686
slog-async = "2.6"
8787
slog-envlogger = "2.2.0"
@@ -90,6 +90,7 @@ slog-scope = "4.4"
9090
slog-stdlog = "4.1"
9191
slog-term = "2.6"
9292
strum = { version = "0.27", features = ["derive"] }
93+
strum_macros = "0.27"
9394
thiserror = "2.0"
9495
tokio = "1.38"
9596
tokio-compat-02 = "0.2"

autoconnect/autoconnect-common/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ version.workspace = true
88

99
[dependencies]
1010
actix-web.workspace = true
11+
bytestring.workspace = true
1112
cadence.workspace = true
1213
futures.workspace = true
1314
futures-locks.workspace = true
@@ -20,6 +21,8 @@ serde_derive.workspace = true
2021
serde_json.workspace = true
2122
slog.workspace = true
2223
slog-scope.workspace = true
24+
strum.workspace = true
25+
strum_macros.workspace = true
2326
uuid.workspace = true
2427

2528

autoconnect/autoconnect-common/src/broadcast.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use std::collections::HashMap;
1414

1515
use serde_derive::{Deserialize, Serialize};
16+
use strum_macros::{AsRefStr, Display};
1617

1718
use autopush_common::errors::{ApcErrorKind, Result};
1819

@@ -72,6 +73,12 @@ struct BroadcastRevision {
7273
broadcast: BroadcastKey,
7374
}
7475

76+
#[derive(Debug, Clone, Copy, PartialEq, Eq, AsRefStr, Display)]
77+
pub enum BroadcastErrorKind {
78+
#[strum(serialize = "Broadcast not found")]
79+
NotFound,
80+
}
81+
7582
/// A provided Broadcast/Version used for `BroadcastSubsInit`, client comparisons, and outgoing
7683
/// deltas
7784
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
@@ -85,7 +92,7 @@ impl Broadcast {
8592
pub fn error(self) -> Broadcast {
8693
Broadcast {
8794
broadcast_id: self.broadcast_id,
88-
version: "Broadcast not found".to_string(),
95+
version: BroadcastErrorKind::NotFound.to_string(),
8996
}
9097
}
9198
}
@@ -191,7 +198,9 @@ impl BroadcastChangeTracker {
191198
let key = self
192199
.broadcast_registry
193200
.lookup_key(&broadcast.broadcast_id)
194-
.ok_or(ApcErrorKind::BroadcastError("Broadcast not found".into()))?;
201+
.ok_or(ApcErrorKind::BroadcastError(
202+
BroadcastErrorKind::NotFound.to_string(),
203+
))?;
195204

196205
if let Some(ver) = self.broadcast_versions.get_mut(&key) {
197206
if *ver == broadcast.version {
@@ -200,7 +209,9 @@ impl BroadcastChangeTracker {
200209
*ver = broadcast.version;
201210
} else {
202211
trace!("📢 Not found: {b_id}");
203-
return Err(ApcErrorKind::BroadcastError("Broadcast not found".into()).into());
212+
return Err(
213+
ApcErrorKind::BroadcastError(BroadcastErrorKind::NotFound.to_string()).into(),
214+
);
204215
}
205216

206217
trace!("📢 New version of {b_id}");

autoconnect/autoconnect-common/src/megaphone.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use std::{collections::HashMap, error::Error, io, sync::Arc, time::Duration};
22

33
use actix_web::rt;
4-
use cadence::{CountedExt, StatsdClient};
4+
use autopush_common::metric_name::MetricName;
5+
use autopush_common::metrics::StatsdClientExt;
6+
use cadence::StatsdClient;
57
use serde_derive::Deserialize;
68
use tokio::sync::RwLock;
79

@@ -39,7 +41,9 @@ pub async fn init_and_spawn_megaphone_updater(
3941
if let Err(e) = updater(&broadcaster, &http, &url, &token).await {
4042
report_updater_error(&metrics, e);
4143
} else {
42-
metrics.incr_with_tags("megaphone.updater.ok").send();
44+
metrics
45+
.incr_with_tags(MetricName::MegaphoneUpdaterOk)
46+
.send();
4347
}
4448
}
4549
});
@@ -59,7 +63,7 @@ fn report_updater_error(metrics: &Arc<StatsdClient>, err: reqwest::Error) {
5963
"unknown"
6064
};
6165
metrics
62-
.incr_with_tags("megaphone.updater.error")
66+
.incr_with_tags(MetricName::MegaphoneUpdaterError)
6367
.with_tag("reason", reason)
6468
.send();
6569
if reason == "unknown" {

autoconnect/autoconnect-common/src/protocol.rs

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,44 @@ use std::collections::HashMap;
1010
use std::str::FromStr;
1111

1212
use serde_derive::{Deserialize, Serialize};
13+
use strum_macros::{AsRefStr, Display, EnumString};
1314
use uuid::Uuid;
1415

1516
use autopush_common::notification::Notification;
1617

18+
/// Message types for WebPush protocol messages.
19+
///
20+
/// This enum should be used instead of string literals when referring to message types.
21+
/// String serialization is handled automatically via the strum traits.
22+
///
23+
/// Example:
24+
/// ```
25+
/// use autoconnect_common::protocol::MessageType;
26+
///
27+
/// let message_type = MessageType::Hello;
28+
/// let message_str = message_type.as_ref(); // Returns "hello"
29+
/// ```
30+
#[derive(Debug, Clone, Copy, PartialEq, Eq, AsRefStr, Display, EnumString)]
31+
#[strum(serialize_all = "snake_case")]
32+
pub enum MessageType {
33+
Hello,
34+
Register,
35+
Unregister,
36+
BroadcastSubscribe,
37+
Ack,
38+
Nack,
39+
Ping,
40+
Notification,
41+
Broadcast,
42+
}
43+
44+
impl MessageType {
45+
/// Returns the expected message type string for error messages
46+
pub fn expected_msg(&self) -> String {
47+
format!(r#"Expected messageType="{}""#, self.as_ref())
48+
}
49+
}
50+
1751
#[derive(Debug, Eq, PartialEq, Serialize)]
1852
#[serde(untagged)]
1953
pub enum BroadcastValue {
@@ -69,6 +103,21 @@ pub enum ClientMessage {
69103
Ping,
70104
}
71105

106+
impl ClientMessage {
107+
/// Get the message type of this message
108+
pub fn message_type(&self) -> MessageType {
109+
match self {
110+
ClientMessage::Hello { .. } => MessageType::Hello,
111+
ClientMessage::Register { .. } => MessageType::Register,
112+
ClientMessage::Unregister { .. } => MessageType::Unregister,
113+
ClientMessage::BroadcastSubscribe { .. } => MessageType::BroadcastSubscribe,
114+
ClientMessage::Ack { .. } => MessageType::Ack,
115+
ClientMessage::Nack { .. } => MessageType::Nack,
116+
ClientMessage::Ping => MessageType::Ping,
117+
}
118+
}
119+
}
120+
72121
impl FromStr for ClientMessage {
73122
type Err = serde_json::error::Error;
74123

@@ -141,11 +190,23 @@ pub enum ServerMessage {
141190
}
142191

143192
impl ServerMessage {
193+
/// Get the message type of this message
194+
pub fn message_type(&self) -> MessageType {
195+
match self {
196+
ServerMessage::Hello { .. } => MessageType::Hello,
197+
ServerMessage::Register { .. } => MessageType::Register,
198+
ServerMessage::Unregister { .. } => MessageType::Unregister,
199+
ServerMessage::Broadcast { .. } => MessageType::Broadcast,
200+
ServerMessage::Notification(..) => MessageType::Notification,
201+
ServerMessage::Ping => MessageType::Ping,
202+
}
203+
}
204+
144205
pub fn to_json(&self) -> Result<String, serde_json::error::Error> {
145206
match self {
146-
// clients recognize {"messageType": "ping"} but traditionally both
147-
// client/server send the empty object version
148-
ServerMessage::Ping => Ok("{}".to_owned()),
207+
// Both client and server understand the verbose `{"messageType": "ping"}` and the abbreviated `{}`
208+
// as valid ping messages. The server defaults to the shorter `{}` form.
209+
ServerMessage::Ping => Ok("{}".to_string()),
149210
_ => serde_json::to_string(self),
150211
}
151212
}

autoconnect/autoconnect-common/src/test_support.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use bytestring::ByteString;
12
use uuid::Uuid;
23

4+
use crate::protocol::MessageType;
35
use autopush_common::{
46
db::{mock::MockDbClient, User},
57
util::timing::ms_since_epoch,
@@ -13,10 +15,23 @@ pub const DUMMY_CHID: Uuid = Uuid::from_u128(0xdeadbeef_0000_0000_abad_1dea00000
1315

1416
/// A minimal websocket Push "hello" message, used by an unregistered UA with
1517
/// no existing channel subscriptions
16-
pub const HELLO: &str = r#"{"messageType": "hello", "use_webpush": true}"#;
17-
/// A post initial registration response
18-
pub const HELLO_AGAIN: &str = r#"{"messageType": "hello", "use_webpush": true,
19-
"uaid": "deadbeef-0000-0000-deca-fbad00000000"}"#;
18+
pub fn hello_json() -> ByteString {
19+
format!(
20+
r#"{{"messageType": "{}", "use_webpush": true}}"#,
21+
MessageType::Hello.as_ref()
22+
)
23+
.into()
24+
}
25+
26+
pub fn hello_again_json() -> ByteString {
27+
format!(
28+
r#"{{"messageType": "{}", "use_webpush": true,
29+
"uaid": "{}"}}"#,
30+
MessageType::Hello.as_ref(),
31+
DUMMY_UAID
32+
)
33+
.into()
34+
}
2035

2136
pub const CURRENT_MONTH: &str = "message_2018_06";
2237

autoconnect/autoconnect-web/src/dockerflow.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ use actix_web::{
55
web::{self, Data, Json},
66
HttpResponse, ResponseError,
77
};
8-
use cadence::CountedExt;
98
use serde_json::json;
109

1110
use autoconnect_settings::AppState;
11+
use autopush_common::metric_name::MetricName;
12+
use autopush_common::metrics::StatsdClientExt;
1213

1314
use crate::error::ApiError;
1415

@@ -47,7 +48,7 @@ pub async fn health_route(state: Data<AppState>) -> Json<serde_json::Value> {
4748
health["reliability"] = json!(state.reliability.health_check().await.unwrap_or_else(|e| {
4849
state
4950
.metrics
50-
.incr_with_tags("error.redis.unavailable")
51+
.incr_with_tags(MetricName::ErrorRedisUnavailable)
5152
.with_tag("application", "autoconnect")
5253
.send();
5354
error!("🔍🟥 Reliability reporting down: {:?}", e);

0 commit comments

Comments
 (0)