1use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6use winterbaume_core::{StateChangeNotifier, StateViewError, StatefulService};
7
8use crate::handlers::AppSyncService;
9use crate::state::AppSyncState;
10use crate::types::{
11 Api, ApiCacheEntry, ApiKeyEntry, ChannelNamespaceEntry, GraphqlApi, SchemaStatus, TypeEntry,
12};
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct AppsyncStateView {
17 #[serde(default)]
19 pub apis: HashMap<String, GraphqlApiView>,
20 #[serde(default)]
22 pub event_apis: HashMap<String, ApiView>,
23 #[serde(default)]
25 pub api_caches: HashMap<String, ApiCacheView>,
26 #[serde(default)]
28 pub api_keys: HashMap<String, Vec<ApiKeyView>>,
29 #[serde(default)]
31 pub channel_namespaces: HashMap<String, Vec<ChannelNamespaceView>>,
32 #[serde(default)]
34 pub types: HashMap<String, Vec<TypeView>>,
35 #[serde(default)]
37 pub schema_statuses: HashMap<String, SchemaStatusView>,
38 #[serde(default)]
40 pub resource_tags: HashMap<String, HashMap<String, String>>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct GraphqlApiView {
46 pub api_id: String,
47 pub name: String,
48 pub authentication_type: String,
49 pub arn: String,
50 #[serde(default)]
51 pub uris: HashMap<String, String>,
52 #[serde(default)]
53 pub tags: HashMap<String, String>,
54 #[serde(default)]
55 pub xray_enabled: bool,
56 #[serde(default)]
57 pub additional_authentication_provider: Option<serde_json::Value>,
58 #[serde(default)]
59 pub lambda_authorizer_config: Option<serde_json::Value>,
60 #[serde(default)]
61 pub user_pool_config: Option<serde_json::Value>,
62 #[serde(default)]
63 pub enhanced_metrics_config: Option<serde_json::Value>,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct ApiView {
69 pub api_id: String,
70 pub name: String,
71 pub api_arn: String,
72 pub created: f64,
73 #[serde(default)]
74 pub tags: HashMap<String, String>,
75 pub owner_contact: Option<String>,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct ApiCacheView {
81 pub api_id: String,
82 pub api_caching_behavior: String,
83 pub r#type: String,
84 pub ttl: i64,
85 pub at_rest_encryption_enabled: bool,
86 pub transit_encryption_enabled: bool,
87 pub status: String,
88 pub health_metrics_config: Option<String>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct ApiKeyView {
94 pub id: String,
95 pub api_id: String,
96 pub description: Option<String>,
97 pub expires: i64,
98 pub deletes: i64,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct ChannelNamespaceView {
104 pub api_id: String,
105 pub name: String,
106 pub channel_namespace_arn: String,
107 pub created: f64,
108 pub last_modified: f64,
109 #[serde(default)]
110 pub tags: HashMap<String, String>,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct TypeView {
116 pub api_id: String,
117 pub name: String,
118 pub definition: Option<String>,
119 pub format: String,
120 pub arn: String,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct SchemaStatusView {
126 pub status: String,
127 pub details: Option<String>,
128}
129
130impl From<&GraphqlApi> for GraphqlApiView {
135 fn from(api: &GraphqlApi) -> Self {
136 GraphqlApiView {
137 api_id: api.api_id.clone(),
138 name: api.name.clone(),
139 authentication_type: api.authentication_type.clone(),
140 arn: api.arn.clone(),
141 uris: api.uris.clone(),
142 tags: api.tags.clone(),
143 xray_enabled: api.xray_enabled,
144 additional_authentication_provider: api.additional_authentication_provider.clone(),
145 lambda_authorizer_config: api.lambda_authorizer_config.clone(),
146 user_pool_config: api.user_pool_config.clone(),
147 enhanced_metrics_config: api.enhanced_metrics_config.clone(),
148 }
149 }
150}
151
152impl From<GraphqlApiView> for GraphqlApi {
153 fn from(v: GraphqlApiView) -> Self {
154 GraphqlApi {
155 api_id: v.api_id,
156 name: v.name,
157 authentication_type: v.authentication_type,
158 arn: v.arn,
159 uris: v.uris,
160 tags: v.tags,
161 xray_enabled: v.xray_enabled,
162 additional_authentication_provider: v.additional_authentication_provider,
163 lambda_authorizer_config: v.lambda_authorizer_config,
164 user_pool_config: v.user_pool_config,
165 enhanced_metrics_config: v.enhanced_metrics_config,
166 }
167 }
168}
169
170impl From<&Api> for ApiView {
171 fn from(api: &Api) -> Self {
172 ApiView {
173 api_id: api.api_id.clone(),
174 name: api.name.clone(),
175 api_arn: api.api_arn.clone(),
176 created: api.created,
177 tags: api.tags.clone(),
178 owner_contact: api.owner_contact.clone(),
179 }
180 }
181}
182
183impl From<ApiView> for Api {
184 fn from(v: ApiView) -> Self {
185 Api {
186 api_id: v.api_id,
187 name: v.name,
188 api_arn: v.api_arn,
189 created: v.created,
190 tags: v.tags,
191 owner_contact: v.owner_contact,
192 }
193 }
194}
195
196impl From<&ApiCacheEntry> for ApiCacheView {
197 fn from(e: &ApiCacheEntry) -> Self {
198 ApiCacheView {
199 api_id: e.api_id.clone(),
200 api_caching_behavior: e.api_caching_behavior.clone(),
201 r#type: e.r#type.clone(),
202 ttl: e.ttl,
203 at_rest_encryption_enabled: e.at_rest_encryption_enabled,
204 transit_encryption_enabled: e.transit_encryption_enabled,
205 status: e.status.clone(),
206 health_metrics_config: e.health_metrics_config.clone(),
207 }
208 }
209}
210
211impl From<ApiCacheView> for ApiCacheEntry {
212 fn from(v: ApiCacheView) -> Self {
213 ApiCacheEntry {
214 api_id: v.api_id,
215 api_caching_behavior: v.api_caching_behavior,
216 r#type: v.r#type,
217 ttl: v.ttl,
218 at_rest_encryption_enabled: v.at_rest_encryption_enabled,
219 transit_encryption_enabled: v.transit_encryption_enabled,
220 status: v.status,
221 health_metrics_config: v.health_metrics_config,
222 }
223 }
224}
225
226impl From<&ApiKeyEntry> for ApiKeyView {
227 fn from(k: &ApiKeyEntry) -> Self {
228 ApiKeyView {
229 id: k.id.clone(),
230 api_id: k.api_id.clone(),
231 description: k.description.clone(),
232 expires: k.expires,
233 deletes: k.deletes,
234 }
235 }
236}
237
238impl From<ApiKeyView> for ApiKeyEntry {
239 fn from(v: ApiKeyView) -> Self {
240 ApiKeyEntry {
241 id: v.id,
242 api_id: v.api_id,
243 description: v.description,
244 expires: v.expires,
245 deletes: v.deletes,
246 }
247 }
248}
249
250impl From<&ChannelNamespaceEntry> for ChannelNamespaceView {
251 fn from(ns: &ChannelNamespaceEntry) -> Self {
252 ChannelNamespaceView {
253 api_id: ns.api_id.clone(),
254 name: ns.name.clone(),
255 channel_namespace_arn: ns.channel_namespace_arn.clone(),
256 created: ns.created,
257 last_modified: ns.last_modified,
258 tags: ns.tags.clone(),
259 }
260 }
261}
262
263impl From<ChannelNamespaceView> for ChannelNamespaceEntry {
264 fn from(v: ChannelNamespaceView) -> Self {
265 ChannelNamespaceEntry {
266 api_id: v.api_id,
267 name: v.name,
268 channel_namespace_arn: v.channel_namespace_arn,
269 created: v.created,
270 last_modified: v.last_modified,
271 tags: v.tags,
272 }
273 }
274}
275
276impl From<&TypeEntry> for TypeView {
277 fn from(t: &TypeEntry) -> Self {
278 TypeView {
279 api_id: t.api_id.clone(),
280 name: t.name.clone(),
281 definition: t.definition.clone(),
282 format: t.format.clone(),
283 arn: t.arn.clone(),
284 }
285 }
286}
287
288impl From<TypeView> for TypeEntry {
289 fn from(v: TypeView) -> Self {
290 TypeEntry {
291 api_id: v.api_id,
292 name: v.name,
293 definition: v.definition,
294 format: v.format,
295 arn: v.arn,
296 }
297 }
298}
299
300impl From<&SchemaStatus> for SchemaStatusView {
301 fn from(s: &SchemaStatus) -> Self {
302 SchemaStatusView {
303 status: s.status.clone(),
304 details: s.details.clone(),
305 }
306 }
307}
308
309impl From<SchemaStatusView> for SchemaStatus {
310 fn from(v: SchemaStatusView) -> Self {
311 SchemaStatus {
312 status: v.status,
313 details: v.details,
314 }
315 }
316}
317
318impl From<&AppSyncState> for AppsyncStateView {
319 fn from(state: &AppSyncState) -> Self {
320 AppsyncStateView {
321 apis: state
322 .apis
323 .iter()
324 .map(|(k, v)| (k.clone(), GraphqlApiView::from(v)))
325 .collect(),
326 event_apis: state
327 .event_apis
328 .iter()
329 .map(|(k, v)| (k.clone(), ApiView::from(v)))
330 .collect(),
331 api_caches: state
332 .api_caches
333 .iter()
334 .map(|(k, v)| (k.clone(), ApiCacheView::from(v)))
335 .collect(),
336 api_keys: state
337 .api_keys
338 .iter()
339 .map(|(k, v)| (k.clone(), v.iter().map(ApiKeyView::from).collect()))
340 .collect(),
341 channel_namespaces: state
342 .channel_namespaces
343 .iter()
344 .map(|(k, v)| {
345 (
346 k.clone(),
347 v.iter().map(ChannelNamespaceView::from).collect(),
348 )
349 })
350 .collect(),
351 types: state
352 .types
353 .iter()
354 .map(|(k, v)| (k.clone(), v.iter().map(TypeView::from).collect()))
355 .collect(),
356 schema_statuses: state
357 .schema_statuses
358 .iter()
359 .map(|(k, v)| (k.clone(), SchemaStatusView::from(v)))
360 .collect(),
361 resource_tags: state.resource_tags.clone(),
362 }
363 }
364}
365
366impl From<AppsyncStateView> for AppSyncState {
367 fn from(view: AppsyncStateView) -> Self {
368 AppSyncState {
369 apis: view
370 .apis
371 .into_values()
372 .map(|v| {
373 let api = GraphqlApi::from(v);
374 (api.api_id.clone(), api)
375 })
376 .collect(),
377 event_apis: view
378 .event_apis
379 .into_values()
380 .map(|v| {
381 let api = Api::from(v);
382 (api.api_id.clone(), api)
383 })
384 .collect(),
385 api_caches: view
386 .api_caches
387 .into_values()
388 .map(|v| {
389 let cache = ApiCacheEntry::from(v);
390 (cache.api_id.clone(), cache)
391 })
392 .collect(),
393 api_keys: view
394 .api_keys
395 .into_iter()
396 .map(|(k, v)| (k, v.into_iter().map(ApiKeyEntry::from).collect()))
397 .collect(),
398 channel_namespaces: view
399 .channel_namespaces
400 .into_iter()
401 .map(|(k, v)| (k, v.into_iter().map(ChannelNamespaceEntry::from).collect()))
402 .collect(),
403 types: view
404 .types
405 .into_iter()
406 .map(|(k, v)| (k, v.into_iter().map(TypeEntry::from).collect()))
407 .collect(),
408 schema_statuses: view
409 .schema_statuses
410 .into_iter()
411 .map(|(k, v)| (k, SchemaStatus::from(v)))
412 .collect(),
413 resource_tags: view.resource_tags,
414 }
415 }
416}
417
418impl StatefulService for AppSyncService {
423 type StateView = AppsyncStateView;
424
425 async fn snapshot(&self, account_id: &str, region: &str) -> Self::StateView {
426 let state = self.state.get(account_id, region);
427 let guard = state.read().await;
428 AppsyncStateView::from(&*guard)
429 }
430
431 async fn restore(
432 &self,
433 account_id: &str,
434 region: &str,
435 view: Self::StateView,
436 ) -> Result<(), StateViewError> {
437 let new_state = AppSyncState::from(view);
438 {
439 let state = self.state.get(account_id, region);
440 *state.write().await = new_state;
441 }
442 self.notify_state_changed(account_id, region).await;
443 Ok(())
444 }
445
446 async fn merge(
447 &self,
448 account_id: &str,
449 region: &str,
450 view: Self::StateView,
451 ) -> Result<(), StateViewError> {
452 let incoming = AppSyncState::from(view);
453 let state = self.state.get(account_id, region);
454 {
455 let mut guard = state.write().await;
456 guard.apis.extend(incoming.apis);
457 guard.event_apis.extend(incoming.event_apis);
458 guard.api_caches.extend(incoming.api_caches);
459 for (k, v) in incoming.api_keys {
460 guard.api_keys.entry(k).or_default().extend(v);
461 }
462 for (k, v) in incoming.channel_namespaces {
463 guard.channel_namespaces.entry(k).or_default().extend(v);
464 }
465 for (k, v) in incoming.types {
466 guard.types.entry(k).or_default().extend(v);
467 }
468 guard.schema_statuses.extend(incoming.schema_statuses);
469 for (k, v) in incoming.resource_tags {
470 guard.resource_tags.entry(k).or_default().extend(v);
471 }
472 }
473 self.notify_state_changed(account_id, region).await;
474 Ok(())
475 }
476
477 fn notifier(&self) -> &StateChangeNotifier<Self::StateView> {
478 &self.notifier
479 }
480}