#![warn(missing_docs)]
use std::error;
use std::fmt;
use std::fmt::Write;
use std::str;
use uuid::Uuid;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ParseDebugIdError;
impl error::Error for ParseDebugIdError {}
impl fmt::Display for ParseDebugIdError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid debug identifier")
}
}
#[derive(Clone, Copy, Debug)]
struct ParseOptions {
allow_hyphens: bool,
require_appendix: bool,
allow_tail: bool,
}
#[repr(C, packed)]
#[derive(Default, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct DebugId {
uuid: Uuid,
appendix: u32,
_padding: [u8; 12],
}
impl DebugId {
pub fn nil() -> Self {
Self::default()
}
pub fn from_uuid(uuid: Uuid) -> Self {
Self::from_parts(uuid, 0)
}
pub fn from_parts(uuid: Uuid, appendix: u32) -> Self {
DebugId {
uuid,
appendix,
_padding: [0; 12],
}
}
pub fn from_guid_age(guid: &[u8], age: u32) -> Result<Self, ParseDebugIdError> {
if guid.len() != 16 {
return Err(ParseDebugIdError);
}
let uuid = Uuid::from_bytes([
guid[3], guid[2], guid[1], guid[0], guid[5], guid[4], guid[7], guid[6], guid[8],
guid[9], guid[10], guid[11], guid[12], guid[13], guid[14], guid[15],
]);
Ok(DebugId::from_parts(uuid, age))
}
pub fn from_breakpad(string: &str) -> Result<Self, ParseDebugIdError> {
let options = ParseOptions {
allow_hyphens: false,
require_appendix: true,
allow_tail: false,
};
Self::parse_str(string, options).ok_or(ParseDebugIdError)
}
pub fn uuid(&self) -> Uuid {
self.uuid
}
pub fn appendix(&self) -> u32 {
self.appendix
}
pub fn is_nil(&self) -> bool {
self.uuid.is_nil() && self.appendix() == 0
}
pub fn breakpad(&self) -> BreakpadFormat<'_> {
BreakpadFormat { inner: self }
}
fn parse_str(string: &str, options: ParseOptions) -> Option<Self> {
let is_hyphenated = string.get(8..9) == Some("-");
if is_hyphenated && !options.allow_hyphens || !string.is_ascii() {
return None;
}
let uuid_len = if is_hyphenated { 36 } else { 32 };
let uuid = string.get(..uuid_len)?.parse().ok()?;
if !options.require_appendix && string.len() == uuid_len {
return Some(Self::from_parts(uuid, 0));
}
let mut appendix_str = &string[uuid_len..];
if is_hyphenated ^ appendix_str.starts_with('-') {
return None;
} else if is_hyphenated {
appendix_str = &appendix_str[1..];
}
if options.allow_tail && appendix_str.len() > 8 {
appendix_str = &appendix_str[..8];
}
let appendix = u32::from_str_radix(appendix_str, 16).ok()?;
Some(Self::from_parts(uuid, appendix))
}
}
impl fmt::Debug for DebugId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DebugId")
.field("uuid", &self.uuid().to_hyphenated_ref().to_string())
.field("appendix", &self.appendix())
.finish()
}
}
impl fmt::Display for DebugId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.uuid.fmt(f)?;
if self.appendix > 0 {
write!(f, "-{:x}", { self.appendix })?;
}
Ok(())
}
}
impl str::FromStr for DebugId {
type Err = ParseDebugIdError;
fn from_str(string: &str) -> Result<Self, ParseDebugIdError> {
let options = ParseOptions {
allow_hyphens: true,
require_appendix: false,
allow_tail: true,
};
Self::parse_str(string, options).ok_or(ParseDebugIdError)
}
}
impl From<Uuid> for DebugId {
fn from(uuid: Uuid) -> Self {
DebugId::from_uuid(uuid)
}
}
impl From<(Uuid, u32)> for DebugId {
fn from(tuple: (Uuid, u32)) -> Self {
let (uuid, appendix) = tuple;
DebugId::from_parts(uuid, appendix)
}
}
#[derive(Debug)]
pub struct BreakpadFormat<'a> {
inner: &'a DebugId,
}
impl<'a> fmt::Display for BreakpadFormat<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:X}{:x}",
self.inner.uuid().to_simple_ref(),
self.inner.appendix()
)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ParseCodeIdError;
impl error::Error for ParseCodeIdError {}
impl fmt::Display for ParseCodeIdError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid code identifier")
}
}
#[derive(Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct CodeId {
inner: String,
}
impl CodeId {
pub fn nil() -> Self {
Self::default()
}
pub fn new(mut string: String) -> Self {
string.retain(|c| c.is_ascii_hexdigit());
string.make_ascii_lowercase();
CodeId { inner: string }
}
pub fn from_binary(slice: &[u8]) -> Self {
let mut string = String::with_capacity(slice.len() * 2);
for byte in slice {
write!(&mut string, "{:02x}", byte).expect("");
}
Self::new(string)
}
pub fn is_nil(&self) -> bool {
self.inner.is_empty()
}
pub fn as_str(&self) -> &str {
self.inner.as_str()
}
}
impl fmt::Display for CodeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.inner)
}
}
impl fmt::Debug for CodeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CodeId({})", self)
}
}
impl From<String> for CodeId {
fn from(string: String) -> Self {
Self::new(string)
}
}
impl From<&'_ str> for CodeId {
fn from(string: &str) -> Self {
Self::new(string.into())
}
}
impl AsRef<str> for CodeId {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl str::FromStr for CodeId {
type Err = ParseCodeIdError;
fn from_str(string: &str) -> Result<Self, ParseCodeIdError> {
Ok(Self::new(string.into()))
}
}
#[cfg(feature = "serde")]
mod serde_support {
use serde::de::{self, Deserialize, Deserializer, Unexpected, Visitor};
use serde::ser::{Serialize, Serializer};
use super::*;
impl Serialize for CodeId {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for CodeId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let string = String::deserialize(deserializer)?;
Ok(CodeId::new(string))
}
}
impl<'de> Deserialize<'de> for DebugId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct V;
impl<'de> Visitor<'de> for V {
type Value = DebugId;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("DebugId")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<DebugId, E> {
value
.parse()
.map_err(|_| de::Error::invalid_value(Unexpected::Str(value), &self))
}
}
deserializer.deserialize_str(V)
}
}
impl Serialize for DebugId {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
}
}
}