a2a_protocol_server/store/
mod.rs1pub mod retention;
9pub mod task_store;
10pub mod tenant;
11
12#[cfg(any(feature = "sqlite", feature = "postgres"))]
14pub(crate) mod cursor;
15
16pub(crate) mod pagination;
18
19#[cfg(feature = "sqlite")]
20pub mod migration;
21#[cfg(feature = "sqlite")]
22pub mod sqlite_store;
23#[cfg(feature = "sqlite")]
24pub mod tenant_sqlite_store;
25
26#[cfg(feature = "postgres")]
27pub mod pg_migration;
28#[cfg(feature = "postgres")]
29pub mod postgres_store;
30#[cfg(feature = "postgres")]
31pub mod tenant_postgres_store;
32
33pub use retention::{terminal_states, PurgeReport, RetentionPolicy};
34pub use task_store::{
35 ArtifactDelta, InMemoryTaskStore, TaskStore, TaskStoreConfig, DEFAULT_MAX_PAGE_SIZE,
36};
37pub use tenant::{TenantAwareInMemoryTaskStore, TenantContext, TenantStoreConfig};
38
39#[cfg(feature = "sqlite")]
52pub(crate) fn status_timestamp_sqlite(ts: Option<&str>) -> Option<String> {
53 let millis = ts.and_then(a2a_protocol_types::parse_iso8601_to_unix_millis)?;
54 let iso = a2a_protocol_types::unix_millis_to_iso8601(millis);
55 Some(format!("{} {}", &iso[..10], &iso[11..23]))
57}
58
59#[cfg(feature = "postgres")]
64pub(crate) fn status_timestamp_rfc3339(ts: Option<&str>) -> Option<String> {
65 let millis = ts.and_then(a2a_protocol_types::parse_iso8601_to_unix_millis)?;
66 Some(a2a_protocol_types::unix_millis_to_iso8601(millis))
67}
68
69#[cfg(feature = "sqlite")]
70pub use migration::{Migration, MigrationRunner};
71#[cfg(feature = "sqlite")]
72pub use sqlite_store::SqliteTaskStore;
73#[cfg(feature = "sqlite")]
74pub use tenant_sqlite_store::TenantAwareSqliteTaskStore;
75
76#[cfg(feature = "postgres")]
77pub use pg_migration::{PgMigration, PgMigrationRunner};
78#[cfg(feature = "postgres")]
79pub use postgres_store::PostgresTaskStore;
80#[cfg(feature = "postgres")]
81pub use tenant_postgres_store::TenantAwarePostgresTaskStore;
82
83#[cfg(test)]
84mod status_timestamp_tests {
85 #[cfg(feature = "sqlite")]
92 #[test]
93 fn sqlite_shape_is_the_column_format() {
94 assert_eq!(
95 super::status_timestamp_sqlite(Some("2026-03-15T12:00:00.123Z")).as_deref(),
96 Some("2026-03-15 12:00:00.123"),
97 );
98 assert_eq!(
101 super::status_timestamp_sqlite(Some("2026-03-15T12:00:00Z")).as_deref(),
102 Some("2026-03-15 12:00:00.000"),
103 );
104 }
105
106 #[cfg(feature = "sqlite")]
107 #[test]
108 fn sqlite_returns_none_for_missing_or_unparseable() {
109 assert_eq!(super::status_timestamp_sqlite(None), None);
110 assert_eq!(super::status_timestamp_sqlite(Some("")), None);
111 assert_eq!(
112 super::status_timestamp_sqlite(Some("not a timestamp")),
113 None
114 );
115 assert_eq!(super::status_timestamp_sqlite(Some("2026-03-15")), None);
116 }
117
118 #[cfg(feature = "sqlite")]
119 #[test]
120 fn sqlite_slicing_survives_out_of_range_years() {
121 for input in [
126 "99999-01-01T00:00:00Z",
127 "999999-12-31T23:59:59.999Z",
128 "1969-12-31T23:59:59Z",
129 "0001-01-01T00:00:00Z",
130 "+010000-01-01T00:00:00Z",
131 ] {
132 let out = super::status_timestamp_sqlite(Some(input));
133 if let Some(s) = out {
134 assert!(
135 s.is_char_boundary(0) && s.len() >= 23,
136 "{input} produced a malformed value: {s:?}"
137 );
138 }
139 }
140 }
141
142 #[cfg(feature = "postgres")]
143 #[test]
144 fn rfc3339_normalizes_to_canonical_utc() {
145 assert_eq!(
146 super::status_timestamp_rfc3339(Some("2026-03-15T12:00:00.123Z")).as_deref(),
147 Some("2026-03-15T12:00:00.123Z"),
148 );
149 assert_eq!(
153 super::status_timestamp_rfc3339(Some("2026-03-15T14:00:00+02:00")).as_deref(),
154 Some("2026-03-15T12:00:00.000Z"),
155 );
156 assert_eq!(super::status_timestamp_rfc3339(None), None);
157 assert_eq!(super::status_timestamp_rfc3339(Some("nope")), None);
158 }
159}