tuwunel_api/client/rendezvous/
put.rs1use axum::{
2 Json,
3 body::Body,
4 extract::{Path, Request, State},
5 response::{IntoResponse, Response},
6};
7use http::{StatusCode, header::IF_MATCH};
8use serde::Serialize;
9use tuwunel_core::{Err, Result, err};
10use tuwunel_service::rendezvous::Put;
11
12use super::{TEXT_PLAIN, ensure_enabled, read_plain_body, session_headers, session_response};
13
14#[derive(Serialize)]
15struct ConcurrentWriteResponse {
16 errcode: &'static str,
17 error: &'static str,
18
19 #[serde(rename = "org.matrix.msc4108.errcode")]
20 unstable_errcode: &'static str,
21}
22
23#[tracing::instrument(level = "debug", skip_all)]
24pub(crate) async fn put_rendezvous_route(
25 State(services): State<crate::State>,
26 Path(id): Path<String>,
27 request: Request,
28) -> Result<Response> {
29 ensure_enabled(&services)?;
30
31 let max_bytes = services.config.rendezvous_session_max_bytes;
32 let (parts, body) = request.into_parts();
33 let data = read_plain_body(&parts.headers, body, max_bytes).await?;
34 let if_match = parts
35 .headers
36 .get(IF_MATCH)
37 .ok_or_else(|| err!(Request(MissingParam("Missing required header: if-match"))))?
38 .to_str()
39 .map_err(|_| err!(Request(InvalidParam("If-Match must be a valid ETag"))))?;
40
41 match services.rendezvous.put(&id, if_match, data) {
42 | Put::NotFound => Err!(Request(NotFound("Rendezvous session not found"))),
43 | Put::Accepted(meta) =>
44 session_response(StatusCode::ACCEPTED, Body::empty(), Some(TEXT_PLAIN), &meta),
45 | Put::PreconditionFailed(meta) => {
46 let body = ConcurrentWriteResponse {
47 errcode: "M_UNKNOWN",
48 error: "ETag does not match",
49 unstable_errcode: "M_CONCURRENT_WRITE",
50 };
51 let mut response = (StatusCode::PRECONDITION_FAILED, Json(body)).into_response();
52
53 session_headers(response.headers_mut(), &meta)?;
54
55 Ok(response)
56 },
57 }
58}