//! Deterministic ETag generation: `"liberux-1-<sha256( body )[..16] hex>"`.
//!
//! The `liberux-1-` prefix is a generation marker — bumping it (to
//! `liberux-2-`) invalidates every cached ETag at clients in case we change
//! the vCard serialization in a way that should be observed as new content.

use sha2::{ Digest, Sha256 };

const GENERATION_PREFIX: &str = "liberux-1-";

/// Compute the ETag for a serialized vCard body. The returned string is
/// the literal HTTP `ETag` header value (already wrapped in quotes per
/// RFC 9110).
pub fn etag_for( body: &[ u8 ] ) -> String
{
	let digest = Sha256::digest( body );
	let hex = hex16( &digest );
	format!( "\"{}{}\"", GENERATION_PREFIX, hex )
}

fn hex16( bytes: &[ u8 ] ) -> String
{
	let mut s = String::with_capacity( 16 );
	for b in bytes.iter().take( 8 )
	{
		s.push_str( &format!( "{:02x}", b ) );
	}
	s
}

#[ cfg( test ) ]
mod tests
{
	use super::*;

	#[ test ]
	fn deterministic()
	{
		let a = etag_for( b"hello" );
		let b = etag_for( b"hello" );
		assert_eq!( a, b );
		assert!( a.starts_with( "\"liberux-1-" ) );
		assert!( a.ends_with( "\"" ) );
	}

	#[ test ]
	fn body_change_changes_etag()
	{
		let a = etag_for( b"BEGIN:VCARD\r\nFN:Ada\r\nEND:VCARD\r\n" );
		let b = etag_for( b"BEGIN:VCARD\r\nFN:Bea\r\nEND:VCARD\r\n" );
		assert_ne!( a, b );
	}

	#[ test ]
	fn etag_length_is_stable()
	{
		// "liberux-1-" (10) + 16 hex + two quotes = 28
		let e = etag_for( b"x" );
		assert_eq!( e.len(), 28 );
	}
}
