use rmcp::ErrorData;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum FpSource {
Generative,
Pool,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GenerateInput {
pub source: FpSource,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub seed: Option<u64>,
}
#[derive(Debug, Serialize, JsonSchema)]
pub struct GenerateOutput {
pub persona: serde_json::Value,
}
const POOL_URL: &str =
"https://github.com/TurtIeSocks/zendriver-rs/releases/latest/download/fingerprint-pool.json";
pub async fn generate(input: GenerateInput) -> Result<GenerateOutput, ErrorData> {
use zendriver::Seed;
let seed = input.seed.map_or_else(Seed::random, Seed::from_u64);
let persona = match input.source {
FpSource::Generative => {
zendriver_fingerprints::generative::Generator::embedded().generate(seed)
}
FpSource::Pool => {
let set = zendriver_fingerprints::pool::load_or_download(POOL_URL)
.await
.map_err(|e| {
ErrorData::internal_error(
format!(
"pool load failed (the pool asset may not be published yet — see issue #25): {e}"
),
None,
)
})?;
set.sample(seed)
}
};
let value = serde_json::to_value(&persona)
.map_err(|e| ErrorData::internal_error(format!("persona serialize: {e}"), None))?;
Ok(GenerateOutput { persona: value })
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn generative_produces_non_null_persona() {
let out = generate(GenerateInput {
source: FpSource::Generative,
seed: Some(42),
})
.await
.expect("generative generate");
assert!(
out.persona.is_object(),
"expected object, got {:?}",
out.persona
);
}
#[tokio::test]
async fn generative_is_deterministic() {
let a = generate(GenerateInput {
source: FpSource::Generative,
seed: Some(7),
})
.await
.expect("a");
let b = generate(GenerateInput {
source: FpSource::Generative,
seed: Some(7),
})
.await
.expect("b");
assert_eq!(a.persona, b.persona);
}
}