Skip to main content

egs_api/api/types/
cosmos.rs

1use serde::{Deserialize, Serialize};
2
3/// Response from `GET /api/cosmos/auth` — session upgrade result.
4///
5/// After calling `set-sid`, this endpoint upgrades the bearer token and
6/// issues EPIC_EG1 / EPIC_EG1_REFRESH JWTs (set as cookies) required
7/// by all other `/api/cosmos/*` endpoints.
8#[allow(missing_docs)]
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct CosmosAuthResponse {
12    pub bearer_token_valid: bool,
13    pub cleared_offline: bool,
14    pub upgraded_bearer_token: bool,
15    pub account_id: String,
16}
17
18/// Error response from Cosmos when not authenticated.
19///
20/// Returned as `{"error": "Not logged in", "isLoggedIn": false}`
21/// when EPIC_EG1 cookie is missing or expired.
22#[allow(missing_docs)]
23#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
24#[serde(rename_all = "camelCase")]
25pub struct CosmosAuthError {
26    pub error: String,
27    pub is_logged_in: bool,
28}
29
30/// Response from `GET /api/cosmos/account` — account info.
31#[allow(missing_docs)]
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33#[serde(rename_all = "camelCase")]
34pub struct CosmosAccount {
35    pub country: String,
36    pub display_name: String,
37    pub email: String,
38    pub id: String,
39    pub preferred_language: String,
40    pub cabined_mode: bool,
41    pub is_logged_in: bool,
42}
43
44/// Response from `GET/POST /api/cosmos/eula/accept`.
45///
46/// GET checks if a EULA is accepted; POST accepts it.
47/// Known EULA IDs: `unreal_engine`, `unreal_engine2`, `realityscan`, `mhc`, `content`
48#[allow(missing_docs)]
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct CosmosEulaResponse {
52    pub accepted: bool,
53}
54
55/// Response from `GET /api/cosmos/policy/aodc` — Age of Digital Consent.
56#[allow(missing_docs)]
57#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
58#[serde(rename_all = "camelCase")]
59pub struct CosmosPolicyAodc {
60    pub failed: bool,
61}
62
63/// Response from `GET /api/cosmos/communication/opt-in`.
64#[allow(missing_docs)]
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66#[serde(rename_all = "camelCase")]
67pub struct CosmosCommOptIn {
68    pub setting_value: bool,
69}
70
71/// Response from Cosmos search on unrealengine.com.
72///
73/// The response shape is derived from JS bundle analysis and may need
74/// adjustment once verified against the live API.
75#[allow(missing_docs)]
76#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77#[serde(rename_all = "camelCase")]
78pub struct CosmosSearchResults {
79    pub results: Option<Vec<CosmosSearchResult>>,
80    pub count: Option<u64>,
81}
82
83/// A single search result from Cosmos.
84#[allow(missing_docs)]
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86#[serde(rename_all = "camelCase")]
87pub struct CosmosSearchResult {
88    pub title: Option<String>,
89    pub slug: Option<String>,
90    pub description: Option<String>,
91    pub url: Option<String>,
92    pub category: Option<String>,
93    pub date: Option<String>,
94    pub image: Option<String>,
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn deserialize_cosmos_auth() {
103        let json = r#"{"bearerTokenValid":true,"clearedOffline":false,"upgradedBearerToken":true,"accountId":"8645b4947bbc4c0092a8b7236df169d1"}"#;
104        let auth: CosmosAuthResponse = serde_json::from_str(json).unwrap();
105        assert!(auth.bearer_token_valid);
106        assert!(auth.upgraded_bearer_token);
107        assert_eq!(auth.account_id, "8645b4947bbc4c0092a8b7236df169d1");
108    }
109
110    #[test]
111    fn deserialize_cosmos_auth_error() {
112        let json = r#"{"error":"Not logged in","isLoggedIn":false}"#;
113        let err: CosmosAuthError = serde_json::from_str(json).unwrap();
114        assert_eq!(err.error, "Not logged in");
115        assert!(!err.is_logged_in);
116    }
117
118    #[test]
119    fn deserialize_cosmos_eula() {
120        let json = r#"{"accepted":true}"#;
121        let eula: CosmosEulaResponse = serde_json::from_str(json).unwrap();
122        assert!(eula.accepted);
123    }
124
125    #[test]
126    fn deserialize_cosmos_account() {
127        let json = r#"{"country":"CZ","displayName":"Acheta Games","email":"m***n@stastnej.ch","id":"8645b4947bbc4c0092a8b7236df169d1","preferredLanguage":"en","cabinedMode":false,"isLoggedIn":true}"#;
128        let account: CosmosAccount = serde_json::from_str(json).unwrap();
129        assert_eq!(account.country, "CZ");
130        assert_eq!(account.display_name, "Acheta Games");
131        assert!(account.is_logged_in);
132    }
133
134    #[test]
135    fn deserialize_policy_aodc() {
136        let json = r#"{"failed":false}"#;
137        let policy: CosmosPolicyAodc = serde_json::from_str(json).unwrap();
138        assert!(!policy.failed);
139    }
140
141    #[test]
142    fn deserialize_comm_opt_in() {
143        let json = r#"{"settingValue":false}"#;
144        let opt: CosmosCommOptIn = serde_json::from_str(json).unwrap();
145        assert!(!opt.setting_value);
146    }
147
148    #[test]
149    fn deserialize_cosmos_search_results() {
150        let json = r#"{"results":[{"title":"Getting Started","slug":"getting-started","description":"Learn how to use UE","url":"/learn/getting-started","category":"tutorials","date":"2025-01-15","image":"https://cdn.example.com/img.png"}],"count":1}"#;
151        let results: CosmosSearchResults = serde_json::from_str(json).unwrap();
152        assert_eq!(results.count, Some(1));
153        let items = results.results.unwrap();
154        assert_eq!(items.len(), 1);
155        assert_eq!(items[0].title, Some("Getting Started".to_string()));
156        assert_eq!(items[0].category, Some("tutorials".to_string()));
157    }
158
159    #[test]
160    fn deserialize_cosmos_search_empty() {
161        let json = r#"{"results":[],"count":0}"#;
162        let results: CosmosSearchResults = serde_json::from_str(json).unwrap();
163        assert_eq!(results.count, Some(0));
164        assert!(results.results.unwrap().is_empty());
165    }
166}