diff options
| author | tv <tv@krebsco.de> | 2026-10-30 20:34:47 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-10-30 20:34:47 +0100 |
| commit | a29a38db0ad7fc8d42a533145c8a489d97702fb5 (patch) | |
| tree | ae1689804484d2aceabf106bb82cd4f0fcf97b36 /src/api/store.rs | |
| parent | caf212f916ce8fb31d7dba8bbe6d537092d324d5 (diff) | |
add support to insert values at list index
Diffstat (limited to 'src/api/store.rs')
| -rw-r--r-- | src/api/store.rs | 76 |
1 files changed, 61 insertions, 15 deletions
diff --git a/src/api/store.rs b/src/api/store.rs index 7e8caed..89123e2 100644 --- a/src/api/store.rs +++ b/src/api/store.rs @@ -39,6 +39,13 @@ pub(crate) struct ReadQuery { } #[derive(Debug, Deserialize)] +pub(crate) struct InsertQuery { + /// Where in the list: the items from there on move up one. At the end without one, + /// or with `-1`. + index: Option<i64>, +} + +#[derive(Debug, Deserialize)] pub(crate) struct DeleteQuery { #[serde(default)] prune: bool, @@ -202,7 +209,19 @@ pub(crate) async fn put_store_entry( } let path = parse_write_path(&state, &user, &key, "PUT")?; + let value = body_value(&headers, &body)?; + + let written = value.clone(); + state + .mutate_doc(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)) +} + +/// A write's body as a value: JSON (the default), or text as a string. +fn body_value(headers: &HeaderMap, body: &Bytes) -> Result<JsonValue, StatusCode> { let content_type = headers .get(CONTENT_TYPE) .and_then(|value| value.to_str().ok()) @@ -211,24 +230,51 @@ pub(crate) async fn put_store_entry( .next() .unwrap_or("") .trim(); - - let value = match content_type { + match content_type { "application/json" => { - serde_json::from_slice::<JsonValue>(&body).map_err(|_| StatusCode::BAD_REQUEST)? + serde_json::from_slice::<JsonValue>(body).map_err(|_| StatusCode::BAD_REQUEST) } - "text/plain" => JsonValue::String( - String::from_utf8(body.to_vec()).map_err(|_| StatusCode::BAD_REQUEST)?, - ), - _ => return Err(StatusCode::UNSUPPORTED_MEDIA_TYPE), - }; - - let written = value.clone(); - state - .mutate_doc(move |doc| store::write_path(doc, &path, &written)) - .await - .map_err(|e| store_status(e, "Failed to write store value"))?; + "text/plain" => String::from_utf8(body.to_vec()) + .map(JsonValue::String) + .map_err(|_| StatusCode::BAD_REQUEST), + _ => Err(StatusCode::UNSUPPORTED_MEDIA_TYPE), + } +} - response_for_value(&value, headers.get(ACCEPT)) +/// `POST /store/{key}`: inserts the body into the list at `key` (`store::insert_path`), +/// at `?index=` or at the end; `201` with where it went (`{"index": n}`). No list there, +/// or an index past its end: `409`, saying which. +pub(crate) async fn post_store_entry( + State(state): State<AppState>, + Extension(user): Extension<AuthenticatedUser>, + Path(key): Path<String>, + Query(query): Query<InsertQuery>, + headers: HeaderMap, + body: Bytes, +) -> Result<Response, StatusCode> { + if !state.is_ready().await { + return Err(StatusCode::SERVICE_UNAVAILABLE); + } + let path = parse_write_path(&state, &user, &key, "POST")?; + let index = match query.index { + None | Some(-1) => None, + Some(index) => Some(usize::try_from(index).map_err(|_| StatusCode::BAD_REQUEST)?), + }; + let value = body_value(&headers, &body)?; + let inserted = state + .mutate_doc(move |doc| store::insert_path(doc, &path, index, &value)) + .await; + match inserted { + Ok(index) => Ok(( + StatusCode::CREATED, + axum::Json(serde_json::json!({ "index": index })), + ) + .into_response()), + Err(error @ (StoreError::NotAList | StoreError::PastTheEnd { .. })) => { + Ok((StatusCode::CONFLICT, error.to_string()).into_response()) + } + Err(error) => Err(store_status(error, "Failed to insert into a list")), + } } /// `DELETE /store/{key}`. |
