Skip to main content

tuwunel_api/client/rendezvous/
put.rs

1use 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}