cloud_api_client: Add `accept_terms_of_service` method (#35452)

Marshall Bowers created

This PR adds an `accept_terms_of_service` method to the
`CloudApiClient`.

Release Notes:

- N/A

Change summary

crates/cloud_api_client/src/cloud_api_client.rs | 68 ++++++++++++++----
crates/cloud_api_types/src/cloud_api_types.rs   |  5 +
2 files changed, 56 insertions(+), 17 deletions(-)

Detailed changes

crates/cloud_api_client/src/cloud_api_client.rs 🔗

@@ -3,6 +3,7 @@ use std::sync::Arc;
 use anyhow::{Result, anyhow};
 pub use cloud_api_types::*;
 use futures::AsyncReadExt as _;
+use http_client::http::request;
 use http_client::{AsyncBody, HttpClientWithUrl, Method, Request};
 use parking_lot::RwLock;
 
@@ -51,17 +52,26 @@ impl CloudApiClient {
         ))
     }
 
+    fn build_request(
+        &self,
+        req: request::Builder,
+        body: impl Into<AsyncBody>,
+    ) -> Result<Request<AsyncBody>> {
+        Ok(req
+            .header("Content-Type", "application/json")
+            .header("Authorization", self.authorization_header()?)
+            .body(body.into())?)
+    }
+
     pub async fn get_authenticated_user(&self) -> Result<GetAuthenticatedUserResponse> {
-        let request = Request::builder()
-            .method(Method::GET)
-            .uri(
+        let request = self.build_request(
+            Request::builder().method(Method::GET).uri(
                 self.http_client
                     .build_zed_cloud_url("/client/users/me", &[])?
                     .as_ref(),
-            )
-            .header("Content-Type", "application/json")
-            .header("Authorization", self.authorization_header()?)
-            .body(AsyncBody::default())?;
+            ),
+            AsyncBody::default(),
+        )?;
 
         let mut response = self.http_client.send(request).await?;
 
@@ -81,25 +91,49 @@ impl CloudApiClient {
         Ok(serde_json::from_str(&body)?)
     }
 
+    pub async fn accept_terms_of_service(&self) -> Result<AcceptTermsOfServiceResponse> {
+        let request = self.build_request(
+            Request::builder().method(Method::POST).uri(
+                self.http_client
+                    .build_zed_cloud_url("/client/terms_of_service/accept", &[])?
+                    .as_ref(),
+            ),
+            AsyncBody::default(),
+        )?;
+
+        let mut response = self.http_client.send(request).await?;
+
+        if !response.status().is_success() {
+            let mut body = String::new();
+            response.body_mut().read_to_string(&mut body).await?;
+
+            anyhow::bail!(
+                "Failed to accept terms of service.\nStatus: {:?}\nBody: {body}",
+                response.status()
+            )
+        }
+
+        let mut body = String::new();
+        response.body_mut().read_to_string(&mut body).await?;
+
+        Ok(serde_json::from_str(&body)?)
+    }
+
     pub async fn create_llm_token(
         &self,
         system_id: Option<String>,
     ) -> Result<CreateLlmTokenResponse> {
-        let mut request_builder = Request::builder()
-            .method(Method::POST)
-            .uri(
-                self.http_client
-                    .build_zed_cloud_url("/client/llm_tokens", &[])?
-                    .as_ref(),
-            )
-            .header("Content-Type", "application/json")
-            .header("Authorization", self.authorization_header()?);
+        let mut request_builder = Request::builder().method(Method::POST).uri(
+            self.http_client
+                .build_zed_cloud_url("/client/llm_tokens", &[])?
+                .as_ref(),
+        );
 
         if let Some(system_id) = system_id {
             request_builder = request_builder.header(ZED_SYSTEM_ID_HEADER_NAME, system_id);
         }
 
-        let request = request_builder.body(AsyncBody::default())?;
+        let request = self.build_request(request_builder, AsyncBody::default())?;
 
         let mut response = self.http_client.send(request).await?;
 

crates/cloud_api_types/src/cloud_api_types.rs 🔗

@@ -41,6 +41,11 @@ pub struct SubscriptionPeriod {
     pub ended_at: Timestamp,
 }
 
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct AcceptTermsOfServiceResponse {
+    pub user: AuthenticatedUser,
+}
+
 #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
 pub struct LlmToken(pub String);