1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//! Operation contracts for the App Engine Admin API API (v1).
//!
//! Auto-generated from the GCP Discovery Document.
//! **Do not edit manually** — modify the manifest and re-run codegen.
//!
//! These are the raw HTTP operations with correct URLs, methods,
//! and parameter ordering. The hand-written `api/appengine.rs` wraps
//! these with ergonomic builders, operation polling, etc.
use crate::types::appengine::*;
use crate::{GcpHttpClient, Result};
use urlencoding::encode;
/// Raw HTTP operations for the App Engine Admin API API.
///
/// These methods encode the correct URL paths, HTTP methods, and
/// parameter ordering from the GCP Discovery Document.
/// They are `pub(crate)` — use the ergonomic wrappers in
/// [`super::appengine::AppengineClient`] instead.
pub struct AppengineOps<'a> {
pub(crate) client: &'a GcpHttpClient,
}
impl<'a> AppengineOps<'a> {
pub(crate) fn new(client: &'a GcpHttpClient) -> Self {
Self { client }
}
fn base_url(&self) -> &str {
#[cfg(any(test, feature = "test-support"))]
{
if let Some(ref base) = self.client.base_url {
return base.trim_end_matches('/');
}
}
"https://appengine.googleapis.com"
}
/// Gets information about an application.
///
/// **GCP API**: `GET v1/apps/{appsId}`
///
/// # Path Parameters
/// - `appsId` — Part of `name`. Required. Name of the Application resource to get. Example: apps/myapp. *(required)*
///
/// # Query Parameters
/// - `includeExtraData` — Options to include extra data
///
/// # Response
/// [`Application`]
#[allow(dead_code)]
pub(crate) async fn get_app(&self, apps_id: &str) -> Result<Application> {
let url = format!("{}/v1/apps/{}", self.base_url(), encode(apps_id),);
let response = self.client.get(&url).await?;
serde_json::from_slice(&response).map_err(|e| crate::GcpError::InvalidResponse {
message: format!("Failed to parse get_app response: {e}"),
body: Some(String::from_utf8_lossy(&response).to_string()),
})
}
/// Lists all the services in the application.
///
/// **GCP API**: `GET v1/apps/{appsId}/services`
///
/// # Path Parameters
/// - `appsId` — Part of `parent`. Required. Name of the parent Application resource. Example: apps/myapp. *(required)*
///
/// # Query Parameters
/// - `pageSize` — Maximum results to return per page.
/// - `pageToken` — Continuation token for fetching the next page of results.
///
/// # Response
/// [`ListServicesResponse`]
#[allow(dead_code)]
pub(crate) async fn list_services(
&self,
apps_id: &str,
page_size: &str,
page_token: &str,
) -> Result<ListServicesResponse> {
let url = format!("{}/v1/apps/{}/services", self.base_url(), encode(apps_id),);
let url =
crate::append_query_params(url, &[("pageSize", page_size), ("pageToken", page_token)]);
let response = self.client.get(&url).await?;
serde_json::from_slice(&response).map_err(|e| crate::GcpError::InvalidResponse {
message: format!("Failed to parse list_services response: {e}"),
body: Some(String::from_utf8_lossy(&response).to_string()),
})
}
/// Gets the current configuration of the specified service.
///
/// **GCP API**: `GET v1/apps/{appsId}/services/{servicesId}`
///
/// # Path Parameters
/// - `appsId` — Part of `name`. Required. Name of the resource requested. Example: apps/myapp/services/default. *(required)*
/// - `servicesId` — Part of `name`. See documentation of `appsId`. *(required)*
///
/// # Response
/// [`Service`]
#[allow(dead_code)]
pub(crate) async fn get_service(&self, apps_id: &str, services_id: &str) -> Result<Service> {
let url = format!(
"{}/v1/apps/{}/services/{}",
self.base_url(),
encode(apps_id),
encode(services_id),
);
let response = self.client.get(&url).await?;
serde_json::from_slice(&response).map_err(|e| crate::GcpError::InvalidResponse {
message: format!("Failed to parse get_service response: {e}"),
body: Some(String::from_utf8_lossy(&response).to_string()),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_get_app() {
let mut mock = crate::MockClient::new();
mock.expect_get("/v1/apps/test-appsId")
.returning_json(serde_json::to_value(Application::fixture()).unwrap());
let client = crate::GcpHttpClient::from_mock(mock);
let ops = AppengineOps::new(&client);
let result = ops.get_app("test-appsId").await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_list_services() {
let mut mock = crate::MockClient::new();
mock.expect_get(
"/v1/apps/test-appsId/services?pageSize=test-pageSize&pageToken=test-pageToken",
)
.returning_json(serde_json::to_value(ListServicesResponse::fixture()).unwrap());
let client = crate::GcpHttpClient::from_mock(mock);
let ops = AppengineOps::new(&client);
let result = ops
.list_services("test-appsId", "test-pageSize", "test-pageToken")
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_get_service() {
let mut mock = crate::MockClient::new();
mock.expect_get("/v1/apps/test-appsId/services/test-servicesId")
.returning_json(serde_json::to_value(Service::fixture()).unwrap());
let client = crate::GcpHttpClient::from_mock(mock);
let ops = AppengineOps::new(&client);
let result = ops.get_service("test-appsId", "test-servicesId").await;
assert!(result.is_ok());
}
}