surrealdb_core/api/
response.rs

1use http::{
2	header::{ACCEPT, CONTENT_TYPE},
3	HeaderMap, StatusCode,
4};
5
6use crate::{err::Error, rpc::format::Format, sql::Value};
7
8use super::{err::ApiError, invocation::ApiInvocation};
9
10#[derive(Debug)]
11pub struct ApiResponse {
12	pub raw: Option<bool>,
13	pub status: StatusCode,
14	pub body: Option<Value>,
15	pub headers: HeaderMap,
16}
17
18impl TryFrom<Value> for ApiResponse {
19	type Error = Error;
20	fn try_from(value: Value) -> Result<Self, Self::Error> {
21		if let Value::Object(mut opts) = value {
22			let raw = opts.remove("raw").map(|v| v.convert_to_bool()).transpose()?;
23			let status = opts
24				.remove("status")
25				.map(|v| -> Result<StatusCode, Error> {
26					// Convert to int
27					let v: i64 = v.coerce_to_int()?.as_int();
28
29					// Convert to u16
30					let v: u16 = v
31						.try_into()
32						.map_err(|_| Error::ArithmeticOverflow(format!("{v} as u16")))?;
33
34					// Convert to StatusCode
35					v.try_into().map_err(|_| {
36						ApiError::InvalidApiResponse(format!("{v} is not a valid HTTP status code"))
37							.into()
38					})
39				})
40				.transpose()?
41				.unwrap_or(StatusCode::OK);
42
43			let headers = opts
44				.remove("headers")
45				.map(|v| v.coerce_to_object()?.try_into())
46				.transpose()?
47				.unwrap_or_default();
48
49			let body = opts.remove("body");
50
51			if opts.len() > 0 {
52				Err(ApiError::InvalidApiResponse("Contains invalid properties".into()).into())
53			} else {
54				Ok(Self {
55					raw,
56					status,
57					body,
58					headers,
59				})
60			}
61		} else {
62			Err(ApiError::InvalidApiResponse("Expected an object".into()).into())
63		}
64	}
65}
66
67impl TryInto<Value> for ApiResponse {
68	type Error = Error;
69	fn try_into(self) -> Result<Value, Error> {
70		Ok(Value::Object(
71			map! {
72				"raw" => Value::from(self.raw.unwrap_or(false)),
73				"status" => Value::from(self.status.as_u16() as i64),
74				"headers" => Value::Object(self.headers.try_into()?),
75				"body", if let Some(body) = self.body => body,
76			}
77			.into(),
78		))
79	}
80}
81
82pub enum ResponseInstruction {
83	Native,
84	Raw,
85	Format(Format),
86}
87
88impl ResponseInstruction {
89	pub fn for_format(invocation: &ApiInvocation) -> Result<Self, Error> {
90		let mime = invocation
91			.headers
92			.get(ACCEPT)
93			.or_else(|| invocation.headers.get(CONTENT_TYPE))
94			.and_then(|v| v.to_str().ok());
95
96		let format = match mime {
97			Some("application/json") => Format::Json,
98			Some("application/cbor") => Format::Cbor,
99			Some("application/pack") => Format::Msgpack,
100			Some("application/surrealdb") => Format::Revision,
101			Some(_) => return Err(Error::ApiError(ApiError::InvalidFormat)),
102			_ => return Err(Error::ApiError(ApiError::MissingFormat)),
103		};
104
105		Ok(ResponseInstruction::Format(format))
106	}
107}