surrealdb_core/api/
response.rs1use 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 let v: i64 = v.coerce_to_int()?.as_int();
28
29 let v: u16 = v
31 .try_into()
32 .map_err(|_| Error::ArithmeticOverflow(format!("{v} as u16")))?;
33
34 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}