use thiserror::Error;
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct DevDeviceId(Uuid);
mod unix;
mod windows;
mod storage {
#[cfg(target_family = "unix")]
pub use super::unix::*;
#[cfg(target_family = "windows")]
pub use super::windows::*;
}
#[derive(Debug, Error)]
pub enum Error {
#[error("Failed to store or retrieve device ID due to storage error: {0}")]
StorageError(String),
#[error("Failed to parse device ID, as UUID due to {0}")]
BadUuidFormat(String),
#[error("Device ID is already set")]
AlreadySet,
}
pub type Result<T> = std::result::Result<T, Error>;
fn generate_id() -> DevDeviceId {
DevDeviceId(Uuid::new_v4())
}
impl DevDeviceId {
pub fn get_or_generate() -> Result<Self> {
match storage::retrieve()? {
Some(id) => Ok(id),
None => {
let id = generate_id();
storage::store(&id)?;
Ok(storage::retrieve()?.unwrap_or(id))
}
}
}
pub fn get() -> Result<Option<Self>> {
storage::retrieve()
}
}
impl std::fmt::Display for DevDeviceId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:x}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_expected_format() {
let uuid = Uuid::new_v4();
let id = DevDeviceId(uuid);
let formatted = format!("{}", id);
let mut buf = vec![0u8; uuid::fmt::Hyphenated::LENGTH];
let hyphenated = uuid::fmt::Hyphenated::from_uuid(uuid);
hyphenated.encode_lower(&mut buf);
let expected = String::from_utf8(buf).expect("Failed to convert to String");
assert_eq!(formatted, expected);
}
#[test]
fn test_get_or_generate_idempotent() {
let id = DevDeviceId::get_or_generate().unwrap();
let id2 = DevDeviceId::get_or_generate().unwrap();
assert_eq!(id, id2);
let id3 = DevDeviceId::get().unwrap().unwrap();
assert_eq!(id, id3);
}
}