1use serde::{Deserialize, Serialize};
4
5pub const WEBSOCKET_VERSION: u16 = 3;
6
7#[derive(Clone, Debug, Serialize, Deserialize)]
8#[serde(deny_unknown_fields)]
9pub struct Envelope<T> {
10 #[serde(rename = "type")]
11 pub kind: String,
12 pub data: T,
13}
14
15impl<T> Envelope<T> {
16 pub fn new(kind: impl Into<String>, data: T) -> Self {
17 Self {
18 kind: kind.into(),
19 data,
20 }
21 }
22}
23
24pub fn http_type(method: &str, path: &str) -> &'static str {
26 let path = path.split('?').next().unwrap_or(path);
27 let path = path
28 .strip_prefix("/api/v1/")
29 .unwrap_or(path)
30 .trim_matches('/');
31 let p: Vec<_> = path.split('/').collect();
32 match (method, p.as_slice()) {
33 (_, ["iam"]) => "iam",
34 (_, ["auth", "login"]) => "login",
35 (_, ["auth", "refresh"]) => "refresh",
36 (_, ["auth", "logout"]) => "logout",
37 (_, ["auth", "me"]) => "me",
38 ("POST", ["conversations"]) => "create_conversation",
39 (_, ["conversations"]) => "conversations",
40 ("POST", ["conversations", _, "messages"]) => "new_message",
41 (_, ["conversations", _, "messages"]) => "messages",
42 ("PATCH", ["conversations", _, "messages", _]) => "edit_message",
43 ("DELETE", ["conversations", _, "messages", _]) => "delete_message",
44 (_, ["conversations", _, "messages", _]) => "message",
45 (_, ["conversations", _, "messages", _, "receipts"]) => "receipt",
46 (_, ["conversations", _, "bundles"]) => "create_bundle",
47 (_, ["conversations", _, "bundles", _]) => "bundle",
48 ("PUT", ["conversations", _, "draft"]) => "put_draft",
49 ("DELETE", ["conversations", _, "draft"]) => "delete_draft",
50 (_, ["conversations", _, "draft"]) => "draft",
51 (_, ["presence", _]) => "presence",
52 (_, ["gifs", _]) => "gifs",
53 ("POST", ["testing-environments"]) => "create_testing_environment",
54 (_, ["testing-environments"]) => "testing_environments",
55 ("PATCH", ["testing-environments", _]) => "update_testing_environment",
56 ("DELETE", ["testing-environments", _]) => "delete_testing_environment",
57 (_, ["testing-environments", _]) => "testing_environment",
58 (_, ["testing-environments", _, "key"]) => "testing_environment_key",
59 (_, ["testing-environments", _, "rotate-key"]) => "rotate_testing_environment_key",
60 (_, ["testing-environments", _, "restore"]) => "restore_testing_environment",
61 (_, ["testing-environments", _, "clean"]) => "clean_testing_environment",
62 (_, ["requests"]) => "request",
63 (_, ["requests", _, "status"]) => "request_status",
64 (_, ["requests", _]) => "request_result",
65 (_, ["status"]) => "relay_status",
66 (_, ["shutdown"]) => "shutdown",
67 _ => "response",
68 }
69}
70
71#[cfg(feature = "http")]
73pub async fn responses(
74 request: axum::extract::Request,
75 next: axum::middleware::Next,
76) -> axum::response::Response {
77 use axum::{
78 body::{Body, Bytes},
79 http::header,
80 };
81 use futures_util::{StreamExt, stream};
82 let kind = http_type(request.method().as_str(), request.uri().path());
83 let response = next.run(request).await;
84 if !response
85 .headers()
86 .get(header::CONTENT_TYPE)
87 .and_then(|h| h.to_str().ok())
88 .is_some_and(|h| h.starts_with("application/json"))
89 {
90 if response.status().is_client_error() || response.status().is_server_error() {
91 use axum::response::IntoResponse;
92 let (parts, _) = response.into_parts();
93 let body = axum::Json(Envelope::new("error", serde_json::json!({"error":{
94 "code":"http_error", "message":parts.status.canonical_reason().unwrap_or("request failed")
95 }}))).into_response().into_body();
96 let mut response = axum::response::Response::from_parts(parts, body);
97 response.headers_mut().remove(header::CONTENT_LENGTH);
98 response.headers_mut().insert(
99 header::CONTENT_TYPE,
100 header::HeaderValue::from_static("application/json"),
101 );
102 return response;
103 }
104 return response;
105 }
106 let kind = if response.status().is_success() {
107 kind
108 } else {
109 "error"
110 };
111 let (mut parts, body) = response.into_parts();
112 parts.headers.remove(header::CONTENT_LENGTH);
113 let prefix = Bytes::from(format!("{{\"type\":\"{kind}\",\"data\":"));
114 let stream = stream::once(async { Ok::<_, axum::Error>(prefix) })
115 .chain(body.into_data_stream())
116 .chain(stream::once(async { Ok(Bytes::from_static(b"}")) }));
117 axum::response::Response::from_parts(parts, Body::from_stream(stream))
118}