use super::policy::{
Clock, Id, Kind, Reason, Refusal, Verdict, key_mentions, names_an_id, names_scheme,
};
use super::time::iso8601_utc;
const TWITTER_EPOCH_MS: i64 = 1_288_834_974_657;
const DISCORD_EPOCH_MS: i64 = 1_420_070_400_000;
const TIMESTAMP_SHIFT: u32 = 22;
const DISCORD_WORDS: [&str; 4] = ["discord", "guild", "channel", "message"];
const TWITTER_WORDS: [&str; 3] = ["twitter", "tweet", "status"];
pub(crate) fn classify(token: &str, key: Option<&str>, clock: Clock) -> Verdict {
if !names_a_snowflake_field(key) {
return Verdict::Ignored;
}
let Ok(value) = token.parse::<u64>() else {
return Verdict::Ignored;
};
let shifted = i64::try_from(value >> TIMESTAMP_SHIFT).expect("42 bits fit i64");
let discord = shifted + DISCORD_EPOCH_MS;
let twitter = shifted + TWITTER_EPOCH_MS;
let Some(milliseconds) = chosen_epoch(key, discord, twitter, clock) else {
return Verdict::Refused(Refusal {
timestamp: Some(iso8601_utc(twitter)),
..Refusal::new(
Some(Kind::Snowflake),
Reason::AmbiguousKind,
format!(
"the Twitter epoch decodes this to {} and the Discord epoch to {}; both are \
plausible and no key here names the platform",
iso8601_utc(twitter),
iso8601_utc(discord)
),
)
});
};
let timestamp = iso8601_utc(milliseconds);
if !clock.is_plausible(milliseconds) {
return Verdict::Refused(Refusal {
timestamp: Some(timestamp),
..Refusal::new(
Some(Kind::Snowflake),
Reason::TimestampImplausible,
"the top 42 bits decode outside the plausible range under either epoch".to_string(),
)
});
}
Verdict::Named(Id::at(Kind::Snowflake, timestamp))
}
fn names_a_snowflake_field(key: Option<&str>) -> bool {
names_an_id(key) || names_scheme(key, "snowflake")
}
fn chosen_epoch(key: Option<&str>, discord: i64, twitter: i64, clock: Clock) -> Option<i64> {
let says_discord = DISCORD_WORDS.iter().any(|word| key_mentions(key, word));
let says_twitter = TWITTER_WORDS.iter().any(|word| key_mentions(key, word));
match (says_discord, says_twitter) {
(true, false) => Some(discord),
(false, true) => Some(twitter),
(true, true) | (false, false) => by_clock(discord, twitter, clock),
}
}
fn by_clock(discord: i64, twitter: i64, clock: Clock) -> Option<i64> {
match (clock.is_plausible(discord), clock.is_plausible(twitter)) {
(true, true) => None,
(true, false) => Some(discord),
(false, _) => Some(twitter),
}
}
#[cfg(test)]
mod tests {
use super::*;
const CLOCK: Clock = Clock::at(1_786_492_800_000);
const DISCORD_ID: &str = "1536886938009600000";
fn named(token: &str, key: &str) -> Id {
match classify(token, Some(key), CLOCK) {
Verdict::Named(id) => id,
other => panic!("{token} was {other:?}, not a named Snowflake"),
}
}
fn refused(token: &str, key: &str) -> Refusal {
match classify(token, Some(key), CLOCK) {
Verdict::Refused(refusal) => refusal,
other => panic!("{token} was {other:?}, not a refusal"),
}
}
#[test]
fn a_bare_integer_with_no_key_is_never_a_snowflake() {
assert_eq!(classify(DISCORD_ID, None, CLOCK), Verdict::Ignored);
}
#[test]
fn an_integer_under_a_key_that_names_no_id_is_ignored() {
for key in ["bytes_written", "population", "account.balance"] {
assert_eq!(
classify(DISCORD_ID, Some(key), CLOCK),
Verdict::Ignored,
"{key}"
);
}
}
#[test]
fn a_key_ending_in_id_is_the_evidence() {
for key in ["id", "user_id", "userId", "USER-ID", "snowflake"] {
assert_ne!(
classify(DISCORD_ID, Some(key), CLOCK),
Verdict::Ignored,
"{key}"
);
}
}
#[test]
fn only_the_leaf_of_the_key_path_is_read_for_id_evidence() {
assert_eq!(
classify(DISCORD_ID, Some("snowflakes.count"), CLOCK),
Verdict::Ignored
);
assert_ne!(
classify(DISCORD_ID, Some("snowflakes.channel_id"), CLOCK),
Verdict::Ignored
);
}
#[test]
fn a_key_naming_the_platform_chooses_its_epoch() {
assert_eq!(
named(DISCORD_ID, "channel_id").timestamp,
Some("2026-08-12T00:00:00.000Z".to_string())
);
assert_eq!(
named("2087328207467446272", "tweet_id").timestamp,
Some("2026-08-12T00:00:00.000Z".to_string())
);
}
#[test]
fn every_platform_word_selects_its_epoch() {
for key in ["discord_id", "guild_id", "channel_id", "message_id"] {
assert_eq!(
named(DISCORD_ID, key).timestamp,
Some("2026-08-12T00:00:00.000Z".to_string()),
"{key}"
);
}
for key in ["twitter_id", "tweet_id", "status_id"] {
assert_eq!(
named("2087328207467446272", key).timestamp,
Some("2026-08-12T00:00:00.000Z".to_string()),
"{key}"
);
}
}
#[test]
fn an_unnamed_platform_leaves_two_epochs_fitting_and_refuses() {
let refusal = refused(DISCORD_ID, "user_id");
assert_eq!(refusal.reason, Reason::AmbiguousKind);
assert_eq!(refusal.kind, Some(Kind::Snowflake));
assert!(refusal.detail.contains("Twitter"), "{}", refusal.detail);
assert!(refusal.detail.contains("Discord"), "{}", refusal.detail);
}
#[test]
fn a_key_naming_two_platforms_is_still_ambiguous() {
assert_eq!(
refused(DISCORD_ID, "discord_tweet_id").reason,
Reason::AmbiguousKind
);
}
#[test]
fn a_value_too_large_for_the_field_is_an_implausible_timestamp() {
let refusal = refused("9223372036854775807", "message_id");
assert_eq!(refusal.reason, Reason::TimestampImplausible);
assert!(refusal.timestamp.is_some(), "the decode is carried");
}
#[test]
fn a_seventeen_digit_identifier_still_decodes() {
assert_eq!(
named("81384788765712384", "guild_id").timestamp,
Some("2015-08-13T13:54:05.698Z".to_string())
);
}
}