diff options
| author | tv <tv@krebsco.de> | 2026-11-01 23:09:28 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-01 23:09:28 +0100 |
| commit | f3c53c499cd745b214ee2b75a6d80424cec669fd (patch) | |
| tree | 10710e9231108ac8ca85c4f2c031f078c038351a /src/api/store.rs | |
| parent | 8fc0bb43e15e9ffa99777e478162c101557d505b (diff) | |
api: add x-continuum-heads to writes
Diffstat (limited to 'src/api/store.rs')
| -rw-r--r-- | src/api/store.rs | 55 |
1 files changed, 35 insertions, 20 deletions
diff --git a/src/api/store.rs b/src/api/store.rs index 7039008..1478d14 100644 --- a/src/api/store.rs +++ b/src/api/store.rs @@ -203,9 +203,22 @@ async fn shallow( const STALE_HEADER: &str = "x-continuum-stale"; /// Header with the document's heads at the time of the read (sorted, comma-separated -/// hex), on request (`?heads=true`). +/// hex), on request (`?heads=true`); and on every write, after it: a read with these +/// (`?at=`) includes the write. const HEADS_HEADER: &str = "x-continuum-heads"; +fn with_heads(mut response: Response, heads: &[ChangeHash]) -> Response { + let joined = heads + .iter() + .map(ToString::to_string) + .collect::<Vec<_>>() + .join(","); + if let Ok(value) = HeaderValue::from_str(&joined) { + response.headers_mut().insert(HEADS_HEADER, value); + } + response +} + fn mark_source( response: Result<Response, ApiError>, source: ReadSource, @@ -213,14 +226,7 @@ fn mark_source( ) -> Result<Response, ApiError> { let mut response = response?; if let Some(heads) = heads { - let joined = heads - .iter() - .map(ToString::to_string) - .collect::<Vec<_>>() - .join(","); - if let Ok(value) = HeaderValue::from_str(&joined) { - response.headers_mut().insert(HEADS_HEADER, value); - } + response = with_heads(response, &heads); } if let ReadSource::Stale(epoch) = source && let Ok(value) = HeaderValue::from_str(&epoch.to_string()) @@ -250,14 +256,17 @@ pub(crate) async fn put_store_entry( let value = body_value(&headers, &body)?; let written = value.clone(); - state + let ((), heads) = state .mutate_doc_for(Some(user.0.clone()), move |doc| { store::write_path(doc, &path, &written) }) .await .map_err(|e| store_status(e, "Failed to write store value"))?; - response_for_value(&value, headers.get(ACCEPT)) + Ok(with_heads( + response_for_value(&value, headers.get(ACCEPT))?, + &heads, + )) } fn etag_for(bytes: &[u8]) -> String { @@ -386,13 +395,16 @@ pub(crate) async fn post_store_entry( return Ok(redirect); } if let Some(by) = query.increment { - let value = state + let (value, heads) = state .mutate_doc_for(Some(user.0.clone()), move |doc| { store::increment_path(doc, &path, by) }) .await .map_err(|e| store_status(e, "Failed to increment a counter"))?; - return response_for_value(&JsonValue::from(value), headers.get(ACCEPT)); + return Ok(with_heads( + response_for_value(&JsonValue::from(value), headers.get(ACCEPT))?, + &heads, + )); } let index = path.last().and_then(|leaf| store::list_index(leaf)); let value = body_value(&headers, &body)?; @@ -402,11 +414,14 @@ pub(crate) async fn post_store_entry( }) .await; match inserted { - Ok(()) => Ok(( - StatusCode::CREATED, - axum::Json(serde_json::json!({ "index": index })), - ) - .into_response()), + Ok(((), heads)) => Ok(with_heads( + ( + StatusCode::CREATED, + axum::Json(serde_json::json!({ "index": index })), + ) + .into_response(), + &heads, + )), Err(error) => Err(store_status(error, "Failed to insert into a list")), } } @@ -437,14 +452,14 @@ pub(crate) async fn delete_store_entry( } let prune = query.prune; - state + let ((), heads) = state .mutate_doc_for(Some(user.0.clone()), move |doc| { store::delete_path(doc, &path, prune) }) .await .map_err(|e| store_status(e, "Failed to delete store value"))?; - Ok(StatusCode::NO_CONTENT.into_response()) + Ok(with_heads(StatusCode::NO_CONTENT.into_response(), &heads)) } /// A path with negative list indexes (`-1` the last item; for an insert, the end): a |
