cloud_api_types: Add more data to the GetAuthenticatedUserResponse (#35384)

This PR adds more data to the `GetAuthenticatedUserResponse`.

We now return more information about the authenticated user, as well as
their plan information.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2025-07-30 23:38:51 -04:00 committed by GitHub
parent b90fd4287f
commit b1a7993544
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 204 additions and 2 deletions

4
Cargo.lock generated
View file

@ -3049,7 +3049,11 @@ dependencies = [
name = "cloud_api_types"
version = "0.1.0"
dependencies = [
"chrono",
"cloud_llm_client",
"pretty_assertions",
"serde",
"serde_json",
"workspace-hack",
]

View file

@ -12,5 +12,11 @@ workspace = true
path = "src/cloud_api_types.rs"
[dependencies]
chrono.workspace = true
cloud_llm_client.workspace = true
serde.workspace = true
workspace-hack.workspace = true
[dev-dependencies]
pretty_assertions.workspace = true
serde_json.workspace = true

View file

@ -1,14 +1,40 @@
mod timestamp;
use serde::{Deserialize, Serialize};
pub use crate::timestamp::Timestamp;
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct GetAuthenticatedUserResponse {
pub user: AuthenticatedUser,
pub feature_flags: Vec<String>,
pub plan: PlanInfo,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct AuthenticatedUser {
pub id: i32,
pub metrics_id: String,
pub avatar_url: String,
pub github_login: String,
pub name: Option<String>,
pub is_staff: bool,
pub accepted_tos_at: Option<Timestamp>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct PlanInfo {
pub plan: cloud_llm_client::Plan,
pub subscription_period: Option<SubscriptionPeriod>,
pub usage: cloud_llm_client::CurrentUsage,
pub trial_started_at: Option<Timestamp>,
pub is_usage_based_billing_enabled: bool,
pub is_account_too_young: bool,
pub has_overdue_invoices: bool,
}
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
pub struct SubscriptionPeriod {
pub started_at: Timestamp,
pub ended_at: Timestamp,
}

View file

@ -0,0 +1,166 @@
use chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// A timestamp with a serialized representation in RFC 3339 format.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct Timestamp(pub DateTime<Utc>);
impl Timestamp {
pub fn new(datetime: DateTime<Utc>) -> Self {
Self(datetime)
}
}
impl From<DateTime<Utc>> for Timestamp {
fn from(value: DateTime<Utc>) -> Self {
Self(value)
}
}
impl From<NaiveDateTime> for Timestamp {
fn from(value: NaiveDateTime) -> Self {
Self(value.and_utc())
}
}
impl Serialize for Timestamp {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let rfc3339_string = self.0.to_rfc3339_opts(SecondsFormat::Millis, true);
serializer.serialize_str(&rfc3339_string)
}
}
impl<'de> Deserialize<'de> for Timestamp {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
let datetime = DateTime::parse_from_rfc3339(&value)
.map_err(serde::de::Error::custom)?
.to_utc();
Ok(Self(datetime))
}
}
#[cfg(test)]
mod tests {
use chrono::NaiveDate;
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn test_timestamp_serialization() {
let datetime = DateTime::parse_from_rfc3339("2023-12-25T14:30:45.123Z")
.unwrap()
.to_utc();
let timestamp = Timestamp::new(datetime);
let json = serde_json::to_string(&timestamp).unwrap();
assert_eq!(json, "\"2023-12-25T14:30:45.123Z\"");
}
#[test]
fn test_timestamp_deserialization() {
let json = "\"2023-12-25T14:30:45.123Z\"";
let timestamp: Timestamp = serde_json::from_str(json).unwrap();
let expected = DateTime::parse_from_rfc3339("2023-12-25T14:30:45.123Z")
.unwrap()
.to_utc();
assert_eq!(timestamp.0, expected);
}
#[test]
fn test_timestamp_roundtrip() {
let original = DateTime::parse_from_rfc3339("2023-12-25T14:30:45.123Z")
.unwrap()
.to_utc();
let timestamp = Timestamp::new(original);
let json = serde_json::to_string(&timestamp).unwrap();
let deserialized: Timestamp = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.0, original);
}
#[test]
fn test_timestamp_from_datetime_utc() {
let datetime = DateTime::parse_from_rfc3339("2023-12-25T14:30:45.123Z")
.unwrap()
.to_utc();
let timestamp = Timestamp::from(datetime);
assert_eq!(timestamp.0, datetime);
}
#[test]
fn test_timestamp_from_naive_datetime() {
let naive_dt = NaiveDate::from_ymd_opt(2023, 12, 25)
.unwrap()
.and_hms_milli_opt(14, 30, 45, 123)
.unwrap();
let timestamp = Timestamp::from(naive_dt);
let expected = naive_dt.and_utc();
assert_eq!(timestamp.0, expected);
}
#[test]
fn test_timestamp_serialization_with_microseconds() {
// Test that microseconds are truncated to milliseconds
let datetime = NaiveDate::from_ymd_opt(2023, 12, 25)
.unwrap()
.and_hms_micro_opt(14, 30, 45, 123456)
.unwrap()
.and_utc();
let timestamp = Timestamp::new(datetime);
let json = serde_json::to_string(&timestamp).unwrap();
// Should be truncated to milliseconds
assert_eq!(json, "\"2023-12-25T14:30:45.123Z\"");
}
#[test]
fn test_timestamp_deserialization_without_milliseconds() {
let json = "\"2023-12-25T14:30:45Z\"";
let timestamp: Timestamp = serde_json::from_str(json).unwrap();
let expected = NaiveDate::from_ymd_opt(2023, 12, 25)
.unwrap()
.and_hms_opt(14, 30, 45)
.unwrap()
.and_utc();
assert_eq!(timestamp.0, expected);
}
#[test]
fn test_timestamp_deserialization_with_timezone() {
let json = "\"2023-12-25T14:30:45.123+05:30\"";
let timestamp: Timestamp = serde_json::from_str(json).unwrap();
// Should be converted to UTC
let expected = NaiveDate::from_ymd_opt(2023, 12, 25)
.unwrap()
.and_hms_milli_opt(9, 0, 45, 123) // 14:30:45 + 5:30 = 20:00:45, but we want UTC so subtract 5:30
.unwrap()
.and_utc();
assert_eq!(timestamp.0, expected);
}
#[test]
fn test_timestamp_deserialization_with_invalid_format() {
let json = "\"invalid-date\"";
let result: Result<Timestamp, _> = serde_json::from_str(json);
assert!(result.is_err());
}
}

View file

@ -308,13 +308,13 @@ pub struct GetSubscriptionResponse {
pub usage: Option<CurrentUsage>,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct CurrentUsage {
pub model_requests: UsageData,
pub edit_predictions: UsageData,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct UsageData {
pub used: u32,
pub limit: UsageLimit,