use serde::{Deserialize, Deserializer, Serialize, Serializer};
use specta::Type;
use std::fmt;
use surrealdb::types::{Kind, Number, RecordId, RecordIdKey, SurrealValue, ToSql, Value, kind};
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum IdOrRecordId {
String(String),
Number(i64),
Record(RecordId),
}
fn record_key_to_id(key: RecordIdKey) -> Result<Id, String> {
match key {
RecordIdKey::String(value) => Ok(Id::String(value)),
RecordIdKey::Number(value) => Ok(Id::Number(value)),
other => Err(format!(
"only string/number id is supported right now, got {}",
other.to_sql()
)),
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Type)]
pub enum Id {
String(String),
Number(i64),
}
impl Id {
pub fn as_string(&self) -> Option<&str> {
match self {
Self::String(value) => Some(value.as_str()),
Self::Number(_) => None,
}
}
pub fn as_number(&self) -> Option<i64> {
match self {
Self::String(_) => None,
Self::Number(value) => Some(*value),
}
}
pub fn into_record_id_key(self) -> RecordIdKey {
match self {
Self::String(value) => RecordIdKey::String(value),
Self::Number(value) => RecordIdKey::Number(value),
}
}
}
impl From<String> for Id {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<&str> for Id {
fn from(value: &str) -> Self {
Self::String(value.to_owned())
}
}
impl From<i64> for Id {
fn from(value: i64) -> Self {
Self::Number(value)
}
}
impl From<Id> for RecordIdKey {
fn from(value: Id) -> Self {
value.into_record_id_key()
}
}
impl fmt::Display for Id {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::String(value) => f.write_str(value),
Self::Number(value) => write!(f, "{value}"),
}
}
}
impl Serialize for Id {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::String(value) => serializer.serialize_str(value),
Self::Number(value) => serializer.serialize_i64(*value),
}
}
}
impl<'de> Deserialize<'de> for Id {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = IdOrRecordId::deserialize(deserializer)?;
match value {
IdOrRecordId::String(value) => Ok(Self::String(value)),
IdOrRecordId::Number(value) => Ok(Self::Number(value)),
IdOrRecordId::Record(record) => record_key_to_id(record.key).map_err(|message| {
<D::Error as serde::de::Error>::custom(format!(
"failed to deserialize id from record id: {message}"
))
}),
}
}
}
impl SurrealValue for Id {
fn kind_of() -> Kind {
kind!(string | number)
}
fn is_value(value: &Value) -> bool {
match value {
Value::String(_) | Value::Number(Number::Int(_)) => true,
Value::RecordId(record) => {
matches!(record.key, RecordIdKey::String(_) | RecordIdKey::Number(_))
}
_ => false,
}
}
fn into_value(self) -> Value {
match self {
Self::String(value) => Value::String(value),
Self::Number(value) => Value::Number(Number::Int(value)),
}
}
fn from_value(value: Value) -> Result<Self, surrealdb::types::Error> {
match value {
Value::String(value) => Ok(Self::String(value)),
Value::Number(Number::Int(value)) => Ok(Self::Number(value)),
Value::RecordId(record) => {
record_key_to_id(record.key).map_err(surrealdb::types::Error::internal)
}
other => Err(surrealdb::types::Error::internal(format!(
"expected string/number/record id for Id, got {}",
other.kind().to_sql()
))),
}
}
}
pub fn deserialize_id_or_record_id_as_string<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
{
let value = Id::deserialize(deserializer)?;
Ok(value.to_string())
}
pub fn serialize_id_as_string<S>(value: &str, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(value)
}
pub fn deserialize_record_id_or_compat_string<'de, D>(deserializer: D) -> Result<RecordId, D::Error>
where
D: Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
match value {
serde_json::Value::String(text) => parse_record_id_or_plain_string(&text, Some("_"))
.map_err(|invalid| {
<D::Error as serde::de::Error>::custom(format!(
"failed to deserialize record id: invalid record id string `{invalid}`"
))
}),
other => serde_json::from_value(other).map_err(|err| {
<D::Error as serde::de::Error>::custom(format!(
"failed to deserialize record id: {err}"
))
}),
}
}
pub fn record_id_to_plain_string(record: &RecordId) -> String {
match &record.key {
RecordIdKey::String(value) => value.trim_matches('`').to_owned(),
RecordIdKey::Number(value) => value.to_string(),
other => other.to_sql(),
}
}
pub fn record_id_to_plain_json(record: &RecordId) -> serde_json::Value {
match &record.key {
RecordIdKey::String(value) => serde_json::Value::String(value.trim_matches('`').to_owned()),
RecordIdKey::Number(value) => serde_json::Value::Number((*value).into()),
other => serde_json::Value::String(other.to_sql()),
}
}
pub fn parse_record_id_or_plain_string<'a>(
text: &'a str,
fallback_table: Option<&str>,
) -> Result<RecordId, &'a str> {
if let Some(record) = crate::parse_record_id_compat_string(text) {
Ok(record)
} else if let Ok(record) = RecordId::parse_simple(text) {
Ok(record)
} else if let Some(table) = fallback_table {
Ok(RecordId::new(table, text.trim_matches('`').to_owned()))
} else {
Err(text)
}
}
pub fn normalize_public_root_id_value(value: &mut serde_json::Value) {
let Some(id) = value.as_object_mut().and_then(|map| map.get_mut("id")) else {
return;
};
let normalized = match id {
serde_json::Value::String(text) => parse_record_id_or_plain_string(text, None)
.ok()
.map(|record| record_id_to_plain_json(&record)),
serde_json::Value::Object(_) => serde_json::from_value::<RecordId>(id.clone())
.ok()
.map(|record| record_id_to_plain_json(&record)),
_ => None,
};
if let Some(normalized) = normalized {
*id = normalized;
}
}
#[cfg(test)]
#[path = "id_tests.rs"]
mod tests;