use super::codec::percent_encode;
pub fn build_qr_url(resolver_base: &str, gtin: &str, serial: &str, batch: Option<&str>) -> String {
let mut uri = format!("{}/01/{}", resolver_base.trim_end_matches('/'), gtin);
if let Some(b) = batch {
uri.push_str(&format!("/10/{}", percent_encode(b)));
}
uri.push_str(&format!("/21/{}", percent_encode(serial)));
uri
}
pub fn short_serial(uuid_bytes: &[u8; 16]) -> String {
let mut serial = String::with_capacity(20);
for &byte in &uuid_bytes[..10] {
serial.push_str(&format!("{byte:02x}"));
}
serial
}
#[cfg(test)]
mod tests {
use super::*;
use crate::digital_link::{DigitalLink, DigitalLinkError};
#[test]
fn short_serial_is_twenty_hex_chars() {
let uuid = [
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba,
0xdc, 0xfe,
];
let serial = short_serial(&uuid);
assert_eq!(serial, "0123456789abcdef1032");
assert_eq!(serial.len(), 20);
assert!(serial.bytes().all(|b| b.is_ascii_hexdigit()));
}
#[test]
fn short_serial_is_deterministic() {
let uuid = [0x7f; 16];
assert_eq!(short_serial(&uuid), short_serial(&uuid));
}
#[test]
fn derived_serial_round_trips_through_parse() {
let uuid = [0xa1; 16];
let serial = short_serial(&uuid);
let url = build_qr_url("https://id.odal-node.io", "09506000134352", &serial, None);
let parsed = DigitalLink::parse(&url).expect("a derived serial must parse");
assert_eq!(parsed.serial.as_deref(), Some(serial.as_str()));
assert!(serial.chars().count() <= 20);
}
#[test]
fn raw_uuid_serial_is_rejected_by_parse() {
let url = build_qr_url(
"https://id.odal-node.io",
"09506000134352",
"0190a9f0-1234-7abc-8def-0123456789ab", None,
);
assert!(matches!(
DigitalLink::parse(&url),
Err(DigitalLinkError::ValueTooLong { code, max_len: 20, actual: 36 }) if code == "21"
));
}
}