use std::convert::From;
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
use std::str::FromStr;
use base64::display::Base64Display;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
use inlinable_string::inline_string::InlineString;
use uuid::Uuid;
#[cfg(feature = "diesel-uuid")]
#[macro_use]
extern crate diesel_derive_newtype;
use crate::errors::{ErrorKind, ResultExt};
mod errors;
#[cfg(feature = "serde")]
mod serde_impl;
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "diesel-uuid", derive(DieselNewType))]
pub struct UuidB64(uuid::Uuid);
impl UuidB64 {
pub fn new() -> UuidB64 {
UuidB64(Uuid::new_v4())
}
pub fn uuid(&self) -> Uuid {
self.0
}
pub fn to_istring(&self) -> InlineString {
let mut buf = InlineString::from("0000000000000000000000"); unsafe {
let raw_buf = buf.as_mut_slice();
URL_SAFE_NO_PAD
.encode_slice(self.0.as_bytes(), &mut raw_buf[0..22])
.unwrap();
}
buf
}
pub fn to_buf(&self, buffer: &mut String) {
URL_SAFE_NO_PAD.encode_string(self.0.as_bytes(), buffer);
}
}
impl FromStr for UuidB64 {
type Err = errors::ErrorKind;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut output = [0; 16];
URL_SAFE_NO_PAD
.decode_slice(s, &mut output)
.chain_err(|| ErrorKind::ParseError(s.into()))?;
let id = Uuid::from_bytes(output);
Ok(UuidB64(id))
}
}
impl Default for UuidB64 {
fn default() -> Self {
Self::new()
}
}
impl<T> From<T> for UuidB64
where
T: Into<Uuid>,
{
fn from(item: T) -> Self {
UuidB64(item.into())
}
}
impl Debug for UuidB64 {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "UuidB64({})", self)
}
}
impl Display for UuidB64 {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
let wrapper = Base64Display::new(self.0.as_bytes(), &URL_SAFE_NO_PAD);
write!(f, "{}", wrapper)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_is_b64() {
let id = UuidB64::new();
let fmted = format!("{}", id);
assert_eq!(fmted.len(), 22);
assert_eq!(format!("UuidB64({})", fmted), format!("{:?}", id));
}
#[test]
fn parse_roundtrips() {
let original = UuidB64::new();
let encoded = format!("{}", original);
let parsed: UuidB64 = encoded.parse().unwrap();
assert_eq!(parsed, original);
}
#[test]
fn from_uuid_works() {
let _ = UuidB64::from(Uuid::new_v4());
}
#[test]
fn to_istring_works() {
let b64 = UuidB64::from(Uuid::parse_str("b0c1ee86-6f46-4f1b-8d8b-7849e75dbcee").unwrap());
assert_eq!(b64.to_istring(), "sMHuhm9GTxuNi3hJ51287g");
for _ in 0..10 {
let b64 = UuidB64::new();
b64.to_istring();
}
}
}
#[cfg(all(test, feature = "diesel-uuid"))]
mod diesel_tests {
use diesel;
use diesel::dsl::sql;
use diesel::pg::PgConnection;
use diesel::prelude::*;
use std::env;
use super::UuidB64;
#[derive(Debug, Clone, PartialEq, Identifiable, Insertable, Queryable)]
#[diesel(table_name = my_entities)]
pub struct MyEntity {
id: UuidB64,
val: i32,
}
table! {
my_entities {
id -> Uuid,
val -> Integer,
}
}
#[cfg(test)]
fn setup() -> PgConnection {
let db_url = env::var("PG_DATABASE_URL").expect("PG_DB_URL must be in the environment");
let mut conn = PgConnection::establish(&db_url).unwrap();
#[allow(deprecated)] let setup = sql::<diesel::sql_types::Bool>(
"CREATE TABLE IF NOT EXISTS my_entities (
id UUID PRIMARY KEY,
val Int
)",
);
setup.execute(&mut conn).expect("Can't create table");
conn
}
#[test]
fn does_roundtrip() {
use self::my_entities::dsl::*;
let mut conn = setup();
let obj = MyEntity {
id: UuidB64::new(),
val: 1,
};
diesel::insert_into(my_entities)
.values(&obj)
.execute(&mut conn)
.expect("Couldn't insert struct into my_entities");
let found: Vec<MyEntity> = my_entities.load(&mut conn).unwrap();
assert_eq!(found[0], obj);
diesel::delete(my_entities.filter(id.eq(&obj.id)))
.execute(&mut conn)
.expect("Couldn't delete existing object");
}
}