a2a_protocol_server/store/
mod.rs1pub mod task_store;
9pub mod tenant;
10
11#[cfg(any(feature = "sqlite", feature = "postgres"))]
13pub(crate) mod cursor;
14
15pub(crate) mod pagination;
17
18#[cfg(feature = "sqlite")]
19pub mod migration;
20#[cfg(feature = "sqlite")]
21pub mod sqlite_store;
22#[cfg(feature = "sqlite")]
23pub mod tenant_sqlite_store;
24
25#[cfg(feature = "postgres")]
26pub mod pg_migration;
27#[cfg(feature = "postgres")]
28pub mod postgres_store;
29#[cfg(feature = "postgres")]
30pub mod tenant_postgres_store;
31
32pub use task_store::{InMemoryTaskStore, TaskStore, TaskStoreConfig};
33pub use tenant::{TenantAwareInMemoryTaskStore, TenantContext, TenantStoreConfig};
34
35#[cfg(feature = "sqlite")]
48pub(crate) fn status_timestamp_sqlite(ts: Option<&str>) -> Option<String> {
49 let millis = ts.and_then(a2a_protocol_types::parse_iso8601_to_unix_millis)?;
50 let iso = a2a_protocol_types::unix_millis_to_iso8601(millis);
51 Some(format!("{} {}", &iso[..10], &iso[11..23]))
53}
54
55#[cfg(feature = "postgres")]
60pub(crate) fn status_timestamp_rfc3339(ts: Option<&str>) -> Option<String> {
61 let millis = ts.and_then(a2a_protocol_types::parse_iso8601_to_unix_millis)?;
62 Some(a2a_protocol_types::unix_millis_to_iso8601(millis))
63}
64
65#[cfg(feature = "sqlite")]
66pub use migration::{Migration, MigrationRunner};
67#[cfg(feature = "sqlite")]
68pub use sqlite_store::SqliteTaskStore;
69#[cfg(feature = "sqlite")]
70pub use tenant_sqlite_store::TenantAwareSqliteTaskStore;
71
72#[cfg(feature = "postgres")]
73pub use pg_migration::{PgMigration, PgMigrationRunner};
74#[cfg(feature = "postgres")]
75pub use postgres_store::PostgresTaskStore;
76#[cfg(feature = "postgres")]
77pub use tenant_postgres_store::TenantAwarePostgresTaskStore;
78
79#[cfg(test)]
80mod status_timestamp_tests {
81 #[cfg(feature = "sqlite")]
88 #[test]
89 fn sqlite_shape_is_the_column_format() {
90 assert_eq!(
91 super::status_timestamp_sqlite(Some("2026-03-15T12:00:00.123Z")).as_deref(),
92 Some("2026-03-15 12:00:00.123"),
93 );
94 assert_eq!(
97 super::status_timestamp_sqlite(Some("2026-03-15T12:00:00Z")).as_deref(),
98 Some("2026-03-15 12:00:00.000"),
99 );
100 }
101
102 #[cfg(feature = "sqlite")]
103 #[test]
104 fn sqlite_returns_none_for_missing_or_unparseable() {
105 assert_eq!(super::status_timestamp_sqlite(None), None);
106 assert_eq!(super::status_timestamp_sqlite(Some("")), None);
107 assert_eq!(
108 super::status_timestamp_sqlite(Some("not a timestamp")),
109 None
110 );
111 assert_eq!(super::status_timestamp_sqlite(Some("2026-03-15")), None);
112 }
113
114 #[cfg(feature = "sqlite")]
115 #[test]
116 fn sqlite_slicing_survives_out_of_range_years() {
117 for input in [
122 "99999-01-01T00:00:00Z",
123 "999999-12-31T23:59:59.999Z",
124 "1969-12-31T23:59:59Z",
125 "0001-01-01T00:00:00Z",
126 "+010000-01-01T00:00:00Z",
127 ] {
128 let out = super::status_timestamp_sqlite(Some(input));
129 if let Some(s) = out {
130 assert!(
131 s.is_char_boundary(0) && s.len() >= 23,
132 "{input} produced a malformed value: {s:?}"
133 );
134 }
135 }
136 }
137
138 #[cfg(feature = "postgres")]
139 #[test]
140 fn rfc3339_normalizes_to_canonical_utc() {
141 assert_eq!(
142 super::status_timestamp_rfc3339(Some("2026-03-15T12:00:00.123Z")).as_deref(),
143 Some("2026-03-15T12:00:00.123Z"),
144 );
145 assert_eq!(
149 super::status_timestamp_rfc3339(Some("2026-03-15T14:00:00+02:00")).as_deref(),
150 Some("2026-03-15T12:00:00.000Z"),
151 );
152 assert_eq!(super::status_timestamp_rfc3339(None), None);
153 assert_eq!(super::status_timestamp_rfc3339(Some("nope")), None);
154 }
155}