Cargo.lock 🔗
@@ -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",
]
Marshall Bowers created
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
Cargo.lock | 4
crates/cloud_api_types/Cargo.toml | 6
crates/cloud_api_types/src/cloud_api_types.rs | 26 ++
crates/cloud_api_types/src/timestamp.rs | 166 +++++++++++++++++++
crates/cloud_llm_client/src/cloud_llm_client.rs | 4
5 files changed, 204 insertions(+), 2 deletions(-)
@@ -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",
]
@@ -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
@@ -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,
}
@@ -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(×tamp).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(×tamp).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(×tamp).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());
+ }
+}
@@ -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,