surrealdb-core 3.2.0

A scalable, distributed, collaborative, document-graph database, for the realtime web
Documentation
pub mod base64 {
	use anyhow::Result;
	use base64::engine::DecodePaddingMode;
	use base64::engine::general_purpose::{
		GeneralPurpose, GeneralPurposeConfig, STANDARD, STANDARD_NO_PAD,
	};
	use base64::{Engine, alphabet};

	use crate::err::Error;
	use crate::fnc::args::Optional;
	use crate::val::{Bytes, Value};

	/// Base64 configuration which supports decoding with or without padding.
	const STANDARD_GENERIC_DECODER: GeneralPurpose = GeneralPurpose::new(
		&alphabet::STANDARD,
		GeneralPurposeConfig::new()
			.with_encode_padding(false)
			.with_decode_padding_mode(DecodePaddingMode::Indifferent),
	);

	/// Encodes a `Bytes` value to a base64 string without padding.
	pub fn encode((arg, Optional(padded)): (Bytes, Optional<bool>)) -> Result<Value> {
		let padded = padded.unwrap_or_default();
		let engine = if padded {
			STANDARD
		} else {
			STANDARD_NO_PAD
		};
		Ok(Value::from(engine.encode(&*arg)))
	}

	/// Decodes a base64 string to a `Bytes` value. It accepts both padded and
	/// non-padded base64 strings.
	pub fn decode((arg,): (String,)) -> Result<Value> {
		Ok(Value::from(Bytes::from(STANDARD_GENERIC_DECODER.decode(arg).map_err(|_| {
			Error::InvalidFunctionArguments {
				name: "encoding::base64::decode".to_owned(),
				message: "invalid base64".to_owned(),
			}
		})?)))
	}
}

pub mod cbor {
	use anyhow::Result;

	use crate::err::Error;
	use crate::rpc::format::cbor;
	use crate::val::{Bytes, Value};

	pub fn encode((arg,): (Value,)) -> Result<Value> {
		// Convert internal value to public, encode, then convert back
		let public_val = crate::val::convert_value_to_public_value(arg)?;
		let val = cbor::encode(public_val).map_err(|_| Error::InvalidFunctionArguments {
			name: "encoding::cbor::encode".to_owned(),
			message: "Value could not be encoded into CBOR".to_owned(),
		})?;

		Ok(Value::Bytes(Bytes::from(val)))
	}

	pub fn decode((arg,): (Bytes,)) -> Result<Value> {
		let public_val =
			cbor::decode(arg.as_ref(), 100).map_err(|_| Error::InvalidFunctionArguments {
				name: "encoding::cbor::decode".to_owned(),
				message: "invalid cbor".to_owned(),
			})?;
		// Convert public value back to internal
		Ok(crate::sql::expression::convert_public_value_to_internal(public_val))
	}
}

pub mod json {
	use anyhow::Result;

	use crate::err::Error;
	use crate::rpc::format::json;
	use crate::val::Value;

	/// Encodes a SurrealDB value to a JSON string.
	pub fn encode((arg,): (Value,)) -> Result<Value> {
		let public_val = crate::val::convert_value_to_public_value(arg)?;
		let val = json::encode_str(public_val).map_err(|_| Error::InvalidFunctionArguments {
			name: "encoding::json::encode".to_owned(),
			message: "Value could not be encoded into JSON".to_owned(),
		})?;
		Ok(Value::from(val))
	}

	/// Decodes a JSON string into a SurrealDB value using an RFC 8259
	/// compliant parser, supporting all valid JSON escape sequences.
	pub fn decode((arg,): (String,)) -> Result<Value> {
		let json: serde_json::Value =
			serde_json::from_str(&arg).map_err(|_| Error::InvalidFunctionArguments {
				name: "encoding::json::decode".to_owned(),
				message: "Invalid JSON".to_owned(),
			})?;
		let public_val = crate::rpc::format::json::json_to_value(json);
		Ok(crate::sql::expression::convert_public_value_to_internal(public_val))
	}
}

#[cfg(test)]
mod tests {
	use super::*;
	use crate::fnc::args::Optional;
	use crate::val::{Bytes, Value};

	#[test]
	fn test_base64_encode() {
		let input = Bytes::from(b"hello".to_vec());
		let result = base64::encode((input.clone(), Optional(None))).unwrap();
		assert_eq!(result, Value::from("aGVsbG8"));

		let result = base64::encode((input, Optional(Some(false)))).unwrap();
		assert_eq!(result, Value::from("aGVsbG8"));
	}

	#[test]
	fn test_base64_encode_padded() {
		let input = Bytes::from(b"hello".to_vec());
		let result = base64::encode((input, Optional(Some(true)))).unwrap();
		assert_eq!(result, Value::from("aGVsbG8="));
	}

	#[test]
	fn test_base64_decode_no_pad() {
		let input = "aGVsbG8".to_string();
		let result = base64::decode((input,)).unwrap();
		assert_eq!(result, Value::from(Bytes::from(b"hello".to_vec())));
	}

	#[test]
	fn test_base64_decode_with_pad() {
		let input = "aGVsbG8=".to_string();
		let result = base64::decode((input,)).unwrap();
		assert_eq!(result, Value::from(Bytes::from(b"hello".to_vec())));
	}

	#[test]
	fn test_json_encode_string() {
		let input = Value::from("hello");
		let result = json::encode((input,)).unwrap();
		assert_eq!(result, Value::from("\"hello\""));
	}

	#[test]
	fn test_json_encode_number() {
		let input = Value::from(42);
		let result = json::encode((input,)).unwrap();
		assert_eq!(result, Value::from("42"));
	}

	#[test]
	fn test_json_decode_object() {
		let input = r#"{"name":"test","value":42}"#.to_string();
		let result = json::decode((input,)).unwrap();
		match result {
			Value::Object(_) => {}
			other => panic!("expected object, got: {other:?}"),
		}
	}

	#[test]
	fn test_json_decode_array() {
		let input = r#"[1, 2, 3]"#.to_string();
		let result = json::decode((input,)).unwrap();
		match result {
			Value::Array(_) => {}
			other => panic!("expected array, got: {other:?}"),
		}
	}

	#[test]
	fn test_json_decode_invalid() {
		let input = r#"{invalid json"#.to_string();
		assert!(json::decode((input,)).is_err());
	}
}