use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use uuid::Uuid;
#[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) struct ParsedSw8 {
pub trace_id: String,
pub segment_id: String,
pub parent_span_id: String,
pub service: String,
pub service_instance: String,
pub endpoint: String,
pub target_address: String,
}
pub(crate) fn encode_sw8(
trace_id: &str,
service: &str,
service_instance: &str,
endpoint: &str,
target_address: &str,
) -> String {
let segment_id = Uuid::new_v4().simple().to_string();
encode_sw8_with_segment_id(
trace_id,
&segment_id,
service,
service_instance,
endpoint,
target_address,
)
}
pub(crate) fn encode_sw8_with_segment_id(
trace_id: &str,
segment_id: &str,
service: &str,
service_instance: &str,
endpoint: &str,
target_address: &str,
) -> String {
encode_sw8_with_ids(
trace_id,
segment_id,
"0",
service,
service_instance,
endpoint,
target_address,
)
}
pub(crate) fn encode_sw8_with_ids(
trace_id: &str,
segment_id: &str,
span_id: &str,
service: &str,
service_instance: &str,
endpoint: &str,
target_address: &str,
) -> String {
[
"1".to_string(),
enc(trace_id),
enc(segment_id),
span_id.to_string(),
enc(service),
enc(service_instance),
enc(endpoint),
enc(target_address),
]
.join("-")
}
pub(crate) fn decode_sw8(value: &str) -> Option<ParsedSw8> {
let parts = value.split('-').collect::<Vec<_>>();
if parts.len() < 8 {
return None;
}
Some(ParsedSw8 {
trace_id: dec(parts[1])?,
segment_id: dec(parts[2])?,
parent_span_id: parts[3].to_string(),
service: dec(parts[4])?,
service_instance: dec(parts[5])?,
endpoint: dec(parts[6])?,
target_address: dec(parts[7])?,
})
}
fn enc(value: &str) -> String {
STANDARD.encode(value.as_bytes())
}
fn dec(value: &str) -> Option<String> {
let bytes = STANDARD.decode(value).ok()?;
String::from_utf8(bytes).ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sw8_round_trips_key_fields() {
let sw8 = encode_sw8("abc", "svc", "inst", "HTTP GET", "example.com:443");
let parsed = decode_sw8(&sw8).unwrap();
assert_eq!(parsed.trace_id, "abc");
assert_eq!(parsed.parent_span_id, "0");
assert_eq!(parsed.service, "svc");
assert_eq!(parsed.service_instance, "inst");
assert_eq!(parsed.endpoint, "HTTP GET");
assert_eq!(parsed.target_address, "example.com:443");
}
#[test]
fn encodes_given_segment_id() {
let sw8 =
encode_sw8_with_segment_id("abc", "span", "svc", "inst", "HTTP GET", "example.com:443");
let parsed = decode_sw8(&sw8).unwrap();
assert_eq!(parsed.trace_id, "abc");
assert_eq!(parsed.segment_id, "span");
assert_eq!(parsed.parent_span_id, "0");
}
#[test]
fn encodes_given_segment_and_span_ids() {
let sw8 = encode_sw8_with_ids(
"abc",
"segment",
"42",
"svc",
"inst",
"HTTP GET",
"example.com:443",
);
let parsed = decode_sw8(&sw8).unwrap();
assert_eq!(parsed.trace_id, "abc");
assert_eq!(parsed.segment_id, "segment");
assert_eq!(parsed.parent_span_id, "42");
}
#[test]
fn decodes_fixture_with_padding() {
let sw8 = "1-YWJj-c2VnbWVudA==-0-c3Zj-aW5zdA==-SFRUUCBHRVQ=-ZXhhbXBsZS5jb206NDQz";
let parsed = decode_sw8(sw8).unwrap();
assert_eq!(parsed.trace_id, "abc");
assert_eq!(parsed.segment_id, "segment");
assert_eq!(parsed.parent_span_id, "0");
}
}