Skip to main content

fakecloud_appsync/
state.rs

1//! Account-partitioned, serializable state for AWS AppSync (`appsync`).
2//!
3//! Every resource is stored as its already-output-valid wire JSON object so
4//! reads echo exactly what writes persisted. All map keys are plain `String`s
5//! (apiId, resource name, ARN, association id), so the snapshot never depends on
6//! the tuple-key serde adapter that has silently broken snapshot serialization
7//! on other services. Sub-resources keyed by a compound `String` (e.g. a
8//! resolver's `typeName::fieldName`) reuse the `::` delimiter, which the
9//! `ResourceName` grammar (`[_A-Za-z][_0-9A-Za-z]*`) can never contain.
10
11use std::collections::BTreeMap;
12use std::sync::Arc;
13
14use parking_lot::RwLock;
15use serde::{Deserialize, Serialize};
16
17use fakecloud_core::multi_account::{AccountState, MultiAccountState};
18use serde_json::Value;
19
20pub const APPSYNC_SNAPSHOT_SCHEMA_VERSION: u32 = 1;
21
22/// A stored GraphQL-schema document + its async creation status.
23#[derive(Debug, Clone, Default, Serialize, Deserialize)]
24pub struct SchemaState {
25    /// The raw SDL schema document ingested by `StartSchemaCreation`.
26    #[serde(default)]
27    pub definition: String,
28    /// Current `SchemaStatus` (`Processing` until the first status read
29    /// settles it to `Success`), matching the async lifecycle of other ops.
30    #[serde(default)]
31    pub status: String,
32    /// Human-readable status details.
33    #[serde(default)]
34    pub details: String,
35}
36
37/// Per-account AWS AppSync state.
38#[derive(Debug, Clone, Default, Serialize, Deserialize)]
39pub struct AppSyncData {
40    /// GraphQL APIs keyed by `apiId`, stored as their `GraphqlApi` wire object.
41    #[serde(default)]
42    pub graphql_apis: BTreeMap<String, Value>,
43    /// Schema state keyed by `apiId`.
44    #[serde(default)]
45    pub schemas: BTreeMap<String, SchemaState>,
46    /// API keys keyed by `apiId` -> `id` -> `ApiKey` wire object.
47    #[serde(default)]
48    pub api_keys: BTreeMap<String, BTreeMap<String, Value>>,
49    /// Data sources keyed by `apiId` -> `name` -> `DataSource` wire object.
50    #[serde(default)]
51    pub data_sources: BTreeMap<String, BTreeMap<String, Value>>,
52    /// Resolvers keyed by `apiId` -> `typeName::fieldName` -> `Resolver`.
53    #[serde(default)]
54    pub resolvers: BTreeMap<String, BTreeMap<String, Value>>,
55    /// Functions keyed by `apiId` -> `functionId` -> `FunctionConfiguration`.
56    #[serde(default)]
57    pub functions: BTreeMap<String, BTreeMap<String, Value>>,
58    /// Schema types keyed by `apiId` -> `typeName` -> `Type` wire object.
59    #[serde(default)]
60    pub types: BTreeMap<String, BTreeMap<String, Value>>,
61    /// API caches keyed by `apiId`, stored as their `ApiCache` wire object.
62    #[serde(default)]
63    pub api_caches: BTreeMap<String, Value>,
64    /// GraphQL-API environment variables keyed by `apiId`.
65    #[serde(default)]
66    pub env_vars: BTreeMap<String, BTreeMap<String, String>>,
67    /// Custom domain names keyed by `domainName` -> `DomainNameConfig`.
68    #[serde(default)]
69    pub domain_names: BTreeMap<String, Value>,
70    /// Domain-name -> `ApiAssociation` links (`AssociateApi`).
71    #[serde(default)]
72    pub api_associations: BTreeMap<String, Value>,
73    /// Event APIs keyed by `apiId`, stored as their `Api` wire object.
74    #[serde(default)]
75    pub apis: BTreeMap<String, Value>,
76    /// Channel namespaces keyed by event-`apiId` -> `name` -> `ChannelNamespace`.
77    #[serde(default)]
78    pub channel_namespaces: BTreeMap<String, BTreeMap<String, Value>>,
79    /// Source-API associations keyed by `associationId` -> `SourceApiAssociation`.
80    #[serde(default)]
81    pub source_api_associations: BTreeMap<String, Value>,
82    /// Data-source introspection jobs keyed by `introspectionId`.
83    #[serde(default)]
84    pub introspections: BTreeMap<String, Value>,
85    /// Tags keyed by resource ARN.
86    #[serde(default)]
87    pub tags: BTreeMap<String, BTreeMap<String, String>>,
88}
89
90/// Current time as an RFC3339 string (AppSync's timestamp members serialise as
91/// epoch-seconds on the restJson1 wire, but stored objects only need to be
92/// self-consistent; epoch floats are used where the model declares a timestamp).
93pub fn now_epoch() -> f64 {
94    chrono::Utc::now().timestamp_millis() as f64 / 1000.0
95}
96
97impl AccountState for AppSyncData {
98    fn new_for_account(_account_id: &str, _region: &str, _endpoint: &str) -> Self {
99        Self::default()
100    }
101}
102
103pub type SharedAppSyncState = Arc<RwLock<MultiAccountState<AppSyncData>>>;
104
105#[derive(Debug, Serialize, Deserialize)]
106pub struct AppSyncSnapshot {
107    pub schema_version: u32,
108    pub accounts: MultiAccountState<AppSyncData>,
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn new_account_is_empty() {
117        let data = AppSyncData::new_for_account("000000000000", "us-east-1", "");
118        assert!(data.graphql_apis.is_empty());
119        assert!(data.tags.is_empty());
120    }
121}