Skip to main content

rusticity_core/
apig.rs

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        // List REST APIs (v1)
60        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        // List HTTP/WebSocket APIs (v2)
113        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}