rusty_cdk_core/apigateway/
builder.rs1use crate::apigateway::{ApiGatewayV2Api, ApiGatewayV2ApiProperties, ApiGatewayV2ApiRef, ApiGatewayV2Integration, ApiGatewayV2IntegrationProperties, ApiGatewayV2Route, ApiGatewayV2RouteProperties, ApiGatewayV2Stage, ApiGatewayV2StageProperties, ApiGatewayV2StageRef, CorsConfiguration};
2use crate::intrinsic::{get_arn, get_ref, join, AWS_ACCOUNT_PSEUDO_PARAM, AWS_PARTITION_PSEUDO_PARAM, AWS_REGION_PSEUDO_PARAM};
3use crate::lambda::{FunctionRef, PermissionBuilder};
4use crate::shared::http::HttpMethod;
5use crate::shared::Id;
6use crate::stack::{Resource, StackBuilder};
7use serde_json::Value;
8use std::time::Duration;
9use crate::wrappers::LambdaPermissionAction;
10
11struct RouteInfo {
17 lambda_id: Id,
18 path: String,
19 method: Option<HttpMethod>,
20 resource_id: String,
21}
22
23pub struct ApiGatewayV2Builder {
47 id: Id,
48 name: Option<String>,
49 disable_execute_api_endpoint: Option<bool>,
50 cors_configuration: Option<CorsConfiguration>,
51 route_info: Vec<RouteInfo>,
52}
53
54impl ApiGatewayV2Builder {
55 pub fn new<T: Into<String>>(id: &str, name: T) -> Self {
61 Self {
62 id: Id(id.to_string()),
63 name: Some(name.into()), disable_execute_api_endpoint: None,
65 cors_configuration: None,
66 route_info: vec![],
67 }
68 }
69
70 pub fn disable_execute_api_endpoint(self, disable_api_endpoint: bool) -> Self {
71 Self {
72 disable_execute_api_endpoint: Some(disable_api_endpoint),
73 ..self
74 }
75 }
76
77 pub fn cors_configuration(self, config: CorsConfiguration) -> Self {
78 Self {
79 cors_configuration: Some(config),
80 ..self
81 }
82 }
83
84 pub fn add_default_route_lambda(mut self, lambda: &FunctionRef) -> Self {
88 self.route_info.push(RouteInfo {
89 lambda_id: lambda.get_id().clone(),
90 path: "$default".to_string(),
91 method: None,
92 resource_id: lambda.get_resource_id().to_string(),
93 });
94 Self { ..self }
95 }
96
97 pub fn add_route_lambda<T: Into<String>>(mut self, path: T, method: HttpMethod, lambda: &FunctionRef) -> Self {
101 let path = path.into();
102 let path = if path.starts_with("/") { path } else { format!("/{}", path) };
103
104 self.route_info.push(RouteInfo {
105 lambda_id: lambda.get_id().clone(),
106 path,
107 method: Some(method),
108 resource_id: lambda.get_resource_id().to_string(),
109 });
110 Self { ..self }
111 }
112
113 pub fn build(
114 self, stack_builder: &mut StackBuilder
115 ) -> (
116 ApiGatewayV2ApiRef,
117 ApiGatewayV2StageRef,
118 ) {
119 let api_resource_id = Resource::generate_id("HttpApiGateway");
120 let stage_resource_id = Resource::generate_id("HttpApiStage");
121 let stage_id = Id::generate_id(&self.id, "Stage");
122
123 self
124 .route_info
125 .into_iter()
126 .for_each(|info| {
127 let route_id = Id::combine_with_resource_id(&self.id, &info.lambda_id);
128 let route_permission_id = Id::generate_id(&self.id, "Permission");
129 let route_integration_id = Id::generate_id(&self.id, "Integration");
130
131 let integration_resource_id = Resource::generate_id("HttpApiIntegration");
132 let route_resource_id = Resource::generate_id("HttpApiRoute");
133
134 PermissionBuilder::new(
135 &route_permission_id,
136 LambdaPermissionAction("lambda:InvokeFunction".to_string()),
137 get_arn(&info.resource_id),
138 "apigateway.amazonaws.com".to_string(),
139 )
140 .source_arn(join(
141 "",
142 vec![
143 Value::String("arn:".to_string()),
144 get_ref(AWS_PARTITION_PSEUDO_PARAM),
145 Value::String(":execute-api:".to_string()),
146 get_ref(AWS_REGION_PSEUDO_PARAM),
147 Value::String(":".to_string()),
148 get_ref(AWS_ACCOUNT_PSEUDO_PARAM),
149 Value::String(":".to_string()),
150 get_ref(&api_resource_id),
151 Value::String(format!("*/*{}", info.path)),
152 ],
153 ))
154 .build(stack_builder);
155
156 let integration = ApiGatewayV2Integration {
157 id: route_integration_id,
158 resource_id: integration_resource_id.clone(),
159 r#type: "AWS::ApiGatewayV2::Integration".to_string(),
160 properties: ApiGatewayV2IntegrationProperties {
161 api_id: get_ref(&api_resource_id),
162 integration_type: "AWS_PROXY".to_string(),
163 payload_format_version: Some("2.0".to_string()),
164 integration_uri: Some(get_arn(&info.resource_id)),
165 integration_method: None,
166 passthrough_behavior: None,
167 request_parameters: None,
168 request_templates: None,
169 response_parameters: None,
170 timeout_in_millis: None,
171 },
172 };
173 stack_builder.add_resource(integration);
174
175 let route_key = if let Some(method) = info.method {
176 let method: String = method.into();
177 format!("{} {}", method, info.path)
178 } else {
179 info.path
180 };
181
182 let route = ApiGatewayV2Route {
183 id: route_id,
184 resource_id: route_resource_id.clone(),
185 r#type: "AWS::ApiGatewayV2::Route".to_string(),
186 properties: ApiGatewayV2RouteProperties {
187 api_id: get_ref(&api_resource_id),
188 route_key,
189 target: Some(join(
190 "",
191 vec![Value::String("integrations/".to_string()), get_ref(&integration_resource_id)],
192 )),
193 },
194 };
195 stack_builder.add_resource(route);
196 });
197
198 stack_builder.add_resource(ApiGatewayV2Stage {
199 id: stage_id,
200 resource_id: stage_resource_id.clone(),
201 r#type: "AWS::ApiGatewayV2::Stage".to_string(),
202 properties: ApiGatewayV2StageProperties {
203 api_id: get_ref(&api_resource_id),
204 stage_name: "$default".to_string(),
205 auto_deploy: true,
206 default_route_settings: None,
207 route_settings: None,
208 },
209 });
210
211 stack_builder.add_resource(ApiGatewayV2Api {
212 id: self.id,
213 resource_id: api_resource_id.clone(),
214 r#type: "AWS::ApiGatewayV2::Api".to_string(),
215 properties: ApiGatewayV2ApiProperties {
216 name: self.name,
217 protocol_type: "HTTP".to_string(),
218 disable_execute_api_endpoint: self.disable_execute_api_endpoint,
219 cors_configuration: self.cors_configuration,
220 },
221 });
222
223 let stage = ApiGatewayV2StageRef::new(stage_resource_id);
224 let api = ApiGatewayV2ApiRef::new(api_resource_id);
225
226 (api, stage)
227 }
228}
229
230pub struct CorsConfigurationBuilder {
231 allow_credentials: Option<bool>,
232 allow_headers: Option<Vec<String>>,
233 allow_methods: Option<Vec<String>>,
234 allow_origins: Option<Vec<String>>,
235 expose_headers: Option<Vec<String>>,
236 max_age: Option<u64>,
237}
238
239impl Default for CorsConfigurationBuilder {
240 fn default() -> Self {
241 Self::new()
242 }
243}
244
245impl CorsConfigurationBuilder {
246 pub fn new() -> Self {
247 Self {
248 allow_credentials: None,
249 allow_headers: None,
250 allow_methods: None,
251 allow_origins: None,
252 expose_headers: None,
253 max_age: None,
254 }
255 }
256
257 pub fn allow_credentials(self, allow: bool) -> Self {
258 Self {
259 allow_credentials: Some(allow),
260 ..self
261 }
262 }
263
264 pub fn allow_headers(self, headers: Vec<String>) -> Self {
265 Self {
266 allow_headers: Some(headers),
267 ..self
268 }
269 }
270
271 pub fn allow_methods(self, methods: Vec<HttpMethod>) -> Self {
272 Self {
273 allow_methods: Some(methods.into_iter().map(Into::into).collect()),
274 ..self
275 }
276 }
277
278 pub fn allow_origins(self, origins: Vec<String>) -> Self {
279 Self {
280 allow_origins: Some(origins),
281 ..self
282 }
283 }
284
285 pub fn expose_headers(self, headers: Vec<String>) -> Self {
286 Self {
287 expose_headers: Some(headers),
288 ..self
289 }
290 }
291
292 pub fn max_age(self, age: Duration) -> Self {
293 Self {
294 max_age: Some(age.as_secs()),
295 ..self
296 }
297 }
298
299 #[must_use]
300 pub fn build(self) -> CorsConfiguration {
301 CorsConfiguration {
302 allow_credentials: self.allow_credentials,
303 allow_headers: self.allow_headers,
304 allow_methods: self.allow_methods,
305 allow_origins: self.allow_origins,
306 expose_headers: self.expose_headers,
307 max_age: self.max_age,
308 }
309 }
310}