guests/toy-signup/src/lib.rs
//! **A toy signup guest** — what proves the signup ABI end to end without a
//! product: the account and the delivered fact live in the host's durable
//! state (`account/<name>` = the session that made it, `fact/<name>` = the
//! signed fact as delivered), a name another session holds is `NameTaken`, a
//! retry of the same session finishes its own account, and `down = true` in
//! the settings is a product that cannot be reached (`Unavailable`).
use monetize_signup_guest::{kinds, EntitlementFact, Host, Manifest, NameVerdict, PaidSignup, SignupError, SignupGuest, SignupOutcome};
pub const PRODUCT: &str = "toy";
#[derive(Default)]
pub struct ToySignup;
fn holder(host: &dyn Host, name: &str) -> Result<Option<String>, SignupError> {
host.state_get(&format!("account/{name}")).map(|v| v.map(|b| String::from_utf8_lossy(&b).into_owned())).map_err(SignupError::Unavailable)
}
impl SignupGuest for ToySignup {
fn manifest(&self) -> Manifest {
Manifest::of_kind(kinds::SIGNUP, PRODUCT, &["state"])
}
fn check(&self, _: &dyn Host, settings: &serde_json::Value) -> Result<(), String> {
match settings.get("refuse_check").and_then(|v| v.as_bool()) {
Some(true) => Err("this toy was told to refuse its settings".into()),
_ => Ok(()),
}
}
fn name_verdict(&self, host: &dyn Host, _: &serde_json::Value, username: &str) -> Result<NameVerdict, SignupError> {
Ok(match holder(host, username)? {
Some(_) => NameVerdict::Taken(format!("username {username:?} is taken; choose another")),
None => NameVerdict::Free,
})
}
fn create(&self, host: &dyn Host, settings: &serde_json::Value, s: &PaidSignup, fact: &EntitlementFact) -> Result<SignupOutcome, SignupError> {
if settings.get("down").and_then(|v| v.as_bool()) == Some(true) {
return Err(SignupError::Unavailable("the toy product is down".into()));
}
let me = format!("{}:{}", s.vendor, s.session);
match holder(host, &s.username)? {
Some(other) if other != me => return Err(SignupError::NameTaken(format!("username {:?} is taken", s.username))),
_ => {}
}
if fact.tenant.0 != s.username || fact.signature.is_empty() {
return Err(SignupError::Refused("the host handed an entitlement that is not this signup's, or unsigned".into()));
}
host.state_put(&format!("account/{}", s.username), me.as_bytes()).map_err(SignupError::Unavailable)?;
let f = serde_json::to_vec(fact).map_err(|e| SignupError::Unavailable(e.to_string()))?;
host.state_put(&format!("fact/{}", s.username), &f).map_err(SignupError::Unavailable)?;
Ok(SignupOutcome::Created { namespace: s.username.clone(), username: s.username.clone() })
}
}
monetize_signup_guest::export_signup!(ToySignup, grants = [state]);