1use serde::{Deserialize, Serialize};
7
8pub const CLI_LATEST_RELEASE_PATH: &str = "/v1/cli/releases/latest";
10
11pub const CLI_TELEMETRY_PATH: &str = "/v1/cli/telemetry";
13
14#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
16pub struct LatestReleaseResponse {
17 pub latest_version: String,
18}
19
20#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(deny_unknown_fields)]
23pub struct TelemetryRequest {
24 pub installation_id: String,
26 pub cli_version: String,
27 pub host_os: HostOs,
28 pub host_arch: HostArch,
29 pub event: TelemetryEvent,
30}
31
32#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
35pub enum TelemetryEvent {
36 CommandOutcome {
37 command: CommandFamily,
38 target: Option<Target>,
39 outcome: CommandOutcome,
40 },
41 RunReady {
42 target: Target,
43 },
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "snake_case")]
49pub enum CommandFamily {
50 Create,
51 Run,
52 Build,
53 Clean,
54 Eject,
55 Format,
56 Lsp,
57 Docs,
58 Dev,
59 SvgImport,
60}
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(rename_all = "snake_case")]
65pub enum CommandOutcome {
66 Succeeded,
67 Failed,
68}
69
70#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
72#[serde(rename_all = "snake_case")]
73pub enum Target {
74 Web,
75 Macos,
76 Ios,
77 Ipados,
78}
79
80#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
82#[serde(rename_all = "snake_case")]
83pub enum HostOs {
84 Macos,
85 Linux,
86 Windows,
87 Other,
88}
89
90#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
92#[serde(rename_all = "snake_case")]
93pub enum HostArch {
94 X86_64,
95 Aarch64,
96 Other,
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn telemetry_wire_format_is_stable() {
105 let request = TelemetryRequest {
106 installation_id: "3f028ebe-ea4d-4fd3-9c1a-f722f736991b".to_owned(),
107 cli_version: "0.38.3".to_owned(),
108 host_os: HostOs::Macos,
109 host_arch: HostArch::Aarch64,
110 event: TelemetryEvent::CommandOutcome {
111 command: CommandFamily::Build,
112 target: Some(Target::Web),
113 outcome: CommandOutcome::Failed,
114 },
115 };
116
117 let json = serde_json::to_string(&request).unwrap();
118 assert_eq!(
119 json,
120 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"}}"#
121 );
122 assert_eq!(
123 serde_json::from_str::<TelemetryRequest>(&json).unwrap(),
124 request
125 );
126 }
127
128 #[test]
129 fn run_ready_wire_format_is_stable() {
130 let event = TelemetryEvent::RunReady {
131 target: Target::Ipados,
132 };
133
134 assert_eq!(
135 serde_json::to_string(&event).unwrap(),
136 r#"{"type":"run_ready","target":"ipados"}"#
137 );
138 }
139
140 #[test]
141 fn unknown_request_fields_are_rejected() {
142 let json = r#"{
143 "installation_id":"3f028ebe-ea4d-4fd3-9c1a-f722f736991b",
144 "cli_version":"0.38.3",
145 "host_os":"linux",
146 "host_arch":"x86_64",
147 "project_path":"/private/example",
148 "event":{"type":"run_ready","target":"web"}
149 }"#;
150
151 assert!(serde_json::from_str::<TelemetryRequest>(json).is_err());
152
153 let event_with_unknown_field =
154 r#"{"type":"run_ready","target":"web","project_name":"private"}"#;
155 assert!(serde_json::from_str::<TelemetryEvent>(event_with_unknown_field).is_err());
156 }
157
158 #[test]
159 fn latest_release_response_is_forward_compatible() {
160 let response = LatestReleaseResponse {
161 latest_version: "0.38.3".to_owned(),
162 };
163 let json = serde_json::to_string(&response).unwrap();
164 assert_eq!(json, r#"{"latest_version":"0.38.3"}"#);
165 assert_eq!(
166 serde_json::from_str::<LatestReleaseResponse>(&json).unwrap(),
167 response
168 );
169 assert_eq!(
170 serde_json::from_str::<LatestReleaseResponse>(
171 r#"{"latest_version":"0.38.3","release_notes_url":"https://pax.dev/releases/0.38.3"}"#
172 )
173 .unwrap(),
174 response
175 );
176 }
177}