diff options
Diffstat (limited to 'src/api/store.rs')
| -rw-r--r-- | src/api/store.rs | 32 |
1 files changed, 28 insertions, 4 deletions
diff --git a/src/api/store.rs b/src/api/store.rs index 8ea0202..409a530 100644 --- a/src/api/store.rs +++ b/src/api/store.rs @@ -2,6 +2,7 @@ use std::io::Error as IoError; +use automerge::ChangeHash; use axum::{ body::{Body, Bytes}, extract::{Path, Query, State}, @@ -25,6 +26,10 @@ use crate::swarm::sync::paths_related; pub(crate) struct ReadQuery { #[serde(default)] conflicts: bool, + /// Return the document's heads at the time of the read (`x-continuum-heads`), e.g. + /// to base a replacing remake on exactly what was read. + #[serde(default)] + heads: bool, } #[derive(Debug, Deserialize)] @@ -47,6 +52,7 @@ fn conflicts_response( tag = "Store", params( ("conflicts" = Option<bool>, Query, description = "Wrap the value and list every conflict resolved on the way"), + ("heads" = Option<bool>, Query, description = "Return the document heads at the time of the read in x-continuum-heads"), ), responses( (status = 200, description = "Returns the complete store as JSON or text, depending on Accept"), @@ -62,9 +68,10 @@ pub(crate) async fn get_store_root( let accept = headers.get(ACCEPT).cloned(); let want_conflicts = query.conflicts; - let (response, source) = state + let (response, source, heads) = state .render_read( true, + query.heads, move |doc| { if want_conflicts { store::read_path_with_conflicts(doc, &Vec::new()) @@ -84,7 +91,7 @@ pub(crate) async fn get_store_root( }, ) .await?; - mark_source(response, source) + mark_source(response, source, heads) } #[utoipa::path( @@ -94,6 +101,7 @@ pub(crate) async fn get_store_root( params( ("key" = String, Path, description = "Slash-separated store path, for example settings/theme"), ("conflicts" = Option<bool>, Query, description = "Wrap the value and list every conflict resolved on the way"), + ("heads" = Option<bool>, Query, description = "Return the document heads at the time of the read in x-continuum-heads"), ), responses( (status = 200, description = "Returns the value as JSON or text, depending on Accept"), @@ -114,9 +122,10 @@ pub(crate) async fn get_store_entry( let accept = headers.get(ACCEPT).cloned(); let want_conflicts = query.conflicts; - let (response, source) = state + let (response, source, heads) = state .render_read( false, + query.heads, move |doc| { if want_conflicts { store::read_path_with_conflicts(doc, &path) @@ -136,17 +145,32 @@ pub(crate) async fn get_store_entry( }, ) .await?; - mark_source(response, source) + mark_source(response, source, heads) } /// Header on reads served from the pre-rejoin document; its value is that epoch. 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`). +const HEADS_HEADER: &str = "x-continuum-heads"; + fn mark_source( response: Result<Response, StatusCode>, source: ReadSource, + heads: Option<Vec<ChangeHash>>, ) -> Result<Response, StatusCode> { 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); + } + } if let ReadSource::Stale(epoch) = source { if let Ok(value) = HeaderValue::from_str(&epoch.to_string()) { response.headers_mut().insert(STALE_HEADER, value); |
