1use crate::config::AwsConfig;
2use anyhow::Result;
3
4#[derive(Clone, Debug)]
5pub struct RestApi {
6 pub id: String,
7 pub name: String,
8 pub description: String,
9 pub created_date: String,
10 pub api_key_source: String,
11 pub endpoint_configuration: String,
12 pub protocol_type: String,
13 pub disable_execute_api_endpoint: bool,
14 pub status: String,
15}
16
17#[derive(Clone, Debug)]
18pub struct Route {
19 pub route_id: String,
20 pub route_key: String,
21 pub target: String,
22 pub authorization_type: String,
23 pub api_key_required: bool,
24 pub arn: String,
25}
26
27#[derive(Clone, Debug)]
28pub struct Resource {
29 pub id: String,
30 pub path: String,
31 pub parent_id: Option<String>,
32 pub methods: Vec<String>,
33 pub display_name: String,
34 pub arn: String,
35}
36
37impl Resource {
38 pub fn methods_display(&self) -> String {
39 if self.methods.is_empty() {
40 String::new()
41 } else {
42 self.methods.join(", ")
43 }
44 }
45}
46
47pub struct ApiGatewayClient {
48 config: AwsConfig,
49}
50
51impl ApiGatewayClient {
52 pub fn new(config: AwsConfig) -> Self {
53 Self { config }
54 }
55
56 pub async fn list_rest_apis(&self) -> Result<Vec<RestApi>> {
57 let mut all_apis = Vec::new();
58
59 let v1_client = self.config.apigateway_client().await;
61 let mut position: Option<String> = None;
62
63 loop {
64 let mut request = v1_client.get_rest_apis();
65 if let Some(pos) = position {
66 request = request.position(pos);
67 }
68
69 let response = request.send().await?;
70
71 if let Some(items) = response.items {
72 for api in items {
73 all_apis.push(RestApi {
74 id: api.id.unwrap_or_default(),
75 name: api.name.unwrap_or_default(),
76 description: api.description.unwrap_or_default(),
77 created_date: api
78 .created_date
79 .map(|dt| {
80 dt.fmt(aws_smithy_types::date_time::Format::DateTime)
81 .unwrap_or_default()
82 })
83 .unwrap_or_default(),
84 api_key_source: api
85 .api_key_source
86 .map(|s| format!("{:?}", s))
87 .unwrap_or_default(),
88 endpoint_configuration: api
89 .endpoint_configuration
90 .and_then(|ec| ec.types)
91 .map(|types| {
92 types
93 .iter()
94 .map(|t| format!("{:?}", t))
95 .collect::<Vec<_>>()
96 .join(", ")
97 })
98 .unwrap_or_default(),
99 protocol_type: "REST".to_string(),
100 disable_execute_api_endpoint: api.disable_execute_api_endpoint,
101 status: "AVAILABLE".to_string(),
102 });
103 }
104 }
105
106 position = response.position;
107 if position.is_none() {
108 break;
109 }
110 }
111
112 let v2_client = self.config.apigatewayv2_client().await;
114 let mut next_token: Option<String> = None;
115
116 loop {
117 let mut request = v2_client.get_apis();
118 if let Some(token) = next_token {
119 request = request.next_token(token);
120 }
121
122 let response = request.send().await?;
123
124 if let Some(items) = response.items {
125 for api in items {
126 all_apis.push(RestApi {
127 id: api.api_id.unwrap_or_default(),
128 name: api.name.unwrap_or_default(),
129 description: api.description.unwrap_or_default(),
130 created_date: api
131 .created_date
132 .map(|dt| {
133 dt.fmt(aws_smithy_types::date_time::Format::DateTime)
134 .unwrap_or_default()
135 })
136 .unwrap_or_default(),
137 api_key_source: "N/A".to_string(),
138 endpoint_configuration: api
139 .api_endpoint
140 .map(|ep| {
141 if ep.contains("execute-api") {
142 "REGIONAL".to_string()
143 } else {
144 "CUSTOM".to_string()
145 }
146 })
147 .unwrap_or_default(),
148 protocol_type: api
149 .protocol_type
150 .map(|p| format!("{:?}", p))
151 .unwrap_or_default(),
152 disable_execute_api_endpoint: api
153 .disable_execute_api_endpoint
154 .unwrap_or(false),
155 status: "AVAILABLE".to_string(),
156 });
157 }
158 }
159
160 next_token = response.next_token;
161 if next_token.is_none() {
162 break;
163 }
164 }
165
166 Ok(all_apis)
167 }
168
169 pub async fn list_routes(&self, api_id: &str) -> Result<Vec<Route>> {
170 let v2_client = self.config.apigatewayv2_client().await;
171 let mut routes = Vec::new();
172 let mut next_token: Option<String> = None;
173
174 loop {
175 let mut request = v2_client.get_routes().api_id(api_id);
176 if let Some(token) = next_token {
177 request = request.next_token(token);
178 }
179
180 let response = request.send().await?;
181
182 if let Some(items) = response.items {
183 for route in items {
184 let route_id = route.route_id.unwrap_or_default();
185 let arn = format!(
186 "arn:aws:apigateway:{}::/apis/{}/routes/{}",
187 self.config.region, api_id, route_id
188 );
189 routes.push(Route {
190 route_id,
191 route_key: route.route_key.unwrap_or_default(),
192 target: route.target.unwrap_or_default(),
193 authorization_type: route
194 .authorization_type
195 .map(|t| format!("{:?}", t))
196 .unwrap_or_else(|| "NONE".to_string()),
197 api_key_required: route.api_key_required.unwrap_or(false),
198 arn,
199 });
200 }
201 }
202
203 next_token = response.next_token;
204 if next_token.is_none() {
205 break;
206 }
207 }
208
209 Ok(routes)
210 }
211
212 pub async fn list_resources(&self, api_id: &str) -> Result<Vec<Resource>> {
213 let v1_client = self.config.apigateway_client().await;
214 let region = &self.config.region;
215 let mut resources = Vec::new();
216 let mut position: Option<String> = None;
217
218 loop {
219 let mut request = v1_client.get_resources().rest_api_id(api_id);
220 if let Some(pos) = position {
221 request = request.position(pos);
222 }
223
224 let response = request.send().await?;
225
226 if let Some(items) = response.items {
227 for resource in items {
228 let methods = resource
229 .resource_methods
230 .map(|m| m.keys().map(|k| k.to_string()).collect())
231 .unwrap_or_default();
232
233 let path = resource.path.unwrap_or_default();
234 let display = if path == "/" {
235 "/".to_string()
236 } else {
237 path.rsplit('/')
238 .next()
239 .map(|s| format!("/{}", s))
240 .unwrap_or(path.clone())
241 };
242
243 let arn = format!(
244 "arn:aws:apigateway:{}::/restapis/{}/resources/{}",
245 region,
246 api_id,
247 resource.id.as_ref().unwrap_or(&String::new())
248 );
249
250 resources.push(Resource {
251 id: resource.id.unwrap_or_default(),
252 path,
253 parent_id: resource.parent_id,
254 methods,
255 display_name: display,
256 arn,
257 });
258 }
259 }
260
261 position = response.position;
262 if position.is_none() {
263 break;
264 }
265 }
266
267 Ok(resources)
268 }
269}