use a2a_protocol_types::artifact::Artifact;
use a2a_protocol_types::error::{A2aError, A2aResult};
use a2a_protocol_types::task::Task;
use super::{to_a2a_error, PostgresTaskStore};
pub(super) const fn append_delta_applies(artifact: &Artifact, count: usize) -> bool {
count != 0 && artifact.parts.len() >= count
}
pub(super) const fn is_last_position(index: usize, artifacts: &[Artifact]) -> bool {
index + 1 == artifacts.len()
}
impl PostgresTaskStore {
pub(super) async fn append_parts(
&self,
task: &Task,
artifacts: &[Artifact],
index: usize,
count: usize,
) -> A2aResult<Option<u64>> {
let Some(artifact) = artifacts.get(index) else {
return Ok(None);
};
if !append_delta_applies(artifact, count) {
return Ok(None);
}
let tail = &artifact.parts[artifact.parts.len() - count..];
let payload = serde_json::to_value(tail)
.map_err(|e| A2aError::internal(format!("failed to serialize parts: {e}")))?;
let Ok(idx) = i32::try_from(index) else {
return Ok(None);
};
let rows = sqlx::query(
"UPDATE tasks SET data = jsonb_set(\
data, ARRAY['artifacts', $3::text, 'parts'], \
(data->'artifacts'->($3::int)->'parts') || $1::jsonb) \
WHERE id = $2 \
AND jsonb_typeof(data->'artifacts') = 'array' \
AND jsonb_typeof(data->'artifacts'->($3::int)->'parts') = 'array'",
)
.bind(&payload)
.bind(task.id.0.as_str())
.bind(idx)
.execute(&self.pool)
.await
.map_err(to_a2a_error)?
.rows_affected();
Ok(Some(rows))
}
pub(super) async fn push_artifact(
&self,
task: &Task,
artifacts: &[Artifact],
index: usize,
) -> A2aResult<Option<u64>> {
if !is_last_position(index, artifacts) {
return Ok(None);
}
let Some(artifact) = artifacts.get(index) else {
return Ok(None);
};
let payload = serde_json::to_value(std::slice::from_ref(artifact))
.map_err(|e| A2aError::internal(format!("failed to serialize artifact: {e}")))?;
let rows = sqlx::query(
"UPDATE tasks SET data = jsonb_set(\
data, ARRAY['artifacts'], (data->'artifacts') || $1::jsonb) \
WHERE id = $2 AND jsonb_typeof(data->'artifacts') = 'array'",
)
.bind(&payload)
.bind(task.id.0.as_str())
.execute(&self.pool)
.await
.map_err(to_a2a_error)?
.rows_affected();
Ok(Some(rows))
}
}
#[cfg(test)]
mod tests {
use super::*;
use a2a_protocol_types::message::Part;
fn artifact_with(parts: usize) -> Artifact {
Artifact::new(
"art",
(0..parts)
.map(|i| Part::text(format!("p{i}")))
.collect::<Vec<_>>(),
)
}
#[test]
fn append_delta_boundary_accepts_exact_and_rejects_overclaim() {
let art = artifact_with(3);
assert!(
append_delta_applies(&art, 3),
"a delta covering every part must be accepted"
);
assert!(
append_delta_applies(&art, 1),
"a delta covering the tail must be accepted"
);
assert!(
!append_delta_applies(&art, 4),
"a delta claiming more parts than exist must fall back"
);
assert!(
!append_delta_applies(&art, 0),
"a zero-count delta describes nothing and must fall back"
);
}
#[test]
fn push_is_last_position_only() {
let two = vec![artifact_with(1), artifact_with(1)];
assert!(
is_last_position(1, &two),
"index 1 of 2 is the last position"
);
assert!(
!is_last_position(0, &two),
"index 0 of 2 is not the last position"
);
let one = vec![artifact_with(1)];
assert!(
is_last_position(0, &one),
"the sole artifact is at the last position"
);
let empty: Vec<Artifact> = Vec::new();
assert!(
!is_last_position(0, &empty),
"no position is last in an empty vector"
);
}
}