use crate::cnf::ID_CHARS; use crate::ctx::Context; use crate::dbs::{Options, Transaction}; use crate::doc::CursorDoc; use crate::err::Error; use crate::sql::array::{array, Array}; use crate::sql::error::IResult; use crate::sql::escape::escape_rid; use crate::sql::ident::ident_raw; use crate::sql::number::integer; use crate::sql::number::Number; use crate::sql::object::{object, Object}; use crate::sql::strand::Strand; use crate::sql::thing::Thing; use crate::sql::uuid::Uuid; use crate::sql::value::Value; use nanoid::nanoid; use nom::branch::alt; use nom::combinator::map; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; use ulid::Ulid; #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)] #[revisioned(revision = 1)] pub enum Gen { Rand, Ulid, Uuid, } #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)] #[revisioned(revision = 1)] pub enum Id { Number(i64), String(String), Array(Array), Object(Object), Generate(Gen), } impl From for Id { fn from(v: i64) -> Self { Self::Number(v) } } impl From for Id { fn from(v: i32) -> Self { Self::Number(v as i64) } } impl From for Id { fn from(v: u64) -> Self { Self::Number(v as i64) } } impl From for Id { fn from(v: String) -> Self { Self::String(v) } } impl From for Id { fn from(v: Array) -> Self { Self::Array(v) } } impl From for Id { fn from(v: Object) -> Self { Self::Object(v) } } impl From for Id { fn from(v: Uuid) -> Self { Self::String(v.to_raw()) } } impl From for Id { fn from(v: Strand) -> Self { Self::String(v.as_string()) } } impl From<&str> for Id { fn from(v: &str) -> Self { Self::String(v.to_owned()) } } impl From<&String> for Id { fn from(v: &String) -> Self { Self::String(v.to_owned()) } } impl From> for Id { fn from(v: Vec<&str>) -> Self { Id::Array(v.into()) } } impl From> for Id { fn from(v: Vec) -> Self { Id::Array(v.into()) } } impl From> for Id { fn from(v: Vec) -> Self { Id::Array(v.into()) } } impl From for Id { fn from(v: Number) -> Self { match v { Number::Int(v) => v.into(), Number::Float(v) => v.to_string().into(), Number::Decimal(v) => v.to_string().into(), } } } impl From for Id { fn from(v: Thing) -> Self { v.id } } impl Id { /// Generate a new random ID pub fn rand() -> Self { Self::String(nanoid!(20, &ID_CHARS)) } /// Generate a new random ULID pub fn ulid() -> Self { Self::String(Ulid::new().to_string()) } /// Generate a new random UUID #[cfg(uuid_unstable)] pub fn uuid() -> Self { Self::String(Uuid::new_v7().to_raw()) } /// Generate a new random UUID #[cfg(not(uuid_unstable))] pub fn uuid() -> Self { Self::String(Uuid::new_v4().to_raw()) } /// Convert the Id to a raw String pub fn to_raw(&self) -> String { match self { Self::Number(v) => v.to_string(), Self::String(v) => v.to_string(), Self::Array(v) => v.to_string(), Self::Object(v) => v.to_string(), Self::Generate(v) => match v { Gen::Rand => "rand()".to_string(), Gen::Ulid => "ulid()".to_string(), Gen::Uuid => "uuid()".to_string(), }, } } } impl Display for Id { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Number(v) => Display::fmt(v, f), Self::String(v) => Display::fmt(&escape_rid(v), f), Self::Array(v) => Display::fmt(v, f), Self::Object(v) => Display::fmt(v, f), Self::Generate(v) => match v { Gen::Rand => Display::fmt("rand()", f), Gen::Ulid => Display::fmt("ulid()", f), Gen::Uuid => Display::fmt("uuid()", f), }, } } } impl Id { /// Process this type returning a computed simple Value pub(crate) async fn compute( &self, ctx: &Context<'_>, opt: &Options, txn: &Transaction, doc: Option<&CursorDoc<'_>>, ) -> Result { match self { Id::Number(v) => Ok(Id::Number(*v)), Id::String(v) => Ok(Id::String(v.clone())), Id::Array(v) => match v.compute(ctx, opt, txn, doc).await? { Value::Array(v) => Ok(Id::Array(v)), _ => unreachable!(), }, Id::Object(v) => match v.compute(ctx, opt, txn, doc).await? { Value::Object(v) => Ok(Id::Object(v)), _ => unreachable!(), }, Id::Generate(v) => match v { Gen::Rand => Ok(Self::rand()), Gen::Ulid => Ok(Self::ulid()), Gen::Uuid => Ok(Self::uuid()), }, } } } pub fn id(i: &str) -> IResult<&str, Id> { alt(( map(integer, Id::Number), map(ident_raw, Id::String), map(object, Id::Object), map(array, Id::Array), ))(i) } #[cfg(test)] mod tests { use super::*; #[test] fn id_int() { let sql = "001"; let res = id(sql); let out = res.unwrap().1; assert_eq!(Id::from(1), out); assert_eq!("1", format!("{}", out)); } #[test] fn id_number() { let sql = "100"; let res = id(sql); let out = res.unwrap().1; assert_eq!(Id::from(100), out); assert_eq!("100", format!("{}", out)); } #[test] fn id_string() { let sql = "test"; let res = id(sql); let out = res.unwrap().1; assert_eq!(Id::from("test"), out); assert_eq!("test", format!("{}", out)); } #[test] fn id_numeric() { let sql = "⟨100⟩"; let res = id(sql); let out = res.unwrap().1; assert_eq!(Id::from("100"), out); assert_eq!("⟨100⟩", format!("{}", out)); } #[test] fn id_either() { let sql = "100test"; let res = id(sql); let out = res.unwrap().1; assert_eq!(Id::from("100test"), out); assert_eq!("100test", format!("{}", out)); } }