surrealdb-core 3.2.2

A scalable, distributed, collaborative, document-graph database, for the realtime web
Documentation
use anyhow::Result;
use headers::{ContentType, HeaderMapExt};
use mime::{APPLICATION_JSON, APPLICATION_OCTET_STREAM, Mime, TEXT_PLAIN};
use surrealdb_types::Value;

use super::common::{APPLICATION_CBOR, APPLICATION_SDB_FB};
use crate::api::err::ApiError;
use crate::api::middleware::common::{APPLICATION_SDB_NATIVE, BodyStrategy};
use crate::api::request::ApiRequest;
use crate::kvs::IntoBytes;
use crate::rpc::format;

pub struct BodyParser<'a> {
	mime: Option<Mime>,
	req: &'a mut ApiRequest,
	strategy: BodyStrategy,
	parse_limit: usize,
}

impl<'a> BodyParser<'a> {
	pub fn new(req: &'a mut ApiRequest, strategy: BodyStrategy, parse_limit: usize) -> Self {
		let mime = req.headers.typed_get::<ContentType>().map(Mime::from);
		Self {
			mime,
			req,
			strategy,
			parse_limit,
		}
	}

	pub async fn process(&mut self) -> Result<()> {
		match self.strategy {
			BodyStrategy::Json => self.json(true),
			BodyStrategy::Cbor => self.cbor(true),
			BodyStrategy::Flatbuffers => self.flatbuffers(true),
			BodyStrategy::Plain => self.plain(true),
			BodyStrategy::Bytes => self.bytes(true),
			BodyStrategy::Native => self.native(true),
			BodyStrategy::Auto => {
				let Some(mime) = &self.mime else {
					return Err(ApiError::MissingContentType.into());
				};

				if mime == &APPLICATION_JSON {
					return self.json(false);
				}

				if mime == &*APPLICATION_CBOR {
					return self.cbor(false);
				}

				if mime == &*APPLICATION_SDB_FB {
					return self.flatbuffers(false);
				}

				if mime == &TEXT_PLAIN {
					return self.plain(false);
				}

				if mime == &APPLICATION_OCTET_STREAM {
					return self.bytes(false);
				}

				if mime == &*APPLICATION_SDB_NATIVE {
					return self.native(false);
				}

				Err(ApiError::UnsupportedContentType(mime.to_string()).into())
			}
		}
	}

	fn is_mime(&self, mime: &Mime) -> bool {
		self.mime.as_ref().map(|x| x == mime).unwrap_or(false)
	}

	fn assert_mime(&self, mime: &Mime) -> Result<()> {
		if !self.is_mime(mime) {
			Err(ApiError::InvalidContentType(mime.to_string()).into())
		} else {
			Ok(())
		}
	}

	fn json(&mut self, validate: bool) -> Result<()> {
		if validate {
			self.assert_mime(&APPLICATION_JSON)?;
		}

		let Value::Bytes(ref bytes) = self.req.body else {
			return Err(ApiError::RequestBodyNotBinary.into());
		};

		self.req.body = format::json::decode(bytes.as_slice(), self.parse_limit)
			.map_err(|_| ApiError::BodyDecodeFailure)?;

		Ok(())
	}

	fn cbor(&mut self, validate: bool) -> Result<()> {
		if validate {
			self.assert_mime(&APPLICATION_CBOR)?;
		}

		let Value::Bytes(ref bytes) = self.req.body else {
			return Err(ApiError::RequestBodyNotBinary.into());
		};

		self.req.body = format::cbor::decode(bytes.as_slice(), self.parse_limit)
			.map_err(|_| ApiError::BodyDecodeFailure)?;

		Ok(())
	}

	fn flatbuffers(&mut self, validate: bool) -> Result<()> {
		if validate {
			self.assert_mime(&APPLICATION_SDB_FB)?;
		}

		let Value::Bytes(ref bytes) = self.req.body else {
			return Err(ApiError::RequestBodyNotBinary.into());
		};

		self.req.body = format::flatbuffers::decode(bytes.as_slice())
			.map_err(|_| ApiError::BodyDecodeFailure)?;

		Ok(())
	}

	fn plain(&mut self, validate: bool) -> Result<()> {
		if validate {
			self.assert_mime(&TEXT_PLAIN)?;
		}

		let Value::Bytes(ref bytes) = self.req.body else {
			return Err(ApiError::RequestBodyNotBinary.into());
		};

		self.req.body = Value::String(String::from_utf8_lossy(bytes.as_slice()).to_string());

		Ok(())
	}

	fn bytes(&mut self, validate: bool) -> Result<()> {
		if validate {
			self.assert_mime(&APPLICATION_OCTET_STREAM)?;
		}

		if !self.req.body.is_bytes() {
			return Err(ApiError::RequestBodyNotBinary.into());
		}

		Ok(())
	}

	fn native(&self, validate: bool) -> Result<()> {
		if validate {
			self.assert_mime(&APPLICATION_SDB_NATIVE)?;
		}

		Ok(())
	}
}