use serde::{Deserialize, Serialize};
pub const CLI_LATEST_RELEASE_PATH: &str = "/v1/cli/releases/latest";
pub const CLI_TELEMETRY_PATH: &str = "/v1/cli/telemetry";
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct LatestReleaseResponse {
pub latest_version: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TelemetryRequest {
pub installation_id: String,
pub cli_version: String,
pub host_os: HostOs,
pub host_arch: HostArch,
pub event: TelemetryEvent,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
pub enum TelemetryEvent {
CommandOutcome {
command: CommandFamily,
target: Option<Target>,
outcome: CommandOutcome,
},
RunReady {
target: Target,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CommandFamily {
Create,
Run,
Build,
Clean,
Eject,
Format,
Lsp,
Docs,
Dev,
SvgImport,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CommandOutcome {
Succeeded,
Failed,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Target {
Web,
Macos,
Ios,
Ipados,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum HostOs {
Macos,
Linux,
Windows,
Other,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum HostArch {
X86_64,
Aarch64,
Other,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn telemetry_wire_format_is_stable() {
let request = TelemetryRequest {
installation_id: "3f028ebe-ea4d-4fd3-9c1a-f722f736991b".to_owned(),
cli_version: "0.38.3".to_owned(),
host_os: HostOs::Macos,
host_arch: HostArch::Aarch64,
event: TelemetryEvent::CommandOutcome {
command: CommandFamily::Build,
target: Some(Target::Web),
outcome: CommandOutcome::Failed,
},
};
let json = serde_json::to_string(&request).unwrap();
assert_eq!(
json,
r#"{"installation_id":"3f028ebe-ea4d-4fd3-9c1a-f722f736991b","cli_version":"0.38.3","host_os":"macos","host_arch":"aarch64","event":{"type":"command_outcome","command":"build","target":"web","outcome":"failed"}}"#
);
assert_eq!(
serde_json::from_str::<TelemetryRequest>(&json).unwrap(),
request
);
}
#[test]
fn run_ready_wire_format_is_stable() {
let event = TelemetryEvent::RunReady {
target: Target::Ipados,
};
assert_eq!(
serde_json::to_string(&event).unwrap(),
r#"{"type":"run_ready","target":"ipados"}"#
);
}
#[test]
fn unknown_request_fields_are_rejected() {
let json = r#"{
"installation_id":"3f028ebe-ea4d-4fd3-9c1a-f722f736991b",
"cli_version":"0.38.3",
"host_os":"linux",
"host_arch":"x86_64",
"project_path":"/private/example",
"event":{"type":"run_ready","target":"web"}
}"#;
assert!(serde_json::from_str::<TelemetryRequest>(json).is_err());
let event_with_unknown_field =
r#"{"type":"run_ready","target":"web","project_name":"private"}"#;
assert!(serde_json::from_str::<TelemetryEvent>(event_with_unknown_field).is_err());
}
#[test]
fn latest_release_response_is_forward_compatible() {
let response = LatestReleaseResponse {
latest_version: "0.38.3".to_owned(),
};
let json = serde_json::to_string(&response).unwrap();
assert_eq!(json, r#"{"latest_version":"0.38.3"}"#);
assert_eq!(
serde_json::from_str::<LatestReleaseResponse>(&json).unwrap(),
response
);
assert_eq!(
serde_json::from_str::<LatestReleaseResponse>(
r#"{"latest_version":"0.38.3","release_notes_url":"https://pax.dev/releases/0.38.3"}"#
)
.unwrap(),
response
);
}
}