From 3e115a7e9cc5846d6c1025a7297a006c2d64709b Mon Sep 17 00:00:00 2001 From: tv Date: Sun, 1 Nov 2026 22:29:54 +0100 Subject: api: add support for counters --- src/api/mod.rs | 7 ++++--- src/api/store.rs | 27 +++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 5 deletions(-) (limited to 'src/api') diff --git a/src/api/mod.rs b/src/api/mod.rs index 8ce855e..dfefcc8 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -201,9 +201,10 @@ impl IntoResponse for ApiError { /// client's; others are logged with `context`, and are a bare `500`. pub(crate) fn store_status(error: StoreError, context: &str) -> ApiError { let status = match error { - StoreError::Conflict | StoreError::NotAList | StoreError::PastTheEnd { .. } => { - StatusCode::CONFLICT - } + StoreError::Conflict + | StoreError::NotAList + | StoreError::PastTheEnd { .. } + | StoreError::NotACounter => StatusCode::CONFLICT, StoreError::NotFound | StoreError::UnknownHeads => StatusCode::NOT_FOUND, StoreError::Rejoining => StatusCode::SERVICE_UNAVAILABLE, other => { diff --git a/src/api/store.rs b/src/api/store.rs index 8b09487..7039008 100644 --- a/src/api/store.rs +++ b/src/api/store.rs @@ -77,6 +77,13 @@ macro_rules! read_at { }; } +#[derive(Debug, Deserialize)] +pub(crate) struct PostQuery { + /// Adds this to the counter at the path instead of inserting the body + /// (`store::increment_path`). + increment: Option, +} + #[derive(Debug, Deserialize)] pub(crate) struct DeleteQuery { #[serde(default)] @@ -355,11 +362,14 @@ fn body_value(headers: &HeaderMap, body: &Bytes) -> Result, Extension(user): Extension, Path(key): Path, + Query(query): Query, RawQuery(raw_query): RawQuery, headers: HeaderMap, body: Bytes, @@ -368,9 +378,22 @@ pub(crate) async fn post_store_entry( return Err(StatusCode::SERVICE_UNAVAILABLE.into()); } let path = parse_write_path(&state, &user, &key, "POST")?; - if let Some(redirect) = to_positive(&state, &path, true, None, raw_query.as_deref()).await? { + // An increment names an item (`-1`: the last); an insert where it goes (`-1`: the end). + let insertion = query.increment.is_none(); + if let Some(redirect) = + to_positive(&state, &path, insertion, None, raw_query.as_deref()).await? + { return Ok(redirect); } + if let Some(by) = query.increment { + let value = 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)); + } let index = path.last().and_then(|leaf| store::list_index(leaf)); let value = body_value(&headers, &body)?; let inserted = state -- cgit v1.3.1