use std::marker::PhantomData;
use thiserror::Error;
use crate::scheme::UrnScheme;
mod traceparent;
mod traits;
const TRACE_BYTE_LEN: usize = 16;
const TRACE_HEX_LEN: usize = TRACE_BYTE_LEN * 2;
const SPAN_BYTE_LEN: usize = 8;
const SPAN_HEX_LEN: usize = SPAN_BYTE_LEN * 2;
const TASK_NODE_SEPARATOR: char = ':';
const ZERO_TRACE_BYTES: [u8; TRACE_BYTE_LEN] = [0; TRACE_BYTE_LEN];
const ZERO_SPAN_BYTES: [u8; SPAN_BYTE_LEN] = [0; SPAN_BYTE_LEN];
const ROOT_TASK_NODE_BYTES: [u8; SPAN_BYTE_LEN] = [0, 0, 0, 0, 0, 0, 0, 1];
#[derive(Debug, Clone, PartialEq, Eq, Error)]
#[error("invalid task identity {value:?}: {message}")]
pub struct TaskIdentityError {
value: String,
message: &'static str,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TaskTreeId<S: UrnScheme> {
bytes: [u8; TRACE_BYTE_LEN],
urn: String,
scheme: PhantomData<S>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TaskNodeId {
bytes: [u8; SPAN_BYTE_LEN],
hex: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TaskId<S: UrnScheme> {
tree: TaskTreeId<S>,
node: TaskNodeId,
urn: String,
}
impl<S: UrnScheme> TaskId<S> {
#[must_use]
pub fn new(tree: TaskTreeId<S>, node: TaskNodeId) -> Self {
let () = S::VALIDATED;
let urn = if node.is_root() {
format!("{}{}", S::TASK_PREFIX, tree.as_w3c_trace_id())
} else {
format!(
"{}{}{TASK_NODE_SEPARATOR}{}",
S::TASK_PREFIX,
tree.as_w3c_trace_id(),
node.as_w3c_span_id()
)
};
Self { tree, node, urn }
}
#[must_use]
pub fn root(tree: TaskTreeId<S>) -> Self {
Self::new(tree, TaskNodeId::root())
}
#[must_use]
pub fn generate() -> Self {
Self::root(TaskTreeId::generate())
}
#[must_use]
pub fn child(&self) -> Self {
Self::new(self.tree.clone(), TaskNodeId::generate())
}
pub fn parse(raw: &str) -> Result<Self, TaskIdentityError> {
let suffix = raw.strip_prefix(S::TASK_PREFIX).ok_or_else(|| {
invalid_format(raw, "URN does not start with the scheme's task prefix")
})?;
let Some((trace, span)) = suffix.split_once(TASK_NODE_SEPARATOR) else {
return Ok(Self::root(TaskTreeId::from_hex(suffix, raw)?));
};
if span.contains(TASK_NODE_SEPARATOR) {
return Err(invalid_format(raw, "task ID has too many fields"));
}
let node = TaskNodeId::from_hex(span, raw)?;
if node.is_root() {
return Err(invalid_format(
raw,
"root task URN must omit the reserved root node",
));
}
Ok(Self::new(TaskTreeId::from_hex(trace, raw)?, node))
}
#[must_use]
pub fn tree(&self) -> &TaskTreeId<S> {
&self.tree
}
#[must_use]
pub fn node(&self) -> &TaskNodeId {
&self.node
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.urn
}
#[must_use]
pub fn as_urn_suffix(&self) -> &str {
&self.urn[S::TASK_PREFIX.len()..]
}
}
impl<S: UrnScheme> TaskTreeId<S> {
fn from_bytes(bytes: [u8; TRACE_BYTE_LEN]) -> Self {
let () = S::VALIDATED;
Self {
urn: urn_from_bytes(S::TREE_PREFIX, &bytes),
bytes,
scheme: PhantomData,
}
}
#[must_use]
pub fn generate() -> Self {
loop {
let bytes = rand::random::<[u8; TRACE_BYTE_LEN]>();
if bytes != ZERO_TRACE_BYTES {
return Self::from_bytes(bytes);
}
}
}
pub fn new(raw: &str) -> Result<Self, TaskIdentityError> {
if let Some(trace_hex) = traceparent::trace_hex(raw)? {
return Self::from_hex(trace_hex, raw);
}
let hex = raw.strip_prefix(S::TREE_PREFIX).unwrap_or(raw);
Self::from_hex(hex, raw)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.urn
}
#[must_use]
pub fn as_w3c_trace_id(&self) -> &str {
&self.urn[S::TREE_PREFIX.len()..]
}
fn from_hex(hex: &str, original: &str) -> Result<Self, TaskIdentityError> {
let bytes = decode_hex_array::<TRACE_BYTE_LEN, TRACE_HEX_LEN>(
hex,
original,
"expected 32 trace hex characters",
)?;
if bytes == ZERO_TRACE_BYTES {
return Err(invalid_format(original, "trace ID must not be all zero"));
}
Ok(Self::from_bytes(bytes))
}
}
impl TaskNodeId {
fn from_bytes(bytes: [u8; SPAN_BYTE_LEN]) -> Self {
Self {
hex: hex_from_bytes(&bytes),
bytes,
}
}
fn root() -> Self {
Self::from_bytes(ROOT_TASK_NODE_BYTES)
}
fn is_root(&self) -> bool {
self.bytes == ROOT_TASK_NODE_BYTES
}
#[must_use]
pub fn generate() -> Self {
loop {
let bytes = rand::random::<[u8; SPAN_BYTE_LEN]>();
if bytes != ZERO_SPAN_BYTES && bytes != ROOT_TASK_NODE_BYTES {
return Self::from_bytes(bytes);
}
}
}
pub fn new(raw: &str) -> Result<Self, TaskIdentityError> {
Self::from_hex(raw, raw)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.hex
}
#[must_use]
pub fn as_w3c_span_id(&self) -> &str {
&self.hex
}
fn from_hex(hex: &str, original: &str) -> Result<Self, TaskIdentityError> {
let bytes = decode_hex_array::<SPAN_BYTE_LEN, SPAN_HEX_LEN>(
hex,
original,
"expected 16 span hex characters",
)?;
if bytes == ZERO_SPAN_BYTES {
return Err(invalid_format(original, "span ID must not be all zero"));
}
Ok(Self::from_bytes(bytes))
}
}
fn decode_hex_array<const BYTE_LEN: usize, const HEX_LEN: usize>(
hex: &str,
original: &str,
message: &'static str,
) -> Result<[u8; BYTE_LEN], TaskIdentityError> {
if hex.len() != HEX_LEN
|| !hex
.chars()
.all(|character| character.is_ascii_hexdigit() && !character.is_ascii_uppercase())
{
return Err(invalid_format(original, message));
}
let mut bytes = [0u8; BYTE_LEN];
for index in 0..BYTE_LEN {
bytes[index] = u8::from_str_radix(&hex[index * 2..index * 2 + 2], 16)
.map_err(|_| invalid_format(original, "invalid hex digit"))?;
}
Ok(bytes)
}
fn urn_from_bytes(prefix: &str, bytes: &[u8]) -> String {
format!("{prefix}{}", hex_from_bytes(bytes))
}
fn hex_from_bytes(bytes: &[u8]) -> String {
use std::fmt::Write;
bytes
.iter()
.fold(String::with_capacity(bytes.len() * 2), |mut hex, byte| {
let _ = write!(hex, "{byte:02x}");
hex
})
}
fn invalid_format(value: &str, message: &'static str) -> TaskIdentityError {
TaskIdentityError {
value: value.to_owned(),
message,
}
}