1#![deny(
70 warnings,
71 bad_style,
72 dead_code,
73 improper_ctypes,
74 non_shorthand_field_patterns,
75 no_mangle_generic_items,
76 overflowing_literals,
77 path_statements,
78 patterns_in_fns_without_body,
79 unconditional_recursion,
80 unused,
81 unused_allocation,
82 unused_comparisons,
83 unused_parens,
84 while_true,
85 missing_debug_implementations,
86 missing_docs,
87 trivial_casts,
88 trivial_numeric_casts,
89 unreachable_pub,
90 unused_extern_crates,
91 unused_import_braces,
92 unused_qualifications,
93 unused_results,
94 deprecated,
95 unknown_lints,
96 unreachable_code,
97 unused_mut
98)]
99
100mod error;
101pub use error::Error;
102
103pub mod table;
105
106pub mod methods;
108
109pub mod setup;
111
112pub use methods::DynamoTableMethods;
114pub use table::{CompositeKey, DynamoTable, GSITable};
115
116pub use aws_config::{
118 BehaviorVersion, Region, SdkConfig, defaults,
119 meta::region::{ProvideRegion, RegionProviderChain},
120 retry::{RetryConfig, RetryMode},
121 timeout::TimeoutConfig,
122};
123
124pub use aws_types::sdk_config::Builder as SdkConfigBuilder;
126
127use aws_sdk_dynamodb::Client as DynamoDbClient;
128use tokio::sync::OnceCell;
129
130static GLOBAL_CLIENT: OnceCell<DynamoDbClient> = OnceCell::const_new();
132
133async fn aws_config_defaults() -> SdkConfig {
147 use aws_config::BehaviorVersion;
148 use aws_types::sdk_config::{RetryConfig, TimeoutConfig};
149 use std::time::Duration;
150
151 let timeout_config = TimeoutConfig::builder()
152 .connect_timeout(Duration::from_secs(3))
153 .read_timeout(Duration::from_secs(20))
154 .operation_timeout(Duration::from_secs(60))
155 .build();
156
157 let mut loader = defaults(BehaviorVersion::latest())
158 .retry_config(
159 RetryConfig::adaptive()
160 .with_max_attempts(3)
161 .with_initial_backoff(Duration::from_secs(1)),
162 )
163 .timeout_config(timeout_config);
164
165 if std::env::var("AWS_PROFILE").unwrap_or_default() == "localstack" {
167 loader = loader.endpoint_url("http://127.0.0.1:4566");
168 }
169
170 loader.load().await
171}
172
173pub async fn init(config: &SdkConfig) {
192 let _ = GLOBAL_CLIENT
193 .get_or_init(|| async { DynamoDbClient::new(config) })
194 .await;
195}
196
197pub async fn init_with_client(client: DynamoDbClient) {
214 let _ = GLOBAL_CLIENT.get_or_init(|| async { client }).await;
215}
216
217pub async fn dynamodb_client() -> &'static DynamoDbClient {
258 GLOBAL_CLIENT
260 .get_or_init(|| async {
261 let config = aws_config_defaults().await;
262 DynamoDbClient::new(&config)
263 })
264 .await
265}
266
267#[cfg(debug_assertions)]
268pub(crate) fn assert_not_reserved_key(key: &str) {
269 #[rustfmt::skip]
271 const KEYS: [&str; 573] = [
272"abort", "absolute", "action", "add", "after", "agent", "aggregate", "all", "allocate", "alter", "analyze", "and", "any", "archive", "are", "array", "as", "asc", "ascii", "asensitive", "assertion", "asymmetric", "at", "atomic", "attach", "attribute", "auth", "authorization", "authorize", "auto", "avg", "back", "backup", "base", "batch", "before", "begin", "between", "bigint", "binary", "bit", "blob", "block", "boolean", "both", "breadth", "bucket", "bulk", "by", "byte", "call", "called", "calling", "capacity", "cascade", "cascaded", "case", "cast", "catalog", "char", "character", "check", "class", "clob", "close", "cluster", "clustered", "clustering", "clusters", "coalesce", "collate", "collation", "collection", "column", "columns", "combine", "comment", "commit", "compact", "compile", "compress", "condition", "conflict", "connect", "connection", "consistency", "consistent", "constraint", "constraints", "constructor", "consumed", "continue", "convert", "copy", "corresponding", "count", "counter", "create", "cross", "cube", "current", "cursor", "cycle", "data", "database", "date", "datetime", "day", "deallocate", "dec", "decimal", "declare", "default", "deferrable", "deferred", "define", "defined", "definition", "delete", "delimited", "depth", "deref", "desc", "describe", "descriptor", "detach", "deterministic", "diagnostics", "directories", "disable", "disconnect", "distinct", "distribute", "do", "domain", "double", "drop", "dump", "duration", "dynamic", "each", "element", "else", "elseif", "empty", "enable", "end", "equal", "equals", "error", "escape", "escaped", "eval", "evaluate", "exceeded", "except", "exception", "exceptions", "exclusive", "exec", "execute", "exists", "exit", "explain", "explode", "export", "expression", "extended", "external", "extract", "fail", "false", "family", "fetch", "fields", "file", "filter", "filtering", "final", "finish", "first", "fixed", "flattern", "float", "for", "force", "foreign", "format", "forward", "found", "free", "from", "full", "function", "functions", "general", "generate", "get", "glob", "global", "go", "goto", "grant", "greater", "group", "grouping", "handler", "hash", "have", "having", "heap", "hidden", "hold", "hour", "identified", "identity", "if", "ignore", "immediate", "import", "in", "including", "inclusive", "increment", "incremental", "index", "indexed", "indexes", "indicator", "infinite", "initially", "inline", "inner", "innter", "inout", "input", "insensitive", "insert", "instead", "int", "integer", "intersect", "interval", "into", "invalidate", "is", "isolation", "item", "items", "iterate", "join", "key", "keys", "lag", "language", "large", "last", "lateral", "lead", "leading", "leave", "left", "length", "less", "level", "like", "limit", "limited", "lines", "list", "load", "local", "localtime", "localtimestamp", "location", "locator", "lock", "locks", "log", "loged", "long", "loop", "lower", "map", "match", "materialized", "max", "maxlen", "member", "merge", "method", "metrics", "min", "minus", "minute", "missing", "mod", "mode", "modifies", "modify", "module", "month", "multi", "multiset", "name", "names", "national", "natural", "nchar", "nclob", "new", "next", "no", "none", "not", "null", "nullif", "number", "numeric", "object", "of", "offline", "offset", "old", "on", "online", "only", "opaque", "open", "operator", "option", "or", "order", "ordinality", "other", "others", "out", "outer", "output", "over", "overlaps", "override", "owner", "pad", "parallel", "parameter", "parameters", "partial", "partition", "partitioned", "partitions", "path", "percent", "percentile", "permission", "permissions", "pipe", "pipelined", "plan", "pool", "position", "precision", "prepare", "preserve", "primary", "prior", "private", "privileges", "procedure", "processed", "project", "projection", "property", "provisioning", "public", "put", "query", "quit", "quorum", "raise", "random", "range", "rank", "raw", "read", "reads", "real", "rebuild", "record", "recursive", "reduce", "ref", "reference", "references", "referencing", "regexp", "region", "reindex", "relative", "release", "remainder", "rename", "repeat", "replace", "request", "reset", "resignal", "resource", "response", "restore", "restrict", "result", "return", "returning", "returns", "reverse", "revoke", "right", "role", "roles", "rollback", "rollup", "routine", "row", "rows", "rule", "rules", "sample", "satisfies", "save", "savepoint", "scan", "schema", "scope", "scroll", "search", "second", "section", "segment", "segments", "select", "self", "semi", "sensitive", "separate", "sequence", "serializable", "session", "set", "sets", "shard", "share", "shared", "short", "show", "signal", "similar", "size", "skewed", "smallint", "snapshot", "some", "source", "space", "spaces", "sparse", "specific", "specifictype", "split", "sql", "sqlcode", "sqlerror", "sqlexception", "sqlstate", "sqlwarning", "start", "state", "static", "status", "storage", "store", "stored", "stream", "string", "struct", "style", "sub", "submultiset", "subpartition", "substring", "subtype", "sum", "super", "symmetric", "synonym", "system", "table", "tablesample", "temp", "temporary", "terminated", "text", "than", "then", "throughput", "time", "timestamp", "timezone", "tinyint", "to", "token", "total", "touch", "trailing", "transaction", "transform", "translate", "translation", "treat", "trigger", "trim", "true", "truncate", "ttl", "tuple", "type", "under", "undo", "union", "unique", "unit", "unknown", "unlogged", "unnest", "unprocessed", "unsigned", "until", "update", "upper", "url", "usage", "use", "user", "users", "using", "uuid", "vacuum", "value", "valued", "values", "varchar", "variable", "variance", "varint", "varying", "view", "views", "virtual", "void", "wait", "when", "whenever", "where", "while", "window", "with", "within", "without", "work", "wrapped", "write", "year", "zone "
273];
274
275 debug_assert!(!KEYS.contains(&key), "Reserved key: {key}");
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281
282 #[test]
283 fn test_composite_key_tuple() {
284 let key: CompositeKey<String, String> =
285 ("user123".to_string(), Some("order456".to_string()));
286 assert_eq!(key.0, "user123");
287 assert_eq!(key.1, Some("order456".to_string()));
288 }
289
290 #[test]
291 fn test_composite_key_no_sort_key() {
292 let key: CompositeKey<String, String> = ("user123".to_string(), None);
293 assert_eq!(key.0, "user123");
294 assert_eq!(key.1, None);
295 }
296
297 #[cfg(debug_assertions)]
298 #[test]
299 #[should_panic(expected = "Reserved key: user")]
300 fn test_assert_reserved_key_panics() {
301 assert_not_reserved_key("user");
302 }
303
304 #[cfg(debug_assertions)]
305 #[test]
306 fn test_assert_not_reserved_key_ok() {
307 assert_not_reserved_key("user_id");
308 assert_not_reserved_key("custom_field");
309 }
310}